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)); |
+ } |
+}; |
+ |