package sync import ( "context" "fmt" "github.com/openziti/zrok/drives/davServer" "io" "io/fs" "os" "path/filepath" "time" ) type FilesystemTargetConfig struct { Root string } type FilesystemTarget struct { cfg *FilesystemTargetConfig root fs.FS tree []*Object } func NewFilesystemTarget(cfg *FilesystemTargetConfig) *FilesystemTarget { root := os.DirFS(cfg.Root) return &FilesystemTarget{cfg: cfg, root: root} } func (t *FilesystemTarget) Inventory() ([]*Object, error) { fi, err := os.Stat(t.cfg.Root) if os.IsNotExist(err) { return nil, nil } if err != nil { return nil, err } if !fi.IsDir() { t.cfg.Root = filepath.Dir(t.cfg.Root) return []*Object{{ Path: "/" + fi.Name(), IsDir: false, Size: fi.Size(), Modified: fi.ModTime(), }}, nil } t.tree = nil if err := fs.WalkDir(t.root, ".", t.recurse); err != nil { return nil, err } return t.tree, nil } func (t *FilesystemTarget) Dir(path string) ([]*Object, error) { des, err := os.ReadDir(t.cfg.Root) if err != nil { return nil, err } var objects []*Object for _, de := range des { fi, err := de.Info() if err != nil { return nil, err } objects = append(objects, &Object{ Path: de.Name(), IsDir: de.IsDir(), Size: fi.Size(), Modified: fi.ModTime(), }) } return objects, nil } func (t *FilesystemTarget) Mkdir(path string) error { return os.MkdirAll(filepath.Join(t.cfg.Root, path), os.ModePerm) } func (t *FilesystemTarget) recurse(path string, d fs.DirEntry, err error) error { if err != nil { return err } fi, err := d.Info() if err != nil { return err } etag := "" if v, ok := fi.(davServer.ETager); ok { etag, err = v.ETag(context.Background()) if err != nil { return err } } else { etag = fmt.Sprintf(`"%x%x"`, fi.ModTime().UTC().UnixNano(), fi.Size()) } if path != "." { outPath := "/" + path if fi.IsDir() { outPath = outPath + "/" } t.tree = append(t.tree, &Object{ Path: outPath, IsDir: fi.IsDir(), Size: fi.Size(), Modified: fi.ModTime(), ETag: etag, }) } return nil } func (t *FilesystemTarget) ReadStream(path string) (io.ReadCloser, error) { return os.Open(filepath.Join(t.cfg.Root, path)) } func (t *FilesystemTarget) WriteStream(path string, stream io.Reader, mode os.FileMode) error { targetPath := filepath.Join(t.cfg.Root, path) if err := os.MkdirAll(filepath.Dir(targetPath), mode); err != nil { return err } f, err := os.Create(targetPath) if err != nil { return err } defer f.Close() _, err = io.Copy(f, stream) if err != nil { return err } return nil } func (t *FilesystemTarget) WriteStreamWithModTime(path string, stream io.Reader, mode os.FileMode, modTime time.Time) error { return t.WriteStream(path, stream, mode) } func (t *FilesystemTarget) Move(src, dest string) error { return os.Rename(filepath.Join(t.cfg.Root, src), filepath.Join(filepath.Dir(t.cfg.Root), dest)) } func (t *FilesystemTarget) Rm(path string) error { return os.RemoveAll(filepath.Join(t.cfg.Root, path)) } func (t *FilesystemTarget) SetModificationTime(path string, mtime time.Time) error { targetPath := filepath.Join(t.cfg.Root, path) if err := os.Chtimes(targetPath, time.Now(), mtime); err != nil { return err } return nil }