mirror of
https://github.com/zen-browser/components.git
synced 2025-07-08 00:10:01 +02:00
Merge pull request #37 from kristijanribaric/feature(workspace-sync)-add-workspace-sync-engine
Feature Sync Workspaces to Services-Sync
This commit is contained in:
commit
7ebaed73c0
3 changed files with 698 additions and 24 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
409
src/ZenWorkspacesSync.mjs
Normal 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);
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue