Left: | ||
Right: |
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 /** | |
42 * Handles migrating all files from localforage db | |
43 * used in the previous implementation by the localForage library | |
44 * to the new adblockplus db that we use as a replacement | |
45 * @return {Promise} | |
46 * Promise to be resolved or rejected once the operation is completed | |
47 */ | |
48 function migrateFiles() | |
49 { | |
50 return openDB(localForageDbConfig) | |
51 .then(localForageDb => | |
52 { | |
53 return getAllFiles(localForageDb, localForageDbConfig.storeName) | |
kzar
2018/06/12 10:46:17
Nit: I guess we can omit the curly braces and the
piscoi.georgiana
2018/06/14 13:41:26
Done.
| |
54 .then(files => | |
55 db.then(dbInstance => files.map(file => | |
56 Promise.all(saveFile(file, dbInstance, dbConfig.storeName))))) | |
Sebastian Noack
2018/06/13 00:55:12
Please apologize if I'm wrong, but to me this seem
piscoi.georgiana
2018/06/14 13:41:27
Spot on, thank you! I've fixed it.
| |
57 .then(() => | |
58 clearObjectStore(localForageDb, localForageDbConfig.storeName)); | |
59 }); | |
60 } | |
61 | |
62 function getAllFiles(dbInstance, storeName) | |
63 { | |
64 return new Promise((resolve, reject) => | |
65 { | |
66 // edge doesn't currently support getAll method on IDBObjectStore interface | |
67 // so a cursor is used to iterate over all objects from the store | |
68 let transaction = dbInstance | |
69 .transaction([storeName], IDBTransaction.READ_ONLY); | |
70 | |
71 let store = transaction.objectStore(storeName); | |
72 let cursorReq = store.openCursor(); | |
73 let filesData = []; | |
74 | |
75 transaction.oncomplete = event => | |
76 { | |
77 resolve(filesData); | |
78 }; | |
79 | |
80 cursorReq.onsuccess = event => | |
81 { | |
82 let cursor = event.currentTarget.result; | |
83 if (cursor) | |
84 { | |
85 let value = cursor.value; | |
86 | |
87 filesData.push({ | |
88 fileName: cursor.key, | |
89 content: value.content, | |
90 lastModified: value.lastModified | |
91 }); | |
92 cursor.continue(); | |
93 } | |
94 }; | |
95 | |
96 cursorReq.onerror = reject; | |
97 }); | |
98 } | |
99 | |
100 function clearObjectStore(dbInstance, storeName) | |
101 { | |
102 return new Promise((resolve, reject) => | |
103 { | |
104 let store = getObjectStore(dbInstance, storeName); | |
105 let req = store.clear(); | |
106 | |
107 req.onsuccess = resolve; | |
108 req.onerror = reject; | |
109 }); | |
110 } | |
111 | |
112 function fileToKey(fileName) | |
113 { | |
114 return keyPrefix + fileName; | |
115 } | |
116 | |
117 function formatFile(name, data) | |
118 { | |
119 return { | |
120 fileName: fileToKey(name), | |
121 content: Array.from(data), | |
122 lastModified: Date.now() | |
123 }; | |
124 } | |
125 | |
126 function openDB({dbName, storeName, version, keyPath}) | |
127 { | |
128 return new Promise((resolve, reject) => | |
129 { | |
130 let req = indexedDB.open(dbName, version); | |
131 | |
132 req.onsuccess = event => | |
133 { | |
134 return resolve(event.currentTarget.result); | |
135 }; | |
136 | |
137 req.onerror = reject; | |
138 | |
139 req.onupgradeneeded = event => | |
140 { | |
141 event | |
142 .currentTarget | |
143 .result | |
144 .createObjectStore(storeName, | |
145 { | |
146 keyPath, | |
147 autoIncrement: true | |
148 }); | |
149 }; | |
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(() => db) | |
220 .then(dbInstance => | |
221 saveFile(formatFile(fileName, data), dbInstance, dbConfig.storeName)); | |
222 }, | |
223 | |
224 /** | |
225 * Reads text lines from a file. | |
226 * @param {string} fileName | |
227 * Name of the file to be read | |
228 * @param {TextSink} listener | |
229 * Function that will be called for each line in the file | |
230 * @return {Promise} | |
231 * Promise to be resolved or rejected once the operation is completed | |
232 */ | |
233 readFromFile(fileName, listener) | |
234 { | |
235 return migrationDone | |
236 .then(() => db) | |
237 .then(dbInstance => getFile(fileName, dbInstance, dbConfig.storeName)) | |
238 .then(entry => | |
239 { | |
240 for (let line of entry.content) | |
241 listener(line); | |
242 }); | |
243 }, | |
244 | |
245 /** | |
246 * Retrieves file metadata. | |
247 * @param {string} fileName | |
248 * Name of the file to be looked up | |
249 * @return {Promise.<StatData>} | |
250 * Promise to be resolved with file metadata once the operation is | |
251 * completed | |
252 */ | |
253 statFile(fileName) | |
254 { | |
255 return migrationDone | |
256 .then(() => db) | |
kzar
2018/06/12 10:46:17
Couldn't we do `db.then(dbInstance => ...` instead
piscoi.georgiana
2018/06/14 13:41:27
Yes, it looks better your way. Thank you!
| |
257 .then(dbInstance => getFile(fileName, dbInstance, dbConfig.storeName)) | |
258 .then(entry => | |
259 { | |
260 return { | |
261 exists: true, | |
262 lastModified: entry.lastModified | |
263 }; | |
264 }) | |
265 .catch(error => | |
266 { | |
267 if (error.type == "NoSuchFile") | |
268 return {exists: false}; | |
269 throw error; | |
270 }); | |
271 }, | |
272 | |
273 /** | |
274 * Renames a file. | |
275 * @param {string} fromFile | |
276 * Name of the file to be renamed | |
277 * @param {string} newName | |
278 * New file name, will be overwritten if exists | |
279 * @return {Promise} | |
280 * Promise to be resolved or rejected once the operation is completed | |
281 */ | |
282 renameFile(fromFile, newName) | |
283 { | |
284 return migrationDone | |
285 .then(() => db) | |
286 .then(dbInstance => | |
287 getFile(fromFile, dbInstance, dbConfig.storeName) | |
288 .then(fileData => | |
289 saveFile( | |
290 { | |
291 fileName: fileToKey(newName), | |
292 content: fileData.content, | |
293 lastModified: fileData.lastModified | |
294 }, | |
295 dbInstance, | |
296 dbConfig.storeName)) | |
297 .then(() => deleteFile(fromFile, dbInstance, dbConfig.storeName))); | |
298 } | |
299 }; | |
300 | |
OLD | NEW |