Project

General

Profile

« Previous | Next » 

Revision 261548ff

Added by koszko about 2 years ago

emply an sh-based build system; make some changes to blocking

View differences:

background/storage.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 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
    }
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
 */
42 21

  
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
    }
22
var exports = {};
51 23

  
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
	}
24
/* We're yet to decide how to handle errors... */
60 25

  
61
	try {
62
	    return browser.storage.local.set(obj);
63
	} catch (e) {
64
	    console.log(e);
65
	}
66
    }
26
/* Here are some basic wrappers for storage API functions */
67 27

  
68
    async function set_var(name, value)
69
    {
70
	return set(TYPE_PREFIX.VAR + name, value);
71
    }
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);
72 37

  
73
    async function get_var(name)
74
    {
75
	return get(TYPE_PREFIX.VAR + name);
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;
76 60
    }
77 61

  
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;
62
    try {
63
	return browser.storage.local.set(obj);
64
    } catch (e) {
65
	console.log(e);
88 66
    }
67
}
89 68

  
90
    /* We maintain in-memory copies of some stored lists. */
69
async function set_var(name, value)
70
{
71
    return set(TYPE_PREFIX.VAR + name, value);
72
}
91 73

  
92
    async function list(prefix)
93
    {
94
	let name = TYPE_NAME[prefix] + "s"; /* Make plural. */
95
	let map = new Map();
74
async function get_var(name)
75
{
76
    return get(TYPE_PREFIX.VAR + name);
77
}
96 78

  
97
	for (let item of await get_list_var(name))
98
	    map.set(item, await get(prefix + item));
79
/*
80
 * A special case of persisted variable is one that contains list
81
 * of items.
82
 */
99 83

  
100
	return {map, prefix, name, listeners : new Set(), lock : make_lock()};
101
    }
84
async function get_list_var(name)
85
{
86
    let list = await get_var(name);
102 87

  
103
    var pages;
104
    var bags;
105
    var scripts;
88
    return list === undefined ? [] : list;
89
}
106 90

  
107
    var list_by_prefix = {};
91
/* We maintain in-memory copies of some stored lists. */
108 92

  
109
    async function init()
110
    {
111
	for (let prefix of list_prefixes)
112
	    list_by_prefix[prefix] = await list(prefix);
93
async function list(prefix)
94
{
95
    let name = TYPE_NAME[prefix] + "s"; /* Make plural. */
96
    let map = new Map();
113 97

  
114
	return exports;
115
    }
98
    for (let item of await get_list_var(name))
99
	map.set(item, await get(prefix + item));
116 100

  
117
    /*
118
     * Facilitate listening to changes
119
     */
101
    return {map, prefix, name, listeners : new Set(), lock : make_lock()};
102
}
120 103

  
121
    exports.add_change_listener = function (cb, prefixes=list_prefixes)
122
    {
123
	if (typeof(prefixes) === "string")
124
	    prefixes = [prefixes];
104
var pages;
105
var bags;
106
var scripts;
125 107

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

  
130
    exports.remove_change_listener = function (cb, prefixes=list_prefixes)
131
    {
132
	if (typeof(prefixes) === "string")
133
	    prefixes = [prefixes];
110
async function init()
111
{
112
    for (let prefix of list_prefixes)
113
	list_by_prefix[prefix] = await list(prefix);
134 114

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

  
139
    function broadcast_change(change, list)
140
    {
141
	for (let listener_callback of list.listeners)
142
	    listener_callback(change);
143
    }
118
/*
119
 * Facilitate listening to changes
120
 */
144 121

  
145
    /* Prepare some hepler functions to get elements of a list */
122
exports.add_change_listener = function (cb, prefixes=list_prefixes)
123
{
124
    if (typeof(prefixes) === "string")
125
	prefixes = [prefixes];
146 126

  
147
    function list_items_it(list, with_values=false)
148
    {
149
	return with_values ? list.map.entries() : list.map.keys();
150
    }
127
    for (let prefix of prefixes)
128
	list_by_prefix[prefix].listeners.add(cb);
129
}
151 130

  
152
    function list_entries_it(list)
153
    {
154
	return list_items_it(list, true);
155
    }
131
exports.remove_change_listener = function (cb, prefixes=list_prefixes)
132
{
133
    if (typeof(prefixes) === "string")
134
	prefixes = [prefixes];
156 135

  
157
    function list_items(list, with_values=false)
158
    {
159
	let array = [];
136
    for (let prefix of prefixes)
137
	list_by_prefix[prefix].listeners.delete(cb);
138
}
160 139

  
161
	for (let item of list_items_it(list, with_values))
162
	    array.push(item);
140
function broadcast_change(change, list)
141
{
142
    for (let listener_callback of list.listeners)
143
	listener_callback(change);
144
}
163 145

  
164
	return array;
165
    }
146
/* Prepare some hepler functions to get elements of a list */
166 147

  
167
    function list_entries(list)
168
    {
169
	return list_items(list, true);
170
    }
148
function list_items_it(list, with_values=false)
149
{
150
    return with_values ? list.map.entries() : list.map.keys();
151
}
171 152

  
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
	}
153
function list_entries_it(list)
154
{
155
    return list_items_it(list, true);
156
}
194 157

  
195
	list.map.set(item, value)
158
function list_items(list, with_values=false)
159
{
160
    let array = [];
196 161

  
197
	let change = {
198
	    prefix : list.prefix,
199
	    item,
200
	    old_val,
201
	    new_val : value
202
	};
162
    for (let item of list_items_it(list, with_values))
163
	array.push(item);
203 164

  
204
	broadcast_change(change, list);
165
    return array;
166
}
205 167

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

  
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;
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);
219 194
    }
