From a2914bda33554e7f83f9eeccca5143de5c1321fa Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 10 Dec 2023 23:00:00 +0100 Subject: [PATCH] Cache save details on the controller Keep a copy of the instance saves details on the controller so that saves can be queried even when the host is disconnected and the controller can properly track and provide updates on the saves to its clients. --- packages/controller/src/ControlConnection.ts | 5 ++ packages/controller/src/Controller.ts | 60 ++++++++++++++- packages/controller/src/HostConnection.ts | 29 +++++++- packages/ctl/src/commands.ts | 3 +- packages/host/src/Host.ts | 21 ++++-- packages/host/src/Instance.ts | 14 ++-- packages/lib/src/data/messages_instance.ts | 78 +++++++++++++++----- packages/lib/src/link/messages.ts | 4 +- packages/lib/src/permissions.ts | 2 +- packages/web_ui/src/model/saves.ts | 29 ++++++-- packages/web_ui/src/util/websocket.ts | 6 +- test/integration/clusterio.js | 22 ++++-- test/integration/index.js | 12 +-- 13 files changed, 219 insertions(+), 66 deletions(-) diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index 9997d74d..b1740ea5 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -85,6 +85,7 @@ export default class ControlConnection extends BaseConnection { this.handle(lib.InstanceConfigSetFieldRequest, this.handleInstanceConfigSetFieldRequest.bind(this)); this.handle(lib.InstanceConfigSetPropRequest, this.handleInstanceConfigSetPropRequest.bind(this)); this.handle(lib.InstanceAssignRequest, this.handleInstanceAssignRequest.bind(this)); + this.handle(lib.InstanceSaveDetailsListRequest, this.handleInstanceSaveDetailsListRequest.bind(this)); this.handle(lib.InstanceRenameSaveRequest, controller.sendRequestToHostByInstanceId.bind(controller)); this.handle(lib.InstanceCopySaveRequest, controller.sendRequestToHostByInstanceId.bind(controller)); this.handle(lib.InstanceDeleteSaveRequest, controller.sendRequestToHostByInstanceId.bind(controller)); @@ -277,6 +278,10 @@ export default class ControlConnection extends BaseConnection { await this._controller.instanceAssign(request.instanceId, request.hostId); } + async handleInstanceSaveDetailsListRequest() { + return [...this._controller.saves.values()]; + } + async handleInstanceDownloadSaveRequest(request: lib.InstanceDownloadSaveRequest) { let { instanceId, name } = request; let stream = await routes.createProxyStream(this._controller.app); diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index d96bc957..4d22eb63 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -57,6 +57,8 @@ export default class Controller { hostsDirty = false; /** True if {@link Controller.instances} has changed since last time it was saved */ instancesDirty = false; + /** True if {@link Controller.saves} has changed since last time it was saved */ + savesDirty = false; /** True if {@link Controller.modPacks} has changed since last time it was saved */ modPacksDirty = false; @@ -98,6 +100,7 @@ export default class Controller { const hosts = await Controller.loadHosts(path.join(databaseDirectory, "hosts.json")); const instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json")); + const saves = await Controller.loadSaves(path.join(databaseDirectory, "saves.json")); const modPacks = await Controller.loadModPacks(path.join(databaseDirectory, "mod-packs.json")); const userManager = new UserManager(config); await userManager.load(path.join(databaseDirectory, "users.json")); @@ -109,6 +112,7 @@ export default class Controller { return [ hosts, instances, + saves, modPacks, modStore, userManager, @@ -125,6 +129,8 @@ export default class Controller { public hosts = new Map(), /** Mapping of instance id to instance info */ public instances = new Map(), + /** Mapping of save id to save details */ + public saves = new Map(), /** Mapping of mod pack id to mod pack */ public modPacks = new Map(), /** Mods stored on the controller */ @@ -151,7 +157,7 @@ export default class Controller { // Handle subscriptions for all internal properties this.subscriptions.handle(lib.HostUpdatesEvent); this.subscriptions.handle(lib.InstanceDetailsUpdatesEvent); - this.subscriptions.handle(lib.InstanceSaveListUpdateEvent); + this.subscriptions.handle(lib.InstanceSaveDetailsUpdatesEvent); this.subscriptions.handle(lib.ModPackUpdatesEvent); this.subscriptions.handle(lib.ModUpdatesEvent); this.subscriptions.handle(lib.UserUpdatesEvent); @@ -428,6 +434,11 @@ export default class Controller { await Controller.saveInstances(path.join(databaseDirectory, "instances.json"), this.instances); } + if (this.savesDirty) { + this.savesDirty = false; + await Controller.saveSaves(path.join(databaseDirectory, "saves.json"), this.saves); + } + if (this.modPacksDirty) { this.modPacksDirty = false; await Controller.saveModPacks(path.join(databaseDirectory, "mod-packs.json"), this.modPacks); @@ -511,6 +522,24 @@ export default class Controller { await lib.safeOutputFile(filePath, JSON.stringify(serialized, null, "\t")); } + static async loadSaves(filePath: string): Promise> { + let json: Static[]; + 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(s => lib.SaveDetails.fromJSON(s)).map(s => [s.id, s])); + } + + static async saveSaves(filePath: string, saves: Map) { + await lib.safeOutputFile(filePath, JSON.stringify([...saves.values()], null, "\t")); + } + + static async loadModPacks(filePath: string): Promise> { let json; try { @@ -703,6 +732,25 @@ export default class Controller { return instance; } + /** + * Removes all saves currently stored for the given instance, if any + * @param instanceId - Id of Instance to clear saves for. + * @internal + */ + clearSavesOfInstance(instanceId: number) { + const updates = []; + for (const [id, save] of this.saves) { + if (save.instanceId === instanceId) { + save.isDeleted = true; + updates.push(save); + this.saves.delete(id); + } + } + if (updates.length) { + this.savesUpdated(updates); + } + } + /** * Change assigned host of an instance * @@ -742,6 +790,9 @@ export default class Controller { } } + // Remove saves recorded from currently assigned host if any + this.clearSavesOfInstance(instanceId); + // Assign to target instance.config.set("instance.assigned_host", hostId); // "fieldChanged" event handler will set this.instancesDirty @@ -772,6 +823,8 @@ export default class Controller { this.instances.delete(instanceId); this.instancesDirty = true; + this.clearSavesOfInstance(instanceId); + let prev = instance.status; instance.status = "deleted"; instance.updatedAt = Date.now(); @@ -817,8 +870,9 @@ export default class Controller { this.subscriptions.broadcast(new lib.InstanceDetailsUpdatesEvent(updates)); } - saveListUpdate(instanceId: number, saves: lib.SaveDetails[]) { - this.subscriptions.broadcast(new lib.InstanceSaveListUpdateEvent(instanceId, saves)); + savesUpdated(saves: lib.SaveDetails[]) { + this.savesDirty = true; + this.subscriptions.broadcast(new lib.InstanceSaveDetailsUpdatesEvent(saves)); } modPacksUpdated(modPacks: lib.ModPack[]) { diff --git a/packages/controller/src/HostConnection.ts b/packages/controller/src/HostConnection.ts index 31844cd7..72185d37 100644 --- a/packages/controller/src/HostConnection.ts +++ b/packages/controller/src/HostConnection.ts @@ -82,7 +82,7 @@ export default class HostConnection extends BaseConnection { this.handle(lib.InstanceStatusChangedEvent, this.handleInstanceStatusChangedEvent.bind(this)); this.handle(lib.InstancesUpdateRequest, this.handleInstancesUpdateRequest.bind(this)); - this.handle(lib.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this)); + this.handle(lib.InstanceSaveDetailsUpdatesEvent, this.handleInstanceSaveDetailsUpdatesEvent.bind(this)); this.handle(lib.LogMessageEvent, this.handleLogMessageEvent.bind(this)); this.handle(lib.InstancePlayerUpdateEvent, this.handleInstancePlayerUpdateEvent.bind(this)); } @@ -268,8 +268,31 @@ export default class HostConnection extends BaseConnection { await this.send(new lib.SyncUserListsEvent(adminlist, banlist, whitelist)); } - async handleInstanceSaveListUpdateEvent(event: lib.InstanceSaveListUpdateEvent) { - this._controller.saveListUpdate(event.instanceId, event.saves); + async handleInstanceSaveDetailsUpdatesEvent(event: lib.InstanceSaveDetailsUpdatesEvent) { + const updates: lib.SaveDetails[] = []; + for (const save of event.updates) { + const existingSave = this._controller.saves.get(save.id); + if (existingSave && save.equals(existingSave)) { + continue; + } + this._controller.saves.set(save.id, save); + updates.push(save); + } + if (event.instanceId !== undefined) { + const updatedSaves = new Set(event.updates.map(s => s.id)); + for (const [id, save] of this._controller.saves) { + if (save.instanceId === event.instanceId && !updatedSaves.has(id)) { + save.isDeleted = true; + updates.push(save); + this._controller.saves.delete(id); + } + } + } + // Hosts eagerly send updates, which means we may get an update + // where nothing actualy changed. + if (updates.length) { + this._controller.savesUpdated(updates); + } } async handleLogMessageEvent(event: lib.LogMessageEvent) { diff --git a/packages/ctl/src/commands.ts b/packages/ctl/src/commands.ts index a1cfdcfe..68459905 100644 --- a/packages/ctl/src/commands.ts +++ b/packages/ctl/src/commands.ts @@ -532,7 +532,8 @@ instanceSaveCommands.add(new lib.Command({ }], handler: async function(args: { instance: string }, control: Control) { let instanceId = await lib.resolveInstance(control, args.instance); - let saves = await control.sendTo({ instanceId }, new lib.InstanceListSavesRequest()); + let saves = await control.sendTo("controller", new lib.InstanceSaveDetailsListRequest()); + saves = saves.filter(save => save.instanceId === instanceId); print(asTable(saves.map( ({ mtimeMs, ...rest }) => ({ ...rest, mtime: new Date(mtimeMs).toLocaleString() }) ))); diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index 222b8bde..a2c700e3 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -347,7 +347,9 @@ export default class Host extends lib.Link { this.handle(lib.InstanceAssignInternalRequest, this.handleInstanceAssignInternalRequest.bind(this)); this.handle(lib.InstanceUnassignInternalRequest, this.handleInstanceUnassignInternalRequest.bind(this)); this.handle(lib.HostMetricsRequest, this.handleHostMetricsRequest.bind(this)); - this.fallbackRequest(lib.InstanceListSavesRequest, this.fallbackInstanceListSavesRequest.bind(this)); + this.fallbackRequest( + lib.InstanceSaveDetailsListRequest, this.fallbackInstanceSaveDetailsListRequest.bind(this), + ); this.handle(lib.InstanceRenameSaveRequest, this.handleInstanceRenameSaveRequest.bind(this)); this.handle(lib.InstanceCopySaveRequest, this.handleInstanceCopySaveRequest.bind(this)); this.handle(lib.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this)); @@ -600,6 +602,9 @@ export default class Host extends lib.Link { ) ); + // Send the new list of saves for this assigned instance to the controller. + await this.sendSaveListUpdate(instanceId, path.join(instanceInfo.path, "saves")); + // save a copy of the instance config let warnedOutput = { _warning: "Changes to this file will be overwritten by the controller's copy.", @@ -700,9 +705,13 @@ export default class Host extends lib.Link { return { results }; } - async fallbackInstanceListSavesRequest(request: lib.InstanceListSavesRequest, src: lib.Address, dst: lib.Address) { + async fallbackInstanceSaveDetailsListRequest( + request: lib.InstanceSaveDetailsListRequest, + src: lib.Address, + dst: lib.Address + ) { let instanceInfo = this.getRequestInstanceInfo(dst.id); - return await Instance.listSaves(path.join(instanceInfo.path, "saves"), null); + return await Instance.listSaves(dst.id, path.join(instanceInfo.path, "saves"), null); } async handleInstanceRenameSaveRequest(request: lib.InstanceRenameSaveRequest) { @@ -788,12 +797,12 @@ export default class Host extends lib.Link { let instanceConnection = this.instanceConnections.get(instanceId); let saveList: lib.SaveDetails[]; if (instanceConnection) { - saveList = await instanceConnection.send(new lib.InstanceListSavesRequest()); + saveList = await instanceConnection.send(new lib.InstanceSaveDetailsListRequest()); } else { - saveList = await Instance.listSaves(savesDir, null); + saveList = await Instance.listSaves(instanceId, savesDir, null); } - this.send(new lib.InstanceSaveListUpdateEvent(instanceId, saveList)); + this.send(new lib.InstanceSaveDetailsUpdatesEvent(saveList, instanceId)); } async handleInstanceDeleteSaveRequest(request: lib.InstanceDeleteSaveRequest) { diff --git a/packages/host/src/Instance.ts b/packages/host/src/Instance.ts index 7b860475..14346176 100644 --- a/packages/host/src/Instance.ts +++ b/packages/host/src/Instance.ts @@ -228,7 +228,7 @@ export default class Instance extends lib.Link { this.handle(lib.InstanceMetricsRequest, this.handleInstanceMetricsRequest.bind(this)); this.handle(lib.InstanceStartRequest, this.handleInstanceStartRequest.bind(this)); this.handle(lib.InstanceLoadScenarioRequest, this.handleInstanceLoadScenarioRequest.bind(this)); - this.handle(lib.InstanceListSavesRequest, this.handleInstanceListSavesRequest.bind(this)); + this.handle(lib.InstanceSaveDetailsListRequest, this.handleInstanceSaveDetailsListRequest.bind(this)); this.handle(lib.InstanceCreateSaveRequest, this.handleInstanceCreateSaveRequest.bind(this)); this.handle(lib.InstanceExportDataRequest, this.handleInstanceExportDataRequest.bind(this)); this.handle(lib.InstanceStopRequest, this.handleInstanceStopRequest.bind(this)); @@ -374,7 +374,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); } } - static async listSaves(savesDir: string, loadedSave: string | null) { + static async listSaves(instanceId: number, savesDir: string, loadedSave: string | null) { let defaultSave = null; if (loadedSave === null) { defaultSave = await lib.getNewestFile( @@ -395,12 +395,14 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); } list.push(new lib.SaveDetails( + instanceId, type, name, stat.size, stat.mtimeMs, name === loadedSave, name === defaultSave, + false, )); } @@ -410,9 +412,9 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); async sendSaveListUpdate() { this.sendTo( "controller", - new lib.InstanceSaveListUpdateEvent( + new lib.InstanceSaveDetailsUpdatesEvent( + await Instance.listSaves(this.id, this.path("saves"), this._loadedSave), this.id, - await Instance.listSaves(this.path("saves"), this._loadedSave), ), ); } @@ -1034,8 +1036,8 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); } } - async handleInstanceListSavesRequest() { - return await Instance.listSaves(this.path("saves"), this._loadedSave); + async handleInstanceSaveDetailsListRequest() { + return await Instance.listSaves(this.id, this.path("saves"), this._loadedSave); } async handleInstanceCreateSaveRequest(request: lib.InstanceCreateSaveRequest) { diff --git a/packages/lib/src/data/messages_instance.ts b/packages/lib/src/data/messages_instance.ts index 91303cd6..17a27adf 100644 --- a/packages/lib/src/data/messages_instance.ts +++ b/packages/lib/src/data/messages_instance.ts @@ -283,62 +283,100 @@ export class InstanceStartRequest { export class SaveDetails { constructor( - public type: string, + public instanceId: number, + public type: "file" | "directory" | "special", public name: string, public size: number, public mtimeMs: number, public loaded: boolean, public loadByDefault: boolean, + public isDeleted: boolean, ) { } static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), "type": StringEnum(["file", "directory", "special"]), "name": Type.String(), "size": Type.Integer(), "mtimeMs": Type.Number(), "loaded": Type.Boolean(), "loadByDefault": Type.Boolean(), + "isDeleted": Type.Boolean(), }); static fromJSON(json: Static) { - return new this(json.type, json.name, json.size, json.mtimeMs, json.loaded, json.loadByDefault); + return new this( + json.instanceId, + json.type, + json.name, + json.size, + json.mtimeMs, + json.loaded, + json.loadByDefault, + json.isDeleted, + ); + } + + /** Concatenation of instance id and name forming a unique string for this save */ + get id() { + return `${this.instanceId}/${this.name}`; + } + + get updatedAt() { + return this.mtimeMs; + } + + /** + * Compare this save with another save. + * @param other - save to compare with. + * @returns true if this save is identical to the provided save + */ + equals(other: SaveDetails) { + return ( + this.instanceId === other.instanceId + && this.type === other.type + && this.name === other.name + && this.size === other.size + && this.mtimeMs === other.mtimeMs + && this.loaded === other.loaded + && this.loadByDefault === other.loadByDefault + && this.isDeleted === other.isDeleted + ); } } -export class InstanceListSavesRequest { - declare ["constructor"]: typeof InstanceListSavesRequest; +export class InstanceSaveDetailsListRequest { + declare ["constructor"]: typeof InstanceSaveDetailsListRequest; static type = "request" as const; - static src = "control" as const; - static dst = "instance" as const; + static src = ["control", "host"] as const; + static dst = ["controller", "instance"] as const; static permission = "core.instance.save.list" as const; static Response = jsonArray(SaveDetails); } -export class InstanceSaveListUpdateEvent { - declare ["constructor"]: typeof InstanceSaveListUpdateEvent; +export class InstanceSaveDetailsUpdatesEvent { + declare ["constructor"]: typeof InstanceSaveDetailsUpdatesEvent; static type = "event" as const; static src = ["instance", "host", "controller"] as const; static dst = ["controller", "control"] as const; - static permission = "core.instance.save.list_subscribe" as const; + static permission = "core.instance.save.list.subscribe" as const; constructor( - public instanceId: number, - public saves: SaveDetails[], - public updatedAt = Date.now(), + public updates: SaveDetails[], + /** + * Present if this update was sent by a host and updates contains all + * saves of the given instance. + */ + public instanceId?: number, ) { } - get isDeleted() { - return false; - } - static jsonSchema = Type.Object({ - "instanceId": Type.Integer(), - "saves": Type.Array(SaveDetails.jsonSchema), - "updatedAt": Type.Number(), + "updates": Type.Array(SaveDetails.jsonSchema), + "instanceId": Type.Optional(Type.Integer()), }); static fromJSON(json: Static) { - return new this(json.instanceId, json.saves.map(i => SaveDetails.fromJSON(i)), json.updatedAt); + return new this(json.updates.map(i => SaveDetails.fromJSON(i)), json.instanceId); } } diff --git a/packages/lib/src/link/messages.ts b/packages/lib/src/link/messages.ts index 4bd0f0ca..13743844 100644 --- a/packages/lib/src/link/messages.ts +++ b/packages/lib/src/link/messages.ts @@ -48,8 +48,8 @@ export const dataClasses: (RequestClass | EventClass) instance.InstanceAssignRequest, instance.InstanceMetricsRequest, instance.InstanceStartRequest, - instance.InstanceListSavesRequest, - instance.InstanceSaveListUpdateEvent, + instance.InstanceSaveDetailsListRequest, + instance.InstanceSaveDetailsUpdatesEvent, instance.InstanceCreateSaveRequest, instance.InstanceRenameSaveRequest, instance.InstanceCopySaveRequest, diff --git a/packages/lib/src/permissions.ts b/packages/lib/src/permissions.ts index 28a7cf89..55a2ca5b 100644 --- a/packages/lib/src/permissions.ts +++ b/packages/lib/src/permissions.ts @@ -201,7 +201,7 @@ definePermission({ description: "List the saves currently on the instance.", }); definePermission({ - name: "core.instance.save.list_subscribe", + name: "core.instance.save.list.subscribe", title: "Subscribe to save list updatess", description: "Subscribe to be notifed on updates to the list of saves of instances.", }); diff --git a/packages/web_ui/src/model/saves.ts b/packages/web_ui/src/model/saves.ts index 05b827c0..6e7296e8 100644 --- a/packages/web_ui/src/model/saves.ts +++ b/packages/web_ui/src/model/saves.ts @@ -10,8 +10,8 @@ export function useSaves(instanceId?: number): lib.SaveDetails[] { let [saves, setSaves] = useState([]); function updateSaves() { - control.sendTo({ instanceId: instanceId! }, new lib.InstanceListSavesRequest()).then(updatedSaves => { - setSaves(updatedSaves); + control.sendTo("controller", new lib.InstanceSaveDetailsListRequest()).then(updatedSaves => { + setSaves(updatedSaves.filter(save => save.instanceId === instanceId)); }).catch(err => { logger.error(`Failed to list instance saves: ${err}`); setSaves([]); @@ -25,11 +25,26 @@ export function useSaves(instanceId?: number): lib.SaveDetails[] { } updateSaves(); - function updateHandler(data: lib.InstanceSaveListUpdateEvent) { - if (data.instanceId !== instanceId) { - return; - } - setSaves(data.saves); + function updateHandler(updates: lib.SaveDetails[]) { + setSaves(oldList => { + let newList = oldList.concat(); + for (const newSave of updates) { + if (newSave.instanceId !== instanceId) { + continue; + } + let index = newList.findIndex(s => s.id === newSave.id); + if (!newSave.isDeleted) { + if (index !== -1) { + newList[index] = newSave; + } else { + newList.push(newSave); + } + } else if (index !== -1) { + newList.splice(index, 1); + } + } + return newList; + }); } control.saveListUpdate.subscribe(updateHandler); diff --git a/packages/web_ui/src/util/websocket.ts b/packages/web_ui/src/util/websocket.ts index 1b50449f..20cd5ed8 100644 --- a/packages/web_ui/src/util/websocket.ts +++ b/packages/web_ui/src/util/websocket.ts @@ -7,7 +7,7 @@ const { logFilter, logger } = lib; type accountHandler = (account: lib.AccountDetails) => void; type hostHandler = (hostDetails: lib.HostDetails) => void; type instanceHandler = (instanceDetails: lib.InstanceDetails) => void; -type saveListHandler = (saveListEvent: lib.InstanceSaveListUpdateEvent) => void; +type saveListHandler = (saveListEvent: lib.SaveDetails) => void; type modPackHandler = (modPack: lib.ModPack) => void; type modInfoHandler = (modInfo: lib.ModInfo) => void; type userHandler = (rawUser: lib.User) => void; @@ -68,8 +68,8 @@ export class Control extends lib.Link { lib.InstanceDetailsUpdatesEvent, event => event.updates, ); - saveListUpdate = new lib.EventSubscriber( - lib.InstanceSaveListUpdateEvent, + saveListUpdate = new lib.EventSubscriber( + lib.InstanceSaveDetailsUpdatesEvent, event => event.updates, ); modPackUpdate = new lib.EventSubscriber( diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index f074ef13..357baa09 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -315,16 +315,16 @@ describe("Integration of Clusterio", function() { describe("instance save create", function() { it("creates a save", async function() { slowTest(this); - getControl().saveListUpdates = []; + getControl().saveUpdates = []; await execCtl("instance save create test"); await checkInstanceStatus(44, "stopped"); }); }); - describe("saveListUpdateEventHandler()", function() { + describe("InstanceSaveDetailsUpdatesEvent", function() { it("should have triggered for the created save", function() { slowTest(this); - assert.equal(getControl().saveListUpdates.slice(-1)[0].saves[0].name, "world.zip"); + assert.equal(getControl().saveUpdates.slice(-1)[0].updates[0].name, "world.zip"); }); }); @@ -391,7 +391,7 @@ describe("Integration of Clusterio", function() { let savesDir = path.join("temp", "test", "instances", "test", "saves"); await fs.copy(path.join(savesDir, "world.zip"), path.join(savesDir, "_autosave1.zip")); await execCtl("instance start test"); - let saves = await getControl().sendTo({ instanceId: 44 }, new lib.InstanceListSavesRequest()); + let saves = await getControl().sendTo({ instanceId: 44 }, new lib.InstanceSaveDetailsListRequest()); let running = saves.find(s => s.loaded); assert(running.name !== "_autosave1.zip"); }); @@ -407,19 +407,19 @@ describe("Integration of Clusterio", function() { assert(log.some(info => /technobabble/.test(info.message)), "Command was not sent"); }); - it("should trigger saveListUpdate on save", async function() { + it("should trigger InstanceSaveDetailsUpdatesEvent on save", async function() { slowTest(this); - getControl().saveListUpdates = []; + getControl().saveUpdates = []; await execCtl("instance send-rcon test /server-save"); let received = false; for (let x = 0; x < 10; x++) { - if (getControl().saveListUpdates.length) { + if (getControl().saveUpdates.length) { received = true; break; } await wait(100); } - assert(received, "saveListUpdate not sent"); + assert(received, "InstanceSaveDetailsUpdatesEvent not sent"); }); }); @@ -827,10 +827,16 @@ describe("Integration of Clusterio", function() { describe("instance delete", function() { it("deletes the instance", async function() { slowTest(this); + getControl().saveUpdates = []; await execCtl("instance delete test"); assert(!await fs.exists(path.join(instancesDir, "test")), "Instance files was not deleted"); let instances = await getInstances(); assert(!instances.has(44), "instance was not deleted from controller"); + + const saveUpdates = getControl().saveUpdates; + assert.equal(saveUpdates.length, 1, "Expected one update sent on saves"); + assert(saveUpdates[0].updates.length > 0, "Expected saves update to contain at least one save updated"); + assert(saveUpdates[0].updates.every(update => update.isDeleted), "Expected all saves to be deleted"); }); }); diff --git a/test/integration/index.js b/test/integration/index.js index d53768e5..33db6e9c 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -26,7 +26,7 @@ class TestControl extends lib.Link { super(connector); this.hostUpdates = []; this.instanceUpdates = []; - this.saveListUpdates = []; + this.saveUpdates = []; this.modUpdates = []; this.modPackUpdates = []; this.userUpdates = []; @@ -42,8 +42,8 @@ class TestControl extends lib.Link { new lib.SubscriptionRequest(lib.InstanceDetailsUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting instance subscriptions:\n${err.stack}`)); this.send( - new lib.SubscriptionRequest(lib.InstanceSaveListUpdateEvent.name, true) - ).catch(err => logger.error(`Error setting save list subscriptions:\n${err.stack}`)); + new lib.SubscriptionRequest(lib.InstanceSaveDetailsUpdatesEvent.name, true) + ).catch(err => logger.error(`Error setting save subscriptions:\n${err.stack}`)); this.send( new lib.SubscriptionRequest(lib.ModUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting mod subscriptions:\n${err.stack}`)); @@ -58,7 +58,7 @@ class TestControl extends lib.Link { this.handle(lib.AccountUpdateEvent); this.handle(lib.HostUpdatesEvent, this.handleHostUpdatesEvent.bind(this)); this.handle(lib.InstanceDetailsUpdatesEvent, this.handleInstanceDetailsUpdatesEvent.bind(this)); - this.handle(lib.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this)); + this.handle(lib.InstanceSaveDetailsUpdatesEvent, this.handleInstanceSaveDetailsUpdatesEvent.bind(this)); this.handle(lib.ModUpdatesEvent, this.handleModUpdatesEvent.bind(this)); this.handle(lib.ModPackUpdatesEvent, this.handleModPackUpdatesEvent.bind(this)); this.handle(lib.UserUpdatesEvent, this.handleUserUpdatesEvent.bind(this)); @@ -72,8 +72,8 @@ class TestControl extends lib.Link { this.instanceUpdates.push(...event.updates); } - async handleInstanceSaveListUpdateEvent(event) { - this.saveListUpdates.push(event); + async handleInstanceSaveDetailsUpdatesEvent(event) { + this.saveUpdates.push(event); } async handleModUpdatesEvent(event) {