Implement folder download as zip in publicFileDownloadHandler
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user