220
    async function _remove_item(item, list)
221
    {
222
	let old_val = list.map.get(item);
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) {
223 260
	if (old_val === undefined)
224 261
	    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

  
262
	new_val = old_val
263
    } else if (new_val === old_val && new_item === old_item) {
244 264
	return old_val;
245 265
    }
246 266

  
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;
267
    if (old_item === new_item || old_val === undefined) {
268
	await _set_item(new_item, new_val, list);
269
	return old_val;
254 270
    }
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 271

  
266
	if (old_item === new_item || old_val === undefined) {
267
	    await _set_item(new_item, new_val, list);
268
	    return old_val;
269
	}
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]);
270 279

  
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]);
280
    list.map.delete(old_item);
278 281

  
279
	list.map.delete(old_item);
282
    let change = {
283
	prefix : list.prefix,
284
	item : old_item,
285
	old_val,
286
	new_val : undefined
287
    };
280 288

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

  
288
	broadcast_change(change, list);
291
    list.map.set(new_item, new_val);
289 292

  
290
	list.map.set(new_item, new_val);
293
    change.item = new_item;
294
    change.old_val = undefined;
295
    change.new_val = new_val;
291 296

  
292
	change.item = new_item;
293
	change.old_val = undefined;
294
	change.new_val = new_val;
297
    broadcast_change(change, list);
295 298

  
296
	broadcast_change(change, list);
297

  
298
	return old_val;
299
    }
299
    return old_val;
300
}
300 301

  
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
    }
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
 */
323 309

  
324
    exports.get = function (prefix, item)
325
    {
326
	return list_by_prefix[prefix].map.get(item);
327
    }
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
 */
328 314

  
329
    exports.remove = async function (prefix, item)
330
    {
331
	return remove_item(item, list_by_prefix[prefix]);
332
    }
315
/*
316
 * For pages data argument is an object with properties `allow'
317
 * and `components'. Item name is url.
318
 */
333 319

  
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
    }
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);
340 371

  
341
    exports.get_all_names = function (prefix)
342
    {
343
	return list_items(list_by_prefix[prefix]);
344
    }
372
    for (let list of lists) {
345 373

  
346
    exports.get_all_names_it = function (prefix)
347
    {
348
	return list_items_it(list_by_prefix[prefix]);
349
    }
374
	let change = {
375
	    prefix : list.prefix,
376
	    new_val : undefined
377
	};
350 378

  
351
    exports.get_all = function (prefix)
352
    {
353
	return list_entries(list_by_prefix[prefix]);
354
    }
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
	}
355 384

  
356
    exports.get_all_it = function (prefix)
357
    {
358
	return list_entries_it(list_by_prefix[prefix]);
385
	list.map = new Map();
359 386
    }
360 387

  
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
	    };
388
    await browser.storage.local.clear();
377 389

  
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
	    }
390
    for (let list of lists)
391
	unlock(list.lock);
392
}
383 393

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

  
387
	await browser.storage.local.clear();
394
const get_storage = make_once(init);
388 395

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

  
393
    window.get_storage = make_once(init);
394
})();
396
/*
397
 * EXPORTS_START
398
 * EXPORT get_storage
399
 * EXPORTS_END
400
 */

Also available in: Unified diff