66cd4e945c
- Add trashed/original_path/trashed_at columns to files table - Implement FUSE unlink: moves files to /.trash/ preserving path structure - Implement FUSE rmdir: removes empty directories - Add trash CLI commands: list, restore, empty - Add SIGHUP handler for CLI-triggered restore - Fix upsert_file returning 0 on UPDATE (query actual ID) - Auto-clear trashed flag when moving files out of /.trash/
167 lines
4.1 KiB
Markdown
167 lines
4.1 KiB
Markdown
**Date**: 2026-05-17
|
|
**Status**: Shipped
|
|
|
|
# Feature: Remove (rm)
|
|
|
|
## Overview
|
|
|
|
MusicFS supports removing files and directories. Deleted files are moved to a virtual `/.trash/` directory and can be restored. The trash is browsable — users can manually move files out.
|
|
|
|
## Behavior
|
|
|
|
### Remove File
|
|
|
|
```bash
|
|
rm "/mnt/music/Artist/Album/track.flac"
|
|
```
|
|
|
|
- File moves to `/.trash/Artist/Album/track.flac`
|
|
- Original directory structure preserved in trash
|
|
- File still accessible via `/.trash/` path
|
|
- Database marks file as `trashed=1` with original path stored
|
|
|
|
### Remove Empty Directory
|
|
|
|
```bash
|
|
rmdir "/mnt/music/Empty Folder"
|
|
```
|
|
|
|
- Removes empty directory from tree
|
|
- Removes from `directories` table if user-created
|
|
- Fails with `ENOTEMPTY` if directory has children
|
|
|
|
### Remove Directory Recursively
|
|
|
|
```bash
|
|
rm -rf "/mnt/music/Artist"
|
|
```
|
|
|
|
- Shell handles recursion (depth-first unlink + rmdir)
|
|
- All files moved to `/.trash/Artist/...`
|
|
- Empty directories removed after files are trashed
|
|
|
|
## The `.trash/` Directory
|
|
|
|
Deleted files live in `/.trash/` with their original path structure:
|
|
|
|
```
|
|
/.trash/
|
|
├── Artist/
|
|
│ └── Album/
|
|
│ ├── track1.flac
|
|
│ └── track2.flac
|
|
└── Other Artist/
|
|
└── song.flac
|
|
```
|
|
|
|
### Browse Trash
|
|
|
|
```bash
|
|
ls "/.trash/"
|
|
ls "/.trash/Artist/Album/"
|
|
```
|
|
|
|
### Manual Restore
|
|
|
|
```bash
|
|
# Move file back manually - trashed flag is automatically cleared
|
|
mv "/.trash/Artist/Album/track.flac" "/Artist/Album/"
|
|
```
|
|
|
|
When moving a file out of `/.trash/`, the database `trashed` flag is automatically cleared.
|
|
|
|
## CLI Commands
|
|
|
|
All trash commands require either `--config` or `--cache-dir`:
|
|
|
|
```bash
|
|
musicfs trash -c config.toml <command>
|
|
musicfs trash --cache-dir ./dev/cache/musicfs <command>
|
|
```
|
|
|
|
### List Deleted Files
|
|
|
|
```bash
|
|
musicfs trash -c config.toml list
|
|
musicfs trash -c config.toml list --origin local-storage
|
|
musicfs trash -c config.toml list --since 7d
|
|
musicfs trash -c config.toml list --path "/Artist"
|
|
```
|
|
|
|
Output shows index, deletion time, and original path.
|
|
|
|
### Restore Files
|
|
|
|
```bash
|
|
# Restore single file or folder
|
|
musicfs trash -c config.toml restore "/Artist/Album/track.flac"
|
|
|
|
# Restore entire folder recursively
|
|
musicfs trash -c config.toml restore "/Artist"
|
|
|
|
# Restore everything
|
|
musicfs trash -c config.toml restore --all
|
|
```
|
|
|
|
CLI restore writes paths to a pending restore file and sends SIGHUP to the daemon.
|
|
The daemon processes pending restores and moves files back from `/.trash/`.
|
|
|
|
### Empty Trash
|
|
|
|
```bash
|
|
# Permanently delete all trashed files
|
|
musicfs trash -c config.toml empty
|
|
|
|
# Delete old items only
|
|
musicfs trash -c config.toml empty --older-than 30d
|
|
|
|
# Delete by path pattern
|
|
musicfs trash -c config.toml empty --pattern "/Artist"
|
|
```
|
|
|
|
**Warning:** Empty permanently removes files from MusicFS database. Origin files are unaffected.
|
|
|
|
## Error Codes
|
|
|
|
| Condition | Error |
|
|
|-----------|-------|
|
|
| Path doesn't exist | `ENOENT` |
|
|
| `rm` on directory (without `-r`) | `EISDIR` |
|
|
| `rmdir` on file | `ENOTDIR` |
|
|
| `rmdir` on non-empty directory | `ENOTEMPTY` |
|
|
| `rmdir` on `/.trash/` | `EPERM` |
|
|
|
|
## Database Schema
|
|
|
|
Files table extended with trash columns:
|
|
|
|
```sql
|
|
trashed INTEGER NOT NULL DEFAULT 0,
|
|
original_path TEXT,
|
|
trashed_at INTEGER
|
|
```
|
|
|
|
Partial index for efficient trash queries:
|
|
```sql
|
|
CREATE INDEX idx_files_trashed ON files(trashed) WHERE trashed = 1;
|
|
```
|
|
|
|
## How It Works
|
|
|
|
1. **Delete (`rm`)**: FUSE `unlink` moves file to `/.trash/`, marks `trashed=1` in DB
|
|
2. **Manual restore (`mv`)**: Moving out of `/.trash/` automatically clears `trashed` flag
|
|
3. **CLI restore**: Writes pending paths, sends SIGHUP to daemon, daemon processes restores
|
|
4. **Empty**: Deletes matching records from database
|
|
|
|
## Persistence
|
|
|
|
- Trashed files persist across remounts (stored in `/.trash/` subtree)
|
|
- Files marked with `trashed=1`, `original_path`, `trashed_at` in database
|
|
- PID file at `{cache_dir}/musicfs.pid` for CLI→daemon communication
|
|
|
|
## Limitations
|
|
|
|
- **No hard delete of remote files**: Origin content is never modified
|
|
- **Trash uses virtual space**: Files still in tree under `/.trash/` until emptied
|
|
- **CLI restore requires running daemon**: Manual `mv` works without daemon
|