| Index: lib/ioIndexedDB.js |
| diff --git a/lib/ioIndexedDB.js b/lib/ioIndexedDB.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5871eed97ef4b554c62e7e1559167ed7648db3c1 |
| --- /dev/null |
| +++ b/lib/ioIndexedDB.js |
| @@ -0,0 +1,291 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-present eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +"use strict"; |
| + |
| + |
| +// from DefaultConfig https://github.com/localForage/localForage/blob/master/src/localforage.js |
|
kzar
2018/06/04 11:06:07
Please could you improve this comment a bit? For e
piscoi.georgiana
2018/06/05 07:15:54
I didn't knew you can link to line numbers. Thanks
|
| +const localForageDefs = { |
|
kzar
2018/06/04 11:06:07
Nit: Would you mind naming this one more consisten
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| + dbName: "localforage", |
| + storeName: "keyvaluepairs", |
| + version: 2 |
| +}; |
| + |
| +const dbConfig = { |
| + dbName: "adbp", |
|
Sebastian Noack
2018/06/01 19:15:46
This acronym seems somewhat bogus. If we abbreviat
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| + storeName: "file", |
| + keyPath: "fileName", |
| + version: 1 |
| +}; |
| + |
| +let dbInstances = {}; |
|
Sebastian Noack
2018/06/01 19:15:46
I wonder whether it would be more appropriate to j
kzar
2018/06/04 11:06:07
Yea, I agree.
piscoi.georgiana
2018/06/05 07:15:54
Done.
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| +/** |
| + * @type Promise |
| + */ |
| +let migrationStatus = {}; |
| + |
| +const keyPrefix = "file:"; |
| + |
| + /** |
| + * Handles migrating a file, if found, from localforage db to the new one |
|
kzar
2018/06/04 11:06:07
Would you mind specifying what "the new one" is he
piscoi.georgiana
2018/06/05 07:15:54
I've changed it, but I'm not 100% sure
|
| + * @param {string} fileName |
| + * Name of the file to be migrated |
| + * @return {Promise} |
| + * Promise to be resolved or rejected once the operation is completed |
| + */ |
| +function migrateFile(fileName) |
| +{ |
| + let fileData; |
| + if (!migrationStatus[fileName]) |
|
kzar
2018/06/04 11:06:07
Nit: When the body of an if statement spans multip
piscoi.georgiana
2018/06/05 07:15:54
It did pass. My editor shows me lint errors, but I
|
| + migrationStatus[fileName] = new Promise((resolve, reject) => |
|
Sebastian Noack
2018/06/01 19:15:46
Perhaps, rather than migrating each file on demand
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| + { |
| + return openDB(localForageDefs) |
| + .then(dbInstance => |
| + getFile(fileName, dbInstance, localForageDefs.storeName)) |
| + .then(file => |
| + { |
| + fileData = file; |
|
Sebastian Noack
2018/06/01 19:15:46
Rather than making fileData a non-local variable,
|
| + return openDB(dbConfig); |
| + }) |
| + .then(dbInstance => |
| + saveFile(fileData, dbInstance, dbConfig.storeName)) |
| + .then(() => |
| + deleteFile( |
| + fileName, |
| + dbInstances[localForageDefs.dbName], |
| + localForageDefs.storeName) |
| + ).then(() => resolve()) |
| + .catch(error => |
| + { |
| + // file or store wasn't found so no migration needed |
| + if (error.type == "NoSuchFile" || error == "NotFoundError") |
| + { |
| + return resolve(); |
| + } |
| + }); |
| + }); |
| + return migrationStatus[fileName]; |
| +} |
| + |
| +function fileToKey(fileName) |
| +{ |
| + return keyPrefix + fileName; |
| +} |
| + |
| +function formatFile(name, data) |
| +{ |
| + return { |
| + fileName: fileToKey(name), |
| + content: Array.from(data), |
| + lastModified: Date.now() |
| + }; |
| +} |
| + |
| +function openDB(config) |
|
kzar
2018/06/04 11:06:07
I guess we could destructure `config` here?
fun
piscoi.georgiana
2018/06/05 07:15:54
Done.
|
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + const {dbName, storeName, version} = config; |
|
Sebastian Noack
2018/06/01 19:15:46
Sorry, this is matter of taste, but at least for t
piscoi.georgiana
2018/06/05 07:15:55
For me, I guess it's mostly reflex from working on
|
| + |
| + if (dbInstances[dbName]) |
| + return resolve(dbInstances[dbName]); |
| + |
| + const req = indexedDB.open(dbName, version); |
| + |
| + req.onsuccess = (event) => |
| + { |
| + dbInstances[dbName] = event.currentTarget.result; |
| + return resolve(dbInstances[dbName]); |
| + }; |
| + |
| + req.onerror = reject; |
| + |
| + req.onupgradeneeded = (event) => |
| + { |
| + event |
| + .currentTarget |
| + .result |
| + .createObjectStore(storeName, |
| + { |
| + keyPath: dbConfig.keyPath, |
| + autoIncrement: true |
| + }); |
| + }; |
| + }); |
| +} |
| + |
| +function getObjectStore(dbInstance, storeName) |
| +{ |
| + return dbInstance |
| + .transaction([storeName], IDBTransaction.READ_WRITE) |
| + .objectStore(storeName); |
| +} |
| + |
| +function getFile(fileName, dbInstance, storeName, from) |
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + const store = getObjectStore(dbInstance, storeName); |
| + const req = store.get(fileToKey(fileName)); |
| + |
| + req.onsuccess = (event) => |
|
kzar
2018/06/04 11:06:07
Nit: While we don't have a rule about adding / omi
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| + { |
| + const result = event.currentTarget.result; |
| + if (result) |
| + resolve(result); |
| + else |
| + reject({type: "NoSuchFile"}); |
|
kzar
2018/06/04 11:06:07
Nit: This line isn't indented correctly.
piscoi.georgiana
2018/06/05 07:15:54
Done.
|
| + }; |
| + req.onerror = reject; |
| + }); |
| +} |
| + |
| +function saveFile(data, dbInstance, storeName, from) |
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + const store = getObjectStore(dbInstance, storeName); |
| + const req = store.put(data); |
| + |
| + req.onsuccess = resolve; |
| + req.onerror = reject; |
| + }); |
| +} |
| + |
| +function deleteFile(fileName, dbInstance, storeName, from) |
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + const store = getObjectStore(dbInstance, storeName); |
| + const req = store.delete(fileToKey(fileName)); |
| + |
| + req.onsuccess = resolve; |
| + req.onerror = reject; |
| + }); |
| +} |
| + |
| +exports.IO = |
| +{ |
| + /** |
| + * Writes text lines to a file. |
| + * @param {string} fileName |
| + * Name of the file to be written |
| + * @param {Iterable.<string>} data |
| + * An array-like or iterable object containing the lines (without line |
| + * endings) |
| + * @return {Promise} |
| + * Promise to be resolved or rejected once the operation is completed |
| + */ |
| + |
|
kzar
2018/06/04 11:06:07
Nit: Please could you remove this newline?
piscoi.georgiana
2018/06/05 07:15:54
Done.
|
| + writeToFile(fileName, data) |
| + { |
| + return migrateFile(fileName) |
| + .then(() => openDB(dbConfig)) |
| + .then(dbInstance => |
| + saveFile( |
| + formatFile(fileName, data), |
| + dbInstance, |
| + dbConfig.storeName) |
| + ); |
| + }, |
| + |
| + /** |
| + * Reads text lines from a file. |
| + * @param {string} fileName |
| + * Name of the file to be read |
| + * @param {TextSink} listener |
| + * Function that will be called for each line in the file |
| + * @return {Promise} |
| + * Promise to be resolved or rejected once the operation is completed |
| + */ |
| + readFromFile(fileName, listener) |
| + { |
| + return migrateFile(fileName) |
| + .then(() => openDB(dbConfig)) |
| + .then(dbInstance => |
| + getFile( |
| + fileName, |
| + dbInstance, |
| + dbConfig.storeName) |
| + ).then(entry => |
| + { |
| + for (let line of entry.content) |
| + listener(line); |
| + }); |
| + }, |
| + |
| + /** |
| + * Retrieves file metadata. |
| + * @param {string} fileName |
| + * Name of the file to be looked up |
| + * @return {Promise.<StatData>} |
| + * Promise to be resolved with file metadata once the operation is |
| + * completed |
| + */ |
| + statFile(fileName) |
| + { |
| + return migrateFile(fileName) |
| + .then(() => openDB(dbConfig)) |
| + .then(dbInstance => |
| + getFile( |
| + fileName, |
| + dbInstance, |
| + dbConfig.storeName) |
| + ).then(entry => |
| + { |
| + return { |
| + exists: true, |
| + lastModified: entry.lastModified |
| + }; |
| + }) |
| + .catch(error => |
| + { |
| + if (error.type == "NoSuchFile") |
| + return {exists: false}; |
| + throw error; |
| + }); |
| + }, |
| + |
| + /** |
| + * Renames a file. |
| + * @param {string} fromFile |
| + * Name of the file to be renamed |
| + * @param {string} newName |
| + * New file name, will be overwritten if exists |
| + * @return {Promise} |
| + * Promise to be resolved or rejected once the operation is completed |
| + */ |
| + |
|
kzar
2018/06/04 11:06:07
Nit: Please could you remove this newline?
piscoi.georgiana
2018/06/05 07:15:55
Done.
|
| + renameFile(fromFile, newName) |
| + { |
| + return migrateFile(fromFile) |
| + .then(() => openDB(dbConfig)) |
| + .then(dbInstance => getFile(fromFile, dbInstance, dbConfig.storeName)) |
| + .then(fileData => |
| + saveFile( |
| + { |
| + fileName: fileToKey(newName), |
| + content: fileData.content, |
| + lastModified: fileData.lastModified |
| + }, |
| + dbInstances[dbConfig.dbName], |
| + dbConfig.storeName) |
| + ).then(() => |
| + deleteFile(fromFile, dbInstances[dbConfig.dbName], dbConfig.storeName)); |
| + } |
| +}; |
| + |