 Issue 29796555:
  Issue 6621  (Closed)
    
  
    Issue 29796555:
  Issue 6621  (Closed) 
  | 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)); | 
| + } | 
| +}; | 
| + |