mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Cache ModInfo to reduce startup time (#606)
Store and use a cache of the loaded ModInfo data to mod-info-cache.json in the mods directory during startup. For simplicity this does not cache mods added to the controller or host after startup, these get added to the cache on the next startup.
This commit is contained in:
1 parent
5dcc66034b
commit
7625fd82e3
4 files changed
+49
No files matched your search
@@ -1,8 +1,10 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import { Static } from "@sinclair/typebox";
|
||||
import { logger } from "./logging";
|
||||
import { ModInfo } from "./data";
|
||||
import TypedEventEmitter from "./TypedEventEmitter";
|
||||
import { safeOutputFile } from "./file_ops";
|
||||
|
||||
export interface ModStoreEvents {
|
||||
/** A stored mod was created, updated or deleted */
|
||||
@@ -74,6 +76,21 @@ export default class ModStore extends TypedEventEmitter<keyof ModStoreEvents, Mo
|
||||
}
|
||||
|
||||
static async fromDirectory(modsDirectory: string) {
|
||||
type ModInfoCache = Static<typeof ModInfo.jsonSchema>[];
|
||||
const cacheFilename = path.join(modsDirectory, "mod-info-cache.json");
|
||||
let cache = new Map<string, ModInfo>();
|
||||
try {
|
||||
const content = await fs.readFile(cacheFilename, "utf8");
|
||||
cache = new Map((JSON.parse(content) as ModInfoCache).map(item => {
|
||||
const mod = ModInfo.fromJSON(item);
|
||||
return [mod.filename, mod];
|
||||
}));
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
logger.warn(`Error loading ${cacheFilename}:\n${err.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
const files = new Map<string, ModInfo>();
|
||||
for (let entry of await fs.readdir(modsDirectory, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
@@ -81,10 +98,23 @@ export default class ModStore extends TypedEventEmitter<keyof ModStoreEvents, Mo
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.name.toLowerCase().endsWith(".zip")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.name.toLowerCase().endsWith(".tmp.zip")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cachedModInfo = cache.get(entry.name);
|
||||
if (cachedModInfo) {
|
||||
const stat = await fs.stat(path.join(modsDirectory, entry.name));
|
||||
if (cachedModInfo.mtimeMs === stat.mtimeMs) {
|
||||
files.set(cachedModInfo.filename, cachedModInfo);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Loading info for Mod ${entry.name}`);
|
||||
let modInfo: ModInfo;
|
||||
try {
|
||||
@@ -96,6 +126,12 @@ export default class ModStore extends TypedEventEmitter<keyof ModStoreEvents, Mo
|
||||
files.set(modInfo.filename, modInfo);
|
||||
}
|
||||
|
||||
if (files.size) {
|
||||
logger.info(`Loaded info for ${files.size} mods`);
|
||||
const content: ModInfoCache = [...files.values()].map(mod => mod.toJSON());
|
||||
await safeOutputFile(cacheFilename, JSON.stringify(content));
|
||||
}
|
||||
|
||||
return new this(modsDirectory, files);
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,11 @@ export default class ModInfo {
|
||||
*/
|
||||
size = 0;
|
||||
|
||||
/**
|
||||
* Last modification time of the file.
|
||||
*/
|
||||
mtimeMs = 0;
|
||||
|
||||
/**
|
||||
* SHA1 hash of this mod
|
||||
*/
|
||||
@@ -147,6 +152,7 @@ export default class ModInfo {
|
||||
static jsonSchema = Type.Object({
|
||||
...this.infoJsonSchema.properties,
|
||||
"size": Type.Optional(Type.Integer()),
|
||||
"mtime_ms": Type.Optional(Type.Number()),
|
||||
"sha1": Type.Optional(Type.String()),
|
||||
"updated_at_ms": Type.Optional(Type.Number()),
|
||||
"is_deleted": Type.Optional(Type.Boolean()),
|
||||
@@ -170,6 +176,7 @@ export default class ModInfo {
|
||||
|
||||
// Additional data
|
||||
if (json.size) { modInfo.size = json.size; }
|
||||
if (json.mtime_ms) { modInfo.mtimeMs = json.mtime_ms; }
|
||||
if (json.sha1) { modInfo.sha1 = json.sha1; }
|
||||
if (json.updated_at_ms) { modInfo.updatedAtMs = json.updated_at_ms; }
|
||||
if (json.is_deleted) { modInfo.isDeleted = json.is_deleted; }
|
||||
@@ -192,6 +199,7 @@ export default class ModInfo {
|
||||
json.dependencies = this.dependencies;
|
||||
}
|
||||
if (this.size) { json.size = this.size; }
|
||||
if (this.mtimeMs) { json.mtime_ms = this.mtimeMs; }
|
||||
if (this.sha1) { json.sha1 = this.sha1; }
|
||||
if (this.updatedAtMs) { json.updated_at_ms = this.updatedAtMs; }
|
||||
if (this.isDeleted) { json.is_deleted = this.isDeleted; }
|
||||
@@ -229,6 +237,7 @@ export default class ModInfo {
|
||||
|
||||
size: stat.size,
|
||||
sha1: await libHash.hashFile(modPath),
|
||||
mtime_ms: stat.mtimeMs,
|
||||
updated_at_ms: stat.mtimeMs,
|
||||
is_deleted: false,
|
||||
});
|
||||
|
||||
@@ -1054,6 +1054,7 @@ describe("Integration of Clusterio", function() {
|
||||
"factorioVersion: 1.1\n" +
|
||||
"dependencies:\n" +
|
||||
`size: ${stat.size}\n` +
|
||||
`mtimeMs: ${stat.mtimeMs}\n` +
|
||||
`sha1: ${hash}\n` +
|
||||
`updatedAtMs: ${stat.mtimeMs}\n` +
|
||||
"isDeleted: false\n",
|
||||
|
||||
@@ -33,6 +33,7 @@ describe("lib/data/ModInfo", function() {
|
||||
check(ModInfo.fromJSON({ dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"] }));
|
||||
check(ModInfo.fromJSON({ filename: "MyMod_1.0.0.zip" }));
|
||||
check(ModInfo.fromJSON({ size: 1024 }));
|
||||
check(ModInfo.fromJSON({ mtime_ms: new Date(2024, 0, 1).getTime() }));
|
||||
check(ModInfo.fromJSON({ sha1: "verified-as-MyMod" }));
|
||||
check(ModInfo.fromJSON({ is_deleted: true }));
|
||||
|
||||
@@ -49,6 +50,7 @@ describe("lib/data/ModInfo", function() {
|
||||
dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"],
|
||||
filename: "MyMod_1.0.0.zip",
|
||||
size: 1024,
|
||||
mtime_ms: new Date(2024, 0, 1).getTime(),
|
||||
sha1: "verified-as-MyMod",
|
||||
is_deleted: true,
|
||||
}));
|
||||
@@ -92,6 +94,7 @@ describe("lib/data/ModInfo", function() {
|
||||
dependencies: [],
|
||||
filename: "empty_mod_1.0.0.zip",
|
||||
size: stat.size,
|
||||
mtime_ms: stat.mtimeMs,
|
||||
updated_at_ms: stat.mtimeMs,
|
||||
sha1: hash,
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user