mirror of
https://github.com/zen-browser/components.git
synced 2025-07-10 04:35:29 +02:00
feat: Add workspace ordering and changes tracking
This commit introduces workspace ordering and a new mechanism for tracking changes to workspaces. **Changes:** - **Workspace Ordering:** Workspaces can now be ordered using a `position` field. This allows for user-defined ordering of workspaces, improving usability. - **Changes Tracking:** A new `zen_workspaces_changes` table is added to track changes to workspaces. This allows for more efficient sync operations and improved error handling. **Benefits:** - **Improved Workspace Management:** Users can now customize the order of their workspaces. - **More Efficient Sync:** Changes tracking enables faster and more accurate synchronization of workspaces across devices. - **Enhanced Error Handling:** Changes tracking helps to identify and resolve conflicts during sync. **Notes:** - This change requires deleting the zen_workspaces table in places db
This commit is contained in:
parent
1c7bc5c501
commit
0d161326ef
3 changed files with 535 additions and 211 deletions
|
@ -84,6 +84,8 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||||
// Set the active workspace ID to the first one if active workspace doesn't exist
|
// 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);
|
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;
|
return this._workspaceCache;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ var ZenWorkspacesStorage = {
|
||||||
|
|
||||||
async _ensureTable() {
|
async _ensureTable() {
|
||||||
await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage._ensureTable', async (db) => {
|
await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage._ensureTable', async (db) => {
|
||||||
|
// Create the main workspaces table if it doesn't exist
|
||||||
await db.execute(`
|
await db.execute(`
|
||||||
CREATE TABLE IF NOT EXISTS zen_workspaces (
|
CREATE TABLE IF NOT EXISTS zen_workspaces (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
|
@ -14,11 +15,22 @@ var ZenWorkspacesStorage = {
|
||||||
icon TEXT,
|
icon TEXT,
|
||||||
is_default INTEGER NOT NULL DEFAULT 0,
|
is_default INTEGER NOT NULL DEFAULT 0,
|
||||||
container_id INTEGER,
|
container_id INTEGER,
|
||||||
|
position INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
updated_at INTEGER NOT NULL
|
updated_at INTEGER NOT NULL,
|
||||||
|
used INTEGER NOT NULL DEFAULT 0
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 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();
|
await this._migrateWorkspacesFromJSON();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -46,22 +58,60 @@ var ZenWorkspacesStorage = {
|
||||||
await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid: workspace.uuid });
|
await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid: workspace.uuid });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then insert or replace the workspace
|
// 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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
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(`
|
await db.executeCached(`
|
||||||
INSERT OR REPLACE INTO zen_workspaces (
|
INSERT OR REPLACE INTO zen_workspaces (
|
||||||
uuid, name, icon, is_default, container_id, created_at, updated_at
|
uuid, name, icon, is_default, container_id, created_at, updated_at, "position"
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:uuid, :name, :icon, :is_default, :container_id,
|
:uuid, :name, :icon, :is_default, :container_id,
|
||||||
COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now),
|
COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now),
|
||||||
:now
|
:now,
|
||||||
|
:position
|
||||||
)
|
)
|
||||||
`, {
|
`, {
|
||||||
uuid: workspace.uuid,
|
uuid: workspace.uuid,
|
||||||
name: workspace.name,
|
name: workspace.name,
|
||||||
icon: workspace.icon || null,
|
icon: workspace.icon || null,
|
||||||
is_default: workspace.default ? 1 : 0,
|
is_default: workspace.default ? 1 : 0,
|
||||||
container_id: workspace.containerTabId || null,
|
container_id: workspace.containerTabId || null,
|
||||||
now
|
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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -78,6 +128,7 @@ var ZenWorkspacesStorage = {
|
||||||
icon: row.getResultByName('icon'),
|
icon: row.getResultByName('icon'),
|
||||||
default: !!row.getResultByName('is_default'),
|
default: !!row.getResultByName('is_default'),
|
||||||
containerTabId: row.getResultByName('container_id'),
|
containerTabId: row.getResultByName('container_id'),
|
||||||
|
position: row.getResultByName('position'),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -89,14 +140,122 @@ var ZenWorkspacesStorage = {
|
||||||
`,
|
`,
|
||||||
{ 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)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.setDefaultWorkspace', async (db) => {
|
await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.setDefaultWorkspace', async (db) => {
|
||||||
await db.executeTransaction(async function () {
|
await db.executeTransaction(async function () {
|
||||||
|
const now = Date.now();
|
||||||
|
// Unset the default flag for all other workspaces
|
||||||
await db.execute(`UPDATE zen_workspaces SET is_default = 0`);
|
await db.execute(`UPDATE zen_workspaces SET is_default = 0`);
|
||||||
|
// Set the default flag for the specified workspace
|
||||||
await db.execute(`UPDATE zen_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid });
|
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)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
await db.execute(`
|
||||||
|
UPDATE zen_workspaces
|
||||||
|
SET "position" = "position" - 1
|
||||||
|
WHERE "position" > :currentOrder AND "position" <= :newOrder
|
||||||
|
`, { currentOrder, newOrder });
|
||||||
|
} else {
|
||||||
|
// Moving up: increment position of workspaces between new and old positions
|
||||||
|
await db.execute(`
|
||||||
|
UPDATE zen_workspaces
|
||||||
|
SET "position" = "position" + 1
|
||||||
|
WHERE "position" >= :newOrder AND "position" < :currentOrder
|
||||||
|
`, { currentOrder, newOrder });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new position for the workspace
|
||||||
|
await db.execute(`
|
||||||
|
UPDATE zen_workspaces
|
||||||
|
SET "position" = :newOrder
|
||||||
|
WHERE uuid = :uuid
|
||||||
|
`, { uuid, newOrder });
|
||||||
|
|
||||||
|
// Mark the workspace as changed
|
||||||
|
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)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,221 +1,384 @@
|
||||||
var { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
|
var { Tracker, Store, SyncEngine } = ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs");
|
||||||
var { LegacyTracker } = ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs");
|
|
||||||
var { Store } = ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs");
|
|
||||||
var { SyncEngine } = ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs");
|
|
||||||
var { CryptoWrapper } = ChromeUtils.importESModule("resource://services-sync/record.sys.mjs");
|
var { CryptoWrapper } = ChromeUtils.importESModule("resource://services-sync/record.sys.mjs");
|
||||||
var { Svc,Utils } = ChromeUtils.importESModule("resource://services-sync/util.sys.mjs");
|
var { Utils } = ChromeUtils.importESModule("resource://services-sync/util.sys.mjs");
|
||||||
var { SCORE_INCREMENT_XLARGE } = ChromeUtils.importESModule("resource://services-sync/constants.sys.mjs");
|
var { SCORE_INCREMENT_XLARGE } = ChromeUtils.importESModule("resource://services-sync/constants.sys.mjs");
|
||||||
|
|
||||||
function ZenWorkspacesTracker(name, engine) {
|
|
||||||
LegacyTracker.call(this, name, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
ZenWorkspacesTracker.prototype = {
|
|
||||||
__proto__: LegacyTracker.prototype,
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this._started) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._log.trace("Starting tracker");
|
|
||||||
Services.obs.addObserver(this, "zen-workspace-added");
|
|
||||||
Services.obs.addObserver(this, "zen-workspace-removed");
|
|
||||||
Services.obs.addObserver(this, "zen-workspace-updated");
|
|
||||||
this._started = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if (!this._started) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._log.trace("Stopping tracker");
|
|
||||||
Services.obs.removeObserver(this, "zen-workspace-added");
|
|
||||||
Services.obs.removeObserver(this, "zen-workspace-removed");
|
|
||||||
Services.obs.removeObserver(this, "zen-workspace-updated");
|
|
||||||
this._started = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
observe(subject, topic, data) {
|
|
||||||
switch (topic) {
|
|
||||||
case "zen-workspace-removed":
|
|
||||||
case "zen-workspace-updated":
|
|
||||||
case "zen-workspace-added":
|
|
||||||
let workspaceID = data;
|
|
||||||
this._log.trace(`Observed ${topic} for ${workspaceID}`);
|
|
||||||
this.addChangedID(workspaceID);
|
|
||||||
this.score += SCORE_INCREMENT_XLARGE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function ZenWorkspacesStore(name, engine) {
|
|
||||||
Store.call(this, name, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
ZenWorkspacesStore.prototype = {
|
|
||||||
__proto__: Store.prototype,
|
|
||||||
|
|
||||||
async getAllIDs() {
|
|
||||||
try {
|
|
||||||
let workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
|
||||||
let ids = {};
|
|
||||||
for (let workspace of workspaces) {
|
|
||||||
ids[workspace.uuid] = true;
|
|
||||||
}
|
|
||||||
return ids;
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error("Error fetching all workspace IDs", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async changeItemID(oldID, newID) {
|
|
||||||
try {
|
|
||||||
let workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
|
||||||
let workspace = workspaces.find(ws => ws.uuid === oldID);
|
|
||||||
if (workspace) {
|
|
||||||
workspace.uuid = newID;
|
|
||||||
await ZenWorkspacesStorage.saveWorkspace(workspace);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error(`Error changing workspace ID from ${oldID} to ${newID}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async itemExists(id) {
|
|
||||||
try {
|
|
||||||
let 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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async createRecord(id, collection) {
|
|
||||||
try {
|
|
||||||
let workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
|
||||||
let workspace = workspaces.find(ws => ws.uuid === id);
|
|
||||||
let record = new ZenWorkspaceRecord(collection, id);
|
|
||||||
|
|
||||||
if (workspace) {
|
|
||||||
record.name = workspace.name;
|
|
||||||
record.icon = workspace.icon;
|
|
||||||
record.default = workspace.default;
|
|
||||||
record.containerTabId = workspace.containerTabId;
|
|
||||||
record.deleted = false;
|
|
||||||
} else {
|
|
||||||
record.deleted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return record;
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error(`Error creating record for workspace ID ${id}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async create(record) {
|
|
||||||
try {
|
|
||||||
// Data validation
|
|
||||||
this._validateRecord(record);
|
|
||||||
|
|
||||||
let workspace = {
|
|
||||||
uuid: record.id,
|
|
||||||
name: record.name,
|
|
||||||
icon: record.icon,
|
|
||||||
default: record.default,
|
|
||||||
containerTabId: record.containerTabId,
|
|
||||||
};
|
|
||||||
await ZenWorkspacesStorage.saveWorkspace(workspace);
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error(`Error creating workspace with ID ${record.id}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async update(record) {
|
|
||||||
try {
|
|
||||||
// Data validation
|
|
||||||
this._validateRecord(record);
|
|
||||||
|
|
||||||
await this.create(record);
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error(`Error updating workspace with ID ${record.id}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async remove(record) {
|
|
||||||
try {
|
|
||||||
await ZenWorkspacesStorage.removeWorkspace(record.id);
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error(`Error removing workspace with ID ${record.id}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async wipe() {
|
|
||||||
try {
|
|
||||||
let workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
|
||||||
for (let workspace of workspaces) {
|
|
||||||
await ZenWorkspacesStorage.removeWorkspace(workspace.uuid);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this._log.error("Error wiping all workspaces", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_validateRecord(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}`);
|
|
||||||
}
|
|
||||||
// 'default' is a boolean; if undefined, default to false
|
|
||||||
if (typeof record.default !== "boolean") {
|
|
||||||
record.default = false;
|
|
||||||
}
|
|
||||||
// 'icon' and 'containerTabId' can be null, but should be validated if present
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function ZenWorkspacesEngine(service) {
|
|
||||||
SyncEngine.call(this, "Workspaces", service);
|
|
||||||
}
|
|
||||||
|
|
||||||
ZenWorkspacesEngine.prototype = {
|
|
||||||
__proto__: SyncEngine.prototype,
|
|
||||||
|
|
||||||
_storeObj: ZenWorkspacesStore,
|
|
||||||
_trackerObj: ZenWorkspacesTracker,
|
|
||||||
_recordObj: ZenWorkspaceRecord,
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Define ZenWorkspaceRecord
|
||||||
function ZenWorkspaceRecord(collection, id) {
|
function ZenWorkspaceRecord(collection, id) {
|
||||||
CryptoWrapper.call(this, collection, id);
|
CryptoWrapper.call(this, collection, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ZenWorkspaceRecord.prototype = {
|
ZenWorkspaceRecord.prototype = Object.create(CryptoWrapper.prototype);
|
||||||
__proto__: CryptoWrapper.prototype,
|
ZenWorkspaceRecord.prototype.constructor = ZenWorkspaceRecord;
|
||||||
_logName: "Sync.Record.ZenWorkspace",
|
|
||||||
|
|
||||||
};
|
ZenWorkspaceRecord.prototype._logName = "Sync.Record.ZenWorkspace";
|
||||||
|
|
||||||
Utils.deferGetSet(ZenWorkspaceRecord, "cleartext", [
|
Utils.deferGetSet(ZenWorkspaceRecord, "cleartext", [
|
||||||
"name",
|
"name",
|
||||||
"icon",
|
"icon",
|
||||||
"default",
|
"default",
|
||||||
"containerTabId",
|
"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);
|
||||||
|
// 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);
|
||||||
|
} 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);
|
||||||
|
// Changes are already marked within ZenWorkspacesStorage.removeWorkspace
|
||||||
|
} 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 (workspace UUID).
|
||||||
|
*/
|
||||||
|
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":
|
||||||
|
const workspaceID = data;
|
||||||
|
this._log.trace(`Observed ${topic} for ${workspaceID}`);
|
||||||
|
// Inform the store about the change
|
||||||
|
await this.engine._store.markChanged(workspaceID);
|
||||||
|
// Bump the score to indicate a high-priority sync
|
||||||
|
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