Backfill missing nitros into the Gamedata bundle on import
After each single import, batch import and batch-regen, copy every .nitro file that is missing from /var/www/Gamedata/bundled/furniture so the emulator can render all imported furniture even when the mirror write was skipped. Reports the copied files in the API/SSE response.
This commit is contained in:
1 parent
99e77d9872
commit
bcd24bfca4
4 files changed
+77
-1
No files matched your search
@@ -11,7 +11,12 @@ import {
|
|||||||
buildFurniEntry,
|
buildFurniEntry,
|
||||||
readFurniData,
|
readFurniData,
|
||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import { downloadFile, ensureDirectories } from "@/lib/services/furni-import";
|
import {
|
||||||
|
downloadFile,
|
||||||
|
ensureDirectories,
|
||||||
|
ensureFurniOwnership,
|
||||||
|
syncNitrosToGamedataBundle,
|
||||||
|
} from "@/lib/services/furni-import";
|
||||||
import { getOfficialHabboFurnidata } from "@/lib/services/habbo-furnidata-cache";
|
import { getOfficialHabboFurnidata } from "@/lib/services/habbo-furnidata-cache";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { convertSwfToNitro } from "@/lib/services/swf-to-nitro";
|
import { convertSwfToNitro } from "@/lib/services/swf-to-nitro";
|
||||||
@@ -254,12 +259,20 @@ export const POST = withAdmin(
|
|||||||
/* non-fatal */
|
/* non-fatal */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep regenerated assets readable by the web server
|
||||||
|
const ownership = await ensureFurniOwnership();
|
||||||
|
|
||||||
|
// Backfill any missing nitros into the emulator's Gamedata bundle
|
||||||
|
const nitrosSynced = await syncNitrosToGamedataBundle();
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: "regen_complete",
|
type: "regen_complete",
|
||||||
succeeded,
|
succeeded,
|
||||||
failed,
|
failed,
|
||||||
skipped,
|
skipped,
|
||||||
furniDataAdded: furniDataEntries.length,
|
furniDataAdded: furniDataEntries.length,
|
||||||
|
ownershipFixed: ownership.fixed,
|
||||||
|
nitrosSynced: nitrosSynced.copied,
|
||||||
duration: Date.now() - startTime,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ensureFurniOwnership,
|
ensureFurniOwnership,
|
||||||
importSingleFurni,
|
importSingleFurni,
|
||||||
reconcileImportedOfferIds,
|
reconcileImportedOfferIds,
|
||||||
|
syncNitrosToGamedataBundle,
|
||||||
} from "@/lib/services/furni-import";
|
} from "@/lib/services/furni-import";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import type { ImportSingleResult } from "@/types/furni";
|
import type { ImportSingleResult } from "@/types/furni";
|
||||||
@@ -216,9 +217,11 @@ export const POST = withAdmin(
|
|||||||
// Reconcile offer_id and fix asset ownership across the whole tree
|
// Reconcile offer_id and fix asset ownership across the whole tree
|
||||||
let offerIdsFixed = 0;
|
let offerIdsFixed = 0;
|
||||||
let ownershipFixed: string[] = [];
|
let ownershipFixed: string[] = [];
|
||||||
|
let nitrosSynced: string[] = [];
|
||||||
try {
|
try {
|
||||||
offerIdsFixed = (await reconcileImportedOfferIds()).fixed;
|
offerIdsFixed = (await reconcileImportedOfferIds()).fixed;
|
||||||
ownershipFixed = (await ensureFurniOwnership()).fixed;
|
ownershipFixed = (await ensureFurniOwnership()).fixed;
|
||||||
|
nitrosSynced = (await syncNitrosToGamedataBundle()).copied;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[import-furni] Post-import reconcile/ownership failed:",
|
"[import-furni] Post-import reconcile/ownership failed:",
|
||||||
@@ -233,6 +236,7 @@ export const POST = withAdmin(
|
|||||||
warnings: withWarnings,
|
warnings: withWarnings,
|
||||||
offerIdsFixed,
|
offerIdsFixed,
|
||||||
ownershipFixed,
|
ownershipFixed,
|
||||||
|
nitrosSynced,
|
||||||
duration: Date.now() - startTime,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
importSingleFurni,
|
importSingleFurni,
|
||||||
reconcileImportedOfferIds,
|
reconcileImportedOfferIds,
|
||||||
resetCatalogPageCache,
|
resetCatalogPageCache,
|
||||||
|
syncNitrosToGamedataBundle,
|
||||||
} from "@/lib/services/furni-import";
|
} from "@/lib/services/furni-import";
|
||||||
import { getOfficialHabboFurnidata } from "@/lib/services/habbo-furnidata-cache";
|
import { getOfficialHabboFurnidata } from "@/lib/services/habbo-furnidata-cache";
|
||||||
import { listFurniture } from "@/lib/services/habbofurni";
|
import { listFurniture } from "@/lib/services/habbofurni";
|
||||||
@@ -411,6 +412,9 @@ export const POST = withAdmin(
|
|||||||
// Keep asset + furnidata folders readable by the web server
|
// Keep asset + furnidata folders readable by the web server
|
||||||
const ownership = await ensureFurniOwnership();
|
const ownership = await ensureFurniOwnership();
|
||||||
|
|
||||||
|
// Backfill any missing nitros into the emulator's Gamedata bundle
|
||||||
|
const nitrosSynced = await syncNitrosToGamedataBundle();
|
||||||
|
|
||||||
const succeeded = results.filter((r) => r.ok).length;
|
const succeeded = results.filter((r) => r.ok).length;
|
||||||
const failed = results.filter((r) => !r.ok).length;
|
const failed = results.filter((r) => !r.ok).length;
|
||||||
|
|
||||||
@@ -421,6 +425,7 @@ export const POST = withAdmin(
|
|||||||
results,
|
results,
|
||||||
offerIdsFixed: reconciled.fixed,
|
offerIdsFixed: reconciled.fixed,
|
||||||
ownershipFixed: ownership.fixed,
|
ownershipFixed: ownership.fixed,
|
||||||
|
nitrosSynced: nitrosSynced.copied,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +481,9 @@ export const POST = withAdmin(
|
|||||||
// Keep asset + furnidata folders readable by the web server
|
// Keep asset + furnidata folders readable by the web server
|
||||||
const ownership = await ensureFurniOwnership();
|
const ownership = await ensureFurniOwnership();
|
||||||
|
|
||||||
|
// Backfill any missing nitros into the emulator's Gamedata bundle
|
||||||
|
const nitrosSynced = await syncNitrosToGamedataBundle();
|
||||||
|
|
||||||
logAudit({
|
logAudit({
|
||||||
userId: ctx.session.user.id,
|
userId: ctx.session.user.id,
|
||||||
action: "furni_import",
|
action: "furni_import",
|
||||||
@@ -498,6 +506,7 @@ export const POST = withAdmin(
|
|||||||
catalogItemId: result.catalogItemId,
|
catalogItemId: result.catalogItemId,
|
||||||
offerIdsFixed: reconciled.fixed,
|
offerIdsFixed: reconciled.fixed,
|
||||||
ownershipFixed: ownership.fixed,
|
ownershipFixed: ownership.fixed,
|
||||||
|
nitrosSynced: nitrosSynced.copied,
|
||||||
spriteCount: result.spriteCount,
|
spriteCount: result.spriteCount,
|
||||||
warnings: result.warnings,
|
warnings: result.warnings,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
DEFAULT_FURNI_SWF_DIR,
|
DEFAULT_FURNI_SWF_DIR,
|
||||||
getFurniAssetDirs,
|
getFurniAssetDirs,
|
||||||
getFurniAssetWriteTargets,
|
getFurniAssetWriteTargets,
|
||||||
|
getGamedataRoot,
|
||||||
} from "@/lib/services/furni-asset-dirs";
|
} from "@/lib/services/furni-asset-dirs";
|
||||||
import {
|
import {
|
||||||
appendFurniEntry,
|
appendFurniEntry,
|
||||||
@@ -346,6 +347,55 @@ export async function ensureFurniOwnership(): Promise<{ fixed: string[] }> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy every .nitro file that is missing from the emulator's Gamedata
|
||||||
|
* bundle directory (/var/www/Gamedata/bundled/furniture) so all imported
|
||||||
|
* furniture stays renderable in-game even when a mirror write was skipped.
|
||||||
|
*/
|
||||||
|
export async function syncNitrosToGamedataBundle(): Promise<{
|
||||||
|
copied: string[];
|
||||||
|
}> {
|
||||||
|
if (process.platform === "win32") return { copied: [] };
|
||||||
|
|
||||||
|
const gamedataRoot = await getGamedataRoot();
|
||||||
|
if (!gamedataRoot) return { copied: [] };
|
||||||
|
|
||||||
|
const targetDir = path.join(gamedataRoot, "bundled/furniture");
|
||||||
|
const { nitroDir } = await getFurniAssetDirs();
|
||||||
|
if (nitroDir === targetDir) return { copied: [] };
|
||||||
|
if (!existsSync(nitroDir)) return { copied: [] };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(targetDir, { recursive: true });
|
||||||
|
} catch {
|
||||||
|
return { copied: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const copied: string[] = [];
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(nitroDir);
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.endsWith(".nitro")) continue;
|
||||||
|
const dest = path.join(targetDir, entry);
|
||||||
|
if (existsSync(dest)) continue;
|
||||||
|
try {
|
||||||
|
await fs.copyFile(path.join(nitroDir, entry), dest);
|
||||||
|
copied.push(entry);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn("[import-furni] nitro sync copy failed", {
|
||||||
|
entry,
|
||||||
|
error: (err as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn("[import-furni] nitro sync scan failed", {
|
||||||
|
error: (err as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { copied };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate the next `catalog_items.id` and run the INSERT through a
|
* Allocate the next `catalog_items.id` and run the INSERT through a
|
||||||
* process-level async mutex so concurrent imports in the same Node process
|
* process-level async mutex so concurrent imports in the same Node process
|
||||||
|
|||||||
Reference in new issue
Block a user