Files
trackpull/docs/file-management.md

95 lines
3.1 KiB
Markdown

# 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` |