Skip to content

Conversation

@sosweetham
Copy link
Member

@sosweetham sosweetham commented Jan 30, 2026

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@sosweetham sosweetham requested a review from coodos as a code owner January 30, 2026 16:05
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s) Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as "Client UI\n(renders items, triggers download)"
  participant Store as "FolderStore\n(getFolderContents)"
  participant Service as "FileService\n(fetch file blobs)"
  participant Zip as "ZipBuilder\n(sanitize & pack)"

  UI->>Store: request folder contents (for each selected folder)
  Store-->>UI: return file + nested folder list (with paths)
  UI->>Service: request file blob (for each file in allFilesToDownload)
  Service-->>UI: return file blob
  UI->>Zip: add file blob with sanitized unique path
  Zip-->>UI: update progress / report errors
  UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation. Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
                 <span class="text-sm text-blue-700">
                     {selectedFileIds.size}
-                    {selectedFileIds.size === 1 ? "file" : "files"} selected
+                    {selectedFileIds.size === 1 ? "item" : "items"} selected
                 </span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
     {selectedFileIds.size}
-    {selectedFileIds.size === 1 ? "file" : "files"} selected
+    {selectedFileIds.size === 1 ? "item" : "items"} selected
 </span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:
+function sanitizeFilename(rawName: string): string {
+    let name = rawName;
+    name = name.replace(/^[a-zA-Z]:/, '');
+    name = name.replace(/\\/g, '/');
+    name = name.replace(/\.\./g, '');
+    const lastSlashIndex = name.lastIndexOf('/');
+    if (lastSlashIndex !== -1) {
+        name = name.slice(lastSlashIndex + 1);
+    }
+    name = name.replace(/[/\\]/g, '');
+    name = name.replace(/^[.\s]+/, '');
+    name = name.trim();
+    // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars
+    name = name.replace(/[\x00-\x1f\x7f]/g, '');
+    if (!name) {
+        name = 'file';
+    }
+    return name;
+}
+
+function sanitizePath(rawPath: string): string {
+    if (!rawPath) return "";
+    const parts = rawPath.split('/').filter(Boolean);
+    const sanitizedParts = parts.map(part => {
+        let p = part;
+        p = p.replace(/\.\./g, '');
+        p = p.replace(/[\\:*?"<>|]/g, '');
+        p = p.replace(/^[.\s]+/, '');
+        p = p.trim();
+        return p || 'folder';
+    });
+    return sanitizedParts.join('/');
+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();
+const usedPaths = new Set<string>();
+const usedPathsLower = new Set<string>(); // For case-insensitive check

 function getUniqueFilePath(originalPath: string, originalName: string): string {
     const sanitizedPath = sanitizePath(originalPath);
     const sanitizedName = sanitizeFilename(originalName);
     const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+    const fullPathLower = fullPath.toLowerCase();
     
-    if (!usedPaths.has(fullPath)) {
+    if (!usedPathsLower.has(fullPathLower)) {
         usedPaths.add(fullPath);
+        usedPathsLower.add(fullPathLower);
         return fullPath;
     }
     // ... rest of collision handling with similar changes

@coodos coodos merged commit f0271fa into main Jan 30, 2026
4 checks passed
@coodos coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants