orbe-launcher/src/main/scanners/merge.ts

56 lines
3.1 KiB
TypeScript

/* Fusion d'un scan avec la bibliothèque existante. Pur : testé sans Windows.
- jeu nouveau : ajouté (addedAt = maintenant)
- jeu connu : chemins / lancement mis à jour, données de l'utilisateur gardées (favori, temps, collections, nom modifié)
- jeu d'un scanner terminé mais plus trouvé : marqué désinstallé (jamais supprimé : on garde temps de jeu et collections)
- scanner en échec ou désactivé : ses jeux ne bougent pas
- doublons : même id, ou même dossier d'installation qu'un jeu d'une plateforme (le jeu ajouté à la main est fusionné dedans) */
import type { Game, ScannerId } from '../../shared/types'
import type { ScanResult } from './index'
import type { ScannedGame } from './types'
export const gameId = (g: Pick<Game, 'platform' | 'platformId'>) => `${g.platform}:${g.platformId}`
const norm = (p?: string) => p?.toLowerCase().replace(/[\\/]+$/, '').replace(/\//g, '\\')
export interface MergeStats { added: number; updated: number; uninstalled: number; merged: number }
export function mergeScan(existing: Game[], result: Pick<ScanResult, 'ok'>, now = Date.now()): { games: Game[]; stats: MergeStats } {
const byId = new Map(existing.map((g) => [g.id, { ...g }]))
const stats: MergeStats = { added: 0, updated: 0, uninstalled: 0, merged: 0 }
const seen = new Set<string>()
for (const [scanner, list] of Object.entries(result.ok) as [ScannerId, ScannedGame[]][]) {
for (const s of list) {
const id = gameId(s)
if (seen.has(id)) continue // même jeu listé deux fois (deux bibliothèques Steam…)
seen.add(id)
const old = byId.get(id)
const fresh = { installDir: s.installDir, exe: s.exe ?? old?.exe, launchUri: s.launchUri, steamAppId: s.steamAppId ?? old?.steamAppId, installed: s.installed ?? true }
if (old) {
Object.assign(old, fresh, old.nameLocked ? {} : { name: s.name }, old.argsLocked ? {} : { args: s.args, workDir: s.workDir })
stats.updated++
} else {
byId.set(id, { id, platform: s.platform, platformId: s.platformId, name: s.name, args: s.args, workDir: s.workDir, ...fresh, favorite: false, hidden: false, addedAt: now, playtime: 0, collections: [], media: {} })
stats.added++
}
}
for (const g of byId.values()) {
if (g.platform === scanner && !seen.has(g.id) && g.installed) { g.installed = false; stats.uninstalled++ }
}
}
// un jeu ajouté à la main dont l'exe est dans le dossier d'un jeu détecté : on fusionne dans le jeu détecté
const platformGames = [...byId.values()].filter((g) => g.platform !== 'manual' && g.installDir)
for (const m of [...byId.values()].filter((g) => g.platform === 'manual' && g.exe)) {
const exe = norm(m.exe)!
const host = platformGames.find((g) => exe.startsWith(norm(g.installDir)! + '\\'))
if (!host) continue
host.playtime += m.playtime
host.favorite ||= m.favorite
host.lastPlayed = Math.max(host.lastPlayed ?? 0, m.lastPlayed ?? 0) || undefined
host.collections = [...new Set([...host.collections, ...m.collections])]
host.media = { ...m.media, ...host.media }
byId.delete(m.id)
stats.merged++
}
return { games: [...byId.values()], stats }
}