package local import ( "errors" "os" "path/filepath" "strings" "sync" "git.tswf.io/infra/go-synapse-backupper/pkg/domain" ) // NewLocalSink creates a new local filesystem sink that writes into the given directory. func NewLocalSink(dir string) domain.Sink { return &localSink{dir: dir} } type localSink struct { dir string } func (sink *localSink) Begin(key string) (domain.SinkTx, error) { tmpPath := filepath.Join(sink.dir, key+".tmp") finalPath := filepath.Join(sink.dir, key) file, err := os.Create(tmpPath) if err != nil { return nil, err } return &localSinkTx{ file: file, tmpPath: tmpPath, finalPath: finalPath, }, nil } func (sink *localSink) List(prefix string) ([]string, error) { entries, err := os.ReadDir(sink.dir) if err != nil { return nil, err } keys := make([]string, 0, len(entries)) for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if prefix != "" && !strings.HasPrefix(name, prefix) { continue } keys = append(keys, name) } return keys, nil } func (sink *localSink) Remove(key string) error { path := filepath.Join(sink.dir, key) return os.Remove(path) } type localSinkTx struct { file *os.File tmpPath string finalPath string committed bool aborted bool mu sync.Mutex } func (transaction *localSinkTx) Write(p []byte) (int, error) { transaction.mu.Lock() defer transaction.mu.Unlock() if transaction.committed || transaction.aborted { return 0, errors.New("transaction already finished") } return transaction.file.Write(p) } func (transaction *localSinkTx) Commit() error { transaction.mu.Lock() defer transaction.mu.Unlock() if transaction.committed || transaction.aborted { return nil } transaction.committed = true if err := transaction.file.Sync(); err != nil { return err } if err := transaction.file.Close(); err != nil { return err } if err := os.Rename(transaction.tmpPath, transaction.finalPath); err != nil { return err } parent, err := os.Open(filepath.Dir(transaction.tmpPath)) if err != nil { return err } defer parent.Close() if err := parent.Sync(); err != nil { return err } return nil } func (transaction *localSinkTx) Abort() error { transaction.mu.Lock() defer transaction.mu.Unlock() if transaction.committed || transaction.aborted { return nil } transaction.aborted = true _ = transaction.file.Close() _ = os.Remove(transaction.tmpPath) return nil }