mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 23:30:46 +00:00
121 lines
3.8 KiB
JavaScript
121 lines
3.8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
|
|
});
|
|
|
|
/**
|
|
* A helper to keep track of synchronization statuses.
|
|
*
|
|
* We rely on a different storage backend than for storing Remote Settings data,
|
|
* because the eventual goal is to be able to detect `IndexedDB` issues and act
|
|
* accordingly.
|
|
*/
|
|
export class SyncHistory {
|
|
// Internal reference to underlying rkv store.
|
|
#store;
|
|
|
|
/**
|
|
* @param {String} source the synchronization source (eg. `"settings-sync"`)
|
|
* @param {Object} options
|
|
* @param {int} options.size Maximum number of entries per source.
|
|
*/
|
|
constructor(source, { size } = { size: 100 }) {
|
|
this.source = source;
|
|
this.size = size;
|
|
}
|
|
|
|
/**
|
|
* Store the synchronization status. The ETag is converted and stored as
|
|
* a millisecond epoch timestamp.
|
|
* The entries with the oldest timestamps will be deleted to maintain the
|
|
* history size under the configured maximum.
|
|
*
|
|
* @param {String} etag the ETag value from the server (eg. `"1647961052593"`)
|
|
* @param {String} status the synchronization status (eg. `"success"`)
|
|
* @param {Object} infos optional additional information to keep track of
|
|
*/
|
|
async store(etag, status, infos = {}) {
|
|
const rkv = await this.#init();
|
|
const timestamp = parseInt(etag.replace('"', ""), 10);
|
|
if (Number.isNaN(timestamp)) {
|
|
throw new Error(`Invalid ETag value ${etag}`);
|
|
}
|
|
const key = `v1-${this.source}\t${timestamp}`;
|
|
const value = { timestamp, status, infos };
|
|
await rkv.put(key, JSON.stringify(value));
|
|
// Trim old entries.
|
|
const allEntries = await this.list();
|
|
for (let i = this.size; i < allEntries.length; i++) {
|
|
let { timestamp } = allEntries[i];
|
|
await rkv.delete(`v1-${this.source}\t${timestamp}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stored history entries for a certain source, sorted by
|
|
* timestamp descending.
|
|
*
|
|
* @returns {Array<Object>} a list of objects
|
|
*/
|
|
async list() {
|
|
const rkv = await this.#init();
|
|
const entries = [];
|
|
// The "from" and "to" key parameters to nsIKeyValueStore.enumerate()
|
|
// are inclusive and exclusive, respectively, and keys are tuples
|
|
// of source and datetime joined by a tab (\t), which is character code 9;
|
|
// so enumerating ["source", "source\n"), where the line feed (\n)
|
|
// is character code 10, enumerates all pairs with the given source.
|
|
for (const { value } of await rkv.enumerate(
|
|
`v1-${this.source}`,
|
|
`v1-${this.source}\n`
|
|
)) {
|
|
try {
|
|
const stored = JSON.parse(value);
|
|
entries.push({ ...stored, datetime: new Date(stored.timestamp) });
|
|
} catch (e) {
|
|
// Ignore malformed entries.
|
|
console.error(e);
|
|
}
|
|
}
|
|
// Sort entries by `timestamp` descending.
|
|
entries.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1));
|
|
return entries;
|
|
}
|
|
|
|
/**
|
|
* Return the most recent entry.
|
|
*/
|
|
async last() {
|
|
// List is sorted from newer to older.
|
|
return (await this.list())[0];
|
|
}
|
|
|
|
/**
|
|
* Wipe out the **whole** store.
|
|
*/
|
|
async clear() {
|
|
const rkv = await this.#init();
|
|
await rkv.clear();
|
|
}
|
|
|
|
/**
|
|
* Initialize the rkv store in the user profile.
|
|
*
|
|
* @returns {Object} the underlying `KeyValueService` instance.
|
|
*/
|
|
async #init() {
|
|
if (!this.#store) {
|
|
// Get and cache a handle to the kvstore.
|
|
const dir = PathUtils.join(PathUtils.profileDir, "settings");
|
|
await IOUtils.makeDirectory(dir);
|
|
this.#store = await lazy.KeyValueService.getOrCreate(dir, "synchistory");
|
|
}
|
|
return this.#store;
|
|
}
|
|
}
|