extract sync and cache from styleMan

This commit is contained in:
tophf 2022-01-23 14:07:55 +03:00
parent e54178a43c
commit c95f74e089
3 changed files with 137 additions and 150 deletions

View File

@ -8,7 +8,7 @@
/* exported
addAPI
bgReady
compareRevision
createCache
*/
const bgReady = {};
@ -26,6 +26,63 @@ function addAPI(methods) {
}
}
function compareRevision(rev1, rev2) {
return rev1 - rev2;
/** Creates a FIFO limit-size map. */
function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
if (onDeleted) {
onDeleted(item.id, item.data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
}

View File

@ -1,6 +1,6 @@
/* global API msg */// msg.js
/* global CHROME URLS isEmptyObj stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global bgReady compareRevision */// common.js
/* global bgReady createCache */// common.js
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js
/* global db */
/* global prefs */
@ -18,8 +18,15 @@ The live preview feature relies on `runtime.connect` and `port.onDisconnect`
to cleanup the temporary code. See livePreview in /edit.
*/
const styleUtil = {};
const styleMan = (() => {
Object.assign(styleUtil, {
id2style,
handleSave,
});
//#region Declarations
/** @typedef {{
@ -29,7 +36,6 @@ const styleMan = (() => {
}} StyleMapData */
/** @type {Map<number,StyleMapData>} */
const dataMap = new Map();
const uuidIndex = new Map();
/** @typedef {Object<styleId,{id: number, code: string[]}>} StyleSectionsToApply */
/** @type {Map<string,{maybeMatch: Set<styleId>, sections: StyleSectionsToApply}>} */
const cachedStyleForUrl = createCache({
@ -77,16 +83,17 @@ const styleMan = (() => {
}
});
prefs.subscribe(['injectionOrder'], (key, value) => {
order = {};
value.forEach((uid, i) => {
const id = uuidIndex.get(uid);
if (id) {
order[id] = i;
}
});
msg.broadcast({method: 'styleSort', order});
});
// TODO: will fix in subsequent commit
// prefs.subscribe(['injectionOrder'], (key, value) => {
// order = {};
// value.forEach((uid, i) => {
// const id = uuidIndex.get(uid);
// if (id) {
// order[id] = i;
// }
// });
// msg.broadcast({method: 'styleSort', order});
// });
//#endregion
//#region Exports
@ -107,7 +114,6 @@ const styleMan = (() => {
if (cache) delete cache.sections[id];
}
dataMap.delete(id);
uuidIndex.delete(style._id);
if (style._usw && style._usw.token) {
// Must be called after the style is deleted from dataMap
API.usw.revoke(id);
@ -120,17 +126,6 @@ const styleMan = (() => {
return id;
},
/** @returns {Promise<number>} style id */
async deleteByUUID(_id, rev) {
if (ready.then) await ready;
const id = uuidIndex.get(_id);
const oldDoc = id && id2style(id);
if (oldDoc && compareRevision(oldDoc._rev, rev) <= 0) {
// FIXME: does it make sense to set reason to 'sync' in deleteByUUID?
return styleMan.delete(id, 'sync');
}
},
/** @returns {Promise<StyleObj>} */
async editSave(style) {
if (ready.then) await ready;
@ -157,12 +152,6 @@ const styleMan = (() => {
return Array.from(dataMap.values(), data2style);
},
/** @returns {Promise<StyleObj>} */
async getByUUID(uuid) {
if (ready.then) await ready;
return id2style(uuidIndex.get(uuid));
},
/** @returns {Promise<StyleSectionsToApply>} */
async getSectionsByUrl(url, id, isInitialApply) {
if (ready.then) await ready;
@ -263,10 +252,9 @@ const styleMan = (() => {
}
}
const events = await db.exec('putMany', items);
return Promise.all(items.map((item, i) => {
afterSave(item, events[i]);
return handleSave(item, {reason: 'import'});
}));
return Promise.all(items.map((item, i) =>
handleSave(item, {reason: 'import'}, events[i])
));
},
/** @returns {Promise<StyleObj>} */
@ -279,31 +267,6 @@ const styleMan = (() => {
return saveStyle(style, {reason});
},
/** @returns {Promise<?StyleObj>} */
async putByUUID(doc) {
if (ready.then) await ready;
const id = uuidIndex.get(doc._id);
if (id) {
doc.id = id;
} else {
delete doc.id;
}
const oldDoc = id && id2style(id);
let diff = -1;
if (oldDoc) {
diff = compareRevision(oldDoc._rev, doc._rev);
if (diff > 0) {
API.sync.put(oldDoc._id, oldDoc._rev);
return;
}
}
if (diff < 0) {
doc.id = await db.exec('put', doc);
uuidIndex.set(doc._id, doc.id);
return handleSave(doc, {reason: 'sync'});
}
},
save: saveStyle,
/** @returns {Promise<number>} style id */
@ -472,29 +435,24 @@ const styleMan = (() => {
fixKnownProblems(style);
}
function afterSave(style, newId) {
if (style.id == null) {
style.id = newId;
}
uuidIndex.set(style._id, style.id);
API.sync.put(style._id, style._rev);
}
async function saveStyle(style, handlingOptions) {
beforeSave(style);
const newId = await db.exec('put', style);
afterSave(style, newId);
return handleSave(style, handlingOptions);
return handleSave(style, handlingOptions, newId);
}
function handleSave(style, {reason, broadcast = true}) {
const data = id2data(style.id);
function handleSave(style, {reason, broadcast = true}, id = style.id) {
if (style.id == null) style.id = id;
const data = id2data(id);
const method = data ? 'styleUpdated' : 'styleAdded';
if (!data) {
storeInMap(style);
} else {
data.style = style;
}
if (reason !== 'sync') {
API.sync.putStyle(style);
}
if (broadcast) broadcastStyleUpdated(style, reason, method);
return style;
}
@ -524,10 +482,7 @@ const styleMan = (() => {
if (updated.length) {
await db.exec('putMany', updated);
}
for (const style of styles) {
storeInMap(style);
uuidIndex.set(style._id, style.id);
}
styles.forEach(storeInMap);
ready = true;
bgReady._resolveStyles();
}
@ -732,64 +687,3 @@ const styleMan = (() => {
//#endregion
})();
/** Creates a FIFO limit-size map. */
function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
if (onDeleted) {
onDeleted(item.id, item.data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
}

View File

@ -1,8 +1,9 @@
/* global API msg */// msg.js
/* global chromeLocal chromeSync */// storage-util.js
/* global compareRevision */// common.js
/* global db */
/* global iconMan */
/* global prefs */
/* global styleUtil */
/* global tokenMan */
'use strict';
@ -28,11 +29,17 @@ const syncMan = (() => {
errorMessage: null,
login: false,
};
const uuidIndex = new Map();
const uuid2style = uuid => styleUtil.id2style(uuidIndex.get(uuid));
const compareRevision = (rev1, rev2) => rev1 - rev2;
let lastError = null;
let ctrl;
let currentDrive;
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = prefs.ready.then(() => {
let ready = prefs.ready.then(async () => {
for (const {id, _id} of await API.styles.getAll()) {
uuidIndex.set(_id, id);
}
ready = true;
prefs.subscribe('sync.enabled',
(_, val) => val === 'none'
@ -52,11 +59,12 @@ const syncMan = (() => {
return {
async delete(...args) {
async delete(_id, rev) {
if (ready.then) await ready;
if (!currentDrive) return;
schedule();
return ctrl.delete(...args);
uuidIndex.delete(_id);
return ctrl.delete(_id, rev);
},
/** @returns {Promise<SyncManager.Status>} */
@ -86,6 +94,11 @@ const syncMan = (() => {
return ctrl.put(...args);
},
putStyle({id, _id, _rev}) {
uuidIndex.set(_id, id);
return syncMan.put(_id, _rev);
},
async setDriveOptions(driveName, options) {
const key = `secure/sync/driveOptions/${driveName}`;
await chromeSync.setValue(key, options);
@ -178,14 +191,37 @@ const syncMan = (() => {
async function initController() {
await require(['/vendor/db-to-cloud/db-to-cloud.min']); /* global dbToCloud */
ctrl = dbToCloud.dbToCloud({
onGet(id) {
return API.styles.getByUUID(id);
onGet: uuid2style,
async onPut(doc) {
const id = uuidIndex.get(doc._id);
const oldDoc = uuid2style(doc._id);
if (id) {
doc.id = id;
} else {
delete doc.id;
}
let diff = -1;
if (oldDoc) {
diff = compareRevision(oldDoc._rev, doc._rev);
if (diff > 0) {
syncMan.put(oldDoc);
return;
}
}
if (diff < 0) {
doc.id = await db.exec('put', doc);
uuidIndex.set(doc._id, doc.id);
return styleUtil.handleSave(doc, {reason: 'sync'});
}
},
onPut(doc) {
return API.styles.putByUUID(doc);
},
onDelete(id, rev) {
return API.styles.deleteByUUID(id, rev);
onDelete(_id, rev) {
const id = uuidIndex.get(_id);
const oldDoc = uuid2style(_id);
if (oldDoc && compareRevision(oldDoc._rev, rev) <= 0) {
// FIXME: does it make sense to set reason to 'sync' in deleteByUUID?
uuidIndex.delete(id);
return API.styles.delete(id, 'sync');
}
},
async onFirstSync() {
for (const i of await API.styles.getAll()) {