diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index c30b0fa..449d034 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -14,11 +14,13 @@ export function getDb(): Database.Database { fs.mkdirSync(dir, { recursive: true }); } + console.log(`[db] opening database at ${DB_PATH}`); const db = new Database(DB_PATH); db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); initSchema(db); _db = db; + console.log('[db] database ready'); return _db; } @@ -81,8 +83,10 @@ function initSchema(db: Database.Database): void { const cols = db.prepare('PRAGMA table_info(games)').all() as Array<{ name: string }>; if (!cols.some((c) => c.name === 'series_id')) { + console.log('[db] migrating: adding series_id column to games'); db.exec( 'ALTER TABLE games ADD COLUMN series_id INTEGER DEFAULT NULL REFERENCES series(id) ON DELETE SET NULL' ); + console.log('[db] migration complete'); } } diff --git a/src/lib/server/scanner.ts b/src/lib/server/scanner.ts index 12e21e8..01a6429 100644 --- a/src/lib/server/scanner.ts +++ b/src/lib/server/scanner.ts @@ -30,11 +30,13 @@ function scanGameFolder( seriesId: number | null ): void { const slug = toSlug(title); + console.log(`[scanner] game: "${title}" → slug="${slug}" series_id=${seriesId} path=${folderPath}`); let entries: fs.Dirent[]; try { entries = fs.readdirSync(folderPath, { withFileTypes: true }); - } catch { + } catch (err) { + console.error(`[scanner] failed to read game folder "${folderPath}":`, err); return; } @@ -45,8 +47,9 @@ function scanGameFolder( (e) => e.isFile() && /^widecover\.(png|jpg|jpeg|webp)$/i.test(e.name) ); - db.prepare( - `INSERT INTO games (slug, title, library, folder_path, has_cover, has_wide, series_id) + try { + db.prepare( + `INSERT INTO games (slug, title, library, folder_path, has_cover, has_wide, series_id) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(slug) DO UPDATE SET title = excluded.title, @@ -56,7 +59,11 @@ function scanGameFolder( has_wide = excluded.has_wide, series_id = excluded.series_id, last_scanned_at = datetime('now')` - ).run(slug, title, library, folderPath, hasCover ? 1 : 0, hasWide ? 1 : 0, seriesId); + ).run(slug, title, library, folderPath, hasCover ? 1 : 0, hasWide ? 1 : 0, seriesId); + } catch (err) { + console.error(`[scanner] DB insert failed for game "${title}" (slug="${slug}"):`, err); + return; + } const game = db.prepare('SELECT id FROM games WHERE slug = ?').get(slug) as { id: number }; @@ -96,19 +103,27 @@ function scanGameFolder( export function scanLibraries(db: Database.Database): void { const GAMES_PATH = env.GAMES_PATH ?? path.join(process.cwd(), 'games'); + console.log(`[scanner] starting scan, GAMES_PATH=${GAMES_PATH}`); + for (const library of ['public', 'private'] as const) { const libPath = path.join(GAMES_PATH, library); - if (!fs.existsSync(libPath)) continue; + if (!fs.existsSync(libPath)) { + console.log(`[scanner] library path not found, skipping: ${libPath}`); + continue; + } let topFolders: fs.Dirent[]; try { topFolders = fs .readdirSync(libPath, { withFileTypes: true }) .filter((d) => d.isDirectory()); - } catch { + } catch (err) { + console.error(`[scanner] failed to read library "${libPath}":`, err); continue; } + console.log(`[scanner] ${library}: found ${topFolders.length} top-level folder(s)`); + for (const folder of topFolders) { const title = folder.name; const folderPath = path.join(libPath, folder.name); @@ -116,18 +131,22 @@ export function scanLibraries(db: Database.Database): void { let entries: fs.Dirent[]; try { entries = fs.readdirSync(folderPath, { withFileTypes: true }); - } catch { + } catch (err) { + console.error(`[scanner] failed to read folder "${folderPath}":`, err); continue; } if (isSeriesFolder(entries)) { const seriesSlug = toSlug(title); + console.log(`[scanner] series: "${title}" → slug="${seriesSlug}"`); + const hasCover = entries.some( (e) => e.isFile() && /^cover\.(png|jpg|jpeg|webp)$/i.test(e.name) ); - db.prepare( - `INSERT INTO series (slug, title, library, folder_path, has_cover) + try { + db.prepare( + `INSERT INTO series (slug, title, library, folder_path, has_cover) VALUES (?, ?, ?, ?, ?) ON CONFLICT(slug) DO UPDATE SET title = excluded.title, @@ -135,20 +154,25 @@ export function scanLibraries(db: Database.Database): void { folder_path = excluded.folder_path, has_cover = excluded.has_cover, last_scanned_at = datetime('now')` - ).run(seriesSlug, title, library, folderPath, hasCover ? 1 : 0); + ).run(seriesSlug, title, library, folderPath, hasCover ? 1 : 0); + } catch (err) { + console.error(`[scanner] DB insert failed for series "${title}":`, err); + continue; + } const seriesRow = db .prepare('SELECT id FROM series WHERE slug = ?') .get(seriesSlug) as { id: number }; - for (const entry of entries) { - if ( - !entry.isDirectory() || - entry.name.toLowerCase().endsWith('.app') || - entry.name.toLowerCase() === 'screenshots' - ) - continue; + const gameSubfolders = entries.filter( + (e) => + e.isDirectory() && + !e.name.toLowerCase().endsWith('.app') && + e.name.toLowerCase() !== 'screenshots' + ); + console.log(`[scanner] → ${gameSubfolders.length} game(s) in series`); + for (const entry of gameSubfolders) { const gameFolderPath = path.join(folderPath, entry.name); scanGameFolder(db, gameFolderPath, entry.name, library, seriesRow.id); } @@ -163,6 +187,7 @@ export function scanLibraries(db: Database.Database): void { .all(library) as Array<{ id: number; folder_path: string }>; for (const row of existingGames) { if (!fs.existsSync(row.folder_path)) { + console.log(`[scanner] removing stale game: ${row.folder_path}`); db.prepare('DELETE FROM games WHERE id = ?').run(row.id); } } @@ -173,8 +198,13 @@ export function scanLibraries(db: Database.Database): void { .all(library) as Array<{ id: number; folder_path: string }>; for (const row of existingSeries) { if (!fs.existsSync(row.folder_path)) { + console.log(`[scanner] removing stale series: ${row.folder_path}`); db.prepare('DELETE FROM series WHERE id = ?').run(row.id); } } } + + const gameCount = (db.prepare('SELECT COUNT(*) as n FROM games').get() as { n: number }).n; + const seriesCount = (db.prepare('SELECT COUNT(*) as n FROM series').get() as { n: number }).n; + console.log(`[scanner] done — ${gameCount} game(s), ${seriesCount} series in DB`); }