diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index a92fcc6..026c552 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -19,8 +19,25 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { ); ChromeUtils.defineLazyGetter(this, 'tabContainer', () => document.getElementById('tabbrowser-tabs')); await ZenWorkspacesStorage.init(); + if(!Weave.Service.engineManager.get("workspaces")) { + Weave.Service.engineManager.register(ZenWorkspacesEngine); + } await this.initializeWorkspaces(); console.info('ZenWorkspaces: ZenWorkspaces initialized'); + + // Add observer for sync completion + Services.obs.addObserver(this, "weave:engine:sync:finish"); + } + + observe(subject, topic, data) { + if (topic === "weave:engine:sync:finish" && data === "workspaces") { + this._workspaceCache = null; // Clear cache to fetch fresh data + this.updateWorkspaceStrip(); + } + } + + updateWorkspaceStrip() { + this._propagateWorkspaceData().catch(console.error); } get shouldHaveWorkspaces() { @@ -69,6 +86,8 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { // Set the active workspace ID to the first one if active workspace doesn't exist Services.prefs.setStringPref('zen.workspaces.active', this._workspaceCache.workspaces[0]?.uuid); } + // sort by position + this._workspaceCache.workspaces.sort((a, b) => (a.position ?? Infinity) - (b.position ?? Infinity)); } return this._workspaceCache; } @@ -540,7 +559,6 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { workspaceData.icon = icon?.label; await this.saveWorkspace(workspaceData); await this._propagateWorkspaceData(); - this.closeWorkspacesSubView(); } onWorkspaceCreationNameChange(event) { @@ -650,7 +668,6 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { let window = { uuid: gZenUIManager.generateUuidv4(), default: isDefault, - used: true, icon: icon, name: name, }; diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs index 5ae9363..c26e57b 100644 --- a/src/ZenWorkspacesStorage.mjs +++ b/src/ZenWorkspacesStorage.mjs @@ -6,6 +6,7 @@ var ZenWorkspacesStorage = { async _ensureTable() { await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage._ensureTable', async (db) => { + // Create the main workspaces table if it doesn't exist await db.execute(` CREATE TABLE IF NOT EXISTS zen_workspaces ( id INTEGER PRIMARY KEY, @@ -14,11 +15,21 @@ var ZenWorkspacesStorage = { icon TEXT, is_default INTEGER NOT NULL DEFAULT 0, container_id INTEGER, + position INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); + + // Create the changes tracking table if it doesn't exist + await db.execute(` + CREATE TABLE IF NOT EXISTS zen_workspaces_changes ( + uuid TEXT PRIMARY KEY, + timestamp INTEGER NOT NULL + ) + `); }); + await this._migrateWorkspacesFromJSON(); }, @@ -29,36 +40,114 @@ var ZenWorkspacesStorage = { const oldWorkspaces = await IOUtils.readJSON(oldWorkspacesPath); if (oldWorkspaces.workspaces) { for (const workspace of oldWorkspaces.workspaces) { - await this.saveWorkspace(workspace); + await this.saveWorkspace(workspace, false); // Disable immediate notification } } await IOUtils.remove(oldWorkspacesPath); } }, - async saveWorkspace(workspace) { + /** + * Private helper method to notify observers with a list of changed UUIDs. + * @param {string} event - The observer event name. + * @param {Array} uuids - Array of changed workspace UUIDs. + */ + _notifyWorkspacesChanged(event, uuids) { + if (uuids.length === 0) return; // No changes to notify + + // Convert the array of UUIDs to a JSON string + const data = JSON.stringify(uuids); + + Services.obs.notifyObservers(null, event, data); + }, + + async saveWorkspace(workspace, notifyObservers = true) { + const changedUUIDs = new Set(); + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.saveWorkspace', async (db) => { const now = Date.now(); - await db.executeCached( - ` - INSERT OR REPLACE INTO zen_workspaces ( - uuid, name, icon, is_default, container_id, created_at, updated_at - ) VALUES ( - :uuid, :name, :icon, :is_default, :container_id, - COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now), - :now - ) - `, - { + + await db.executeTransaction(async function() { + // If the workspace is set as default, unset is_default for all other workspaces + if (workspace.default) { + await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid: workspace.uuid }); + + // Collect UUIDs of workspaces that were unset as default + const unsetDefaultRows = await db.execute(`SELECT uuid FROM zen_workspaces WHERE is_default = 0 AND uuid != :uuid`, { uuid: workspace.uuid }); + for (const row of unsetDefaultRows) { + changedUUIDs.add(row.getResultByName('uuid')); + } + } + + // Get the current maximum position + const maxOrderResult = await db.execute(`SELECT MAX("position") as max_position FROM zen_workspaces`); + const maxOrder = maxOrderResult[0].getResultByName('max_position') || 0; + + let newOrder; + + if ('position' in workspace && workspace.position !== null && Number.isInteger(workspace.position)) { + // If position is provided, check if it's already occupied + const occupiedOrderResult = await db.execute(` + SELECT uuid FROM zen_workspaces WHERE "position" = :position AND uuid != :uuid + `, { position: workspace.position, uuid: workspace.uuid }); + + if (occupiedOrderResult.length > 0) { + // If the position is occupied, shift the positions of subsequent workspaces + await db.execute(` + UPDATE zen_workspaces + SET "position" = "position" + 1 + WHERE "position" >= :position AND uuid != :uuid + `, { position: workspace.position, uuid: workspace.uuid }); + + // Collect UUIDs of workspaces whose positions were shifted + for (const row of occupiedOrderResult) { + changedUUIDs.add(row.getResultByName('uuid')); + } + } + + newOrder = workspace.position; + } else { + // If no position is provided, set it to the last position + newOrder = maxOrder + 1; + } + + // Insert or replace the workspace + await db.executeCached(` + INSERT OR REPLACE INTO zen_workspaces ( + uuid, name, icon, is_default, container_id, created_at, updated_at, "position" + ) VALUES ( + :uuid, :name, :icon, :is_default, :container_id, + COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now), + :now, + :position + ) + `, { uuid: workspace.uuid, name: workspace.name, icon: workspace.icon || null, is_default: workspace.default ? 1 : 0, container_id: workspace.containerTabId || null, now, - } - ); + position: newOrder + }); + + // Record the change in the changes tracking table + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid, :timestamp) + `, { + uuid: workspace.uuid, + timestamp: Math.floor(now / 1000) // Unix timestamp in seconds + }); + + // Add the main workspace UUID to the changed set + changedUUIDs.add(workspace.uuid); + }); }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); + } }, async getWorkspaces() { @@ -72,26 +161,185 @@ var ZenWorkspacesStorage = { icon: row.getResultByName('icon'), default: !!row.getResultByName('is_default'), containerTabId: row.getResultByName('container_id'), + position: row.getResultByName('position'), })); }, - async removeWorkspace(uuid) { + async removeWorkspace(uuid, notifyObservers = true) { + const changedUUIDs = [uuid]; + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.removeWorkspace', async (db) => { await db.execute( - ` - DELETE FROM zen_workspaces WHERE uuid = :uuid - `, - { uuid } + ` + DELETE FROM zen_workspaces WHERE uuid = :uuid + `, + { uuid } ); + + // Record the removal as a change + const now = Date.now(); + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid, :timestamp) + `, { + uuid, + timestamp: Math.floor(now / 1000) + }); + }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-removed", changedUUIDs); + } + }, + + async wipeAllWorkspaces() { + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.wipeAllWorkspaces', async (db) => { + await db.execute(`DELETE FROM zen_workspaces`); + await db.execute(`DELETE FROM zen_workspaces_changes`); }); }, - async setDefaultWorkspace(uuid) { + async setDefaultWorkspace(uuid, notifyObservers = true) { + const changedUUIDs = []; + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.setDefaultWorkspace', async (db) => { await db.executeTransaction(async function () { - await db.execute(`UPDATE zen_workspaces SET is_default = 0`); + const now = Date.now(); + // Unset the default flag for all other workspaces + await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid }); + + // Collect UUIDs of workspaces that were unset as default + const unsetDefaultRows = await db.execute(`SELECT uuid FROM zen_workspaces WHERE is_default = 0 AND uuid != :uuid`, { uuid }); + for (const row of unsetDefaultRows) { + changedUUIDs.push(row.getResultByName('uuid')); + } + + // Set the default flag for the specified workspace await db.execute(`UPDATE zen_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid }); + + // Record the change for the specified workspace + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid, :timestamp) + `, { + uuid, + timestamp: Math.floor(now / 1000) + }); + + // Add the main workspace UUID to the changed set + changedUUIDs.push(uuid); + }); + }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-updated", changedUUIDs); + } + }, + + async markChanged(uuid) { + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.markChanged', async (db) => { + const now = Date.now(); + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid, :timestamp) + `, { + uuid, + timestamp: Math.floor(now / 1000) }); }); }, + + async getChangedIDs() { + const db = await PlacesUtils.promiseDBConnection(); + const rows = await db.execute(` + SELECT uuid, timestamp FROM zen_workspaces_changes + `); + const changes = {}; + for (const row of rows) { + changes[row.getResultByName('uuid')] = row.getResultByName('timestamp'); + } + return changes; + }, + + async clearChangedIDs() { + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.clearChangedIDs', async (db) => { + await db.execute(`DELETE FROM zen_workspaces_changes`); + }); + }, + + async updateWorkspaceOrder(uuid, newOrder, notifyObservers = true) { + const changedUUIDs = [uuid]; + + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.updateWorkspaceOrder', async (db) => { + await db.executeTransaction(async function () { + // Get the current position of the workspace + const currentOrderResult = await db.execute(` + SELECT "position" FROM zen_workspaces WHERE uuid = :uuid + `, { uuid }); + const currentOrder = currentOrderResult[0].getResultByName('position'); + + if (currentOrder === newOrder) { + return; // No change needed + } + + if (newOrder > currentOrder) { + // Moving down: decrement position of workspaces between old and new positions + const rows = await db.execute(` + SELECT uuid FROM zen_workspaces + WHERE "position" > :currentOrder AND "position" <= :newOrder + `, { currentOrder, newOrder }); + + await db.execute(` + UPDATE zen_workspaces + SET "position" = "position" - 1 + WHERE "position" > :currentOrder AND "position" <= :newOrder + `, { currentOrder, newOrder }); + + for (const row of rows) { + changedUUIDs.push(row.getResultByName('uuid')); + } + } else { + // Moving up: increment position of workspaces between new and old positions + const rows = await db.execute(` + SELECT uuid FROM zen_workspaces + WHERE "position" >= :newOrder AND "position" < :currentOrder + `, { currentOrder, newOrder }); + + await db.execute(` + UPDATE zen_workspaces + SET "position" = "position" + 1 + WHERE "position" >= :newOrder AND "position" < :currentOrder + `, { currentOrder, newOrder }); + + for (const row of rows) { + changedUUIDs.push(row.getResultByName('uuid')); + } + } + + // Set the new position for the workspace + await db.execute(` + UPDATE zen_workspaces + SET "position" = :newOrder + WHERE uuid = :uuid + `, { uuid, newOrder }); + + // Record the change for the specified workspace + const now = Date.now(); + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid, :timestamp) + `, { + uuid, + timestamp: Math.floor(now / 1000) + }); + + // Add the main workspace UUID to the changed set + changedUUIDs.push(uuid); + }); + }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-updated", changedUUIDs); + } + }, }; diff --git a/src/ZenWorkspacesSync.mjs b/src/ZenWorkspacesSync.mjs new file mode 100644 index 0000000..5a7737e --- /dev/null +++ b/src/ZenWorkspacesSync.mjs @@ -0,0 +1,409 @@ +var { Tracker, Store, SyncEngine } = ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs"); +var { CryptoWrapper } = ChromeUtils.importESModule("resource://services-sync/record.sys.mjs"); +var { Utils } = ChromeUtils.importESModule("resource://services-sync/util.sys.mjs"); +var { SCORE_INCREMENT_XLARGE } = ChromeUtils.importESModule("resource://services-sync/constants.sys.mjs"); + + + +// Define ZenWorkspaceRecord +function ZenWorkspaceRecord(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +ZenWorkspaceRecord.prototype = Object.create(CryptoWrapper.prototype); +ZenWorkspaceRecord.prototype.constructor = ZenWorkspaceRecord; + +ZenWorkspaceRecord.prototype._logName = "Sync.Record.ZenWorkspace"; + +Utils.deferGetSet(ZenWorkspaceRecord, "cleartext", [ + "name", + "icon", + "default", + "containerTabId", + "position" +]); + +// Define ZenWorkspacesStore +function ZenWorkspacesStore(name, engine) { + Store.call(this, name, engine); +} + +ZenWorkspacesStore.prototype = Object.create(Store.prototype); +ZenWorkspacesStore.prototype.constructor = ZenWorkspacesStore; + +/** + * Initializes the store by loading the current changeset. + */ +ZenWorkspacesStore.prototype.initialize = async function () { + await Store.prototype.initialize.call(this); + // Additional initialization if needed +}; + +/** + * Retrieves all workspace IDs from the storage. + * @returns {Object} An object mapping workspace UUIDs to true. + */ +ZenWorkspacesStore.prototype.getAllIDs = async function () { + try { + const workspaces = await ZenWorkspacesStorage.getWorkspaces(); + const ids = {}; + for (const workspace of workspaces) { + ids[workspace.uuid] = true; + } + return ids; + } catch (error) { + this._log.error("Error fetching all workspace IDs", error); + throw error; + } +}; + +/** + * Handles changing the ID of a workspace. + * @param {String} oldID - The old UUID. + * @param {String} newID - The new UUID. + */ +ZenWorkspacesStore.prototype.changeItemID = async function (oldID, newID) { + try { + const workspaces = await ZenWorkspacesStorage.getWorkspaces(); + const workspace = workspaces.find(ws => ws.uuid === oldID); + if (workspace) { + workspace.uuid = newID; + await ZenWorkspacesStorage.saveWorkspace(workspace,false); + // Mark the new ID as changed for sync + await ZenWorkspacesStorage.markChanged(newID); + } + } catch (error) { + this._log.error(`Error changing workspace ID from ${oldID} to ${newID}`, error); + throw error; + } +}; + +/** + * Checks if a workspace exists. + * @param {String} id - The UUID of the workspace. + * @returns {Boolean} True if the workspace exists, false otherwise. + */ +ZenWorkspacesStore.prototype.itemExists = async function (id) { + try { + const workspaces = await ZenWorkspacesStorage.getWorkspaces(); + return workspaces.some(ws => ws.uuid === id); + } catch (error) { + this._log.error(`Error checking if workspace exists with ID ${id}`, error); + throw error; + } +}; + +/** + * Creates a record for a workspace. + * @param {String} id - The UUID of the workspace. + * @param {String} collection - The collection name. + * @returns {ZenWorkspaceRecord} The workspace record. + */ +ZenWorkspacesStore.prototype.createRecord = async function (id, collection) { + try { + const workspaces = await ZenWorkspacesStorage.getWorkspaces(); + const workspace = workspaces.find(ws => ws.uuid === id); + const record = new ZenWorkspaceRecord(collection, id); + + if (workspace) { + record.name = workspace.name; + record.icon = workspace.icon; + record.default = workspace.default; + record.containerTabId = workspace.containerTabId; + record.position = workspace.position; + record.deleted = false; + } else { + record.deleted = true; + } + + return record; + } catch (error) { + this._log.error(`Error creating record for workspace ID ${id}`, error); + throw error; + } +}; + +/** + * Creates a new workspace. + * @param {ZenWorkspaceRecord} record - The workspace record to create. + */ +ZenWorkspacesStore.prototype.create = async function (record) { + try { + this._validateRecord(record); + const workspace = { + uuid: record.id, + name: record.name, + icon: record.icon, + default: record.default, + containerTabId: record.containerTabId, + position: record.position + }; + await ZenWorkspacesStorage.saveWorkspace(workspace,false); + } catch (error) { + this._log.error(`Error creating workspace with ID ${record.id}`, error); + throw error; + } +}; + +/** + * Updates an existing workspace. + * @param {ZenWorkspaceRecord} record - The workspace record to update. + */ +ZenWorkspacesStore.prototype.update = async function (record) { + try { + this._validateRecord(record); + await this.create(record); // Reuse create for update + } catch (error) { + this._log.error(`Error updating workspace with ID ${record.id}`, error); + throw error; + } +}; + +/** + * Removes a workspace. + * @param {ZenWorkspaceRecord} record - The workspace record to remove. + */ +ZenWorkspacesStore.prototype.remove = async function (record) { + try { + await ZenWorkspacesStorage.removeWorkspace(record.id, false); + } catch (error) { + this._log.error(`Error removing workspace with ID ${record.id}`, error); + throw error; + } +}; + +/** + * Wipes all workspaces from the storage. + */ +ZenWorkspacesStore.prototype.wipe = async function () { + try { + await ZenWorkspacesStorage.wipeAllWorkspaces(); + } catch (error) { + this._log.error("Error wiping all workspaces", error); + throw error; + } +}; + +/** + * Validates a workspace record. + * @param {ZenWorkspaceRecord} record - The workspace record to validate. + */ +ZenWorkspacesStore.prototype._validateRecord = function (record) { + if (!record.id || typeof record.id !== "string") { + throw new Error("Invalid workspace ID"); + } + if (!record.name || typeof record.name !== "string") { + throw new Error(`Invalid workspace name for ID ${record.id}`); + } + if (typeof record.default !== "boolean") { + record.default = false; + } + if (record.icon != null && typeof record.icon !== "string") { + throw new Error(`Invalid icon for workspace ID ${record.id}`); + } + if (record.containerTabId != null && typeof record.containerTabId !== "number") { + throw new Error(`Invalid containerTabId for workspace ID ${record.id}`); + } + if(record.position != null && typeof record.position !== "number") { + throw new Error(`Invalid position for workspace ID ${record.id}`); + } +}; + +/** + * Retrieves changed workspace IDs since the last sync. + * @returns {Object} An object mapping workspace UUIDs to their change timestamps. + */ +ZenWorkspacesStore.prototype.getChangedIDs = async function () { + try { + return await ZenWorkspacesStorage.getChangedIDs(); + } catch (error) { + this._log.error("Error retrieving changed IDs from storage", error); + throw error; + } +}; + +/** + * Clears all recorded changes after a successful sync. + */ +ZenWorkspacesStore.prototype.clearChangedIDs = async function () { + try { + await ZenWorkspacesStorage.clearChangedIDs(); + } catch (error) { + this._log.error("Error clearing changed IDs in storage", error); + throw error; + } +}; + +/** + * Marks a workspace as changed. + * @param {String} uuid - The UUID of the workspace that changed. + */ +ZenWorkspacesStore.prototype.markChanged = async function (uuid) { + try { + await ZenWorkspacesStorage.markChanged(uuid); + } catch (error) { + this._log.error(`Error marking workspace ${uuid} as changed`, error); + throw error; + } +}; + +/** + * Finalizes the store by ensuring all pending operations are completed. + */ +ZenWorkspacesStore.prototype.finalize = async function () { + await Store.prototype.finalize.call(this); +}; + + +// Define ZenWorkspacesTracker +function ZenWorkspacesTracker(name, engine) { + Tracker.call(this, name, engine); + this._ignoreAll = false; + + // Observe profile-before-change to stop the tracker gracefully + Services.obs.addObserver(this.asyncObserver, "profile-before-change"); +} + +ZenWorkspacesTracker.prototype = Object.create(Tracker.prototype); +ZenWorkspacesTracker.prototype.constructor = ZenWorkspacesTracker; + +/** + * Retrieves changed workspace IDs by delegating to the store. + * @returns {Object} An object mapping workspace UUIDs to their change timestamps. + */ +ZenWorkspacesTracker.prototype.getChangedIDs = async function () { + try { + return await this.engine._store.getChangedIDs(); + } catch (error) { + this._log.error("Error retrieving changed IDs from store", error); + throw error; + } +}; + +/** + * Clears all recorded changes after a successful sync. + */ +ZenWorkspacesTracker.prototype.clearChangedIDs = async function () { + try { + await this.engine._store.clearChangedIDs(); + } catch (error) { + this._log.error("Error clearing changed IDs in store", error); + throw error; + } +}; + +/** + * Called when the tracker starts. Registers observers to listen for workspace changes. + */ +ZenWorkspacesTracker.prototype.onStart = function () { + if (this._started) { + return; + } + this._log.trace("Starting tracker"); + // Register observers for workspace changes + Services.obs.addObserver(this.asyncObserver, "zen-workspace-added"); + Services.obs.addObserver(this.asyncObserver, "zen-workspace-removed"); + Services.obs.addObserver(this.asyncObserver, "zen-workspace-updated"); + this._started = true; +}; + +/** + * Called when the tracker stops. Unregisters observers. + */ +ZenWorkspacesTracker.prototype.onStop = function () { + if (!this._started) { + return; + } + this._log.trace("Stopping tracker"); + // Unregister observers for workspace changes + Services.obs.removeObserver(this.asyncObserver, "zen-workspace-added"); + Services.obs.removeObserver(this.asyncObserver, "zen-workspace-removed"); + Services.obs.removeObserver(this.asyncObserver, "zen-workspace-updated"); + this._started = false; +}; + +/** + * Handles observed events and marks workspaces as changed accordingly. + * @param {nsISupports} subject - The subject of the notification. + * @param {String} topic - The topic of the notification. + * @param {String} data - Additional data (JSON stringified array of UUIDs). + */ +ZenWorkspacesTracker.prototype.observe = async function (subject, topic, data) { + if (this.ignoreAll) { + return; + } + + try { + switch (topic) { + case "profile-before-change": + await this.stop(); + break; + case "zen-workspace-removed": + case "zen-workspace-updated": + case "zen-workspace-added": + let workspaceIDs; + if (data) { + try { + workspaceIDs = JSON.parse(data); + if (!Array.isArray(workspaceIDs)) { + throw new Error("Parsed data is not an array"); + } + } catch (parseError) { + this._log.error(`Failed to parse workspace UUIDs from data: ${data}`, parseError); + return; + } + } else { + this._log.error(`No data received for event ${topic}`); + return; + } + + this._log.trace(`Observed ${topic} for UUIDs: ${workspaceIDs.join(", ")}`); + + // Process each UUID + for (const workspaceID of workspaceIDs) { + if (typeof workspaceID === "string") { + // Inform the store about the change + await this.engine._store.markChanged(workspaceID); + } else { + this._log.warn(`Invalid workspace ID encountered: ${workspaceID}`); + } + } + + // Bump the score once after processing all changes + if (workspaceIDs.length > 0) { + this.score += SCORE_INCREMENT_XLARGE; + } + break; + } + } catch (error) { + this._log.error(`Error handling ${topic} in observe method`, error); + } +}; + +/** + * Finalizes the tracker by ensuring all pending operations are completed. + */ +ZenWorkspacesTracker.prototype.finalize = async function () { + await Tracker.prototype.finalize.call(this); +}; + + +// Define ZenWorkspacesEngine +function ZenWorkspacesEngine(service) { + SyncEngine.call(this, "Workspaces", service); +} + +ZenWorkspacesEngine.prototype = Object.create(SyncEngine.prototype); +ZenWorkspacesEngine.prototype.constructor = ZenWorkspacesEngine; + +ZenWorkspacesEngine.prototype._storeObj = ZenWorkspacesStore; +ZenWorkspacesEngine.prototype._trackerObj = ZenWorkspacesTracker; +ZenWorkspacesEngine.prototype._recordObj = ZenWorkspaceRecord; +ZenWorkspacesEngine.prototype.version = 1; + +ZenWorkspacesEngine.prototype.syncPriority = 10; +ZenWorkspacesEngine.prototype.allowSkippedRecord = false; + +Object.setPrototypeOf(ZenWorkspacesEngine.prototype, SyncEngine.prototype); + +