fix(files): upsert uploads by default and dedupe project root
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 stable) (pull_request) Successful in 25s
Tests / Test (Go 1.25) (pull_request) Successful in 27s
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 stable) (pull_request) Successful in 25s
Tests / Test (Go 1.25) (pull_request) Successful in 27s
OnlyOffice allows duplicate stem|ext in the same folder; agents hit this via projects files upload and put-md without --folder. Default all upload paths to replace-by-stem, add no-clobber via --no-replace, and scan projectFolder in dedupe. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,10 +3,15 @@ package onlyoffice
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrFileExists is returned when --no-replace / no-clobber upload hits an existing stem|ext.
|
||||
var ErrFileExists = errors.New("onlyoffice: file already exists in folder (use replace or delete first)")
|
||||
|
||||
// FileEntryStem returns the logical basename without duplicated extensions.
|
||||
// OO often stores title="foo.docx" and fileExst=".docx" (UI shows foo.docx.docx).
|
||||
func FileEntryStem(f *FileEntry) string {
|
||||
@@ -116,6 +121,38 @@ func (c *Client) DeleteFilesByStem(ctx context.Context, folderID, stem string) (
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// AssertNoFileConflict reports ErrFileExists when localPath stem|ext is already in folderID.
|
||||
func (c *Client) AssertNoFileConflict(ctx context.Context, folderID, localPath string) error {
|
||||
files, err := c.FolderFiles(ctx, folderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stem := UploadStemFromLocal(localPath)
|
||||
ext := UploadExtFromLocal(localPath)
|
||||
matches := FindFilesByDedupKey(files, stem, ext)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
ids := make([]string, 0, len(matches))
|
||||
for _, f := range matches {
|
||||
ids = append(ids, fmt.Sprintf("%d", FileEntryNumericID(f)))
|
||||
}
|
||||
return fmt.Errorf("%w: %s%s in folder %s (existing file ids: %s)",
|
||||
ErrFileExists, stem, ext, folderID, strings.Join(ids, ", "))
|
||||
}
|
||||
|
||||
// UploadProjectFileNoClobber uploads only when stem|ext is not already in the project folder.
|
||||
func (c *Client) UploadProjectFileNoClobber(ctx context.Context, projectID, localPath string) (*FileEntry, error) {
|
||||
folderID, err := c.projectFolderID(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.AssertNoFileConflict(ctx, folderID, localPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.UploadProjectFile(ctx, projectID, localPath)
|
||||
}
|
||||
|
||||
// UploadToFolderReplacing deletes same stem+ext files then uploads localPath.
|
||||
func (c *Client) UploadToFolderReplacing(ctx context.Context, folderID, localPath string) (*FileEntry, []int, error) {
|
||||
stem := UploadStemFromLocal(localPath)
|
||||
|
||||
Reference in New Issue
Block a user