feat(webdav): create by extension and update by id
Add CreateDavFile/CreateDavTextFile/CreateDavHtmlFile to create an empty file
(the upload endpoint rejects empty bodies) and UpdateDavFile to replace an
existing file in place via PUT /files/{id}/update.
The /upload endpoint creates a second file when the title already exists, so a
WebDAV overwrite must go through /update to keep a single file. uploadReader now
takes the HTTP method.
This commit is contained in:
+86
-3
@@ -133,6 +133,67 @@ func (c *Client) CreateDavFolder(ctx context.Context, parentID, title string) (*
|
||||
return env.Response, nil
|
||||
}
|
||||
|
||||
// CreateDavFile creates an empty file titled title inside parentID via
|
||||
// POST /api/2.0/files/{folderId}/file. When enableExternalExt is true the
|
||||
// extension is kept as-is instead of being coerced to .docx/.xlsx/.pptx,
|
||||
// which is what non-office WebDAV PUTs need. Mirrors ASC.WebDav createFile.
|
||||
func (c *Client) CreateDavFile(ctx context.Context, parentID, title string, enableExternalExt bool) (*DavFile, error) {
|
||||
body := map[string]any{"title": title, "EnableExternalExt": enableExternalExt}
|
||||
path := "/api/2.0/files/" + url.PathEscape(parentID) + "/file"
|
||||
out, err := c.postJSONObject(ctx, path, body)
|
||||
if err != nil {
|
||||
out, err = c.postJSONObject(ctx, path+".json", body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return davFileFromMap(out), nil
|
||||
}
|
||||
|
||||
// CreateDavTextFile creates a new text file (POST /api/2.0/files/{folderId}/text).
|
||||
// The reference ASC.WebDav implementation sends a single space as content
|
||||
// because the portal rejects fully empty bodies; the same applies here.
|
||||
func (c *Client) CreateDavTextFile(ctx context.Context, parentID, title string) (*DavFile, error) {
|
||||
return c.createDavContentFile(ctx, parentID, title, "/text")
|
||||
}
|
||||
|
||||
// CreateDavHtmlFile creates a new HTML file (POST .../html).
|
||||
func (c *Client) CreateDavHtmlFile(ctx context.Context, parentID, title string) (*DavFile, error) {
|
||||
return c.createDavContentFile(ctx, parentID, title, "/html")
|
||||
}
|
||||
|
||||
func (c *Client) createDavContentFile(ctx context.Context, parentID, title, suffix string) (*DavFile, error) {
|
||||
body := map[string]any{"title": title, "content": " "}
|
||||
path := "/api/2.0/files/" + url.PathEscape(parentID) + suffix
|
||||
out, err := c.postJSONObject(ctx, path, body)
|
||||
if err != nil {
|
||||
out, err = c.postJSONObject(ctx, path+".json", body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return davFileFromMap(out), nil
|
||||
}
|
||||
|
||||
// davFileFromMap renders a create-file response object as a DavFile. Only the
|
||||
// fields the portal reliably returns are mapped.
|
||||
func davFileFromMap(m map[string]any) *DavFile {
|
||||
if m == nil {
|
||||
return &DavFile{}
|
||||
}
|
||||
f := &DavFile{
|
||||
ID: stringField(m, "id"),
|
||||
Title: stringField(m, "title"),
|
||||
Size: flexInt(m["pureContentLength"]),
|
||||
Updated: stringField(m, "updated"),
|
||||
ViewURL: stringField(m, "viewUrl"),
|
||||
}
|
||||
if f.Size == 0 {
|
||||
f.Size = flexInt(m["contentLength"])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// RenameDavFolder renames a folder.
|
||||
//
|
||||
// Deprecated: use FileStore.Rename via Client.Files()/Client.FileStore.
|
||||
@@ -268,7 +329,7 @@ func (c *Client) deleteDavItem(ctx context.Context, path string, body any) error
|
||||
//
|
||||
// Deprecated: use FileStore.Upload via Client.Files()/Client.FileStore.
|
||||
func (c *Client) UploadDavFile(ctx context.Context, folderID, fileName string, src io.Reader) (*DavFile, error) {
|
||||
raw, err := c.uploadReader(ctx, "/api/2.0/files/"+url.PathEscape(folderID)+"/upload", "file", fileName, src)
|
||||
raw, err := c.uploadReader(ctx, http.MethodPost, "/api/2.0/files/"+url.PathEscape(folderID)+"/upload", "file", fileName, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -331,7 +392,7 @@ func (c *Client) deleteJSON(ctx context.Context, path string, body any) (json.Ra
|
||||
}
|
||||
|
||||
// uploadReader uploads a stream to path under the given form field name.
|
||||
func (c *Client) uploadReader(ctx context.Context, path, fieldName, fileName string, src io.Reader) (json.RawMessage, error) {
|
||||
func (c *Client) uploadReader(ctx context.Context, method, path, fieldName, fileName string, src io.Reader) (json.RawMessage, error) {
|
||||
var buf bytes.Buffer
|
||||
mw := multipart.NewWriter(&buf)
|
||||
part, err := mw.CreateFormFile(fieldName, fileName)
|
||||
@@ -348,7 +409,7 @@ func (c *Client) uploadReader(ctx context.Context, path, fieldName, fileName str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL()+path, &buf)
|
||||
req, err := http.NewRequestWithContext(ctx, method, c.baseURL()+path, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -370,6 +431,28 @@ func (c *Client) uploadReader(ctx context.Context, path, fieldName, fileName str
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// UpdateDavFile uploads src as a new version of the existing file fileID
|
||||
// (PUT /api/2.0/files/{id}/update). Unlike UploadDavFile, it replaces the
|
||||
// existing file instead of creating a second file with the same title — the
|
||||
// ONLYOFFICE /upload endpoint duplicates on a title clash.
|
||||
func (c *Client) UpdateDavFile(ctx context.Context, fileID, fileName string, src io.Reader) (*DavFile, error) {
|
||||
path := "/api/2.0/files/" + url.PathEscape(fileID) + "/update"
|
||||
raw, err := c.uploadReader(ctx, http.MethodPut, path, "file", fileName, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var env struct {
|
||||
Response *DavFile `json:"response"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if env.Response == nil {
|
||||
return nil, fmt.Errorf("onlyoffice: empty update response")
|
||||
}
|
||||
return env.Response, nil
|
||||
}
|
||||
|
||||
func nums(ids []string) []json.Number {
|
||||
out := make([]json.Number, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
|
||||
Reference in New Issue
Block a user