feat: add .nitro upload + sync-all + repair-icons features
- Upload .nitro bundles directly with full DB, catalog, and furnidata integration - Generate SQL migration files on upload (optional) - Auto-sync missing furniture from all configured clone sources (SSE batch) - Repair missing icons by extracting from local .nitro or downloading from sources - All behind ASSETS_IMPORT permission
This commit is contained in:
1 parent
c9144dc293
commit
804daeffca
13 files changed
+1484
-1
No files matched your search
@@ -0,0 +1,126 @@
|
||||
import { existsSync, promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { extractFurniIconPng } from "@/lib/services/clone-icon";
|
||||
import { listSources } from "@/lib/services/clone-sources";
|
||||
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { ensureDirectories } from "./furni-import";
|
||||
|
||||
export interface RepairEvent {
|
||||
type: "item_progress" | "batch_complete" | "error";
|
||||
classname?: string;
|
||||
status?: string;
|
||||
index?: number;
|
||||
message?: string;
|
||||
warnings?: string[];
|
||||
succeeded?: number;
|
||||
failed?: number;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export async function repairMissingIcons(
|
||||
onEvent?: (evt: RepairEvent) => void,
|
||||
): Promise<{ succeeded: number; failed: number }> {
|
||||
await ensureDirectories();
|
||||
const { iconDir, nitroDir } = await getFurniAssetDirs();
|
||||
|
||||
const items = await prisma.$queryRaw<
|
||||
Array<{ id: number; item_name: string }>
|
||||
>`SELECT id, item_name FROM items_base ORDER BY id`;
|
||||
|
||||
if (items.length === 0) {
|
||||
onEvent?.({
|
||||
type: "batch_complete",
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
duration: 0,
|
||||
});
|
||||
return { succeeded: 0, failed: 0 };
|
||||
}
|
||||
|
||||
let succeeded = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const { item_name: classname } = items[i];
|
||||
const iconPath = path.join(iconDir, `${classname}_icon.png`);
|
||||
|
||||
if (existsSync(iconPath)) continue;
|
||||
|
||||
onEvent?.({
|
||||
type: "item_progress",
|
||||
classname,
|
||||
status: "started",
|
||||
index: i,
|
||||
});
|
||||
|
||||
let ok = false;
|
||||
const warnings: string[] = [];
|
||||
|
||||
const nitroPath = path.join(nitroDir, `${classname}.nitro`);
|
||||
if (existsSync(nitroPath)) {
|
||||
try {
|
||||
const nitroBuf = await fs.readFile(nitroPath);
|
||||
const icon = extractFurniIconPng(nitroBuf);
|
||||
if (icon) {
|
||||
await fs.writeFile(iconPath, icon);
|
||||
ok = true;
|
||||
} else {
|
||||
warnings.push("no icon asset in .nitro bundle");
|
||||
}
|
||||
} catch (err) {
|
||||
warnings.push(`extract from .nitro failed: ${(err as Error).message}`);
|
||||
}
|
||||
} else {
|
||||
warnings.push(".nitro file not found on disk");
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
const sources = await listSources();
|
||||
for (const source of sources) {
|
||||
try {
|
||||
const dl = await downloadFile(
|
||||
`${source.iconBaseUrl}/${classname}_icon.png`,
|
||||
iconPath,
|
||||
{ maxRetries: 1, validate: "png" },
|
||||
);
|
||||
if (dl.ok) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
warnings.push(`download from ${source.name} failed`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
succeeded++;
|
||||
onEvent?.({
|
||||
type: "item_progress",
|
||||
classname,
|
||||
status: "done",
|
||||
index: i,
|
||||
warnings: warnings.length ? warnings : undefined,
|
||||
});
|
||||
} else {
|
||||
failed++;
|
||||
onEvent?.({
|
||||
type: "item_progress",
|
||||
classname,
|
||||
status: "failed",
|
||||
index: i,
|
||||
message: warnings[0] || "could not obtain icon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onEvent?.({
|
||||
type: "batch_complete",
|
||||
succeeded,
|
||||
failed,
|
||||
});
|
||||
|
||||
return { succeeded, failed };
|
||||
}
|
||||
Reference in new issue
Block a user