Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Mercado committed Feb 1, 2024
2 parents e956247 + 8f5988e commit 77130c4
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 68 deletions.
59 changes: 34 additions & 25 deletions src/preload/util/scanRoms.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { Game, System } from "@common/types";
import path from "path";
import { isEqual } from "lodash"
import ShortUniqueId from "short-unique-id";
import { MainPaths } from "@common/types/Paths";
import { readdir, stat } from "fs/promises";

const uid = new ShortUniqueId();

const systemExtnameMap: Record<string, Set<string>> = {}

const scanRoms = async (
cleanupMissingGames = false,
paths: MainPaths,
currentSystems: System[],
currentGames: Game[]
) => {
const getGameLookupKey = (romname: string, systemId: string, rompath: string[] = []) => `${romname}-${systemId}-${rompath.join("_")}`
const gameLookupMap: Record<string, Game> = currentGames.reduce((acc, game) => {
const key = getGameLookupKey(game.romname, game.system, game.rompath);
acc[key] = game;
return acc;
}, {})

const { ROMs: ROM_PATH } = paths;

const addedDate = new Date().toUTCString();
const newGames: Game[] = [];
const romsDir = await readdir(ROM_PATH);

const compareRomPaths = (fromGame: string[] | undefined, fromScan: string[]) => {
if (!fromGame || !fromGame?.length) {
return !fromScan.length
} else {
return isEqual(fromGame, fromScan)
}
}

// remove leading periods, make lowercase
const normalizeExtname = (extname: string) => {
const leadingPeriodRemoved = extname.startsWith(".")
Expand All @@ -38,35 +37,43 @@ const scanRoms = async (

const scanFolder = async (systemConfig: System, pathTokens: string[] = []) => {
const dir = path.join(ROM_PATH, systemConfig.id, ...pathTokens);
let contents = await readdir(dir);
let contents: string[];

try {
contents = await readdir(dir);
} catch(e) {
console.error(`Failed to read ${systemConfig.name} directory at "${dir}"`);
return;
}

// handle multi-part games by filtering out other tracks/discs
contents = contents.filter(entry => !entry.match(/\((Track|Disc) [^1]\)/));
if(!contents.length) return;
if(contents.includes('.eh-ignore')) return;

const extnames = systemExtnameMap[systemConfig.id] || (() => {
const extnames = new Set(systemConfig.fileExtensions.map(normalizeExtname))
systemExtnameMap[systemConfig.id] = extnames;
return extnames;
})()

for (const entry of contents) {
const entryPath = path.join(dir, entry);
const entryExt = path.extname(entry);
const entryStat = await stat(entryPath);

if (entryStat.isDirectory()) {
scanFolder(systemConfig, [...pathTokens, entry]);
await scanFolder(systemConfig, [...pathTokens, entry]);
continue;
}

if (!systemConfig
.fileExtensions
.map(normalizeExtname)
.includes(normalizeExtname(entryExt))
) continue;
if (!extnames.has(normalizeExtname(entryExt))) continue;

const gameConfigEntry = currentGames.find(game => (
game.romname === entry
&& game.system === systemConfig.id
&& compareRomPaths(game.rompath, pathTokens)
))
const lookupKey = getGameLookupKey(entry, systemConfig.id, pathTokens);
const gameConfigEntry = gameLookupMap[lookupKey];

if(gameConfigEntry) {
cleanupMissingGames && newGames.push(gameConfigEntry);
newGames.push(gameConfigEntry);
continue;
}

Expand All @@ -81,14 +88,16 @@ const scanRoms = async (
}
}

const scanQueue: Promise<void>[] = [];
for (const system of romsDir) {
const systemConfig = currentSystems.find(config => config.id === system);
if (!systemConfig) continue;

await scanFolder(systemConfig)
scanQueue.push(scanFolder(systemConfig))
}

return [...(cleanupMissingGames ? [] : currentGames), ...newGames];
await Promise.allSettled(scanQueue);
return newGames;
}

export default scanRoms;
1 change: 0 additions & 1 deletion src/renderer/src/atoms/defaults/systems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,5 +318,4 @@ const parsedSystems: System[] = defaultSystems.map(system => {
}
}).filter(Boolean) as System[]

console.log(parsedSystems)
export default parsedSystems
4 changes: 2 additions & 2 deletions src/renderer/src/atoms/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ const mainAtoms = arrayConfigAtoms<Game>({ storageKey: 'games' });
const scanGamesAtom = atom(null,
async (get, set) => {
const newGames = await window.scanRoms(
true,
get(pathsAtom),
get(systemMainAtoms.lists.all),
get(mainAtoms.lists.all)
);

set(mainAtoms.lists.all, newGames);

return newGames.length;
Expand Down Expand Up @@ -73,7 +73,7 @@ const recentlyViewedAtom = atomFamily((filter: RecentlyViewedFilters) => atom((g
new Date(b.lastViewed!).valueOf() - new Date(a.lastViewed!).valueOf()
)
.slice(0, 8)
}));
}), deepEqual);

const launchGameAtom = atom(null, async (get, set, gameId: string) => {
const systemsList = get(systemMainAtoms.lists.all);
Expand Down
11 changes: 3 additions & 8 deletions src/renderer/src/components/GridScroller/GridScroller.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ScrollerProps } from "../Scroller";
import MediaTile from "../MediaTile/MediaTile";
import { useId, useMemo } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeGrid as Grid } from "react-window"
Expand All @@ -12,6 +11,7 @@ import Label from "../Label/Label";
import css from "./GridScroller.module.scss"
import { Game } from "@common/types/Game";
import { System } from "@common/types/System";
import GameTile from "../MediaTile/Presets/GameTile";

