feat(files): canonical Entry + FileStore REST/DAV adapters (#35)
Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go 1.25) (pull_request) Successful in 18s
Tests / Test (Go stable) (pull_request) Successful in 18s
Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go 1.25) (pull_request) Successful in 18s
Tests / Test (Go stable) (pull_request) Successful in 18s
This commit is contained in:
+189
@@ -0,0 +1,189 @@
|
||||
package onlyoffice
|
||||
|
||||
// davStore implements FileStore on top of the Documents/WebDAV methods in
|
||||
// files_webdav.go. The Documents fileops calls need folder and file ids
|
||||
// separated, so ids are classified through Stat before move/copy/rename/delete.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// davStore is a FileStore over the WebDAV-oriented Documents API.
|
||||
type davStore struct{ c *Client }
|
||||
|
||||
// Name reports the backend name.
|
||||
func (s *davStore) Name() string { return ProviderDAV }
|
||||
|
||||
// List returns the files and folders directly below parentID.
|
||||
func (s *davStore) List(ctx context.Context, parentID string) ([]Entry, error) {
|
||||
var out []Entry
|
||||
err := retryStoreOp(ctx, func() error {
|
||||
l, err := s.c.ListDavFolder(ctx, parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries := make([]Entry, 0, len(l.Folders)+len(l.Files))
|
||||
for _, f := range l.Folders {
|
||||
entries = append(entries, DavFolderToEntry(f, ProviderDAV))
|
||||
}
|
||||
for _, f := range l.Files {
|
||||
entries = append(entries, DavFileToEntry(f, ProviderDAV))
|
||||
}
|
||||
out = entries
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Stat resolves a folder or file entry by id. A folder answers ListDavFolder
|
||||
// with its own metadata in Current; otherwise the file metadata API is used.
|
||||
func (s *davStore) Stat(ctx context.Context, id string) (Entry, error) {
|
||||
return s.stat(ctx, id)
|
||||
}
|
||||
|
||||
// CreateFolder creates a subfolder under parentID.
|
||||
func (s *davStore) CreateFolder(ctx context.Context, parentID, title string) (Entry, error) {
|
||||
var out Entry
|
||||
err := retryStoreOp(ctx, func() error {
|
||||
f, err := s.c.CreateDavFolder(ctx, parentID, title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f == nil {
|
||||
return fmt.Errorf("onlyoffice: dav store: empty create-folder response")
|
||||
}
|
||||
out = DavFolderToEntry(*f, ProviderDAV)
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Upload streams r into parentID as title. The reader is buffered once so a
|
||||
// retry re-sends the same bytes instead of an exhausted stream.
|
||||
func (s *davStore) Upload(ctx context.Context, parentID, title string, r io.Reader) (Entry, error) {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return Entry{}, err
|
||||
}
|
||||
var out Entry
|
||||
err = retryStoreOp(ctx, func() error {
|
||||
f, err := s.c.UploadDavFile(ctx, parentID, title, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f == nil {
|
||||
return fmt.Errorf("onlyoffice: dav store: empty upload response")
|
||||
}
|
||||
out = DavFileToEntry(*f, ProviderDAV)
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Download streams the file bytes into w.
|
||||
func (s *davStore) Download(ctx context.Context, id string, w io.Writer) (int64, error) {
|
||||
var n int64
|
||||
err := retryStoreOp(ctx, func() error {
|
||||
var e error
|
||||
n, e = s.c.DownloadDavFile(ctx, id, w)
|
||||
return e
|
||||
})
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Move moves ids into parentID, splitting folders from files.
|
||||
func (s *davStore) Move(ctx context.Context, ids []string, parentID string) error {
|
||||
folders, files, err := s.split(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(folders) == 0 && len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retryStoreOp(ctx, func() error {
|
||||
return s.c.MoveDavItems(ctx, folders, files, parentID)
|
||||
})
|
||||
}
|
||||
|
||||
// Copy copies ids into parentID, splitting folders from files.
|
||||
func (s *davStore) Copy(ctx context.Context, ids []string, parentID string) error {
|
||||
folders, files, err := s.split(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(folders) == 0 && len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retryStoreOp(ctx, func() error {
|
||||
return s.c.CopyDavItems(ctx, folders, files, parentID)
|
||||
})
|
||||
}
|
||||
|
||||
// Rename renames a folder or file.
|
||||
func (s *davStore) Rename(ctx context.Context, id, title string) error {
|
||||
e, err := s.stat(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return retryStoreOp(ctx, func() error {
|
||||
if e.Kind == Folder {
|
||||
return s.c.RenameDavFolder(ctx, id, title)
|
||||
}
|
||||
return s.c.RenameDavFile(ctx, id, title)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes ids, splitting folders from files.
|
||||
func (s *davStore) Delete(ctx context.Context, ids []string) error {
|
||||
folders, files, err := s.split(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(folders) == 0 && len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retryStoreOp(ctx, func() error {
|
||||
return s.c.DeleteDavItems(ctx, folders, files)
|
||||
})
|
||||
}
|
||||
|
||||
// stat resolves a single id to a folder or file Entry.
|
||||
func (s *davStore) stat(ctx context.Context, id string) (Entry, error) {
|
||||
var out Entry
|
||||
err := retryStoreOp(ctx, func() error {
|
||||
if l, err := s.c.ListDavFolder(ctx, id); err == nil {
|
||||
if l != nil && l.Current.ID != "" && l.Current.ID == id {
|
||||
out = DavFolderToEntry(l.Current, ProviderDAV)
|
||||
return nil
|
||||
}
|
||||
} else if Transient(err) {
|
||||
return err
|
||||
}
|
||||
f, err := s.c.GetFile(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = FileEntryToEntry(f, ProviderDAV)
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// split classifies ids into folder and file id lists.
|
||||
func (s *davStore) split(ctx context.Context, ids []string) (folders, files []string, err error) {
|
||||
for _, id := range ids {
|
||||
e, err := s.stat(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if e.Kind == Folder {
|
||||
folders = append(folders, id)
|
||||
} else {
|
||||
files = append(files, id)
|
||||
}
|
||||
}
|
||||
return folders, files, nil
|
||||
}
|
||||
Reference in New Issue
Block a user