OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Class representing the results of a scan operation. | 6 * Class representing the results of a scan operation. |
7 * | 7 * |
8 * @interface | 8 * @interface |
9 */ | 9 */ |
10 importer.MediaScanner = function() {}; | 10 importer.MediaScanner = function() {}; |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
96 if (index > -1) { | 96 if (index > -1) { |
97 this.observers_.splice(index, 1); | 97 this.observers_.splice(index, 1); |
98 } else { | 98 } else { |
99 console.warn('Ignoring request to remove observer that is not registered.'); | 99 console.warn('Ignoring request to remove observer that is not registered.'); |
100 } | 100 } |
101 }; | 101 }; |
102 | 102 |
103 /** @override */ | 103 /** @override */ |
104 importer.DefaultMediaScanner.prototype.scanDirectory = function(directory) { | 104 importer.DefaultMediaScanner.prototype.scanDirectory = function(directory) { |
105 var scan = this.createScanResult_(); | 105 var scan = this.createScanResult_(); |
106 console.info(scan.name + ': Scanning directory ' + directory.fullPath); | |
107 | |
106 var watcher = this.watcherFactory_( | 108 var watcher = this.watcherFactory_( |
107 /** @this {importer.DefaultMediaScanner} */ | 109 /** @this {importer.DefaultMediaScanner} */ |
108 function() { | 110 function() { |
109 scan.invalidateScan(); | 111 scan.cancel(); |
110 this.notify_(importer.ScanEvent.INVALIDATED, scan); | 112 this.notify_(importer.ScanEvent.INVALIDATED, scan); |
111 }.bind(this)); | 113 }.bind(this)); |
112 | 114 |
113 this.crawlDirectory_(directory, watcher) | 115 this.crawlDirectory_(directory, watcher) |
114 .then(this.scanMediaFiles_.bind(this, scan)) | 116 .then(this.scanMediaFiles_.bind(this, scan)) |
115 .then(scan.resolve) | 117 .then(scan.resolve) |
116 .catch(scan.reject); | 118 .catch(scan.reject); |
117 | 119 |
118 scan.whenFinal() | 120 scan.whenFinal() |
119 .then( | 121 .then( |
120 /** @this {importer.DefaultMediaScanner} */ | 122 /** @this {importer.DefaultMediaScanner} */ |
121 function() { | 123 function() { |
124 console.info(scan.name + ': Finished.'); | |
122 this.notify_(importer.ScanEvent.FINALIZED, scan); | 125 this.notify_(importer.ScanEvent.FINALIZED, scan); |
123 }.bind(this)); | 126 }.bind(this)); |
124 | 127 |
125 return scan; | 128 return scan; |
126 }; | 129 }; |
127 | 130 |
128 /** @override */ | 131 /** @override */ |
129 importer.DefaultMediaScanner.prototype.scanFiles = function(entries) { | 132 importer.DefaultMediaScanner.prototype.scanFiles = function(entries) { |
130 if (entries.length === 0) { | 133 if (entries.length === 0) { |
131 throw new Error('Cannot scan empty list.'); | 134 throw new Error('Cannot scan empty list.'); |
132 } | 135 } |
133 var scan = this.createScanResult_(); | 136 var scan = this.createScanResult_(); |
137 console.info( | |
138 scan.name + ': Scanning fixed set of ' + | |
139 entries.length + ' entries.'); | |
140 | |
134 var watcher = this.watcherFactory_( | 141 var watcher = this.watcherFactory_( |
135 /** @this {importer.DefaultMediaScanner} */ | 142 /** @this {importer.DefaultMediaScanner} */ |
136 function() { | 143 function() { |
137 scan.invalidateScan(); | 144 scan.cancel(); |
138 this.notify_(importer.ScanEvent.INVALIDATED, scan); | 145 this.notify_(importer.ScanEvent.INVALIDATED, scan); |
139 }.bind(this)); | 146 }.bind(this)); |
140 | 147 |
141 var scanPromises = entries.map(this.onUniqueFileFound_.bind(this, scan)); | 148 var scanPromises = entries.map(this.onUniqueFileFound_.bind(this, scan)); |
142 | 149 |
143 Promise.all(scanPromises) | 150 Promise.all(scanPromises) |
144 .then(scan.resolve) | 151 .then(scan.resolve) |
145 .catch(scan.reject); | 152 .catch(scan.reject); |
146 | 153 |
147 scan.whenFinal() | 154 scan.whenFinal() |
148 .then( | 155 .then( |
149 /** @this {importer.DefaultMediaScanner} */ | 156 /** @this {importer.DefaultMediaScanner} */ |
150 function() { | 157 function() { |
158 console.info(scan.name + ': Finished.'); | |
151 this.notify_(importer.ScanEvent.FINALIZED, scan); | 159 this.notify_(importer.ScanEvent.FINALIZED, scan); |
152 }.bind(this)); | 160 }.bind(this)); |
153 | 161 |
154 return scan; | 162 return scan; |
155 }; | 163 }; |
156 | 164 |
157 /** @const {number} */ | 165 /** @const {number} */ |
158 importer.DefaultMediaScanner.SCAN_BATCH_SIZE = 1; | 166 importer.DefaultMediaScanner.SCAN_BATCH_SIZE = 1; |
159 | 167 |
160 /** | 168 /** |
161 * @param {!importer.DefaultScanResult} scan | 169 * @param {!importer.DefaultScanResult} scan |
162 * @param {!Array<!FileEntry>} entries | 170 * @param {!Array<!FileEntry>} entries |
163 * @return {!Promise} Resolves when scanning is finished. | 171 * @return {!Promise} Resolves when scanning is finished normally |
172 * or canceled. | |
164 * @private | 173 * @private |
165 */ | 174 */ |
166 importer.DefaultMediaScanner.prototype.scanMediaFiles_ = | 175 importer.DefaultMediaScanner.prototype.scanMediaFiles_ = |
167 function(scan, entries) { | 176 function(scan, entries) { |
168 var handleFileEntry = this.onFileEntryFound_.bind(this, scan); | 177 var handleFileEntry = this.onFileEntryFound_.bind(this, scan); |
169 | 178 |
170 /** | 179 /** |
171 * @param {number} begin The beginning offset in the list of entries | 180 * @param {number} begin The beginning offset in the list of entries |
172 * to process. | 181 * to process. |
173 * @return {!Promise} | 182 * @return {!Promise} |
174 */ | 183 */ |
175 var scanChunk = function(begin) { | 184 var scanBatch = function(begin) { |
185 if (scan.canceled()) { | |
186 console.debug( | |
187 scan.name + ': Skipping remaining ' + | |
188 (entries.length - begin) + | |
189 ' entries. Scan was canceled.'); | |
190 return Promise.resolve(); | |
191 } | |
192 | |
176 // the second arg to slice is an exclusive end index, so we +1 batch size. | 193 // the second arg to slice is an exclusive end index, so we +1 batch size. |
177 var end = begin + importer.DefaultMediaScanner.SCAN_BATCH_SIZE + 1; | 194 var end = begin + importer.DefaultMediaScanner.SCAN_BATCH_SIZE ; |
mtomasz
2015/04/01 03:55:40
nit: \s; -> ;
Steve McKay
2015/04/01 04:00:13
Done.
| |
195 console.log(scan.name + ': Processing batch ' + begin + '-' + (end - 1)); | |
196 var batch = entries.slice(begin, end); | |
197 | |
178 return Promise.all( | 198 return Promise.all( |
179 entries.slice(begin, end).map(handleFileEntry)) | 199 batch.map(handleFileEntry)) |
180 .then( | 200 .then( |
201 /** @this {importer.DefaultMediaScanner} */ | |
181 function() { | 202 function() { |
182 if (end < entries.length) { | 203 if (end < entries.length) { |
183 return scanChunk(end); | 204 return scanBatch(end); |
184 } | 205 } |
185 }); | 206 }); |
186 }; | 207 }; |
187 | 208 |
188 return scanChunk(0); | 209 return scanBatch(0); |
189 }; | 210 }; |
190 | 211 |
191 /** | 212 /** |
192 * Notifies all listeners at some point in the near future. | 213 * Notifies all listeners at some point in the near future. |
193 * | 214 * |
194 * @param {!importer.ScanEvent} event | 215 * @param {!importer.ScanEvent} event |
195 * @param {!importer.DefaultScanResult} result | 216 * @param {!importer.DefaultScanResult} result |
196 * @private | 217 * @private |
197 */ | 218 */ |
198 importer.DefaultMediaScanner.prototype.notify_ = function(event, result) { | 219 importer.DefaultMediaScanner.prototype.notify_ = function(event, result) { |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
314 * @interface | 335 * @interface |
315 */ | 336 */ |
316 importer.ScanResult = function() {}; | 337 importer.ScanResult = function() {}; |
317 | 338 |
318 /** | 339 /** |
319 * @return {boolean} true if scanning is complete. | 340 * @return {boolean} true if scanning is complete. |
320 */ | 341 */ |
321 importer.ScanResult.prototype.isFinal; | 342 importer.ScanResult.prototype.isFinal; |
322 | 343 |
323 /** | 344 /** |
324 * @return {boolean} true if scanning is invalidated. | 345 * Notifies the scan to stop working. Some in progress work |
346 * may continue, but no new work will be undertaken. | |
325 */ | 347 */ |
326 importer.ScanResult.prototype.isInvalidated; | 348 importer.ScanResult.prototype.cancel; |
349 | |
350 /** | |
351 * @return {boolean} True if the scan has been canceled. Some | |
352 * work started prior to cancelation may still be ongoing. | |
353 */ | |
354 importer.ScanResult.prototype.canceled; | |
327 | 355 |
328 /** | 356 /** |
329 * Returns all files entries discovered so far. The list will be | 357 * Returns all files entries discovered so far. The list will be |
330 * complete only after scanning has completed and {@code isFinal} | 358 * complete only after scanning has completed and {@code isFinal} |
331 * returns {@code true}. | 359 * returns {@code true}. |
332 * | 360 * |
333 * @return {!Array<!FileEntry>} | 361 * @return {!Array<!FileEntry>} |
334 */ | 362 */ |
335 importer.ScanResult.prototype.getFileEntries; | 363 importer.ScanResult.prototype.getFileEntries; |
336 | 364 |
337 /** | 365 /** |
338 * Returns a promise that fires when scanning is complete. | 366 * Returns a promise that fires when scanning is finished |
367 * normally or has been canceled. | |
339 * | 368 * |
340 * @return {!Promise<!importer.ScanResult>} | 369 * @return {!Promise<!importer.ScanResult>} |
341 */ | 370 */ |
342 importer.ScanResult.prototype.whenFinal; | 371 importer.ScanResult.prototype.whenFinal; |
343 | 372 |
344 /** | 373 /** |
345 * @return {!importer.ScanResult.Statistics} | 374 * @return {!importer.ScanResult.Statistics} |
346 */ | 375 */ |
347 importer.ScanResult.prototype.getStatistics; | 376 importer.ScanResult.prototype.getStatistics; |
348 | 377 |
349 /** | 378 /** |
350 * @typedef {{ | 379 * @typedef {{ |
351 * scanDuration: number, | 380 * scanDuration: number, |
352 * newFileCount: number, | 381 * newFileCount: number, |
353 * duplicates: !Object<!importer.Disposition, number>, | 382 * duplicates: !Object<!importer.Disposition, number>, |
354 * sizeBytes: number | 383 * sizeBytes: number |
355 * }} | 384 * }} |
356 */ | 385 */ |
357 importer.ScanResult.Statistics; | 386 importer.ScanResult.Statistics; |
358 | 387 |
359 /** | 388 /** |
360 * Results of a scan operation. The object is "live" in that data can and | 389 * Results of a scan operation. The object is "live" in that data can and |
361 * will change as the scan operation discovers files. | 390 * will change as the scan operation discovers files. |
362 * | 391 * |
363 * <p>The scan is complete, and the object will become static once the | 392 * <p>The scan is complete, and the object will become static once the |
364 * {@code whenFinal} promise resolves. | 393 * {@code whenFinal} promise resolves. |
365 * | 394 * |
395 * Note that classes implementing this should provide a read-only | |
396 * {@code name} field. | |
397 * | |
366 * @constructor | 398 * @constructor |
367 * @struct | 399 * @struct |
368 * @implements {importer.ScanResult} | 400 * @implements {importer.ScanResult} |
369 * | 401 * |
370 * @param {function(!FileEntry): !Promise.<string>} hashGenerator Hash-code | 402 * @param {function(!FileEntry): !Promise.<string>} hashGenerator Hash-code |
371 * generator used to dedupe within the scan results itself. | 403 * generator used to dedupe within the scan results itself. |
372 */ | 404 */ |
373 importer.DefaultScanResult = function(hashGenerator) { | 405 importer.DefaultScanResult = function(hashGenerator) { |
406 /** @private {number} */ | |
407 this.scanId_ = importer.generateId(); | |
374 | 408 |
375 /** @private {function(!FileEntry): !Promise.<string>} */ | 409 /** @private {function(!FileEntry): !Promise.<string>} */ |
376 this.createHashcode_ = hashGenerator; | 410 this.createHashcode_ = hashGenerator; |
377 | 411 |
378 /** | 412 /** |
379 * List of file entries found while scanning. | 413 * List of file entries found while scanning. |
380 * @private {!Array<!FileEntry>} | 414 * @private {!Array<!FileEntry>} |
381 */ | 415 */ |
382 this.fileEntries_ = []; | 416 this.fileEntries_ = []; |
383 | 417 |
(...skipping 19 matching lines...) Expand all Loading... | |
403 | 437 |
404 /** | 438 /** |
405 * The point in time when the last scan activity occured. | 439 * The point in time when the last scan activity occured. |
406 * @type {Date} | 440 * @type {Date} |
407 */ | 441 */ |
408 this.lastScanActivity_ = this.scanStarted_; | 442 this.lastScanActivity_ = this.scanStarted_; |
409 | 443 |
410 /** | 444 /** |
411 * @private {boolean} | 445 * @private {boolean} |
412 */ | 446 */ |
413 this.invalidated_ = false; | 447 this.canceled_ = false; |
414 | 448 |
415 /** @private {!importer.Resolver.<!importer.ScanResult>} */ | 449 /** @private {!importer.Resolver.<!importer.ScanResult>} */ |
416 this.resolver_ = new importer.Resolver(); | 450 this.resolver_ = new importer.Resolver(); |
417 }; | 451 }; |
418 | 452 |
419 /** @struct */ | 453 /** @struct */ |
420 importer.DefaultScanResult.prototype = { | 454 importer.DefaultScanResult.prototype = { |
455 /** @return {string} */ | |
456 get name() { return 'ScanResult(' + this.scanId_ + ')' }, | |
421 | 457 |
422 /** @return {function()} */ | 458 /** @return {function()} */ |
423 get resolve() { return this.resolver_.resolve.bind(null, this); }, | 459 get resolve() { return this.resolver_.resolve.bind(null, this); }, |
424 | 460 |
425 /** @return {function(*=)} */ | 461 /** @return {function(*=)} */ |
426 get reject() { return this.resolver_.reject; } | 462 get reject() { return this.resolver_.reject; } |
427 }; | 463 }; |
428 | 464 |
429 /** @override */ | 465 /** @override */ |
430 importer.DefaultScanResult.prototype.isFinal = function() { | 466 importer.DefaultScanResult.prototype.isFinal = function() { |
431 return this.resolver_.settled; | 467 return this.resolver_.settled; |
432 }; | 468 }; |
433 | 469 |
434 importer.DefaultScanResult.prototype.isInvalidated = function() { | |
435 return this.invalidated_; | |
436 }; | |
437 | |
438 /** @override */ | 470 /** @override */ |
439 importer.DefaultScanResult.prototype.getFileEntries = function() { | 471 importer.DefaultScanResult.prototype.getFileEntries = function() { |
440 return this.fileEntries_; | 472 return this.fileEntries_; |
441 }; | 473 }; |
442 | 474 |
443 /** @override */ | 475 /** @override */ |
444 importer.DefaultScanResult.prototype.whenFinal = function() { | 476 importer.DefaultScanResult.prototype.whenFinal = function() { |
445 return this.resolver_.promise; | 477 return this.resolver_.promise; |
446 }; | 478 }; |
447 | 479 |
448 /** | 480 /** @override */ |
449 * Invalidates this scan. | 481 importer.DefaultScanResult.prototype.cancel = function() { |
450 */ | 482 this.canceled_ = true; |
451 importer.DefaultScanResult.prototype.invalidateScan = function() { | 483 }; |
452 this.invalidated_ = true; | 484 |
485 /** @override */ | |
486 importer.DefaultScanResult.prototype.canceled = function() { | |
487 return this.canceled_; | |
453 }; | 488 }; |
454 | 489 |
455 /** | 490 /** |
456 * Adds a file to results. | 491 * Adds a file to results. |
457 * | 492 * |
458 * @param {!FileEntry} entry | 493 * @param {!FileEntry} entry |
459 * @return {!Promise.<boolean>} True if the file as added, false if it was | 494 * @return {!Promise.<boolean>} True if the file as added, false if it was |
460 * rejected as a dupe. | 495 * rejected as a dupe. |
461 */ | 496 */ |
462 importer.DefaultScanResult.prototype.addFileEntry = function(entry) { | 497 importer.DefaultScanResult.prototype.addFileEntry = function(entry) { |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
590 if (!this.watchedDirectories_[event.entry.toURL()]) | 625 if (!this.watchedDirectories_[event.entry.toURL()]) |
591 return; | 626 return; |
592 this.triggered = true; | 627 this.triggered = true; |
593 for (var url in this.watchedDirectories_) { | 628 for (var url in this.watchedDirectories_) { |
594 chrome.fileManagerPrivate.removeFileWatch(url, function() {}); | 629 chrome.fileManagerPrivate.removeFileWatch(url, function() {}); |
595 } | 630 } |
596 chrome.fileManagerPrivate.onDirectoryChanged.removeListener( | 631 chrome.fileManagerPrivate.onDirectoryChanged.removeListener( |
597 assert(this.listener_)); | 632 assert(this.listener_)); |
598 this.callback_(); | 633 this.callback_(); |
599 }; | 634 }; |
OLD | NEW |