Chromium Code Reviews| 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 |