Project

General

Profile

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

haketilo / background / storage.js @ fb9c808c

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 list_by_prefix = {};
105

    
106
async function init()
107
{
108
    for (let prefix of list_prefixes)
109
	list_by_prefix[prefix] = await list(prefix);
110

    
111
    return exports;
112
}
113

    
114
/*
115
 * Facilitate listening to changes
116
 */
117

    
118
exports.add_change_listener = function (cb, prefixes=list_prefixes)
119
{
120
    if (typeof(prefixes) === "string")
121
	prefixes = [prefixes];
122

    
123
    for (let prefix of prefixes)
124
	list_by_prefix[prefix].listeners.add(cb);
125
}
126

    
127
exports.remove_change_listener = function (cb, prefixes=list_prefixes)
128
{
129
    if (typeof(prefixes) === "string")
130
	prefixes = [prefixes];
131

    
132
    for (let prefix of prefixes)
133
	list_by_prefix[prefix].listeners.delete(cb);
134
}
135

    
136
function broadcast_change(change, list)
137
{
138
    for (let listener_callback of list.listeners)
139
	listener_callback(change);
140
}
141

    
142
/* Prepare some hepler functions to get elements of a list */
143

    
144
function list_items_it(list, with_values=false)
145
{
146
    return with_values ? list.map.entries() : list.map.keys();
147
}
148

    
149
function list_entries_it(list)
150
{
151
    return list_items_it(list, true);
152
}
153

    
154
function list_items(list, with_values=false)
155
{
156
    let array = [];
157

    
158
    for (let item of list_items_it(list, with_values))
159
	array.push(item);
160

    
161
    return array;
162
}
163

    
164
function list_entries(list)
165
{
166
    return list_items(list, true);
167
}
168

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

    
192
    list.map.set(item, value)
193

    
194
    let change = {
195
	prefix : list.prefix,
196
	item,
197
	old_val,
198
	new_val : value
199
    };
200

    
201
    broadcast_change(change, list);
202

    
203
    return old_val;
204
}
205

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

    
223
    let key = list.prefix + item;
224
    let items = list_items(list);
225
    let index = items.indexOf(item);
226
    items.splice(index, 1);
227

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

    
230
    list.map.delete(item);
231

    
232
    let change = {
233
	prefix : list.prefix,
234
	item,
235
	old_val,
236
	new_val : undefined
237
    };
238

    
239
    broadcast_change(change, list);
240

    
241
    return old_val;
242
}
243

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

    
263
    if (old_item === new_item || old_val === undefined) {
264
	await _set_item(new_item, new_val, list);
265
	return old_val;
266
    }
267

    
268
    let new_key = list.prefix + new_item;
269
    let old_key = list.prefix + old_item;
270
    let items = list_items(list);
271
    let index = items.indexOf(old_item);
272
    items[index] = new_item;
273
    await setn([old_key, undefined, new_key, new_val,
274
		"_" + list.name, items]);
275

    
276
    list.map.delete(old_item);
277

    
278
    let change = {
279
	prefix : list.prefix,
280
	item : old_item,
281
	old_val,
282
	new_val : undefined
283
    };
284

    
285
    broadcast_change(change, list);
286

    
287
    list.map.set(new_item, new_val);
288

    
289
    change.item = new_item;
290
    change.old_val = undefined;
291
    change.new_val = new_val;
292

    
293
    broadcast_change(change, list);
294

    
295
    return old_val;
296
}
297

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

    
306
/*
307
 * For bags, item name is chosen by user, data is an array of 2-element
308
 * arrays with type prefix and script/bag names.
309
 */
310

    
311
/*
312
 * For pages data argument is an object with properties `allow'
313
 * and `components'. Item name is url.
314
 */
315

    
316
exports.set = async function (prefix, item, data)
317
{
318
    return set_item(item, data, list_by_prefix[prefix]);
319
}
320

    
321
exports.get = function (prefix, item)
322
{
323
    return list_by_prefix[prefix].map.get(item);
324
}
325

    
326
exports.remove = async function (prefix, item)
327
{
328
    return remove_item(item, list_by_prefix[prefix]);
329
}
330

    
331
exports.replace = async function (prefix, old_item, new_item,
332
				  new_data=undefined)
333
{
334
    return replace_item(old_item, new_item, list_by_prefix[prefix],
335
			new_data);
336
}
337

    
338
exports.get_all_names = function (prefix)
339
{
340
    return list_items(list_by_prefix[prefix]);
341
}
342

    
343
exports.get_all_names_it = function (prefix)
344
{
345
    return list_items_it(list_by_prefix[prefix]);
346
}
347

    
348
exports.get_all = function (prefix)
349
{
350
    return list_entries(list_by_prefix[prefix]);
351
}
352

    
353
exports.get_all_it = function (prefix)
354
{
355
    return list_entries_it(list_by_prefix[prefix]);
356
}
357

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

    
365
    for (let list of lists)
366
	await lock(list.lock);
367

    
368
    for (let list of lists) {
369

    
370
	let change = {
371
	    prefix : list.prefix,
372
	    new_val : undefined
373
	};
374

    
375
	for (let [item, val] of list_entries_it(list)) {
376
	    change.item = item;
377
	    change.old_val = val;
378
	    broadcast_change(change, list);
379
	}
380

    
381
	list.map = new Map();
382
    }
383

    
384
    await browser.storage.local.clear();
385

    
386
    for (let list of lists)
387
	unlock(list.lock);
388
}
389

    
390
const get_storage = make_once(init);
391

    
392
/*
393
 * EXPORTS_START
394
 * EXPORT get_storage
395
 * EXPORTS_END
396
 */
(6-6/7)