aboutsummaryrefslogtreecommitdiff
path: root/common/indexeddb.js
diff options
context:
space:
mode:
Diffstat (limited to 'common/indexeddb.js')
-rw-r--r--common/indexeddb.js115
1 files changed, 90 insertions, 25 deletions
diff --git a/common/indexeddb.js b/common/indexeddb.js
index 96d19b7..1741c91 100644
--- a/common/indexeddb.js
+++ b/common/indexeddb.js
@@ -45,6 +45,7 @@
* IMPORTS_START
* IMPORT initial_data
* IMPORT entities
+ * IMPORT broadcast
* IMPORTS_END
*/
@@ -124,22 +125,43 @@ async function get_db()
await _save_items(initial_data.resources, initial_data.mappings, ctx);
}
- db = opened_db;
+ if (db)
+ opened_db.close();
+ else
+ db = opened_db;
return db;
}
+/* Helper function used by make_context(). */
+function reject_discard(context)
+{
+ broadcast.discard(context.sender);
+ broadcast.close(context.sender);
+ context.reject();
+}
+
+/* Helper function used by make_context(). */
+function resolve_flush(context)
+{
+ broadcast.close(context.sender);
+ context.resolve();
+}
+
/* Helper function used by start_items_transaction() and get_db(). */
function make_context(transaction, files)
{
- files = files || {};
- const context = {transaction, files, file_uses: {}};
+ const sender = broadcast.sender_connection();
+ files = files || {};
let resolve, reject;
- context.result = new Promise((...cbs) => [resolve, reject] = cbs);
+ const result = new Promise((...cbs) => [resolve, reject] = cbs);
+
+ const context =
+ {sender, transaction, resolve, reject, result, files, file_uses: {}};
- context.transaction.oncomplete = resolve;
- context.transaction.onerror = reject;
+ transaction.oncomplete = () => resolve_flush(context);
+ transaction.onerror = () => reject_discard(context);
return context;
}
@@ -221,13 +243,6 @@ async function finalize_items_transaction(context)
return context.result;
}
-async function with_items_transaction(cb, item_store_names, files={})
-{
- const context = await start_items_transaction(item_store_names, files);
- await cb(context);
- await finalize_items_transaction(context);
-}
-
/*
* How a sample data argument to the function below might look like:
*
@@ -265,10 +280,10 @@ async function with_items_transaction(cb, item_store_names, files={})
* }
* }
*/
-async function save_items(transaction, data)
+async function save_items(data)
{
- const items_store_names = ["resources", "mappings"];
- const context = start_items_transaction(items_store_names, data.files);
+ const item_store_names = ["resources", "mappings"];
+ const context = await start_items_transaction(item_store_names, data.files);
return _save_items(data.resources, data.mappings, context);
}
@@ -302,6 +317,8 @@ async function save_item(item, context)
for (const file_ref of entities.get_files(item))
await incr_file_uses(context, file_ref);
+ broadcast.prepare(context.sender, `idb_changes_${store_name}`,
+ item.identifier);
await _remove_item(store_name, item.identifier, context, false);
await idb_put(context.transaction, store_name, item);
}
@@ -326,30 +343,78 @@ async function _remove_item(store_name, identifier, context)
*/
async function remove_item(store_name, identifier, context)
{
+ broadcast.prepare(context.sender, `idb_changes_${store_name}`, identifier);
await _remove_item(store_name, identifier, context);
await idb_del(context.transaction, store_name, identifier);
}
-const remove_resource =
- (identifier, ctx) => remove_item("resources", identifier, ctx);
-const remove_mapping =
- (identifier, ctx) => remove_item("mappings", identifier, ctx);
+/* Callback used when listening to broadcasts while tracking db changes. */
+async function track_change(tracking, identifier)
+{
+ const transaction =
+ (await haketilodb.get()).transaction([tracking.store_name]);
+ const new_val = await idb_get(transaction, tracking.store_name, identifier);
+
+ tracking.onchange({identifier, new_val});
+}
+
+/*
+ * Monitor changes to `store_name` IndexedDB object store.
+ *
+ * `store_name` should be either "resources" or "mappings".
+ *
+ * `onchange` should be a callback that will be called when an item is added,
+ * modified or removed from the store. The callback will be passed an object
+ * representing the change as its first argument. This object will have the
+ * form:
+ * {
+ * identifier: "the identifier of modified resource/mapping",
+ * new_val: undefined // `undefined` if item removed, item object otherwise
+ * }
+ *
+ * Returns a [tracking, all_current_items] array where `tracking` is an object
+ * that can be later passed to haketilodb.untrack() to stop tracking changes and
+ * `all_current_items` is an array of items currently present in the object
+ * store.
+ *
+ * It is possible that `onchange` gets spuriously fired even when an item is not
+ * actually modified or that it only gets called once after multiple quick
+ * changes to an item.
+ */
+async function track(store_name, onchange)
+{
+ const tracking = {store_name, onchange};
+ tracking.listener =
+ broadcast.listener_connection(msg => track_change(tracking, msg[1]));
+ broadcast.subscribe(tracking.listener, `idb_changes_${store_name}`);
+
+ const transaction = (await haketilodb.get()).transaction([store_name]);
+ const all_req = transaction.objectStore(store_name).getAll();
+
+ return [tracking, (await wait_request(all_req)).target.result];
+}
+
+function untrack(tracking)
+{
+ broadcast.close(tracking.listener);
+}
const haketilodb = {
get: get_db,
save_items,
save_item,
- remove_resource,
- remove_mapping,
+ remove_resource: (id, ctx) => remove_item("resources", id, ctx),
+ remove_mapping: (id, ctx) => remove_item("mappings", id, ctx),
start_items_transaction,
- finalize_items_transaction
+ finalize_items_transaction,
+ track_resources: onchange => track("resources", onchange),
+ track_mappings: onchange => track("mappings", onchange),
+ untrack
};
/*
* EXPORTS_START
* EXPORT haketilodb
* EXPORT idb_get
- * EXPORT idb_put
- * EXPORT idb_del
* EXPORTS_END
*/