Files
yellowjacket/.planning/research/ARCHITECTURE.md
T
yonlu cdf8bff01a docs: restructure v1.1 milestone for multi-library support
- Rename milestone from 'Features & Extensibility' to 'Multi-Library Support'
- Replace old phases 10-14 (TAG/SMRT/GAP/MB/LAYOUT/PLUG) with 20 multi-library requirements (LIB/LSCAN/VIEW/PLAY/DATA)
- Move deferred features to Future Requirements
- Update research files with multi-library research (Stack, Features, Architecture, Pitfalls)
- Architecture: hybrid model (library_id on audio_files only), libraries in DB, sequential scanning, phantom tracks
2026-03-08 11:54:37 -04:00

119 lines
4.3 KiB
Markdown

# Architecture Research: Multi-Library Integration
**Researched:** 2026-03-08
**Confidence:** HIGH
## Design Decision: Hybrid Model
- **`library_id` on `audio_files` only** — physical file binding
- **Artists, albums, recordings, genres stay global** — shared reference data
- **Unified presentation by default** — optional library filter
- **Cross-library playlists** — playlists reference audio_file_id
- **Phantom tracks on removal** — playlist entries preserved with metadata
## Database Changes
### New Table: libraries
```sql
CREATE TABLE IF NOT EXISTS libraries (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
path TEXT NOT NULL UNIQUE,
scan_concurrency TEXT NOT NULL DEFAULT 'auto',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_scanned_at DATETIME
);
```
### Migration 6: Multi-Library Support
Order of operations:
1. Create `libraries` table
2. Read TOML `DirectoryPath`, insert as default library
3. `ALTER TABLE audio_files ADD COLUMN library_id INTEGER NOT NULL DEFAULT {defaultLibID}`
4. Create index on `audio_files(library_id)`
5. Rebuild `playlist_tracks` with SET NULL FK + phantom columns
6. Drop and recreate `track_metadata` VIEW with `library_id`
7. Set `PRAGMA user_version = 6`
### playlist_tracks Rebuild (for phantom support)
```sql
CREATE TABLE playlist_tracks_new (
id INTEGER PRIMARY KEY,
playlist_id INTEGER NOT NULL,
audio_file_id INTEGER, -- NOW NULLABLE
position INTEGER NOT NULL,
phantom_file_path TEXT,
phantom_title TEXT,
phantom_artist TEXT,
phantom_album TEXT,
FOREIGN KEY(playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY(audio_file_id) REFERENCES audio_files(id) ON DELETE SET NULL
);
```
Two-phase library removal:
1. Populate phantom metadata BEFORE deleting audio_files
2. Delete audio_files -> SET NULL triggers -> phantom columns preserve display info
### track_metadata VIEW (updated)
Add `af.library_id` to SELECT list. Same JOIN structure. Consumers get library_id for filtering.
## Scan Pipeline Changes
- `Scan()` -> `ScanLibrary(libraryID int64)` — accepts library ID, loads path from DB
- `ScanAllLibraries()` — sequential iteration, one at a time
- Orphan cleanup scoped to library being scanned
- Entity cache (artists, albums) remains per-scan and works correctly (shared entities)
- Progress events include library_id and library_name
## Orphan Cleanup (Library Removal)
Reference-counting bottom-up deletes in single transaction:
```
audio_files (library_id = X) -> DELETE
recordings (no remaining audio_files) -> DELETE
release_group_recordings (orphaned) -> DELETE
recording_genres (orphaned) -> DELETE
release_groups (no remaining recordings) -> DELETE
artist_credit (no remaining references) -> DELETE
artist_credit_artist (orphaned) -> DELETE
artists (no remaining credits) -> DELETE
genres (no remaining recording links) -> DELETE
cover_art (no remaining release_groups) -> DELETE
```
## FTS5 Search Index
Contentless FTS5 (`content=''`) works naturally:
- Search queries JOIN `search_index` on `track_metadata` (which now has `library_id`)
- Library-filtered search: add `AND tm.library_id = ?` to WHERE clause
- Stale entries after library removal filtered out by JOIN (same as current orphan behavior)
- Consider migrating to `contentless_delete=1` (SQLite 3.43.0+) for per-row DELETE support
## Frontend Architecture
- `libraryStore` gains: library list, active filter (null = all), persistence in localStorage
- Backend filtering (not frontend) — pass libraryID to backend queries
- `library-manager` component redesigned: library list view, add/remove/rename, per-library scan
- All browse views check active filter when fetching data
- New events: LibraryAdded, LibraryRemoved, LibraryRenamed
- Existing scan events gain library_id in payload
## Config Migration
- TOML `[Library].DirectoryPath` read once during migration 6, inserted as default library
- Post-migration: library management through DB only
- `ScanConcurrency` moves per-library (DB column) with global default fallback
- `SetLibraryDirectory()` and `GetLibraryDirectory()` deprecated
## Build Order
1. Schema & Migration (foundation)
2. Backend scan pipeline (per-library scanning)
3. Backend API (CRUD, filtered queries, events)
4. Frontend (library manager, filter, store updates)