package cache import ( "bytes" "context" "encoding/json" "io" "os" "path/filepath" "sync" "time" ) type Directory struct { BaseFolder string MaxLife time.Duration lock sync.Mutex } var _ Cache = &Directory{} func (d *Directory) GetPath(key string) string { return filepath.Join(d.BaseFolder, key+".json") } func (d *Directory) Cleanup(_ context.Context) error { d.lock.Lock() defer func() { d.lock.Unlock() }() // go through the BaseFilder looking for any files that are older than MaxLife return filepath.Walk(d.BaseFolder, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // ignore directories if info.IsDir() { return nil } // only files that end in .json if filepath.Ext(path) != ".json" { return nil } // if the openFile is older than MaxLife, delete it if time.Since(info.ModTime()) > d.MaxLife { return os.Remove(path) } return nil }) } // AutoCleanupRoutine will continually loop and cleanup the directory, until the context is cancelled or an error occurs // returns nil on context cancellation, or an error if one occurs during cleanup func (d *Directory) AutoCleanupRoutine(ctx context.Context) error { for { select { case <-ctx.Done(): return nil case <-time.After(d.MaxLife): err := d.Cleanup(ctx) if err != nil { return err } } } } func (d *Directory) openFile(key string) (*os.File, error) { path := d.GetPath(key) return os.Open(path) } func (d *Directory) Set(key string, value io.Reader) error { d.lock.Lock() defer d.lock.Unlock() fp, err := d.openFile(key) if err != nil { return err } defer func(fp *os.File) { _ = fp.Close() }(fp) _, err = io.Copy(fp, value) return err } func (d *Directory) SetJSON(key string, value any) error { d.lock.Lock() defer d.lock.Unlock() fp, err := d.openFile(key) if err != nil { return err } defer func(fp *os.File) { _ = fp.Close() }(fp) return json.NewEncoder(fp).Encode(value) } func (d *Directory) SetString(key, value string) error { return d.Set(key, bytes.NewReader([]byte(value))) } func (d *Directory) Get(key string, w io.Writer) error { d.lock.Lock() defer d.lock.Unlock() fp, err := d.openFile(key) if err != nil { return err } defer fp.Close() _, err = io.Copy(w, fp) return err } func (d *Directory) GetJSON(key string, v any) error { d.lock.Lock() defer d.lock.Unlock() fp, err := d.openFile(key) if err != nil { return err } defer fp.Close() return json.NewEncoder(fp).Encode(v) } func (d *Directory) GetString(key string) (string, error) { var buf bytes.Buffer err := d.Get(key, &buf) return buf.String(), err } func (d *Directory) Delete(key string) error { d.lock.Lock() defer d.lock.Unlock() return os.Remove(d.GetPath(key)) }