diff --git a/go_cloud/api b/go_cloud/api index ed3cf5e..c3327c6 100755 Binary files a/go_cloud/api and b/go_cloud/api differ diff --git a/go_cloud/internal/http/wopi_handlers.go b/go_cloud/internal/http/wopi_handlers.go index ed5eed7..6a61dd2 100644 --- a/go_cloud/internal/http/wopi_handlers.go +++ b/go_cloud/internal/http/wopi_handlers.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "path" + "path/filepath" "strings" "sync" "time" @@ -524,7 +525,7 @@ func wopiPutFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, return } - // Read file content from request body + // Read file content from request body first content, err := io.ReadAll(r.Body) if err != nil { fmt.Printf("[WOPI-STORAGE] Failed to read request body: %v\n", err) @@ -533,28 +534,84 @@ func wopiPutFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, } defer r.Body.Close() + // Check for suggested target (used for export operations like Save as PDF) + suggestedTarget := r.Header.Get("X-WOPI-SuggestedTarget") + isExport := suggestedTarget != "" + + var targetFile *database.File + var targetRemotePath string + + if isExport { + // Parse suggested target + var newName string + if strings.HasPrefix(suggestedTarget, ".") { + // Extension only, e.g., ".pdf" + baseName := strings.TrimSuffix(file.Name, filepath.Ext(file.Name)) + newName = baseName + suggestedTarget + } else { + // Full filename, e.g., "document.pdf" + newName = suggestedTarget + } + + // Determine new path + var newPath string + if file.OrgID != nil { + // For org files, place in same directory + dir := filepath.Dir(file.Path) + newPath = filepath.Join(dir, newName) + } else { + // For user files, place in same directory + dir := filepath.Dir(file.Path) + newPath = filepath.Join(dir, newName) + } + + // Create new file entry in database + newFile, err := db.CreateFile(r.Context(), file.OrgID, file.UserID, newName, newPath, "application/pdf", int64(len(content))) + if err != nil { + fmt.Printf("[WOPI-EXPORT] Failed to create export file: %v\n", err) + errors.WriteError(w, errors.CodeInternal, "Failed to create export file", http.StatusInternalServerError) + return + } + + targetFile = newFile + + // Set remote path for the new file + if file.OrgID != nil { + rel := strings.TrimPrefix(newPath, "/") + targetRemotePath = path.Join("/orgs", file.OrgID.String(), rel) + } else { + targetRemotePath = newPath + } + + fmt.Printf("[WOPI-EXPORT] Export operation: original=%s new=%s path=%s\n", file.Name, newName, targetRemotePath) + } else { + // Normal save operation + targetFile = file + targetRemotePath = remotePath + } + // Upload to storage - fmt.Printf("[WOPI-STORAGE] PutFile uploading: file=%s remotePath=%s\n", fileID, remotePath) - err = webDAVClient.Upload(r.Context(), remotePath, strings.NewReader(string(content)), int64(len(content))) + fmt.Printf("[WOPI-STORAGE] PutFile uploading: file=%s remotePath=%s\n", targetFile.ID.String(), targetRemotePath) + err = webDAVClient.Upload(r.Context(), targetRemotePath, strings.NewReader(string(content)), int64(len(content))) if err != nil { - fmt.Printf("[WOPI-STORAGE] Failed to upload file: file=%s path=%s error=%v\n", fileID, file.Path, err) + fmt.Printf("[WOPI-STORAGE] Failed to upload file: file=%s path=%s error=%v\n", targetFile.ID.String(), targetFile.Path, err) errors.WriteError(w, errors.CodeInternal, "Failed to save file", http.StatusInternalServerError) return } // Update file size and modification time in database newSize := int64(len(content)) - err = db.UpdateFileSize(r.Context(), fileUUID, newSize, &userID) + err = db.UpdateFileSize(r.Context(), targetFile.ID, newSize, &userID) if err != nil { - fmt.Printf("[WOPI-STORAGE] Failed to update file size: file=%s error=%v\n", fileID, err) + fmt.Printf("[WOPI-STORAGE] Failed to update file size: file=%s error=%v\n", targetFile.ID.String(), err) // Don't fail the upload, just log the warning } - fmt.Printf("[WOPI-STORAGE] PutFile: file=%s user=%s bytes=%d\n", fileID, userID.String(), newSize) + fmt.Printf("[WOPI-STORAGE] PutFile: file=%s user=%s bytes=%d\n", targetFile.ID.String(), userID.String(), newSize) // Return response response := models.WOPIPutFileResponse{ - ItemVersion: fileUUID.String(), + ItemVersion: targetFile.ID.String(), } w.Header().Set("Content-Type", "application/json")