feat(oo): kontoblatt bulk tools, oo dav, update, retry (#22)
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 1m22s
Tests / Test (Go stable) (pull_request) Successful in 1m21s

This commit is contained in:
2026-09-14 21:03:39 +00:00
parent 7f56332285
commit af8e3b053a
9 changed files with 1320 additions and 10 deletions
+68 -10
View File
@@ -144,27 +144,85 @@ func (c *Client) RenameDavFile(ctx context.Context, id, title string) error {
}
// MoveDavItems moves the given folders and/or files into destFolderID.
// The fileops API answers 200 with per-operation error strings even when
// nothing moves (e.g. missing permission), so the response is parsed and the
// first operation error is returned instead of a silent nil.
func (c *Client) MoveDavItems(ctx context.Context, folderIDs, fileIDs []string, destFolderID string) error {
_, err := c.putJSON(ctx, "/api/2.0/files/fileops/move", map[string]any{
raw, err := c.putJSON(ctx, "/api/2.0/files/fileops/move", map[string]any{
"folderIds": nums(folderIDs),
"fileIds": nums(fileIDs),
"destFolderId": num(destFolderID),
"resolveType": "Skip",
"holdResult": true,
})
return err
if err != nil {
return err
}
return fileopsError(raw)
}
// CopyDavItems copies the given folders and/or files into destFolderID.
// Per-operation errors are surfaced like in MoveDavItems.
func (c *Client) CopyDavItems(ctx context.Context, folderIDs, fileIDs []string, destFolderID string) error {
_, err := c.putJSON(ctx, "/api/2.0/files/fileops/copy", map[string]any{
raw, err := c.putJSON(ctx, "/api/2.0/files/fileops/copy", map[string]any{
"folderIds": nums(folderIDs),
"fileIds": nums(fileIDs),
"destFolderId": num(destFolderID),
"conflictResolveType": "Skip",
"deleteAfter": true,
})
return err
if err != nil {
return err
}
return fileopsError(raw)
}
// ListFileOps returns the currently active file operations
// (GET /api/2.0/files/fileops) for status polling.
func (c *Client) ListFileOps(ctx context.Context) ([]map[string]any, error) {
raw, err := c.getJSON(ctx, "/api/2.0/files/fileops")
if err != nil {
return nil, err
}
resp, err := responseField(raw, "response")
if err != nil {
return nil, err
}
if len(resp) == 0 || string(resp) == "null" {
return nil, nil
}
var ops []map[string]any
if err := json.Unmarshal(resp, &ops); err != nil {
return nil, err
}
return ops, nil
}
// fileopsError extracts per-operation "error" strings from a fileops/move or
// fileops/copy envelope. A 200 with error entries means nothing moved.
func fileopsError(raw json.RawMessage) error {
resp, err := responseField(raw, "response")
if err != nil {
return err
}
var ops []struct {
Error *string `json:"error"`
Finished *bool `json:"finished"`
Progress *int `json:"progress"`
}
if err := json.Unmarshal(resp, &ops); err != nil {
return nil // not an operations envelope — nothing to report
}
var errs []string
for _, op := range ops {
if op.Error != nil && *op.Error != "" {
errs = append(errs, *op.Error)
}
}
if len(errs) > 0 {
return fmt.Errorf("onlyoffice: fileops: %s", strings.Join(errs, "; "))
}
return nil
}
// DeleteDavItems deletes the given folders and/or files.
@@ -376,12 +434,12 @@ func (f *DavFolder) UnmarshalJSON(b []byte) error {
// UnmarshalJSON decodes a file row, capturing size and timestamps.
func (f *DavFile) UnmarshalJSON(b []byte) error {
var raw struct {
ID *json.Number `json:"id"`
Title *string `json:"title"`
PureSize *int64 `json:"pureContentLength"`
SizeStr *string `json:"contentLength"`
Updated *string `json:"updated"`
ViewURL *string `json:"viewUrl"`
ID *json.Number `json:"id"`
Title *string `json:"title"`
PureSize *int64 `json:"pureContentLength"`
SizeStr *string `json:"contentLength"`
Updated *string `json:"updated"`
ViewURL *string `json:"viewUrl"`
}
if err := json.Unmarshal(b, &raw); err != nil {
return err