initial version

This commit is contained in:
2026-03-25 16:18:23 -04:00
parent aeec7cae36
commit 88595bee90
27 changed files with 7959 additions and 1 deletions

77
src/lib/games.ts Normal file
View File

@@ -0,0 +1,77 @@
import fs from 'fs'
import path from 'path'
import type { Game } from '@/types'
const HIDDEN_FILES = /^\./
/**
* Finds the first file in a directory whose basename (without extension)
* matches the given pattern (case-insensitive).
*/
function findFile(dir: string, pattern: RegExp): string | null {
let entries: string[]
try {
entries = fs.readdirSync(dir)
} catch {
return null
}
const match = entries.find(
(entry) => !HIDDEN_FILES.test(entry) && pattern.test(path.basename(entry, path.extname(entry)))
)
return match ?? null
}
function fileApiUrl(libraryId: string, relativePath: string): string {
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
}
export function scanGamesLibrary(libraryRoot: string, libraryId: string): Game[] {
let gameDirs: string[]
try {
gameDirs = fs
.readdirSync(libraryRoot, { withFileTypes: true })
.filter((d) => d.isDirectory() && !HIDDEN_FILES.test(d.name))
.map((d) => d.name)
} catch {
return []
}
const games: Game[] = []
for (const dirName of gameDirs) {
const gamePath = path.join(libraryRoot, dirName)
// Find the .zip file (first match)
let zipFile: string | null = null
try {
const allFiles = fs.readdirSync(gamePath)
zipFile = allFiles.find((f) => f.toLowerCase().endsWith('.zip')) ?? null
} catch {
// skip unreadable dirs
continue
}
if (!zipFile) continue
// Case-insensitive cover matching
const coverFile = findFile(gamePath, /^cover$/i)
const wideCoverFile = findFile(gamePath, /^widecover$/i)
const id = encodeURIComponent(dirName)
const zipRelPath = path.join(dirName, zipFile)
games.push({
id,
title: dirName,
coverUrl: coverFile
? fileApiUrl(libraryId, path.join(dirName, coverFile))
: null,
wideCoverUrl: wideCoverFile
? fileApiUrl(libraryId, path.join(dirName, wideCoverFile))
: null,
zipPath: zipRelPath,
})
}
return games.sort((a, b) => a.title.localeCompare(b.title))
}