Implement folder download as zip in publicFileDownloadHandler

This commit is contained in:
Leon Bösche
2026-01-28 23:43:02 +01:00
parent 03d8a03f7c
commit 7f668b51f9
2 changed files with 108 additions and 3 deletions

View File

@@ -423,7 +423,10 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
right: 8,
child: IconButton(
onPressed: _logout,
icon: Icon(Icons.logout, color: AppTheme.errorColor),
icon: Icon(
Icons.power_settings_new_rounded,
color: AppTheme.errorColor,
),
tooltip: 'Logout',
iconSize: 28,
splashColor: Colors.transparent,

View File

@@ -3186,9 +3186,111 @@ func publicFileDownloadHandler(w http.ResponseWriter, r *http.Request, db *datab
return
}
// Check if it's a folder - cannot download folders
// Check if it's a folder - if so, create a zip download
if file.Type == "folder" {
errors.WriteError(w, errors.CodeInvalidArgument, "Cannot download folders", http.StatusBadRequest)
// Get all files under this folder path
var folderFiles []database.File
var err error
if link.OrgID != nil {
// Org folder - need user ID from context or file owner
folderFiles, err = db.GetAllOrgFilesUnderPath(r.Context(), *link.OrgID, *file.UserID, file.Path)
} else {
// User folder
folderFiles, err = db.GetAllUserFilesUnderPath(r.Context(), *file.UserID, file.Path)
}
if err != nil {
errors.LogError(r, err, "Failed to get folder contents")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
// Filter out sub-folders (only include files)
var filesToZip []database.File
for _, f := range folderFiles {
if f.Type == "file" {
filesToZip = append(filesToZip, f)
}
}
if len(filesToZip) == 0 {
errors.WriteError(w, errors.CodeInvalidArgument, "Folder is empty", http.StatusBadRequest)
return
}
// Create zip file in memory
var zipBuffer bytes.Buffer
zipWriter := zip.NewWriter(&zipBuffer)
// Get WebDAV client for the file's owner
client, err := getUserWebDAVClient(r.Context(), db, *file.UserID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
if err != nil {
errors.LogError(r, err, "Failed to get WebDAV client")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
// Add each file to the zip
for _, fileToZip := range filesToZip {
// Download file from storage
downloadCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
resp, err := client.Download(downloadCtx, fileToZip.Path, "")
cancel()
if err != nil {
errors.LogError(r, err, fmt.Sprintf("Failed to download file %s for zip", fileToZip.Path))
continue // Skip this file, continue with others
}
// Read file content
fileData, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
errors.LogError(r, err, fmt.Sprintf("Failed to read file %s for zip", fileToZip.Path))
continue
}
// Create zip entry - use relative path within the folder
relativePath := strings.TrimPrefix(fileToZip.Path, file.Path)
if strings.HasPrefix(relativePath, "/") {
relativePath = strings.TrimPrefix(relativePath, "/")
}
zipFile, err := zipWriter.Create(relativePath)
if err != nil {
errors.LogError(r, err, fmt.Sprintf("Failed to create zip entry for %s", relativePath))
continue
}
// Write file data to zip
_, err = zipFile.Write(fileData)
if err != nil {
errors.LogError(r, err, fmt.Sprintf("Failed to write file %s to zip", relativePath))
continue
}
}
// Close zip writer
err = zipWriter.Close()
if err != nil {
errors.LogError(r, err, "Failed to close zip writer")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
// Add CORS headers for public access
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Range")
// Set headers for zip download
w.Header().Set("Content-Type", "application/zip")
zipFilename := fmt.Sprintf("%s.zip", file.Name)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", zipFilename))
w.Header().Set("Content-Length", fmt.Sprintf("%d", zipBuffer.Len()))
// Stream the zip file
w.Write(zipBuffer.Bytes())
return
}