mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Merge branch 'master' into feature/plugin-template
This commit is contained in:
6 files changed
+51
-1
No files matched your search
@@ -3,3 +3,4 @@ coverage:
|
||||
project:
|
||||
default:
|
||||
if_ci_failed: success
|
||||
informational: true
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ function TransferModal(props: ModalProps) {
|
||||
autoFocus
|
||||
showSearch
|
||||
filterOption={(input, option) => (
|
||||
(option?.title.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0
|
||||
(option?.title!.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0
|
||||
)}
|
||||
>
|
||||
{[...instances.values()].filter(
|
||||
|
||||
@@ -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