package storage import ( "encoding/gob" "log" "os" "path" "time" ) func NewDirStore(clearInterval time.Duration, path string) Store { store := &dir{ clearInterval: clearInterval, timeLayout: time.RFC3339Nano, path: path, close: make(chan bool), } go store.clearExpired() return store } type dir struct { clearInterval time.Duration timeLayout string path string close chan bool } func (store *dir) Create(password []byte, expiresAt time.Time) (string, error) { for range 1000 { id := generateId(24) path := store.getPath(id) file, err := os.OpenFile( path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.ModePerm, ) switch { case os.IsExist(err): continue case err != nil: return "", err } defer file.Close() entry := entry{ Password: password, ExpiresAt: expiresAt, } err = gob.NewEncoder(file).Encode(&entry) if err != nil { log.Printf("%s", err) return "", err } return id, nil } return "", ErrFull } func (store *dir) Get(id string) ([]byte, error) { path := store.getPath(id) file, err := os.OpenFile( path, os.O_RDONLY, 0, ) switch { case os.IsNotExist(err): return nil, ErrNotFound case err != nil: return nil, err } defer file.Close() var entry entry err = gob.NewDecoder(file).Decode(&entry) if err != nil { return nil, err } return entry.Password, nil } func (store *dir) Delete(id string) error { path := store.getPath(id) err := os.Remove(path) if err != nil { return nil } return nil } func (store *dir) Close() error { store.close <- true return nil } func (store *dir) clearExpired() error { ticker := time.NewTicker(store.clearInterval) defer ticker.Stop() for { select { case <-store.close: return nil case <-ticker.C: // TODO: Error handling? now := time.Now() entries, err := os.ReadDir(store.path) if err != nil { continue } for _, file := range entries { id := file.Name() path := store.getPath(id) file, err := os.OpenFile( path, os.O_RDONLY, 0, ) if err != nil { continue } defer file.Close() var entry entry err = gob.NewDecoder(file).Decode(&entry) if err != nil { continue } if now.After(entry.ExpiresAt) { // Close file early as we need to delete it file.Close() store.Delete(id) } } } } } func (store *dir) getPath(id string) string { return path.Join(store.path, id) }