| OLD | NEW | 
| (Empty) |  | 
 |    1 /* | 
 |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
 |    3  * Copyright (C) 2006-present eyeo GmbH | 
 |    4  * | 
 |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
 |    6  * it under the terms of the GNU General Public License version 3 as | 
 |    7  * published by the Free Software Foundation. | 
 |    8  * | 
 |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
 |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |   12  * GNU General Public License for more details. | 
 |   13  * | 
 |   14  * You should have received a copy of the GNU General Public License | 
 |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
 |   16  */ | 
 |   17  | 
 |   18 "use strict"; | 
 |   19  | 
 |   20  | 
 |   21 // values from the DefaultConfig | 
 |   22 // https://github.com/localForage/localForage/blob/2cdbd74/src/localforage.js#L4
     2-L51 | 
 |   23 const localForageDbConfig = { | 
 |   24   dbName: "localforage", | 
 |   25   storeName: "keyvaluepairs", | 
 |   26   version: 2 | 
 |   27 }; | 
 |   28  | 
 |   29 const dbConfig = { | 
 |   30   dbName: "adblockplus", | 
 |   31   storeName: "file", | 
 |   32   keyPath: "fileName", | 
 |   33   version: 1 | 
 |   34 }; | 
 |   35  | 
 |   36 let db = openDB(dbConfig); | 
 |   37 let migrationDone = migrateFiles(); | 
 |   38  | 
 |   39 const keyPrefix = "file:"; | 
 |   40  | 
 |   41 function openDB({dbName, storeName, version, keyPath}) | 
 |   42 { | 
 |   43   return new Promise((resolve, reject) => | 
 |   44   { | 
 |   45     let req = indexedDB.open(dbName, version); | 
 |   46  | 
 |   47     req.onsuccess = event => | 
 |   48     { | 
 |   49       return resolve(event.currentTarget.result); | 
 |   50     }; | 
 |   51  | 
 |   52     req.onerror = reject; | 
 |   53  | 
 |   54     req.onupgradeneeded = event => | 
 |   55     { | 
 |   56       event | 
 |   57       .currentTarget | 
 |   58       .result | 
 |   59       .createObjectStore(storeName, | 
 |   60         { | 
 |   61           keyPath, | 
 |   62           autoIncrement: true | 
 |   63         }); | 
 |   64     }; | 
 |   65   }); | 
 |   66 } | 
 |   67  | 
 |   68 /** | 
 |   69  * Handles migrating all files from localforage db | 
 |   70  * used in the previous implementation by the localForage library | 
 |   71  * to the new adblockplus db that we use as a replacement | 
 |   72  * @return {Promise} | 
 |   73  *    Promise to be resolved or rejected once the operation is completed | 
 |   74  */ | 
 |   75 function migrateFiles() | 
 |   76 { | 
 |   77   return openDB(localForageDbConfig) | 
 |   78     .then(localForageDb => | 
 |   79       getAllFiles(localForageDb, localForageDbConfig.storeName) | 
 |   80         .then(files => | 
 |   81           db.then(dbInstance => | 
 |   82             Promise.all(files.map(file => | 
 |   83               saveFile(file, dbInstance, dbConfig.storeName))))) | 
 |   84         .then(() => | 
 |   85           clearObjectStore(localForageDb, localForageDbConfig.storeName)) | 
 |   86     ); | 
 |   87 } | 
 |   88  | 
 |   89 function getAllFiles(dbInstance, storeName) | 
 |   90 { | 
 |   91   return new Promise((resolve, reject) => | 
 |   92   { | 
 |   93     // edge doesn't currently support getAll method on IDBObjectStore interface | 
 |   94     // so a cursor is used to iterate over all objects from the store | 
 |   95     let transaction = dbInstance | 
 |   96       .transaction([storeName], IDBTransaction.READ_ONLY); | 
 |   97  | 
 |   98     let store = transaction.objectStore(storeName); | 
 |   99     let cursorReq = store.openCursor(); | 
 |  100     let filesData = []; | 
 |  101  | 
 |  102     transaction.oncomplete = event => | 
 |  103     { | 
 |  104       resolve(filesData); | 
 |  105     }; | 
 |  106  | 
 |  107     cursorReq.onsuccess = event => | 
 |  108     { | 
 |  109       let cursor = event.currentTarget.result; | 
 |  110       if (cursor) | 
 |  111       { | 
 |  112         let value = cursor.value; | 
 |  113  | 
 |  114         filesData.push({ | 
 |  115           fileName: cursor.key, | 
 |  116           content: value.content, | 
 |  117           lastModified: value.lastModified | 
 |  118         }); | 
 |  119         cursor.continue(); | 
 |  120       } | 
 |  121     }; | 
 |  122  | 
 |  123     cursorReq.onerror = reject; | 
 |  124   }); | 
 |  125 } | 
 |  126  | 
 |  127 function clearObjectStore(dbInstance, storeName) | 
 |  128 { | 
 |  129   return new Promise((resolve, reject) => | 
 |  130   { | 
 |  131     let store = getObjectStore(dbInstance, storeName); | 
 |  132     let req = store.clear(); | 
 |  133  | 
 |  134     req.onsuccess = resolve; | 
 |  135     req.onerror = reject; | 
 |  136   }); | 
 |  137 } | 
 |  138  | 
 |  139 function fileToKey(fileName) | 
 |  140 { | 
 |  141   return keyPrefix + fileName; | 
 |  142 } | 
 |  143  | 
 |  144 function formatFile(name, data) | 
 |  145 { | 
 |  146   return { | 
 |  147     fileName: fileToKey(name), | 
 |  148     content: Array.from(data), | 
 |  149     lastModified: Date.now() | 
 |  150   }; | 
 |  151 } | 
 |  152  | 
 |  153 function getObjectStore(dbInstance, storeName) | 
 |  154 { | 
 |  155   return dbInstance | 
 |  156     .transaction([storeName], IDBTransaction.READ_WRITE) | 
 |  157     .objectStore(storeName); | 
 |  158 } | 
 |  159  | 
 |  160 function getFile(fileName, dbInstance, storeName) | 
 |  161 { | 
 |  162   return new Promise((resolve, reject) => | 
 |  163   { | 
 |  164     let store = getObjectStore(dbInstance, storeName); | 
 |  165     let req = store.get(fileToKey(fileName)); | 
 |  166  | 
 |  167     req.onsuccess = event => | 
 |  168     { | 
 |  169       let result = event.currentTarget.result; | 
 |  170  | 
 |  171       if (result) | 
 |  172         resolve(result); | 
 |  173       else | 
 |  174         reject({type: "NoSuchFile"}); | 
 |  175     }; | 
 |  176     req.onerror = reject; | 
 |  177   }); | 
 |  178 } | 
 |  179  | 
 |  180 function saveFile(data, dbInstance, storeName) | 
 |  181 { | 
 |  182   return new Promise((resolve, reject) => | 
 |  183   { | 
 |  184     let store = getObjectStore(dbInstance, storeName); | 
 |  185     let req = store.put(data); | 
 |  186  | 
 |  187     req.onsuccess = resolve; | 
 |  188     req.onerror = reject; | 
 |  189   }); | 
 |  190 } | 
 |  191  | 
 |  192 function deleteFile(fileName, dbInstance, storeName) | 
 |  193 { | 
 |  194   return new Promise((resolve, reject) => | 
 |  195   { | 
 |  196     let store = getObjectStore(dbInstance, storeName); | 
 |  197     let req = store.delete(fileToKey(fileName)); | 
 |  198  | 
 |  199     req.onsuccess = resolve; | 
 |  200     req.onerror = reject; | 
 |  201   }); | 
 |  202 } | 
 |  203  | 
 |  204 exports.IO = | 
 |  205 { | 
 |  206   /** | 
 |  207    * Writes text lines to a file. | 
 |  208    * @param {string} fileName | 
 |  209    *    Name of the file to be written | 
 |  210    * @param {Iterable.<string>} data | 
 |  211    *    An array-like or iterable object containing the lines (without line | 
 |  212    *    endings) | 
 |  213    * @return {Promise} | 
 |  214    *    Promise to be resolved or rejected once the operation is completed | 
 |  215    */ | 
 |  216   writeToFile(fileName, data) | 
 |  217   { | 
 |  218     return migrationDone | 
 |  219       .then(() => | 
 |  220         db.then(dbInstance => | 
 |  221           saveFile( | 
 |  222             formatFile(fileName, data), dbInstance, dbConfig.storeName))); | 
 |  223   }, | 
 |  224  | 
 |  225   /** | 
 |  226    * Reads text lines from a file. | 
 |  227    * @param {string} fileName | 
 |  228    *    Name of the file to be read | 
 |  229    * @param {TextSink} listener | 
 |  230    *    Function that will be called for each line in the file | 
 |  231    * @return {Promise} | 
 |  232    *    Promise to be resolved or rejected once the operation is completed | 
 |  233    */ | 
 |  234   readFromFile(fileName, listener) | 
 |  235   { | 
 |  236     return migrationDone | 
 |  237       .then(() => | 
 |  238         db.then(dbInstance => | 
 |  239           getFile(fileName, dbInstance, dbConfig.storeName)) | 
 |  240           .then(entry => | 
 |  241           { | 
 |  242             for (let line of entry.content) | 
 |  243               listener(line); | 
 |  244           })); | 
 |  245   }, | 
 |  246  | 
 |  247   /** | 
 |  248    * Retrieves file metadata. | 
 |  249    * @param {string} fileName | 
 |  250    *    Name of the file to be looked up | 
 |  251    * @return {Promise.<StatData>} | 
 |  252    *    Promise to be resolved with file metadata once the operation is | 
 |  253    *    completed | 
 |  254    */ | 
 |  255   statFile(fileName) | 
 |  256   { | 
 |  257     return migrationDone | 
 |  258       .then(() => | 
 |  259         db.then(dbInstance => | 
 |  260           getFile(fileName, dbInstance, dbConfig.storeName)) | 
 |  261         .then(entry => | 
 |  262         { | 
 |  263           return { | 
 |  264             exists: true, | 
 |  265             lastModified: entry.lastModified | 
 |  266           }; | 
 |  267         })) | 
 |  268       .catch(error => | 
 |  269       { | 
 |  270         if (error.type == "NoSuchFile") | 
 |  271           return {exists: false}; | 
 |  272         throw error; | 
 |  273       }); | 
 |  274   }, | 
 |  275  | 
 |  276   /** | 
 |  277    * Renames a file. | 
 |  278    * @param {string} fromFile | 
 |  279    *    Name of the file to be renamed | 
 |  280    * @param {string} newName | 
 |  281    *    New file name, will be overwritten if exists | 
 |  282    * @return {Promise} | 
 |  283    *    Promise to be resolved or rejected once the operation is completed | 
 |  284    */ | 
 |  285   renameFile(fromFile, newName) | 
 |  286   { | 
 |  287     return migrationDone | 
 |  288       .then(() => | 
 |  289         db.then(dbInstance => | 
 |  290           getFile(fromFile, dbInstance, dbConfig.storeName) | 
 |  291           .then(fileData => | 
 |  292             saveFile( | 
 |  293               { | 
 |  294                 fileName: fileToKey(newName), | 
 |  295                 content: fileData.content, | 
 |  296                 lastModified: fileData.lastModified | 
 |  297               }, | 
 |  298               dbInstance, | 
 |  299               dbConfig.storeName)) | 
 |  300           .then(() => deleteFile(fromFile, dbInstance, dbConfig.storeName)))); | 
 |  301   } | 
 |  302 }; | 
 |  303  | 
| OLD | NEW |