Project

General

Profile

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

haketilo / background / storage.js @ b93f26bf

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

    
8
"use strict";
9

    
10
(() => {
11
    const TYPE_PREFIX = window.TYPE_PREFIX;
12
    const TYPE_NAME = window.TYPE_NAME;
13
    const list_prefixes = window.list_prefixes;
14
    const make_lock = window.make_lock;
15
    const lock = window.lock;
16
    const unlock = window.unlock;
17
    const make_once = window.make_once;
18
    const browser = window.browser;
19
    const is_chrome = window.is_chrome;
20

    
21
    var exports = {};
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
107
    var list_by_prefix = {};
108

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

    
114
	return exports;
115
    }
116

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

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

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

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

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

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

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

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

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

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

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

    
164
	return array;
165
    }
166

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

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

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

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

    
204
	broadcast_change(change, list);
205

    
206
	return old_val;
207
    }
208

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

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

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

    
233
	list.map.delete(item);
234

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

    
242
	broadcast_change(change, list);
243

    
244
	return old_val;
245
    }
246

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

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

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

    
279
	list.map.delete(old_item);
280

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

    
288
	broadcast_change(change, list);
289

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

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

    
296
	broadcast_change(change, list);
297

    
298
	return old_val;
299
    }
300

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

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

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

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

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

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

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

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

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

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

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

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

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

    
371
	for (let list of lists) {
372

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

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

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

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

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

    
393
    window.get_storage = make_once(init);
394
})();
(6-6/7)