DB-first library reads, mixed library indexing, and manual NFO refresh
- API reads now serve from media_items cache instead of scanning the filesystem on every request; scans (manual or scheduled) remain the write path - NFO metadata is no longer parsed automatically during scans; title falls back to folder/filename — metadata can be refreshed per-item via the kabob menu - Mixed libraries are now indexed in media_items (new mixed_file item type) with file_path stored; scanMixed walks recursively and upserts all files - Added file_path column to media_items and migrated item_type CHECK constraint to include mixed_file via safe table-recreation migration - New POST /api/nfo-refresh endpoint reads the .nfo for a single item and patches its DB row (supports movie, tv_series, tv_episode) - Added "Refresh metadata" button to movie and TV series kabob menus Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,13 +81,14 @@ function initDb(db: Database.Database): void {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
library_id TEXT NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
||||
item_key TEXT NOT NULL UNIQUE,
|
||||
item_type TEXT NOT NULL CHECK(item_type IN ('movie','tv_series','tv_season','tv_episode','game','game_series')),
|
||||
item_type TEXT NOT NULL CHECK(item_type IN ('movie','tv_series','tv_season','tv_episode','game','game_series','mixed_file')),
|
||||
parent_key TEXT,
|
||||
title TEXT,
|
||||
year INTEGER,
|
||||
plot TEXT,
|
||||
genres TEXT,
|
||||
metadata TEXT,
|
||||
file_path TEXT,
|
||||
scanned_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
@@ -96,6 +97,7 @@ function initDb(db: Database.Database): void {
|
||||
`)
|
||||
|
||||
migrateLibrariesType(db)
|
||||
migrateMediaItemsSchema(db)
|
||||
seedAppSettings(db)
|
||||
}
|
||||
|
||||
@@ -113,6 +115,53 @@ function seedAppSettings(db: Database.Database): void {
|
||||
}
|
||||
}
|
||||
|
||||
function migrateMediaItemsSchema(db: Database.Database): void {
|
||||
const row = db
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='media_items'")
|
||||
.get() as { sql: string } | undefined
|
||||
|
||||
if (!row) return
|
||||
|
||||
const needsFilePath = !row.sql.includes('file_path')
|
||||
const needsMixedFile = !row.sql.includes("'mixed_file'")
|
||||
|
||||
if (!needsFilePath && !needsMixedFile) return
|
||||
|
||||
// Determine whether the current table already has file_path (partial migration)
|
||||
const hasFilePath = !needsFilePath ? 'file_path,' : 'NULL as file_path,'
|
||||
|
||||
db.exec(`
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE media_items_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
library_id TEXT NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
||||
item_key TEXT NOT NULL UNIQUE,
|
||||
item_type TEXT NOT NULL CHECK(item_type IN (
|
||||
'movie','tv_series','tv_season','tv_episode',
|
||||
'game','game_series','mixed_file')),
|
||||
parent_key TEXT,
|
||||
title TEXT,
|
||||
year INTEGER,
|
||||
plot TEXT,
|
||||
genres TEXT,
|
||||
metadata TEXT,
|
||||
file_path TEXT,
|
||||
scanned_at INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO media_items_new
|
||||
SELECT id, library_id, item_key, item_type, parent_key,
|
||||
title, year, plot, genres, metadata,
|
||||
${hasFilePath}
|
||||
scanned_at
|
||||
FROM media_items;
|
||||
DROP TABLE media_items;
|
||||
ALTER TABLE media_items_new RENAME TO media_items;
|
||||
CREATE INDEX media_items_library_id ON media_items(library_id);
|
||||
CREATE INDEX media_items_parent_key ON media_items(parent_key);
|
||||
COMMIT;
|
||||
`)
|
||||
}
|
||||
|
||||
function migrateLibrariesType(db: Database.Database): void {
|
||||
const row = db
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='libraries'")
|
||||
|
||||
Reference in New Issue
Block a user