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.
This commit is contained in:
Hornwitser committed 2023-12-11 20:21:43 +01:00
1 parent d186f85773
commit a2914bda33
13 files changed
+219 -66

No files matched your search

@@ -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);
+57 -3
View File
@@ -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<number, HostInfo>(),
/** Mapping of instance id to instance info */
public instances = new Map<number, InstanceInfo>(),
/** Mapping of save id to save details */
public saves = new Map<string, lib.SaveDetails>(),
/** Mapping of mod pack id to mod pack */
public modPacks = new Map<number, lib.ModPack>(),
/** 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<Map<string, lib.SaveDetails>> {
let json: Static<typeof lib.SaveDetails.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(s => lib.SaveDetails.fromJSON(s)).map(s => [s.id, s]));
}
static async saveSaves(filePath: string, saves: Map<string, lib.SaveDetails>) {
await lib.safeOutputFile(filePath, JSON.stringify([...saves.values()], null, "\t"));
}
static async loadModPacks(filePath: string): Promise<Map<number, lib.ModPack>> {
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[]) {
+26 -3
View File
@@ -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) {
+2 -1
View File
@@ -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() })
)));
+15 -6
View File
@@ -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) {
+8 -6
View File
@@ -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) {
+58 -20
View File
@@ -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<typeof this.jsonSchema>) {
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<typeof this.jsonSchema>) {
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);
}
}
+2 -2
View File
@@ -48,8 +48,8 @@ export const dataClasses: (RequestClass<unknown, unknown> | EventClass<unknown>)
instance.InstanceAssignRequest,
instance.InstanceMetricsRequest,
instance.InstanceStartRequest,
instance.InstanceListSavesRequest,
instance.InstanceSaveListUpdateEvent,
instance.InstanceSaveDetailsListRequest,
instance.InstanceSaveDetailsUpdatesEvent,
instance.InstanceCreateSaveRequest,
instance.InstanceRenameSaveRequest,
instance.InstanceCopySaveRequest,
+1 -1
View File
@@ -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.",
});
+22 -7
View File
@@ -10,8 +10,8 @@ export function useSaves(instanceId?: number): lib.SaveDetails[] {
let [saves, setSaves] = useState<lib.SaveDetails[]>([]);
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);
+3 -3
View File
@@ -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>(
lib.InstanceSaveListUpdateEvent,
saveListUpdate = new lib.EventSubscriber<lib.InstanceSaveDetailsUpdatesEvent, lib.SaveDetails[]>(
lib.InstanceSaveDetailsUpdatesEvent, event => event.updates,
);
modPackUpdate = new lib.EventSubscriber<lib.ModPackUpdatesEvent, lib.ModPack[]>(
+14 -8
View File
@@ -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");
});
});
+6 -6
View File
@@ -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) {