Merge pull request #37 from kristijanribaric/feature(workspace-sync)-add-workspace-sync-engine

Feature Sync Workspaces to Services-Sync
This commit is contained in:
mauro 🤙 2024-10-05 22:23:05 +02:00 committed by GitHub
commit 7ebaed73c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 698 additions and 24 deletions

View file

@ -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,
};

View file

@ -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<string>} 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);
}
},
};

409
src/ZenWorkspacesSync.mjs Normal file
View file

@ -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);