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
190 lines
5.0 KiB
Go
190 lines
5.0 KiB
Go
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
|
|
}
|