Add hosts, instances, systems

This commit is contained in:
Cooldude2606 committed 2024-05-22 16:39:22 +01:00
1 parent 509fab358a
commit 609db31027
10 files changed
+135 -203

No files matched your search

+4 -4
View File
@@ -66,9 +66,9 @@ void new lib.Gauge(
if (!controller || !controller.hosts) {
return;
}
for (let [id, host] of controller.hosts) {
for (const host of controller.hosts.values()) {
gauge.labels({
host_id: String(id),
host_id: String(host.id),
host_name: host.name,
}).set(1);
};
@@ -86,9 +86,9 @@ void new lib.Gauge(
if (!controller || !controller.instances) {
return;
}
for (let [id, instance] of controller.instances) {
for (const instance of controller.instances.values()) {
gauge.labels({
instance_id: String(id),
instance_id: String(instance.id),
instance_name: String(instance.config.get("instance.name")),
host_id: String(instance.config.get("instance.assigned_host")),
}).set(1);
+3 -3
View File
@@ -195,7 +195,7 @@ export default class ControlConnection extends BaseConnection {
}
async handleHostRevokeTokensRequest(request: lib.HostRevokeTokensRequest) {
const host = this._controller.hosts.get(request.hostId);
const host = this._controller.hosts.getMutable(request.hostId);
if (!host) {
throw new Error(`Unknown host id (${request.hostId})`);
}
@@ -207,7 +207,7 @@ export default class ControlConnection extends BaseConnection {
hostConnection.connector.terminate();
}
this._controller.hostsUpdated([host]);
this._controller.hosts.set(host);
}
async handleHostListRequest(): Promise<lib.HostDetails[]> {
@@ -410,7 +410,7 @@ export default class ControlConnection extends BaseConnection {
async handleModPackDeleteRequest(request: lib.ModPackDeleteRequest) {
let { id } = request;
let modPack = this._controller.modPacks.getCopy(id);
let modPack = this._controller.modPacks.getMutable(id);
if (!modPack) {
throw new lib.RequestError(`Mod pack with ID ${id} does not exist`);
}
+64 -130
View File
@@ -55,13 +55,6 @@ export default class Controller {
config: lib.ControllerConfig;
app: Application;
/** True if (@link Controller.systems} has changed since last time it was saved */
systemsDirty = false;
/** True if {@link Controller.hosts} has changed since last time it was saved */
hostsDirty = false;
/** True if {@link Controller.instances} has changed since last time it was saved */
instancesDirty = false;
httpServer: http.Server | null = null;
httpServerCloser: HttpCloser | null = null;
httpsServer: https.Server | null = null;
@@ -107,16 +100,31 @@ export default class Controller {
let databaseDirectory = config.get("controller.database_directory");
await fs.ensureDir(databaseDirectory);
const systems = await Controller.loadSystems(path.join(databaseDirectory, "systems.json"));
const hosts = await Controller.loadHosts(path.join(databaseDirectory, "hosts.json"));
const instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json"));
const systems = new lib.SubscribableDatastore(...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "systems.json"),
lib.SystemInfo.fromJSON
).bootstrap());
const hosts = new lib.SubscribableDatastore(...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "hosts.json"),
HostInfo.fromJSON,
this.migrateHosts, this.finaliseHosts,
).bootstrap());
const instances = new lib.SubscribableDatastore(...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "instances.json"),
json => InstanceInfo.fromJSON(json, "controller"),
this.migrateInstances, this.finaliseInstances,
).bootstrap());
const saves = new lib.SubscribableDatastore(...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "saves.json"), lib.SaveDetails
path.join(databaseDirectory, "saves.json"),
lib.SaveDetails.fromJSON
).bootstrap());
const modPacks = new lib.SubscribableDatastore(...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "mod-packs.json"), lib.ModPack
path.join(databaseDirectory, "mod-packs.json"),
lib.ModPack.fromJSON
).bootstrap());
const userManager = new UserManager(config);
@@ -148,11 +156,11 @@ export default class Controller {
* will restart the controller on non-zero exit codes.
*/
public canRestart: boolean = false,
public systems = new Map<lib.SystemInfo["id"], lib.SystemInfo>(),
public systems = new lib.SubscribableDatastore<lib.SystemInfo>(),
/** Mapping of host id to host info */
public hosts = new Map<number, HostInfo>(),
public hosts = new lib.SubscribableDatastore<HostInfo>(),
/** Mapping of instance id to instance info */
public instances = new Map<number, InstanceInfo>(),
public instances = new lib.SubscribableDatastore<InstanceInfo>(),
/** Mapping of save id to save details */
public saves = new lib.SubscribableDatastore<lib.SaveDetails>(),
/** Mapping of mod pack id to mod pack */
@@ -182,17 +190,18 @@ export default class Controller {
this.subscriptions.handle(lib.SystemInfoUpdateEvent, this.handleSystemInfoSubscription.bind(this));
this.subscriptions.handle(lib.HostUpdatesEvent, this.handleHostSubscription.bind(this));
this.subscriptions.handle(lib.InstanceDetailsUpdatesEvent, this.handleInstanceDetailsSubscription.bind(this));
this.subscriptions.handle(
lib.InstanceSaveDetailsUpdatesEvent, this.handleInstanceSaveDetailsSubscription.bind(this)
);
this.saves.on("update", this.savesUpdated.bind(this));
this.subscriptions.handle(lib.ModPackUpdatesEvent, this.handleModPackSubscription.bind(this));
this.modPacks.on("update", this.modPacksUpdated.bind(this));
this.subscriptions.handle(lib.ModUpdatesEvent, this.handleModSubscription.bind(this));
this.subscriptions.handle(lib.UserUpdatesEvent, this.handleUserSubscription.bind(this));
// Handle updates for datastores
this.hosts.on("update", this.hostsUpdated.bind(this));
this.instances.on("update", this.instanceDetailsUpdated.bind(this));
this.saves.on("update", this.savesUpdated.bind(this));
this.modPacks.on("update", this.modPacksUpdated.bind(this));
}
async start(args: ControllerArgs) {
@@ -509,9 +518,8 @@ export default class Controller {
requests.push(lib.gatherSystemInfo("controller", this.canRestart));
const newMetrics = await Promise.all(requests);
for (const metric of newMetrics) {
this.systems.set(metric.id, metric);
this.systems.set(metric);
}
this.systemsDirty = true;
this.subscriptions.broadcast(new lib.SystemInfoUpdateEvent(newMetrics));
} catch (err: any) {
logger.error(`Unexpected error updating system infos:\n${err.stack ?? err.message}`);
@@ -554,21 +562,10 @@ export default class Controller {
}
let databaseDirectory = this.config.get("controller.database_directory");
if (this.systemsDirty) {
this.systemsDirty = false;
await Controller.saveSystems(path.join(databaseDirectory, "systems.json"), this.systems);
}
if (this.hostsDirty) {
this.hostsDirty = false;
await Controller.saveHosts(path.join(databaseDirectory, "hosts.json"), this.hosts);
}
if (this.instancesDirty) {
this.instancesDirty = false;
await Controller.saveInstances(path.join(databaseDirectory, "instances.json"), this.instances);
}
await this.systems.save();
await this.hosts.save();
await this.instances.save();
await this.saves.save();
await this.modPacks.save();
@@ -579,92 +576,42 @@ export default class Controller {
await lib.invokeHook(this.plugins, "onSaveData");
}
static async loadSystems(filePath: string): Promise<Map<lib.SystemInfo["id"], lib.SystemInfo>> {
let json: Static<typeof lib.SystemInfo.jsonSchema>[];
try {
json = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
} catch (err: any) {
if (err.code !== "ENOENT") {
throw err;
}
return new Map();
}
return new Map(json.map(m => lib.SystemInfo.fromJSON(m)).map(m => [m.id, m]));
}
static async saveSystems(filePath: string, systems: Map<lib.SystemInfo["id"], lib.SystemInfo>) {
await lib.safeOutputFile(filePath, JSON.stringify([...systems.values()], null, "\t"));
}
static async loadHosts(filePath: string): Promise<Map<number, HostInfo>> {
let serialized: [number, Static<typeof HostInfo.jsonSchema>][];
try {
serialized = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
} catch (err: any) {
if (err.code !== "ENOENT") {
throw err;
}
return new Map();
}
static migrateHosts(rawJson: unknown[]): Static<typeof HostInfo.jsonSchema>[] {
const serialized = rawJson as Static<typeof HostInfo.jsonSchema>[];
// TODO: migrate remove after release.
if (serialized.length && !(serialized[0] instanceof Array)) {
return new Map(); // Discard old format.
return []; // Discard old format.
}
const hosts = new Map();
for (let [id, json] of serialized) {
const host = HostInfo.fromJSON(json);
if (host.connected) {
host.connected = false;
host.updatedAtMs = Date.now();
}
hosts.set(id, host);
return serialized;
}
static finaliseHosts(host: HostInfo): HostInfo {
if (host.connected) {
host.connected = false;
host.updatedAtMs = Date.now();
}
return hosts;
return host;
}
static async saveHosts(filePath: string, hosts: Map<number, HostInfo>) {
await lib.safeOutputFile(filePath, JSON.stringify([...hosts.entries()], null, "\t"));
static migrateInstances(rawJson: unknown[]): Static<typeof InstanceInfo.jsonSchema>[] {
const serialized = rawJson as Static<typeof InstanceInfo.jsonSchema>[];
return serialized.map(json => {
if (!json.config) { // migrate: from pre Alpha 14 format.
return { config: json as any, status: "running" }; // Use running to force updatedAtMs
}
return json;
});
}
static async loadInstances(filePath: string): Promise<Map<number, InstanceInfo>> {
logger.info(`Loading ${filePath}`);
let instances = new Map();
try {
let serialized = JSON.parse(
await fs.readFile(filePath, { encoding: "utf8" })
) as Static<typeof InstanceInfo.jsonSchema>[];
for (let json of serialized) {
if (!json.config) { // migrate: from pre Alpha 14 format.
json = { config: json as any, status: "running" }; // Use running to force updatedAtMs
}
const instance = InstanceInfo.fromJSON(json, "controller");
const status = instance.config.get("instance.assigned_host") === null ? "unassigned" : "unknown";
if (instance.status !== status) {
instance.status = status;
instance.updatedAtMs = Date.now();
}
instances.set(instance.id, instance);
}
} catch (err: any) {
if (err.code !== "ENOENT") {
throw err;
}
return new Map();
static finaliseInstances(instance: InstanceInfo): InstanceInfo {
const status = instance.config.get("instance.assigned_host") === null ? "unassigned" : "unknown";
if (instance.status !== status) {
instance.status = status;
instance.updatedAtMs = Date.now();
}
return instances;
}
static async saveInstances(filePath: string, instances: Map<number, InstanceInfo>) {
let serialized = [...instances.values()];
await lib.safeOutputFile(filePath, JSON.stringify(serialized, null, "\t"));
return instance;
}
static async loadJsonObject(filePath: string, throwOnMissing: boolean = false): Promise<any> {
@@ -782,11 +729,6 @@ export default class Controller {
}
hostsUpdated(hosts: HostInfo[]) {
const now = Date.now();
for (const host of hosts) {
host.updatedAtMs = now;
}
this.hostsDirty = true;
let updates = hosts.map(host => host.toHostDetails());
this.subscriptions.broadcast(new lib.HostUpdatesEvent(updates));
}
@@ -863,11 +805,9 @@ export default class Controller {
instanceConfig.set("factorio.settings", settings);
let instance = new InstanceInfo(instanceConfig, "unassigned", undefined, Date.now());
this.instances.set(instanceId, instance);
this.instancesDirty = true;
this.instances.set(instance);
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance);
this.addInstanceHooks(instance);
this.instanceDetailsUpdated([instance]);
return instance;
}
@@ -924,7 +864,6 @@ export default class Controller {
// Assign to target
instance.config.set("instance.assigned_host", hostId ?? null);
// "fieldChanged" event handler will set this.instancesDirty
if (hostId !== undefined && newHostConnection) {
await newHostConnection.send(
new lib.InstanceAssignInternalRequest(instanceId, instance.config.toRemote("host"))
@@ -932,8 +871,7 @@ export default class Controller {
} else {
instance.status = "unassigned";
}
instance.updatedAtMs = Date.now();
this.instanceDetailsUpdated([instance]);
this.instances.set(instance);
}
/**
@@ -950,16 +888,13 @@ export default class Controller {
if (hostId !== null) {
await this.sendTo({ hostId }, new lib.InstanceDeleteInternalRequest(instanceId));
}
this.instances.delete(instanceId);
this.instancesDirty = true;
let prev = instance.status;
this.instances.delete(instance);
this.clearSavesOfInstance(instanceId);
this.userManager.clearStatsOfInstance(instanceId);
let prev = instance.status;
instance.status = "deleted";
instance.updatedAtMs = Date.now();
this.instanceDetailsUpdated([instance]);
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance, prev);
}
@@ -988,10 +923,9 @@ export default class Controller {
instance.config.on("fieldChanged", (field: string, curr: any, prev: any) => {
instance.updatedAtMs = Date.now();
if (field === "instance.name") {
this.instanceDetailsUpdated([instance]);
this.instances.set(instance); // Trigger updated logic
}
this.instancesDirty = true;
lib.invokeHook(this.plugins, "onInstanceConfigFieldChanged", instance, field, curr, prev);
});
}
+11 -20
View File
@@ -42,8 +42,7 @@ export default class HostConnection extends BaseConnection {
0,
false,
);
this._controller.hosts.set(this.id, this.info);
this._controller.hostsUpdated([this.info]);
this._controller.hosts.set(this.info);
this._checkPluginVersions();
@@ -57,10 +56,9 @@ export default class HostConnection extends BaseConnection {
}
this.connector.on("close", () => {
const now = Date.now();
// Update status to unknown for instances on this host.
const instances: InstanceInfo[] = [];
for (let instance of this._controller.instances.values()) {
for (let instance of this._controller.instances.valuesMutable()) {
if (instance.config.get("instance.assigned_host") !== this.id) {
continue;
}
@@ -68,13 +66,12 @@ export default class HostConnection extends BaseConnection {
instances.push(instance);
let prev = instance.status;
instance.status = "unknown";
instance.updatedAtMs = now;
lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
}
this._controller.instanceDetailsUpdated(instances);
this._controller.instances.setMany(instances);
this.info.connected = false;
this._controller.hostsUpdated([this.info]);
this._controller.hosts.set(this.info);
});
this.handle(lib.HostInfoUpdateEvent, this.handleHostInfoUpdateEvent.bind(this));
@@ -158,11 +155,11 @@ export default class HostConnection extends BaseConnection {
async handleHostInfoUpdateEvent(event: lib.HostInfoUpdateEvent) {
this.info.name = event.update.name;
this.info.publicAddress = event.update.publicAddress;
this._controller.hostsUpdated([this.info]);
this._controller.hosts.set(this.info);
}
async handleInstanceStatusChangedEvent(request: lib.InstanceStatusChangedEvent) {
let instance = this._controller.instances.get(request.instanceId);
let instance = this._controller.instances.getMutable(request.instanceId);
// It's possible to get updates from an instance that does not exist
// or is not assigned to the host it originated from if it was
@@ -188,9 +185,8 @@ export default class HostConnection extends BaseConnection {
let prev = instance.status;
instance.status = request.status;
instance.gamePort = request.gamePort;
instance.updatedAtMs = Date.now();
this._controller.instances.set(instance);
logger.verbose(`Instance ${instance.config.get("instance.name")} State: ${instance.status}`);
this._controller.instanceDetailsUpdated([instance]);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
}
@@ -210,7 +206,7 @@ export default class HostConnection extends BaseConnection {
const instanceConfig = new lib.InstanceConfig("controller");
instanceConfig.update(instanceData.config, false, "host");
let controllerInstance = this._controller.instances.get(instanceConfig.get("instance.id"));
let controllerInstance = this._controller.instances.getMutable(instanceConfig.get("instance.id"));
if (controllerInstance) {
// Check if this instance is assigned somewhere else.
if (controllerInstance.config.get("instance.assigned_host") !== this.id) {
@@ -225,9 +221,8 @@ export default class HostConnection extends BaseConnection {
let prev = controllerInstance.status;
controllerInstance.status = instanceData.status;
controllerInstance.gamePort = instanceData.gamePort;
controllerInstance.updatedAtMs = Date.now();
logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`);
instanceUpdates.push(controllerInstance);
logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`);
await lib.invokeHook(
this._controller.plugins, "onInstanceStatusChanged", controllerInstance, prev
);
@@ -242,21 +237,17 @@ export default class HostConnection extends BaseConnection {
instanceData.gamePort,
Date.now(),
);
this._controller.instances.set(instanceConfig.get("instance.id"), newInstance);
this._controller.instancesDirty = true;
instanceUpdates.push(newInstance);
this._controller.addInstanceHooks(newInstance);
await this.send(
new lib.InstanceAssignInternalRequest(
instanceConfig.get("instance.id"), instanceConfig.toRemote("host")
)
);
instanceUpdates.push(newInstance);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance);
}
if (instanceUpdates.length) {
this._controller.instanceDetailsUpdated(instanceUpdates);
}
this._controller.instances.setMany(instanceUpdates);
// Push lists to make sure they are in sync.
let adminlist: Set<string> = new Set();
+7
View File
@@ -48,6 +48,13 @@ export default class InstanceInfo {
return this.status === "deleted";
}
set isDeleted(value: boolean) {
if (value === false) {
throw new Error("Setting isDeleted to false is not supported, use status instead.");
}
this.status = "deleted";
}
/** Shorthand for `instance.config.get("instance.id")` */
get id():number {
return this.config.get("instance.id");
+1 -1
View File
@@ -218,7 +218,7 @@ async function uploadExport(req: Request, res: Response) {
res.sendStatus(400);
return;
}
let modPack = res.app.locals.controller.modPacks.getCopy(modPackId);
let modPack = res.app.locals.controller.modPacks.getMutable(modPackId);
if (!modPack) {
res.sendStatus(400);
return;
+40 -42
View File
@@ -2,23 +2,11 @@ import fs from "fs-extra";
import { safeOutputFile } from "./file_ops";
import { SubscribableValue } from "./subscriptions";
import { JSONDeserialisable } from "./data/composites";
import { EventEmitter } from "stream";
type DatastoreKey = string | number;
type DatastoreValue = string | number | boolean | object;
// Deep copy required to support the read only property of datastore values
function deepCopy<V extends DatastoreValue>(value: V): V {
if (value instanceof Object) {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, deepCopy(v)])
) as V;
}
return value;
}
// Abstract class which can provide saving and loading capabilities to a datastore
export abstract class DatastoreProvider<
K extends DatastoreKey,
@@ -50,11 +38,13 @@ export class MemoryDatastoreProvider<
export class JsonDatastoreProvider<
K extends DatastoreKey,
V extends DatastoreValue,
J, // Intermediate type returned from migrations
> extends DatastoreProvider<K, V> {
constructor(
private filePath: string,
private dataClass: JSONDeserialisable<V>,
private migrations: (rawJson: unknown) => Array<[K, unknown]> = v => v as any,
private fromJson: (json: J) => V,
private migrations: (rawJson: Record<DatastoreKey, unknown>) => Record<DatastoreKey, J> = v => v as any,
private finalise: (obj: V) => V = v => v,
) {
super();
}
@@ -81,7 +71,7 @@ export class JsonDatastoreProvider<
const serialized = this.migrations(rawJson);
// Convert to data class objects
return new Map(serialized.map((k, v) => [k, this.dataClass.fromJSON(v)]));
return new Map(Object.entries(serialized).map(([k, v]) => [k, this.finalise(this.fromJson(v))]));
}
}
@@ -89,11 +79,13 @@ export class JsonDatastoreProvider<
export class JsonIdDatastoreProvider<
K extends DatastoreKey,
V extends DatastoreValue & { id: K },
J, // Intermediate type returned from migrations
> extends DatastoreProvider<K, V> {
constructor(
private filePath: string,
private dataClass: JSONDeserialisable<V>,
private migrations: (rawJson: unknown) => Array<{ id: K }> = v => v as any,
private fromJson: (json: J) => V,
private migrations: (rawJson: Array<unknown>) => Array<J> = v => v as any,
private finalise: (obj: V) => V = v => v,
) {
super();
}
@@ -121,7 +113,7 @@ export class JsonIdDatastoreProvider<
// Convert to data class objects
return new Map(serialized.map((e) => {
const v = this.dataClass.fromJSON(e);
const v = this.finalise(this.fromJson(e));
return [v.id, v];
}));
}
@@ -132,10 +124,10 @@ export abstract class BaseDatastore<
K extends DatastoreKey,
V extends DatastoreValue,
> extends EventEmitter {
protected dirty = false;
private dirty = false;
constructor(
protected provider: DatastoreProvider<K, V> = new MemoryDatastoreProvider(),
private provider: DatastoreProvider<K, V> = new MemoryDatastoreProvider(),
protected data = new Map<K, V>(),
) {
super();
@@ -155,6 +147,14 @@ export abstract class BaseDatastore<
this.dirty = false;
}
// Set the dirty flag and call update handlers
protected touch(updates: any[]) {
if (updates.length) {
this.dirty = true;
this.emit("update", updates);
}
}
// Returns true if the datastore has the value, false otherwise
has(key: K) {
return this.data.has(key);
@@ -165,10 +165,8 @@ export abstract class BaseDatastore<
return this.data.get(key) as Readonly<V> ?? undefined;
}
// Get a copy of a value from the datastore
getCopy(key: K) {
// const v = this.data.get(key);
// return v === undefined ? v : deepCopy(v);
// Get a mutable reference to a value in the datastore, call set after use
getMutable(key: K) {
return this.data.get(key);
}
@@ -176,6 +174,11 @@ export abstract class BaseDatastore<
values() {
return this.data.values() as IterableIterator<Readonly<V>>;
}
// Returns all values in the datastore as mutable references, call setMany after use
valuesMutable() {
return this.data.values();
}
}
// General key value mapping which can be saved and loaded with the file system
@@ -186,35 +189,33 @@ export class Datastore<
// Set the value in the datastore, be careful of race conditions if you await any functions before calling set
set(key: K, value: V) {
this.data.set(key, value);
this.dirty = true;
this.emit("update", [key, value]);
this.touch([[key, value]]);
}
// Set many values at once from an array of key value pairs
setMany(pairs: [K, V][]) {
this.dirty ||= pairs.length > 0;
for (const [k, v] of pairs) {
this.data.set(k, v);
}
this.emit("update", pairs);
this.touch(pairs);
}
// Delete a value in the datastore
delete(key: K) {
const value = this.data.get(key);
this.data.delete(key);
this.dirty = true;
this.emit("update", [key, value, true]);
this.touch([[key, value!, true]]);
}
// Delete many values at once from an array of keys
deleteMany(keys: K[]) {
this.dirty ||= keys.length > 0;
const updates = [] as [K, V, true][];
for (const key of keys) {
const value = this.data.get(key);
updates.push([key, value!, true]);
this.data.delete(key);
}
this.emit("update", keys);
this.touch(updates);
}
}
@@ -225,39 +226,36 @@ export class SubscribableDatastore<
> extends BaseDatastore<V["id"], V> {
// Set the value in the datastore, be careful of race conditions if you await any functions before calling set
set(value: V) {
value.updatedAtMs = Date.now();
this.data.set(value.id, value);
this.dirty = true;
this.emit("update", [value]);
this.touch([value]);
}
// Set many values at once from an array of key value pairs
setMany(values: V[]) {
const nowMs = Date.now();
this.dirty ||= values.length > 0;
for (const value of values) {
value.updatedAtMs = nowMs;
this.data.set(value.id, value);
}
this.emit("update", values);
this.touch(values);
}
// Delete a value in the datastore
delete(value: V) {
this.data.delete(value.id);
value.isDeleted = true;
value.updatedAtMs = Date.now();
this.emit("update", [value]);
value.isDeleted = true;
this.touch([value]);
}
// Delete many values at once from an array of values
deleteMany(values: V[]) {
this.dirty ||= values.length > 0;
const nowMs = Date.now();
for (const value of values) {
value.isDeleted = true;
value.updatedAtMs = Date.now();
value.updatedAtMs = nowMs;
this.data.delete(value.id);
}
this.emit("update", values);
this.touch(values);
}
}
+2
View File
@@ -61,6 +61,8 @@ describe("player_auth", function() {
return load;
} else if (field === "factorio.settings") {
return { name };
} else if (field === "instance.id") {
return id;
}
throw new Error(`field ${field} not implemented`);
},
+1 -1
View File
@@ -39,7 +39,7 @@ function connectInstance(controller, host, instanceId) {
instanceConfig.set("instance.id", instanceId);
instanceConfig.set("instance.assigned_host", host.config.get("host.id"));
host.instanceInfos.set(instanceId, { path: "invalid", config: instanceConfig });
controller.instances.set(instanceId, new InstanceInfo(instanceConfig, "stopped"));
controller.instances.set(new InstanceInfo(instanceConfig, "stopped"));
const instance = new Instance(host, instanceSide, "invalid", "invalid", instanceConfig);
const instanceConnection = new InstanceConnection(hostSide, host, instance);
host.instanceConnections.set(instanceId, instanceConnection);
+2 -2
View File
@@ -182,8 +182,8 @@ class MockController {
["test", ControllerUser.fromJSON({ name: "test", roles: [0, 1] }, this.userManager)],
["player", ControllerUser.fromJSON({ name: "player", roles: [1] }, this.userManager)],
]);
this.instances = new Map();
this.hosts = new Map();
this.instances = new lib.Datastore();
this.hosts = new lib.Datastore();
this.handles = new Map();
}