Project

General

Profile

Download (8.1 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / background / storage.js @ 6b12a034

1
/**
2
 * Hachette storage manager
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Redistribution terms are gathered in the `copyright' file.
6
 */
7

    
8
/*
9
 * IMPORTS_START
10
 * IMPORT TYPE_PREFIX
11
 * IMPORT TYPE_NAME
12
 * IMPORT list_prefixes
13
 * IMPORT make_lock
14
 * IMPORT lock
15
 * IMPORT unlock
16
 * IMPORT make_once
17
 * IMPORT browser
18
 * IMPORT is_chrome
19
 * IMPORTS_END
20
 */
21

    
22
var exports = {};
23

    
24
/* We're yet to decide how to handle errors... */
25

    
26
/* Here are some basic wrappers for storage API functions */
27

    
28
async function get(key)
29
{
30
    try {
31
	/* Fix for fact that Chrome does not use promises here */
32
	let promise = is_chrome ?
33
	    new Promise((resolve, reject) =>
34
			chrome.storage.local.get(key,
35
						 val => resolve(val))) :
36
	    browser.storage.local.get(key);
37

    
38
	return (await promise)[key];
39
    } catch (e) {
40
	console.log(e);
41
    }
42
}
43

    
44
async function set(key, value)
45
{
46
    try {
47
	return browser.storage.local.set({[key]: value});
48
    } catch (e) {
49
	console.log(e);
50
    }
51
}
52

    
53
async function setn(keys_and_values)
54
{
55
    let obj = Object();
56
    while (keys_and_values.length > 1) {
57
	let value = keys_and_values.pop();
58
	let key = keys_and_values.pop();
59
	obj[key] = value;
60
    }
61

    
62
    try {
63
	return browser.storage.local.set(obj);
64
    } catch (e) {
65
	console.log(e);
66
    }
67
}
68

    
69
async function set_var(name, value)
70
{
71
    return set(TYPE_PREFIX.VAR + name, value);
72
}
73

    
74
async function get_var(name)
75
{
76
    return get(TYPE_PREFIX.VAR + name);
77
}
78

    
79
/*
80
 * A special case of persisted variable is one that contains list
81
 * of items.
82
 */
83

    
84
async function get_list_var(name)
85
{
86
    let list = await get_var(name);
87

    
88
    return list === undefined ? [] : list;
89
}
90

    
91
/* We maintain in-memory copies of some stored lists. */
92

    
93
async function list(prefix)
94
{
95
    let name = TYPE_NAME[prefix] + "s"; /* Make plural. */
96
    let map = new Map();
97

    
98
    for (let item of await get_list_var(name))
99
	map.set(item, await get(prefix + item));
100

    
101
    return {map, prefix, name, listeners : new Set(), lock : make_lock()};
102
}
103

    
104
var pages;
105
var bags;
106
var scripts;
107

    
108
var list_by_prefix = {};
109

    
110
async function init()
111
{
112
    for (let prefix of list_prefixes)
113
	list_by_prefix[prefix] = await list(prefix);
114

    
115
    return exports;
116
}
117

    
118
/*
119
 * Facilitate listening to changes
120
 */
121

    
122
exports.add_change_listener = function (cb, prefixes=list_prefixes)
123
{
124
    if (typeof(prefixes) === "string")
125
	prefixes = [prefixes];
126

    
127
    for (let prefix of prefixes)
128
	list_by_prefix[prefix].listeners.add(cb);
129
}
130

    
131
exports.remove_change_listener = function (cb, prefixes=list_prefixes)
132
{
133
    if (typeof(prefixes) === "string")
134
	prefixes = [prefixes];
135

    
136
    for (let prefix of prefixes)
137
	list_by_prefix[prefix].listeners.delete(cb);
138
}
139

    
140
function broadcast_change(change, list)
141
{
142
    for (let listener_callback of list.listeners)
143
	listener_callback(change);
144
}
145

    
146
/* Prepare some hepler functions to get elements of a list */
147

    
148
function list_items_it(list, with_values=false)
149
{
150
    return with_values ? list.map.entries() : list.map.keys();
151
}
152

    
153
function list_entries_it(list)
154
{
155
    return list_items_it(list, true);
156
}
157

    
158
function list_items(list, with_values=false)
159
{
160
    let array = [];
161

    
162
    for (let item of list_items_it(list, with_values))
163
	array.push(item);
164

    
165
    return array;
166
}
167

    
168
function list_entries(list)
169
{
170
    return list_items(list, true);
171
}
172

    
173
/*
174
 * Below we make additional effort to update map of given kind of items
175
 * every time an item is added/removed to keep everything coherent.
176
 */
177
async function set_item(item, value, list)
178
{
179
    await lock(list.lock);
180
    let result = await _set_item(...arguments);
181
    unlock(list.lock)
182
    return result;
183
}
184
async function _set_item(item, value, list)
185
{
186
    let key = list.prefix + item;
187
    let old_val = list.map.get(item);
188
    if (old_val === undefined) {
189
	let items = list_items(list);
190
	items.push(item);
191
	await setn([key, value, "_" + list.name, items]);
192
    } else {
193
	await set(key, value);
194
    }
195

    
196
    list.map.set(item, value)
197

    
198
    let change = {
199
	prefix : list.prefix,
200
	item,
201
	old_val,
202
	new_val : value
203
    };
204

    
205
    broadcast_change(change, list);
206

    
207
    return old_val;
208
}
209

    
210
// TODO: The actual idea to set value to undefined is good - this way we can
211
//       also set a new list of items in the same API call. But such key
212
//       is still stored in the storage. We need to somehow remove it later.
213
//       For that, we're going to have to store 1 more list of each kind.
214
async function remove_item(item, list)
215
{
216
    await lock(list.lock);
217
    let result = await _remove_item(...arguments);
218
    unlock(list.lock)
219
    return result;
220
}
221
async function _remove_item(item, list)
222
{
223
    let old_val = list.map.get(item);
224
    if (old_val === undefined)
225
	return;
226

    
227
    let key = list.prefix + item;
228
    let items = list_items(list);
229
    let index = items.indexOf(item);
230
    items.splice(index, 1);
231

    
232
    await setn([key, undefined, "_" + list.name, items]);
233

    
234
    list.map.delete(item);
235

    
236
    let change = {
237
	prefix : list.prefix,
238
	item,
239
	old_val,
240
	new_val : undefined
241
    };
242

    
243
    broadcast_change(change, list);
244

    
245
    return old_val;
246
}
247

    
248
// TODO: same as above applies here
249
async function replace_item(old_item, new_item, list, new_val=undefined)
250
{
251
    await lock(list.lock);
252
    let result = await _replace_item(...arguments);
253
    unlock(list.lock)
254
    return result;
255
}
256
async function _replace_item(old_item, new_item, list, new_val=undefined)
257
{
258
    let old_val = list.map.get(old_item);
259
    if (new_val === undefined) {
260
	if (old_val === undefined)
261
	    return;
262
	new_val = old_val
263
    } else if (new_val === old_val && new_item === old_item) {
264
	return old_val;
265
    }
266

    
267
    if (old_item === new_item || old_val === undefined) {
268
	await _set_item(new_item, new_val, list);
269
	return old_val;
270
    }
271

    
272
    let new_key = list.prefix + new_item;
273
    let old_key = list.prefix + old_item;
274
    let items = list_items(list);
275
    let index = items.indexOf(old_item);
276
    items[index] = new_item;
277
    await setn([old_key, undefined, new_key, new_val,
278
		"_" + list.name, items]);
279

    
280
    list.map.delete(old_item);
281

    
282
    let change = {
283
	prefix : list.prefix,
284
	item : old_item,
285
	old_val,
286
	new_val : undefined
287
    };
288

    
289
    broadcast_change(change, list);
290

    
291
    list.map.set(new_item, new_val);
292

    
293
    change.item = new_item;
294
    change.old_val = undefined;
295
    change.new_val = new_val;
296

    
297
    broadcast_change(change, list);
298

    
299
    return old_val;
300
}
301

    
302
/*
303
 * For scripts, item name is chosen by user, data should be
304
 * an object containing:
305
 * - script's url and hash or
306
 * - script's text or
307
 * - all three
308
 */
309

    
310
/*
311
 * For bags, item name is chosen by user, data is an array of 2-element
312
 * arrays with type prefix and script/bag names.
313
 */
314

    
315
/*
316
 * For pages data argument is an object with properties `allow'
317
 * and `components'. Item name is url.
318
 */
319

    
320
exports.set = async function (prefix, item, data)
321
{
322
    return set_item(item, data, list_by_prefix[prefix]);
323
}
324

    
325
exports.get = function (prefix, item)
326
{
327
    return list_by_prefix[prefix].map.get(item);
328
}
329

    
330
exports.remove = async function (prefix, item)
331
{
332
    return remove_item(item, list_by_prefix[prefix]);
333
}
334

    
335
exports.replace = async function (prefix, old_item, new_item,
336
				  new_data=undefined)
337
{
338
    return replace_item(old_item, new_item, list_by_prefix[prefix],
339
			new_data);
340
}
341

    
342
exports.get_all_names = function (prefix)
343
{
344
    return list_items(list_by_prefix[prefix]);
345
}
346

    
347
exports.get_all_names_it = function (prefix)
348
{
349
    return list_items_it(list_by_prefix[prefix]);
350
}
351

    
352
exports.get_all = function (prefix)
353
{
354
    return list_entries(list_by_prefix[prefix]);
355
}
356

    
357
exports.get_all_it = function (prefix)
358
{
359
    return list_entries_it(list_by_prefix[prefix]);
360
}
361

    
362
/* Finally, a quick way to wipe all the data. */
363
// TODO: maybe delete items in such order that none of them ever references
364
// an already-deleted one?
365
exports.clear = async function ()
366
{
367
    let lists = list_prefixes.map((p) => list_by_prefix[p]);
368

    
369
    for (let list of lists)
370
	await lock(list.lock);
371

    
372
    for (let list of lists) {
373

    
374
	let change = {
375
	    prefix : list.prefix,
376
	    new_val : undefined
377
	};
378

    
379
	for (let [item, val] of list_entries_it(list)) {
380
	    change.item = item;
381
	    change.old_val = val;
382
	    broadcast_change(change, list);
383
	}
384

    
385
	list.map = new Map();
386
    }
387

    
388
    await browser.storage.local.clear();
389

    
390
    for (let list of lists)
391
	unlock(list.lock);
392
}
393

    
394
const get_storage = make_once(init);
395

    
396
/*
397
 * EXPORTS_START
398
 * EXPORT get_storage
399
 * EXPORTS_END
400
 */
(7-7/8)