OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 /** | |
8 * @constructor | |
9 * | |
10 * @param {!RecordStorage} storage | |
11 */ | |
12 function ImportHistory(storage) { | |
13 | |
14 /** @private {!RecordStorage} */ | |
15 this.storage_ = storage; | |
16 | |
17 /** @private {!Object.<string, !Array.<string>>} */ | |
18 this.history_ = {}; | |
19 }; | |
mtomasz
2014/10/29 01:10:41
nit: ; seems redundant here.
Steve McKay
2014/10/29 16:57:21
Done.
| |
20 | |
21 /** | |
22 * Prints an error to the console. | |
23 * | |
24 * @param {!Error} error | |
25 * @private | |
26 */ | |
27 ImportHistory.handleError_ = function(error) { | |
28 console.error(error.stack || error); | |
29 }; | |
30 | |
31 /** | |
32 * Use this factory method to get a fully ready instance of ImportHistory. | |
33 * | |
34 * @param {!RecordStorage} storage | |
35 * | |
36 * @return {!Promise.<!ImportHistory>} Resolves when history instance is ready. | |
37 */ | |
38 ImportHistory.load = function(storage) { | |
39 var history = new ImportHistory(storage); | |
40 return history.reload().then( | |
41 function() { | |
42 return history; | |
43 }); | |
44 }; | |
45 | |
46 /** | |
47 * Reloads history from disk. Should be called when the file | |
48 * is synced. | |
49 * | |
50 * @return {!Promise} Resolves when history has been loaded. | |
51 */ | |
52 ImportHistory.prototype.reload = function() { | |
53 this.history_ = {}; | |
54 return this.storage_.readAll() | |
55 .then( | |
56 function(entries) { | |
57 entries.forEach( | |
58 function(entry) { | |
59 this.updateHistoryRecord_(entry[0], entry[1]); | |
60 }.bind(this)); | |
61 }.bind(this)) | |
62 .catch(ImportHistory.handleError_); | |
63 }; | |
64 | |
65 /** | |
66 * Adds a history entry to the in-memory history model. | |
67 * @param {string} key | |
68 * @param {string} destination | |
69 */ | |
70 ImportHistory.prototype.updateHistoryRecord_ = function(key, destination) { | |
71 if (key in this.history_) { | |
72 this.history_[key].push(destination); | |
73 } else { | |
74 this.history_[key] = [destination]; | |
75 } | |
76 }; | |
77 | |
78 /** | |
79 * @param {!FileEntry} entry | |
80 * @param {string} destination | |
81 * @return {!Promise.<boolean>} Settles with true if the FileEntry | |
82 * was previously imported to the specified destination. | |
83 */ | |
84 ImportHistory.prototype.wasImported = function(entry, destination) { | |
85 return this.createKey_(entry) | |
86 .then( | |
87 function(key) { | |
88 return this.getDestinations_(key).indexOf(destination) >= 0; | |
89 }.bind(this)) | |
90 .catch(ImportHistory.handleError_); | |
91 }; | |
92 | |
93 /** | |
94 * @param {!FileEntry} entry | |
95 * @param {string} destination | |
96 * @return {!Promise.<boolean>} Settles with true if the FileEntry | |
97 * was previously imported to the specified destination. | |
98 */ | |
99 ImportHistory.prototype.markImported = function(entry, destination) { | |
100 return this.createKey_(entry) | |
101 .then(this.addDestination_.bind(this, destination)) | |
102 .catch(ImportHistory.handleError_); | |
103 }; | |
104 | |
105 /** | |
106 * @param {string} key | |
107 * @return {!Array.<string>} The list of previously noted | |
108 * destinations, or an empty array, if none. | |
109 */ | |
110 ImportHistory.prototype.getDestinations_ = function(key) { | |
111 return key in this.history_ ? this.history_[key] : []; | |
112 }; | |
113 | |
114 /** | |
115 * @param {string} destination | |
116 * @param {string} key | |
117 * @return {!Promise} | |
118 */ | |
119 ImportHistory.prototype.addDestination_ = function(destination, key) { | |
120 this.updateHistoryRecord_(key, destination); | |
121 return this.storage_.write([key, destination]); | |
122 }; | |
123 | |
124 /** | |
125 * @param {!FileEntry} entry | |
126 * @return {!Promise.<string>} Settles with a the key is available. | |
127 */ | |
128 ImportHistory.prototype.createKey_ = function(fileEntry) { | |
129 var entry = new FileEntryWrapper(fileEntry); | |
130 return new Promise( | |
131 function(resolve, reject) { | |
132 entry.getMetadata() | |
133 .then( | |
134 function(metadata) { | |
135 if (!('modificationTime' in metadata)) { | |
136 reject('File entry missing "modificationTime" field.'); | |
137 } else if (!('size' in metadata)) { | |
138 reject('File entry missing "size" field.'); | |
139 } else { | |
140 resolve( | |
141 metadata['modificationTime'] + '_' + metadata['size']); | |
142 } | |
143 }.bind(this)); | |
144 }.bind(this)); | |
145 }; | |
146 | |
147 /** | |
148 * An simple record storage mechanism. | |
149 * | |
150 * @interface | |
151 */ | |
152 function RecordStorage(entry) {}; | |
153 | |
154 /** | |
155 * Adds a new record. | |
156 * | |
157 * @param {!Array.<*>} record | |
158 * @return {!Promise} Resolves when record is added. | |
159 */ | |
160 RecordStorage.prototype.write; | |
mtomasz
2014/10/29 01:10:41
Will this compile? We used to write an empty metho
Steve McKay
2014/10/29 16:57:21
This is purely in anticipation of Closure compilat
| |
161 | |
162 /** | |
163 * Reads all records. | |
164 * | |
165 * @return {!Promise.<!Array.<!Array.<*>>>} | |
166 */ | |
167 RecordStorage.prototype.readAll; | |
168 | |
169 /** | |
170 * A {@code RecordStore} that persists data in a {@code FileEntry}. | |
171 * | |
172 * @param {!FileEntry} entry | |
173 * | |
174 * @constructor | |
175 * @implements {RecordStorage} | |
176 */ | |
177 function FileEntryRecordStorage(entry) { | |
178 /** @private {!FileEntryWrapper} */ | |
179 this.entry_ = new FileEntryWrapper(entry); | |
180 }; | |
181 | |
182 /** | |
183 * Prints an error to the console. | |
184 * | |
185 * @param {!Error} error | |
186 * @private | |
187 */ | |
188 FileEntryRecordStorage.handleError_ = function(error) { | |
189 console.error(error.stack || error); | |
190 }; | |
191 | |
192 /** @override */ | |
193 FileEntryRecordStorage.prototype.write = function(record) { | |
194 // TODO(smckay): should we make an effort to reuse a file writer? | |
195 return this.entry_.createWriter() | |
196 .then(this.writeRecord_.bind(this, record)) | |
197 .catch(FileEntryRecordStorage.handleError_); | |
198 }; | |
199 | |
200 /** | |
201 * Appends a new record to the end of the file. | |
202 * | |
203 * @param {!Object} record | |
204 * @param {!FileWriter} writer | |
205 * @return {!Promise} Resolves when write is complete. | |
206 * @private | |
207 */ | |
208 FileEntryRecordStorage.prototype.writeRecord_ = function(record, writer) { | |
209 return new Promise( | |
210 function(resolve, reject) { | |
211 var blob = new Blob( | |
212 [JSON.stringify(record) + ',\n'], | |
213 {type: 'application/json; charset=utf-8'}); | |
mtomasz
2014/10/29 01:10:41
It's not really a valid json. How about simply pla
Steve McKay
2014/10/29 16:57:21
Done. text/plain; charset=UTF-8
| |
214 | |
215 writer.onwriteend = function() { | |
216 resolve(); | |
217 }; | |
218 writer.onerror = function() { | |
219 reject(); | |
220 }; | |
221 | |
222 writer.seek(writer.length); | |
223 writer.write(blob); | |
224 }.bind(this)); | |
225 }; | |
226 | |
227 /** @override */ | |
228 FileEntryRecordStorage.prototype.readAll = function() { | |
229 return this.entry_.file() | |
230 .then(this.readFileAsText_.bind(this), function() { return '';}) | |
mtomasz
2014/10/29 01:10:41
nit: space after ; missing.
Steve McKay
2014/10/29 16:57:21
Done.
| |
231 .then(this.parse_.bind(this)) | |
232 .then( | |
233 function(entries) { | |
234 return entries; | |
235 }) | |
236 .catch(FileEntryRecordStorage.handleError_); | |
237 }; | |
238 | |
239 /** | |
240 * Reads all lines from the entry. | |
241 * | |
242 * @param {!File} file | |
243 * @return {!Promise.<!Array.<string>>} | |
244 * @private | |
245 */ | |
246 FileEntryRecordStorage.prototype.readFileAsText_ = function(file) { | |
247 return new Promise( | |
248 function(resolve, reject) { | |
249 | |
250 var reader = new FileReader(); | |
251 | |
252 reader.onloadend = function() { | |
253 if (!!reader.error) { | |
254 FileEntryRecordStorage.handleError_(reader.error); | |
255 resolve(''); | |
256 } else { | |
257 resolve(reader.result); | |
258 } | |
259 }; | |
260 | |
261 reader.onerror = function(error) { | |
262 FileEntryRecordStorage.handleError_(error); | |
263 reject(e); | |
264 }; | |
265 | |
266 reader.readAsText(file); | |
267 }.bind(this)); | |
268 }; | |
269 | |
270 /** | |
271 * Parses the text. | |
272 * | |
273 * @param {string} text | |
274 * @return {!Promise.<!Array.<!Object>>} | |
275 * @private | |
276 */ | |
277 FileEntryRecordStorage.prototype.parse_ = function(text) { | |
278 return new Promise( | |
279 function(resolve, reject) { | |
280 if (text.length == 0) { | |
281 resolve([]); | |
282 } else { | |
283 // Dress up the contents of the file like an array, | |
284 // so the JSON object can parse it using JSON.parse. | |
285 // That means we need to both: | |
286 // 1) Strip the trailing ',\n' from the last record | |
287 // 2) Surround the whole string in brackets. | |
288 // NOTE: JSON.parse is WAY faster than parsing this | |
289 // ourselves in javascript. | |
290 var json = '[' + text.substring(0, text.length - 2) + ']'; | |
291 resolve(JSON.parse(json)); | |
mtomasz
2014/10/29 01:10:41
Date objects will not deserialize properly from js
Steve McKay
2014/10/29 16:57:22
The value is just a string. There's no need to par
| |
292 } | |
293 }.bind(this)); | |
294 }; | |
295 | |
296 /** | |
297 * An Object storage mechanism built on top of a FileEntry. | |
298 * | |
299 * @param {!FileEntry} entry | |
300 * | |
301 * @constructor | |
302 * @private | |
303 */ | |
304 function FileEntryWrapper(entry) { | |
305 /** @private {!FileEntry} */ | |
306 this.entry_ = entry; | |
307 }; | |
308 | |
309 /** | |
310 * A "Promisary" wrapper around entry.getWriter. | |
311 * @return {!Promise.<!FileWriter>} | |
312 */ | |
313 FileEntryWrapper.prototype.createWriter = function() { | |
314 return new Promise(this.entry_.createWriter.bind(this.entry_)); | |
315 }; | |
316 | |
317 /** | |
318 * A "Promisary" wrapper around entry.file. | |
319 * @return {!Promise.<!File>} | |
320 */ | |
321 FileEntryWrapper.prototype.file = function() { | |
322 return new Promise(this.entry_.file.bind(this.entry_)); | |
323 }; | |
324 | |
325 /** | |
326 * @return {!Promise.<!Object>} Data format not yet verified. | |
327 * Either a string, or an object. | |
328 */ | |
329 FileEntryWrapper.prototype.getMetadata = function() { | |
330 return new Promise(this.entry_.getMetadata.bind(this.entry_)); | |
331 }; | |
OLD | NEW |