mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
Make downloaded game assets persistent and offline-friendly
This commit is contained in:
@@ -70,8 +70,13 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refreshInstalledGames = async () => {
|
const refreshInstalledGames = async () => {
|
||||||
|
try {
|
||||||
const availableResponse = await sendNodecgMessage<RemoteGame[]>('scoreko-assets:listRemoteGames');
|
const availableResponse = await sendNodecgMessage<RemoteGame[]>('scoreko-assets:listRemoteGames');
|
||||||
availableGames.value = Array.isArray(availableResponse) ? availableResponse : [];
|
availableGames.value = Array.isArray(availableResponse) ? availableResponse : [];
|
||||||
|
} catch {
|
||||||
|
availableGames.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
||||||
installedGames.value = Array.isArray(response) ? response : [];
|
installedGames.value = Array.isArray(response) ? response : [];
|
||||||
const configResponse = await sendNodecgMessage<{ assetsBaseUrl?: string }>('scoreko-assets:getAssetsBaseUrl');
|
const configResponse = await sendNodecgMessage<{ assetsBaseUrl?: string }>('scoreko-assets:getAssetsBaseUrl');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { nodecg } from './util/nodecg.js';
|
import { nodecg } from './util/nodecg.js';
|
||||||
@@ -35,7 +35,36 @@ type HttpGameTitlesFile = Record<string, unknown> | HttpGameTitleEntry[];
|
|||||||
|
|
||||||
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const bundleRoot = path.resolve(extensionDir, '..');
|
const bundleRoot = path.resolve(extensionDir, '..');
|
||||||
const assetsRoot = path.join(bundleRoot, 'game-assets');
|
const legacyAssetsRoot = path.join(bundleRoot, 'game-assets');
|
||||||
|
const nodecgRoot = path.resolve(bundleRoot, '..', '..');
|
||||||
|
const assetsRoot = path.join(nodecgRoot, 'db', `${nodecg.bundleName}-game-assets`);
|
||||||
|
|
||||||
|
let assetsStorageReady = false;
|
||||||
|
|
||||||
|
const ensureAssetsStorageReady = async () => {
|
||||||
|
if (assetsStorageReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await mkdir(path.dirname(assetsRoot), { recursive: true });
|
||||||
|
|
||||||
|
const [currentStats, legacyStats] = await Promise.all([
|
||||||
|
stat(assetsRoot).catch(() => null),
|
||||||
|
stat(legacyAssetsRoot).catch(() => null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!currentStats && legacyStats?.isDirectory()) {
|
||||||
|
await rename(legacyAssetsRoot, assetsRoot).catch(async () => {
|
||||||
|
await mkdir(assetsRoot, { recursive: true });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await mkdir(assetsRoot, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
assetsStorageReady = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ensureAssetsStorageReady();
|
||||||
|
|
||||||
const assetsRouter = nodecg.Router();
|
const assetsRouter = nodecg.Router();
|
||||||
|
|
||||||
@@ -240,10 +269,21 @@ const listHttpFiles = async (game: RemoteGame): Promise<AssetFileEntry[]> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listInstalledGames = async () => {
|
const listInstalledGames = async () => {
|
||||||
|
await ensureAssetsStorageReady();
|
||||||
const entries = await readdir(assetsRoot, { withFileTypes: true }).catch(() => []);
|
const entries = await readdir(assetsRoot, { withFileTypes: true }).catch(() => []);
|
||||||
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listInstalledGamesAsRemote = async (): Promise<RemoteGame[]> => {
|
||||||
|
const installedGames = await listInstalledGames();
|
||||||
|
return installedGames.map((slug) => ({
|
||||||
|
slug,
|
||||||
|
repoFolder: slug,
|
||||||
|
title: titleFromSlug(slug),
|
||||||
|
logoFile: `${slug}.png`,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const parseCharacterNames = (content: string, gameTitle: string) => {
|
const parseCharacterNames = (content: string, gameTitle: string) => {
|
||||||
const parsed = JSON.parse(content) as unknown;
|
const parsed = JSON.parse(content) as unknown;
|
||||||
const names = Array.isArray(parsed)
|
const names = Array.isArray(parsed)
|
||||||
@@ -277,6 +317,7 @@ const listInstalledCharacterNamesByGame = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const downloadGameAssets = async (gameSlug: string) => {
|
const downloadGameAssets = async (gameSlug: string) => {
|
||||||
|
await ensureAssetsStorageReady();
|
||||||
const customTitles = await fetchCustomGameTitles();
|
const customTitles = await fetchCustomGameTitles();
|
||||||
const game: RemoteGame = {
|
const game: RemoteGame = {
|
||||||
slug: gameSlug,
|
slug: gameSlug,
|
||||||
@@ -330,6 +371,7 @@ const downloadGameAssets = async (gameSlug: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeGameAssets = async (gameSlug: string) => {
|
const removeGameAssets = async (gameSlug: string) => {
|
||||||
|
await ensureAssetsStorageReady();
|
||||||
const customTitles = await fetchCustomGameTitles();
|
const customTitles = await fetchCustomGameTitles();
|
||||||
const destinationFolder = path.join(assetsRoot, gameSlug);
|
const destinationFolder = path.join(assetsRoot, gameSlug);
|
||||||
await rm(destinationFolder, { recursive: true, force: true });
|
await rm(destinationFolder, { recursive: true, force: true });
|
||||||
@@ -346,8 +388,19 @@ nodecg.listenFor('scoreko-assets:listRemoteGames', async (_payload: unknown, ack
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ack(null, await listRemoteGames());
|
const remoteGames = await listRemoteGames();
|
||||||
|
ack(null, remoteGames);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
const installedGames = await listInstalledGamesAsRemote();
|
||||||
|
if (installedGames.length > 0) {
|
||||||
|
ack(null, installedGames);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
ack((error as Error).message);
|
ack((error as Error).message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user