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 https://github.com/localForage/localForage/blob /2cdbd74/src/localforage.js#L42-L51 | |
22 const localForageDbConfig = { | |
23 dbName: "localforage", | |
24 storeName: "keyvaluepairs", | |
25 version: 2 | |
26 }; | |
27 | |
28 const dbConfig = { | |
29 dbName: "adblockplus", | |
30 storeName: "file", | |
31 keyPath: "fileName", | |
32 version: 1 | |
33 }; | |
34 | |
35 let db; | |
kzar
2018/06/05 12:38:53
I'd rather we stored the connection promise here,
piscoi.georgiana
2018/06/11 07:02:45
Done.
| |
36 let localForageDb; | |
kzar
2018/06/05 12:38:53
We don't use the localforage database connection o
piscoi.georgiana
2018/06/11 07:02:44
Done.
| |
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(dbConfig) | |
kzar
2018/06/05 12:38:53
Nit: I think we should move the openDB function's
piscoi.georgiana
2018/06/11 07:02:45
Is there any specific rule/preference regarding re
kzar
2018/06/12 10:46:16
No rule that I know of, just something I try to do
Sebastian Noack
2018/06/13 00:55:11
The idea is to increase code locality, i.e. so tha
piscoi.georgiana
2018/06/14 13:41:26
Done.
| |
51 .then(dbInstance => | |
52 { | |
53 db = dbInstance; | |
kzar
2018/06/05 12:38:53
(I think assigning something from a promise which
piscoi.georgiana
2018/06/11 07:02:44
Done.
| |
54 return openDB(localForageDbConfig); | |
55 }) | |
56 .then(dbInstance => | |
57 { | |
58 localForageDb = dbInstance; | |
59 return getAllFiles(localForageDb, localForageDbConfig.storeName); | |
60 }) | |
61 .then(files => | |
62 files.map(file => | |
63 Promise.all(saveFile(file, db, dbConfig.storeName)))) | |
kzar
2018/06/05 12:38:53
Supposing a file exists in both databases wouldn't
piscoi.georgiana
2018/06/11 07:02:45
Yes, it will override any file that is present in
kzar
2018/06/12 10:46:16
What do you think Sebastian? I think I'd prefer th
Sebastian Noack
2018/06/13 00:55:11
I think it's fine to copy the file over from local
| |
64 .then(() => | |
65 clearObjectStore(localForageDb, localForageDbConfig.storeName)); | |
kzar
2018/06/05 12:38:53
I wonder what will happen the next time we try to
piscoi.georgiana
2018/06/11 07:02:44
No, it won't throw. The cursor value will be null,
kzar
2018/06/12 10:46:16
Acknowledged.
| |
66 } | |
67 | |
68 function getAllFiles(dbInstance, storeName) | |
69 { | |
70 return new Promise((resolve, reject) => | |
71 { | |
72 // edge doesn't currently support getAll method on IDBObjectStore interface | |
73 // so a cursor is used to iterate over all objects from the store | |
74 let transaction = dbInstance | |
75 .transaction([storeName], IDBTransaction.READ_ONLY); | |
76 | |
77 let store = transaction.objectStore(storeName); | |
78 let cursorReq = store.openCursor(); | |
79 let filesData = []; | |
80 | |
81 transaction.oncomplete = event => | |
82 { | |
83 resolve(filesData); | |
84 }; | |
85 | |
86 cursorReq.onsuccess = event => | |
87 { | |
88 let cursor = event.currentTarget.result; | |
89 if (cursor) | |
90 { | |
91 let value = cursor.value; | |
92 | |
93 filesData.push({ | |
94 fileName: cursor.key, | |
95 content: value.content, | |
96 lastModified: value.lastModified | |
97 }); | |
98 cursor.continue(); | |
99 } | |
100 }; | |
101 | |
102 cursorReq.onerror = reject; | |
103 }); | |
104 } | |
105 | |
106 function clearObjectStore(dbInstance, storeName) | |
107 { | |
108 return new Promise((resolve, reject) => | |
109 { | |
110 let store = getObjectStore(dbInstance, storeName); | |
111 let req = store.clear(); | |
112 | |
113 req.onsuccess = resolve; | |
114 req.onerror = reject; | |
115 }); | |
116 } | |
117 | |
118 function fileToKey(fileName) | |
119 { | |
120 return keyPrefix + fileName; | |
121 } | |
122 | |
123 function formatFile(name, data) | |
124 { | |
125 return { | |
126 fileName: fileToKey(name), | |
127 content: Array.from(data), | |
128 lastModified: Date.now() | |
129 }; | |
130 } | |
131 | |
132 function openDB({dbName, storeName, version, keyPath}) | |
133 { | |
134 return new Promise((resolve, reject) => | |
135 { | |
136 let req = indexedDB.open(dbName, version); | |
137 | |
138 req.onsuccess = event => | |
139 { | |
140 return resolve(event.currentTarget.result); | |
141 }; | |
142 | |
143 req.onerror = reject; | |
144 | |
145 req.onupgradeneeded = event => | |
146 { | |
147 event | |
148 .currentTarget | |
149 .result | |
150 .createObjectStore(storeName, | |
151 { | |
152 keyPath, | |
153 autoIncrement: true | |
154 }); | |
155 }; | |
156 }); | |
157 } | |
158 | |
159 function getObjectStore(dbInstance, storeName) | |
160 { | |
161 return dbInstance | |
162 .transaction([storeName], IDBTransaction.READ_WRITE) | |
163 .objectStore(storeName); | |
164 } | |
165 | |
166 function getFile(fileName, dbInstance, storeName) | |
167 { | |
168 return new Promise((resolve, reject) => | |
169 { | |
170 let store = getObjectStore(dbInstance, storeName); | |
171 let req = store.get(fileToKey(fileName)); | |
172 | |
173 req.onsuccess = event => | |
174 { | |
175 let result = event.currentTarget.result; | |
176 | |
177 if (result) | |
178 resolve(result); | |
179 else | |
180 reject({type: "NoSuchFile"}); | |
181 }; | |
182 req.onerror = reject; | |
183 }); | |
184 } | |
185 | |
186 function saveFile(data, dbInstance, storeName) | |
187 { | |
188 return new Promise((resolve, reject) => | |
189 { | |
190 let store = getObjectStore(dbInstance, storeName); | |
191 let req = store.put(data); | |
192 | |
193 req.onsuccess = resolve; | |
194 req.onerror = reject; | |
195 }); | |
196 } | |
197 | |
198 function deleteFile(fileName, dbInstance, storeName) | |
199 { | |
200 return new Promise((resolve, reject) => | |
201 { | |
202 let store = getObjectStore(dbInstance, storeName); | |
203 let req = store.delete(fileToKey(fileName)); | |
204 | |
205 req.onsuccess = resolve; | |
206 req.onerror = reject; | |
207 }); | |
208 } | |
209 | |
210 exports.IO = | |
211 { | |
212 /** | |
213 * Writes text lines to a file. | |
214 * @param {string} fileName | |
215 * Name of the file to be written | |
216 * @param {Iterable.<string>} data | |
217 * An array-like or iterable object containing the lines (without line | |
218 * endings) | |
219 * @return {Promise} | |
220 * Promise to be resolved or rejected once the operation is completed | |
221 */ | |
222 writeToFile(fileName, data) | |
223 { | |
224 return migrationDone | |
225 .then(() => saveFile(formatFile(fileName, data), db, dbConfig.storeName)); | |
226 }, | |
227 | |
228 /** | |
229 * Reads text lines from a file. | |
230 * @param {string} fileName | |
231 * Name of the file to be read | |
232 * @param {TextSink} listener | |
233 * Function that will be called for each line in the file | |
234 * @return {Promise} | |
235 * Promise to be resolved or rejected once the operation is completed | |
236 */ | |
237 readFromFile(fileName, listener) | |
238 { | |
239 return migrationDone | |
240 .then(() => getFile(fileName, db, dbConfig.storeName)) | |
241 .then(entry => | |
242 { | |
243 for (let line of entry.content) | |
244 listener(line); | |
245 }); | |
246 }, | |
247 | |
248 /** | |
249 * Retrieves file metadata. | |
250 * @param {string} fileName | |
251 * Name of the file to be looked up | |
252 * @return {Promise.<StatData>} | |
253 * Promise to be resolved with file metadata once the operation is | |
254 * completed | |
255 */ | |
256 statFile(fileName) | |
257 { | |
258 return migrationDone | |
259 .then(() => getFile(fileName, db, dbConfig.storeName)) | |
260 .then(entry => | |
261 { | |
262 return { | |
263 exists: true, | |
264 lastModified: entry.lastModified | |
265 }; | |
266 }) | |
267 .catch(error => | |
268 { | |
269 if (error.type == "NoSuchFile") | |
270 return {exists: false}; | |
271 throw error; | |
272 }); | |
273 }, | |
274 | |
275 /** | |
276 * Renames a file. | |
277 * @param {string} fromFile | |
278 * Name of the file to be renamed | |
279 * @param {string} newName | |
280 * New file name, will be overwritten if exists | |
281 * @return {Promise} | |
282 * Promise to be resolved or rejected once the operation is completed | |
283 */ | |
284 renameFile(fromFile, newName) | |
285 { | |
286 return migrationDone | |
287 .then(() => getFile(fromFile, db, dbConfig.storeName)) | |
288 .then(fileData => | |
289 saveFile( | |
290 { | |
291 fileName: fileToKey(newName), | |
292 content: fileData.content, | |
293 lastModified: fileData.lastModified | |
294 }, | |
295 db, | |
296 dbConfig.storeName)) | |
297 .then(() => deleteFile(fromFile, db, dbConfig.storeName)); | |
298 } | |
299 }; | |
300 | |
OLD | NEW |