handle series
This commit is contained in:
@@ -5,22 +5,104 @@ import { detectPlatform } from '$lib/platform';
|
||||
import { toSlug, isImageFile } from '$lib/utils';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
function isSeriesFolder(entries: fs.Dirent[]): boolean {
|
||||
return entries.some(
|
||||
(e) =>
|
||||
e.isDirectory() &&
|
||||
!e.name.toLowerCase().endsWith('.app') &&
|
||||
e.name.toLowerCase() !== 'screenshots'
|
||||
);
|
||||
}
|
||||
|
||||
function scanGameFolder(
|
||||
db: Database.Database,
|
||||
folderPath: string,
|
||||
title: string,
|
||||
library: 'public' | 'private',
|
||||
seriesId: number | null
|
||||
): void {
|
||||
const slug = toSlug(title);
|
||||
|
||||
let entries: fs.Dirent[];
|
||||
try {
|
||||
entries = fs.readdirSync(folderPath, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasCover = entries.some(
|
||||
(e) => e.isFile() && /^cover\.(png|jpg|jpeg|webp)$/i.test(e.name)
|
||||
);
|
||||
const hasWide = entries.some(
|
||||
(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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
library = excluded.library,
|
||||
folder_path = excluded.folder_path,
|
||||
has_cover = excluded.has_cover,
|
||||
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);
|
||||
|
||||
const game = db.prepare('SELECT id FROM games WHERE slug = ?').get(slug) as { id: number };
|
||||
|
||||
db.prepare('DELETE FROM game_files WHERE game_id = ?').run(game.id);
|
||||
for (const entry of entries) {
|
||||
const platform = detectPlatform(entry.name, entry.isDirectory());
|
||||
if (platform === null) continue;
|
||||
|
||||
const isDir = entry.isDirectory() ? 1 : 0;
|
||||
let fileSize = 0;
|
||||
if (!isDir) {
|
||||
try {
|
||||
fileSize = fs.statSync(path.join(folderPath, entry.name)).size;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO game_files (game_id, filename, rel_path, platform, is_dir, file_size)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
).run(game.id, entry.name, entry.name, platform, isDir, fileSize);
|
||||
}
|
||||
|
||||
db.prepare('DELETE FROM screenshots WHERE game_id = ?').run(game.id);
|
||||
const ssDir = path.join(folderPath, 'screenshots');
|
||||
if (fs.existsSync(ssDir)) {
|
||||
const ssFiles = fs.readdirSync(ssDir).filter(isImageFile).sort();
|
||||
ssFiles.forEach((filename, i) => {
|
||||
db.prepare(
|
||||
`INSERT INTO screenshots (game_id, filename, rel_path, sort_order)
|
||||
VALUES (?, ?, ?, ?)`
|
||||
).run(game.id, filename, `screenshots/${filename}`, i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function scanLibraries(db: Database.Database): void {
|
||||
const GAMES_PATH = env.GAMES_PATH ?? path.join(process.cwd(), 'games');
|
||||
for (const library of ['public', 'private'] as const) {
|
||||
const libPath = path.join(GAMES_PATH, library);
|
||||
if (!fs.existsSync(libPath)) continue;
|
||||
|
||||
let gameFolders: fs.Dirent[];
|
||||
let topFolders: fs.Dirent[];
|
||||
try {
|
||||
gameFolders = fs.readdirSync(libPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
||||
topFolders = fs
|
||||
.readdirSync(libPath, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory());
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const folder of gameFolders) {
|
||||
for (const folder of topFolders) {
|
||||
const title = folder.name;
|
||||
const slug = toSlug(title);
|
||||
const folderPath = path.join(libPath, folder.name);
|
||||
|
||||
let entries: fs.Dirent[];
|
||||
@@ -30,69 +112,61 @@ export function scanLibraries(db: Database.Database): void {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasCover = entries.some(
|
||||
(e) => e.isFile() && /^cover\.(png|jpg|jpeg|webp)$/i.test(e.name)
|
||||
);
|
||||
const hasWide = entries.some(
|
||||
(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)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
library = excluded.library,
|
||||
folder_path = excluded.folder_path,
|
||||
has_cover = excluded.has_cover,
|
||||
has_wide = excluded.has_wide,
|
||||
last_scanned_at = datetime('now')`
|
||||
).run(slug, title, library, folderPath, hasCover ? 1 : 0, hasWide ? 1 : 0);
|
||||
|
||||
const game = db.prepare('SELECT id FROM games WHERE slug = ?').get(slug) as { id: number };
|
||||
|
||||
db.prepare('DELETE FROM game_files WHERE game_id = ?').run(game.id);
|
||||
for (const entry of entries) {
|
||||
const platform = detectPlatform(entry.name, entry.isDirectory());
|
||||
if (platform === null) continue;
|
||||
|
||||
const isDir = entry.isDirectory() ? 1 : 0;
|
||||
let fileSize = 0;
|
||||
if (!isDir) {
|
||||
try {
|
||||
fileSize = fs.statSync(path.join(folderPath, entry.name)).size;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (isSeriesFolder(entries)) {
|
||||
const seriesSlug = toSlug(title);
|
||||
const hasCover = entries.some(
|
||||
(e) => e.isFile() && /^cover\.(png|jpg|jpeg|webp)$/i.test(e.name)
|
||||
);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO game_files (game_id, filename, rel_path, platform, is_dir, file_size)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
).run(game.id, entry.name, entry.name, platform, isDir, fileSize);
|
||||
}
|
||||
`INSERT INTO series (slug, title, library, folder_path, has_cover)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
library = excluded.library,
|
||||
folder_path = excluded.folder_path,
|
||||
has_cover = excluded.has_cover,
|
||||
last_scanned_at = datetime('now')`
|
||||
).run(seriesSlug, title, library, folderPath, hasCover ? 1 : 0);
|
||||
|
||||
db.prepare('DELETE FROM screenshots WHERE game_id = ?').run(game.id);
|
||||
const ssDir = path.join(folderPath, 'screenshots');
|
||||
if (fs.existsSync(ssDir)) {
|
||||
const ssFiles = fs.readdirSync(ssDir).filter(isImageFile).sort();
|
||||
ssFiles.forEach((filename, i) => {
|
||||
db.prepare(
|
||||
`INSERT INTO screenshots (game_id, filename, rel_path, sort_order)
|
||||
VALUES (?, ?, ?, ?)`
|
||||
).run(game.id, filename, `screenshots/${filename}`, i);
|
||||
});
|
||||
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 gameFolderPath = path.join(folderPath, entry.name);
|
||||
scanGameFolder(db, gameFolderPath, entry.name, library, seriesRow.id);
|
||||
}
|
||||
} else {
|
||||
scanGameFolder(db, folderPath, title, library, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove DB rows for game folders that no longer exist on disk
|
||||
const existing = db
|
||||
const existingGames = db
|
||||
.prepare('SELECT id, folder_path FROM games WHERE library = ?')
|
||||
.all(library) as Array<{ id: number; folder_path: string }>;
|
||||
for (const row of existing) {
|
||||
for (const row of existingGames) {
|
||||
if (!fs.existsSync(row.folder_path)) {
|
||||
db.prepare('DELETE FROM games WHERE id = ?').run(row.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove DB rows for series folders that no longer exist on disk
|
||||
const existingSeries = db
|
||||
.prepare('SELECT id, folder_path FROM series WHERE library = ?')
|
||||
.all(library) as Array<{ id: number; folder_path: string }>;
|
||||
for (const row of existingSeries) {
|
||||
if (!fs.existsSync(row.folder_path)) {
|
||||
db.prepare('DELETE FROM series WHERE id = ?').run(row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user