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) {