const activeCellAtom = atomFamily((_id: string) => atom({
row: 0,
Expand All @@ -31,16 +31,11 @@ const GameCell = ({ columnIndex, rowIndex, style, data }) => {

return (
<div className={css.tileWrapper} style={style} >
<MediaTile
<GameTile
active={isActive}
swapTransform
className={css.tile}
media={gameData.poster
? {
background: gameData.poster
}
: { background: gameData.screenshot, foreground: gameData.logo, foregroundText: gameData.name ?? gameData.romname }
}
game={gameData}
/>
</div>
)
Expand Down
8 changes: 5 additions & 3 deletions src/renderer/src/components/MediaTile/MediaTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ export interface TileMedia {
foregroundText?: string
}

interface Props {
export type AspectRatio = "landscape" | "square"

export interface MediaTileProps {
active?: boolean
activeRef?: React.RefObject<HTMLDivElement>
aspectRatio?: "landscape" | "square"
aspectRatio?: AspectRatio
style?: CSSProperties
swapTransform?: boolean
className?: string
Expand All @@ -29,7 +31,7 @@ const MediaTile = ({
style,
swapTransform,
media
}: Props) => {
}: MediaTileProps) => {
return (
<div
className={classNames(css.elem, css[aspectRatio], {
Expand Down
28 changes: 28 additions & 0 deletions src/renderer/src/components/MediaTile/Presets/GameTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Game } from "@common/types"
import MediaTile, { MediaTileProps, TileMedia } from "../MediaTile"

type Props = Omit<MediaTileProps, "media"> & { game: Game }

const GameTile = ({
game,
aspectRatio = "landscape",
...props
}: Props) => {
const tileMedia: TileMedia = (() => {
if (
game.poster
&& (!game.gameTileDisplayType || game.gameTileDisplayType === "poster")
&& aspectRatio === "landscape"
) return { background: game.poster }

return { background: game.screenshot, foreground: game.logo, foregroundText: game.name ?? game.romname }
})()

return <MediaTile
media={tileMedia}
aspectRatio={aspectRatio}
{...props}
/>
}

export default GameTile
26 changes: 26 additions & 0 deletions src/renderer/src/components/MediaTile/Presets/SystemTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { System } from "@common/types"
import MediaTile, { MediaTileProps } from "../MediaTile"

type Props = Omit<MediaTileProps, "media"> & { system: System }

const SystemTile = ({
system,
aspectRatio = "landscape",
...props
}: Props) => {
const tileMedia = {
foreground: {
resourceType: "logo",
resourceCollection: "systems",
resourceId: system.id,
}
} as const

return <MediaTile
media={tileMedia}
aspectRatio={aspectRatio}
{...props}
/>
}

export default SystemTile
37 changes: 11 additions & 26 deletions src/renderer/src/components/Scroller/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { CSSProperties, Ref, useEffect, useRef, useState } from "react";
import css from "./Scroller.module.scss"
import { useKeepVisible, useOnInput } from "../../hooks"
import { Input, ScrollType } from "../../enums";
import MediaTile, { TileMedia } from "../MediaTile/MediaTile";
import Label from "../Label/Label";
import { System } from "@common/types/System";
import { Game } from "@common/types";
import SystemTile from "../MediaTile/Presets/SystemTile";
import GameTile from "../MediaTile/Presets/GameTile";

export interface ScrollerProps<T extends Game | System> {
aspectRatio?: "landscape" | "square"
Expand Down Expand Up @@ -59,32 +60,16 @@ export const Scroller = <T extends Game | System>({
const elemIsActive = i === activeIndex
const isSystem = getIsSystem(elem);

const tileMedia: TileMedia = (() => {
if(isSystem) return {
foreground: {
resourceType: "logo",
resourceCollection: "systems",
resourceId: elem.id,
}
}

if(elem.poster
&& (!elem.gameTileDisplayType || elem.gameTileDisplayType === "poster")
&& aspectRatio === "landscape"
) return { background: elem.poster }

return { background: elem.screenshot, foreground: elem.logo, foregroundText: elem.name ?? elem.romname }
})()
const tileProps = {
key: elem.id,
activeRef: activeRef,
active: elemIsActive && isActive,
aspectRatio
}

return (
<MediaTile
media={tileMedia}
key={elem.id}
activeRef={activeRef}
active={elemIsActive && isActive}
aspectRatio={aspectRatio}
/>
)
return isSystem
? <SystemTile system={elem} {...tileProps} />
: <GameTile game={elem} {...tileProps} />
})

useOnInput((input) => {
Expand Down
13 changes: 10 additions & 3 deletions src/renderer/src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { Showcase, ShowcaseContent } from "../../components/Showcase"
import css from "./Home.module.scss"
import { useAtom } from "jotai";
Expand Down Expand Up @@ -48,7 +48,7 @@ export const Home = () => {
}
})()

const scrollers = [
const scrollers = useMemo(() => [
{
id: "continue-playing",
elems: recentlyPlayedGamesList,
Expand Down Expand Up @@ -94,7 +94,14 @@ export const Home = () => {
? "landscape" as const
: "square" as const
})) as ScrollerConfig<Game>[]
]
], [
recentlyPlayedGamesList,
recentlyAddedGamesList,
systemsList,
recentlyViewedGamesList,
collectionsList
])


return (
<div
Expand Down

0 comments on commit 77130c4

Please sign in to comment.