- 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
4.3 KiB
4.3 KiB
Architecture Research: Multi-Library Integration
Researched: 2026-03-08 Confidence: HIGH
Design Decision: Hybrid Model
library_idonaudio_filesonly — 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
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:
- Create
librariestable - Read TOML
DirectoryPath, insert as default library ALTER TABLE audio_files ADD COLUMN library_id INTEGER NOT NULL DEFAULT {defaultLibID}- Create index on
audio_files(library_id) - Rebuild
playlist_trackswith SET NULL FK + phantom columns - Drop and recreate
track_metadataVIEW withlibrary_id - Set
PRAGMA user_version = 6
playlist_tracks Rebuild (for phantom support)
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:
- Populate phantom metadata BEFORE deleting audio_files
- 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 DBScanAllLibraries()— 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_indexontrack_metadata(which now haslibrary_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
libraryStoregains: library list, active filter (null = all), persistence in localStorage- Backend filtering (not frontend) — pass libraryID to backend queries
library-managercomponent 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].DirectoryPathread once during migration 6, inserted as default library - Post-migration: library management through DB only
ScanConcurrencymoves per-library (DB column) with global default fallbackSetLibraryDirectory()andGetLibraryDirectory()deprecated
Build Order
- Schema & Migration (foundation)
- Backend scan pipeline (per-library scanning)
- Backend API (CRUD, filtered queries, events)
- Frontend (library manager, filter, store updates)