fix(files): REST FileStore resolves folders for stat/rename/move/delete (#62)
This commit is contained in:
+67
-41
@@ -7,12 +7,9 @@ package onlyoffice
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// restStore is a FileStore over the REST Documents API.
|
// restStore is a FileStore over the REST Documents API.
|
||||||
@@ -39,11 +36,25 @@ func (s *restStore) List(ctx context.Context, parentID string) ([]Entry, error)
|
|||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat returns file metadata. The REST adapter resolves files only; folders
|
// Stat returns file or folder metadata. Folders are resolved through the
|
||||||
// are listed by their parent (use List).
|
// listing endpoint (their own id appears as the listing's Current); other ids
|
||||||
|
// fall back to the file metadata API.
|
||||||
func (s *restStore) Stat(ctx context.Context, id string) (Entry, error) {
|
func (s *restStore) Stat(ctx context.Context, id string) (Entry, error) {
|
||||||
|
return s.stat(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat resolves a single id to a folder or file Entry.
|
||||||
|
func (s *restStore) stat(ctx context.Context, id string) (Entry, error) {
|
||||||
var out Entry
|
var out Entry
|
||||||
err := retryStoreOp(ctx, func() error {
|
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, ProviderREST)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if Transient(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f, err := s.c.GetFile(ctx, id)
|
f, err := s.c.GetFile(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -124,56 +135,84 @@ func (s *restStore) Download(ctx context.Context, id string, w io.Writer) (int64
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move moves file ids into parentID. The REST MoveFiles endpoint handles files
|
// Move moves folders and/or files into parentID. Ids are classified through
|
||||||
// only; folder moves are not exposed by this adapter.
|
// stat so folder moves use folderIds and file moves use fileIds on the shared
|
||||||
|
// fileops/move endpoint.
|
||||||
func (s *restStore) Move(ctx context.Context, ids []string, parentID string) error {
|
func (s *restStore) Move(ctx context.Context, ids []string, parentID string) error {
|
||||||
dest, err := strconv.Atoi(strings.TrimSpace(parentID))
|
folders, files, err := s.split(ctx, ids)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("onlyoffice: rest store: move: non-numeric destination folder id %q", parentID)
|
|
||||||
}
|
|
||||||
fileIDs, err := numericIDs(ids)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return retryStoreOp(ctx, func() error {
|
if len(folders) == 0 && len(files) == 0 {
|
||||||
_, err := s.c.MoveFiles(ctx, dest, fileIDs)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copies file ids into parentID. files.go has no copy method, so the
|
|
||||||
// shared REST fileops copy endpoint (CopyDavItems) is used.
|
|
||||||
func (s *restStore) Copy(ctx context.Context, ids []string, parentID string) error {
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return retryStoreOp(ctx, func() error {
|
return retryStoreOp(ctx, func() error {
|
||||||
return s.c.CopyDavItems(ctx, nil, ids, parentID)
|
return s.c.MoveDavItems(ctx, folders, files, parentID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename sets a new title (including extension) for a file.
|
// Copy copies folders and/or files into parentID. files.go has no copy method,
|
||||||
|
// so the shared REST fileops copy endpoint (CopyDavItems) is used.
|
||||||
|
func (s *restStore) 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 sets a new title (including extension) for a file or folder.
|
||||||
func (s *restStore) Rename(ctx context.Context, id, title string) error {
|
func (s *restStore) Rename(ctx context.Context, id, title string) error {
|
||||||
|
e, err := s.stat(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Kind == Folder {
|
||||||
|
return retryStoreOp(ctx, func() error {
|
||||||
|
return s.c.RenameDavFolder(ctx, id, title)
|
||||||
|
})
|
||||||
|
}
|
||||||
return retryStoreOp(ctx, func() error {
|
return retryStoreOp(ctx, func() error {
|
||||||
_, err := s.c.RenameFile(ctx, id, title)
|
_, err := s.c.RenameFile(ctx, id, title)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete permanently deletes file ids.
|
// Delete permanently deletes folders and/or files.
|
||||||
func (s *restStore) Delete(ctx context.Context, ids []string) error {
|
func (s *restStore) Delete(ctx context.Context, ids []string) error {
|
||||||
fileIDs, err := numericIDs(ids)
|
folders, files, err := s.split(ctx, ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(fileIDs) == 0 {
|
if len(folders) == 0 && len(files) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return retryStoreOp(ctx, func() error {
|
return retryStoreOp(ctx, func() error {
|
||||||
return s.c.DeleteFiles(ctx, fileIDs)
|
return s.c.DeleteDavItems(ctx, folders, files)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// split classifies ids into folder and file id lists.
|
||||||
|
func (s *restStore) 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
|
||||||
|
}
|
||||||
|
|
||||||
// entriesFromFolderMap converts a ListFolder response map into canonical
|
// entriesFromFolderMap converts a ListFolder response map into canonical
|
||||||
// entries, reusing the DavFile/DavFolder decoders for robust size handling.
|
// entries, reusing the DavFile/DavFolder decoders for robust size handling.
|
||||||
func entriesFromFolderMap(m map[string]any, provider string) ([]Entry, error) {
|
func entriesFromFolderMap(m map[string]any, provider string) ([]Entry, error) {
|
||||||
@@ -215,16 +254,3 @@ func folderEntryFromMap(m map[string]any, parentID, provider string) (Entry, err
|
|||||||
e = DavFolderToEntry(f, provider)
|
e = DavFolderToEntry(f, provider)
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// numericIDs parses Documents numeric ids from strings.
|
|
||||||
func numericIDs(ids []string) ([]int, error) {
|
|
||||||
out := make([]int, 0, len(ids))
|
|
||||||
for _, id := range ids {
|
|
||||||
n, err := strconv.Atoi(strings.TrimSpace(id))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("onlyoffice: rest store: non-numeric id %q", id)
|
|
||||||
}
|
|
||||||
out = append(out, n)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user