# File Management ## Overview Each user has an isolated directory under `/downloads/{user_id}/`. The frontend provides a browser-style file tree with support for downloading individual files, downloading folders as ZIP archives, and deleting files or folders. --- ## Directory Structure ``` /downloads/ └── {user_id}/ └── {collection or album name}/ ├── Track 1.flac ├── Track 2.flac └── cover.jpg ``` Single-track downloads are automatically wrapped in a folder named after the track. --- ## Security All file access goes through a path traversal check: 1. The requested relative path is joined with the user's base directory. 2. `.resolve()` canonicalizes the result (expands `..`, symlinks, etc.). 3. The resolved path is checked to ensure it starts with the resolved user directory. 4. Any path that escapes the user directory returns `400 Bad Request`. Admins can browse any user's directory through dedicated admin endpoints that accept a `user_id` parameter. --- ## API Endpoints ### User endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/api/files` | GET | List directory contents at `?path=` (relative to user root) | | `/api/files/download` | GET | Download a single file at `?path=` | | `/api/files/download-folder` | GET | Download a directory as a ZIP at `?path=` | | `/api/files/delete` | DELETE | Delete a file or directory at `?path=` | ### Admin endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/api/admin/files` | GET | List a specific user's directory; requires `?user_id=` and optional `?path=` | | `/api/admin/files/download` | GET | Download a file from any user's directory | | `/api/admin/files/download-folder` | GET | Download a folder as ZIP from any user's directory | | `/api/admin/files/delete` | DELETE | Delete from any user's directory | --- ## Directory Listing Response ```json [ { "name": "Album Name", "path": "Album Name", "is_dir": true }, { "name": "Track.flac", "path": "Album Name/Track.flac", "is_dir": false, "size": 24601234 } ] ``` Directories always appear before files. Paths are relative to the user's root. --- ## ZIP Downloads Folder downloads are streamed directly as a ZIP file using Python's `zipfile` module. Files are added with paths relative to the requested folder, so the ZIP extracts cleanly into a single directory. --- ## Post-Processing (after download) After a Votify or Monochrome download completes, several cleanup steps run automatically: 1. **Flatten nested directories** — removes redundant intermediate folders. 2. **Rename from metadata** — reads embedded ID3/FLAC tags and renames files to `Title - Artist.ext` (see `rename_from_metadata()` in `utils.py`). 3. **Wrap single tracks** — if a download produces only one file with no folder, it is moved into a named subfolder. 4. **Cleanup empty dirs** — removes any directories left empty after the above steps. --- ## Key Files | File | Relevance | |------|-----------| | [app.py](../app.py) | All file route handlers | | [utils.py](../utils.py) | `sanitize_filename`, `rename_from_metadata`, `cleanup_empty_dirs` |