| 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 15 matching lines...) Expand all Loading... |
| 26 importer.MediaScanner.prototype.addObserver; | 26 importer.MediaScanner.prototype.addObserver; |
| 27 | 27 |
| 28 /** | 28 /** |
| 29 * Remove a previously registered observer. | 29 * Remove a previously registered observer. |
| 30 * | 30 * |
| 31 * @param {!importer.ScanObserver} observer | 31 * @param {!importer.ScanObserver} observer |
| 32 */ | 32 */ |
| 33 importer.MediaScanner.prototype.removeObserver; | 33 importer.MediaScanner.prototype.removeObserver; |
| 34 | 34 |
| 35 /** | 35 /** |
| 36 * Class representing the results of a scan operation. | |
| 37 * | |
| 38 * @interface | |
| 39 */ | |
| 40 importer.ScanResult = function() {}; | |
| 41 | |
| 42 /** | |
| 43 * @return {boolean} true if scanning is complete. | |
| 44 */ | |
| 45 importer.ScanResult.prototype.isFinal; | |
| 46 | |
| 47 /** | |
| 48 * @return {boolean} true if scanning is invalidated. | |
| 49 */ | |
| 50 importer.ScanResult.prototype.isInvalidated; | |
| 51 | |
| 52 /** | |
| 53 * Returns all files entries discovered so far. The list will be | |
| 54 * complete only after scanning has completed and {@code isFinal} | |
| 55 * returns {@code true}. | |
| 56 * | |
| 57 * @return {!Array.<!FileEntry>} | |
| 58 */ | |
| 59 importer.ScanResult.prototype.getFileEntries; | |
| 60 | |
| 61 /** | |
| 62 * Returns a promise that fires when scanning is complete. | |
| 63 * | |
| 64 * @return {!Promise.<!importer.ScanResult>} | |
| 65 */ | |
| 66 importer.ScanResult.prototype.whenFinal; | |
| 67 | |
| 68 /** | |
| 69 * @return {!importer.ScanResult.Statistics} | |
| 70 */ | |
| 71 importer.ScanResult.prototype.getStatistics; | |
| 72 | |
| 73 /** | |
| 74 * @typedef {{ | |
| 75 * scanDuration: number, | |
| 76 * newFileCount: number, | |
| 77 * duplicateFileCount: number, | |
| 78 * sizeBytes: number | |
| 79 * }} | |
| 80 */ | |
| 81 importer.ScanResult.Statistics; | |
| 82 | |
| 83 /** | |
| 84 * Recursively scans through a list of given files and directories, and creates | 36 * Recursively scans through a list of given files and directories, and creates |
| 85 * a list of media files. | 37 * a list of media files. |
| 86 * | 38 * |
| 87 * @constructor | 39 * @constructor |
| 88 * @struct | 40 * @struct |
| 89 * @implements {importer.MediaScanner} | 41 * @implements {importer.MediaScanner} |
| 90 * | 42 * |
| 91 * @param {function(!FileEntry): !Promise.<string>} hashGenerator | 43 * @param {function(!FileEntry): !Promise.<string>} hashGenerator |
| 92 * @param {!importer.HistoryLoader} historyLoader | 44 * @param {function(!FileEntry, !importer.Destination): |
| 45 * !Promise<!importer.Disposition>} dispositionChecker |
| 93 * @param {!importer.DirectoryWatcherFactory} watcherFactory | 46 * @param {!importer.DirectoryWatcherFactory} watcherFactory |
| 94 */ | 47 */ |
| 95 importer.DefaultMediaScanner = function( | 48 importer.DefaultMediaScanner = |
| 96 hashGenerator, historyLoader, watcherFactory) { | 49 function(hashGenerator, dispositionChecker, watcherFactory) { |
| 97 | |
| 98 /** @private {!importer.HistoryLoader} */ | |
| 99 this.historyLoader_ = historyLoader; | |
| 100 | 50 |
| 101 /** | 51 /** |
| 102 * A little factory for DefaultScanResults which allows us to forgo | 52 * A little factory for DefaultScanResults which allows us to forgo |
| 103 * the saving it's dependencies in our fields. | 53 * the saving it's dependencies in our fields. |
| 104 * @return {!importer.DefaultScanResult} | 54 * @return {!importer.DefaultScanResult} |
| 105 */ | 55 */ |
| 106 this.createScanResult_ = function() { | 56 this.createScanResult_ = function() { |
| 107 return new importer.DefaultScanResult(hashGenerator); | 57 return new importer.DefaultScanResult(hashGenerator); |
| 108 }; | 58 }; |
| 109 | 59 |
| 110 /** @private {!Array.<!importer.ScanObserver>} */ | 60 /** @private {!Array.<!importer.ScanObserver>} */ |
| 111 this.observers_ = []; | 61 this.observers_ = []; |
| 112 | 62 |
| 113 /** | 63 /** |
| 64 * @param {!FileEntry} entry |
| 65 * @param {!importer.Destination} destination |
| 66 * @return {!Promise<!importer.Disposition>} |
| 67 */ |
| 68 this.getDisposition_ = dispositionChecker; |
| 69 |
| 70 /** |
| 114 * @private {!importer.DirectoryWatcherFactory} | 71 * @private {!importer.DirectoryWatcherFactory} |
| 115 * @const | 72 * @const |
| 116 */ | 73 */ |
| 117 this.watcherFactory_ = watcherFactory; | 74 this.watcherFactory_ = watcherFactory; |
| 118 }; | 75 }; |
| 119 | 76 |
| 120 /** @override */ | 77 /** @override */ |
| 121 importer.DefaultMediaScanner.prototype.addObserver = function(observer) { | 78 importer.DefaultMediaScanner.prototype.addObserver = function(observer) { |
| 122 this.observers_.push(observer); | 79 this.observers_.push(observer); |
| 123 }; | 80 }; |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 251 /** | 208 /** |
| 252 * Finds all files beneath directory. | 209 * Finds all files beneath directory. |
| 253 * | 210 * |
| 254 * @param {!importer.DefaultScanResult} scan | 211 * @param {!importer.DefaultScanResult} scan |
| 255 * @param {!FileEntry} entry | 212 * @param {!FileEntry} entry |
| 256 * @return {!Promise} | 213 * @return {!Promise} |
| 257 * @private | 214 * @private |
| 258 */ | 215 */ |
| 259 importer.DefaultMediaScanner.prototype.onFileEntryFound_ = | 216 importer.DefaultMediaScanner.prototype.onFileEntryFound_ = |
| 260 function(scan, entry) { | 217 function(scan, entry) { |
| 261 return this.hasHistoryDuplicate_(entry) | 218 return this.getDisposition_(entry, importer.Destination.GOOGLE_DRIVE) |
| 262 .then( | 219 .then( |
| 263 /** | 220 /** |
| 264 * @param {boolean} duplicate | 221 * @param {!importer.Disposition} disposition The disposition |
| 222 * of the entry. Either some sort of dupe, or an original. |
| 265 * @return {!Promise} | 223 * @return {!Promise} |
| 266 * @this {importer.DefaultMediaScanner} | 224 * @this {importer.DefaultMediaScanner} |
| 267 */ | 225 */ |
| 268 function(duplicate) { | 226 function(disposition) { |
| 269 return duplicate ? | 227 return disposition === importer.Disposition.ORIGINAL ? |
| 270 this.onDuplicateFileFound_(scan, entry) : | 228 this.onUniqueFileFound_(scan, entry) : |
| 271 this.onUniqueFileFound_(scan, entry); | 229 this.onDuplicateFileFound_(scan, entry, disposition); |
| 272 }.bind(this)); | 230 }.bind(this)); |
| 273 }; | 231 }; |
| 274 | 232 |
| 275 /** | 233 /** |
| 276 * Adds a newly discovered file to the given scan result. | 234 * Adds a newly discovered file to the given scan result. |
| 277 * | 235 * |
| 278 * @param {!importer.DefaultScanResult} scan | 236 * @param {!importer.DefaultScanResult} scan |
| 279 * @param {!FileEntry} entry | 237 * @param {!FileEntry} entry |
| 280 * @return {!Promise} | 238 * @return {!Promise} |
| 281 * @private | 239 * @private |
| (...skipping 17 matching lines...) Expand all Loading... |
| 299 } | 257 } |
| 300 }.bind(this)); | 258 }.bind(this)); |
| 301 }; | 259 }; |
| 302 | 260 |
| 303 /** | 261 /** |
| 304 * Adds a duplicate file to the given scan result. This is to track the number | 262 * Adds a duplicate file to the given scan result. This is to track the number |
| 305 * of duplicates that are being encountered. | 263 * of duplicates that are being encountered. |
| 306 * | 264 * |
| 307 * @param {!importer.DefaultScanResult} scan | 265 * @param {!importer.DefaultScanResult} scan |
| 308 * @param {!FileEntry} entry | 266 * @param {!FileEntry} entry |
| 267 * @param {!importer.Disposition} disposition |
| 309 * @return {!Promise} | 268 * @return {!Promise} |
| 310 * @private | 269 * @private |
| 311 */ | 270 */ |
| 312 importer.DefaultMediaScanner.prototype.onDuplicateFileFound_ = | 271 importer.DefaultMediaScanner.prototype.onDuplicateFileFound_ = |
| 313 function(scan, entry) { | 272 function(scan, entry, disposition) { |
| 314 scan.addDuplicateEntry(entry); | 273 scan.addDuplicateEntry(entry, disposition); |
| 315 return Promise.resolve(); | 274 return Promise.resolve(); |
| 316 }; | 275 }; |
| 317 | 276 |
| 318 /** | 277 /** |
| 319 * @param {!FileEntry} entry | 278 * Class representing the results of a scan operation. |
| 320 * @return {!Promise.<boolean>} True if there is a history-entry-duplicate | 279 * |
| 321 * for the file. | 280 * @interface |
| 322 * @private | |
| 323 */ | 281 */ |
| 324 importer.DefaultMediaScanner.prototype.hasHistoryDuplicate_ = function(entry) { | 282 importer.ScanResult = function() {}; |
| 325 return this.historyLoader_.getHistory() | 283 |
| 326 .then( | 284 /** |
| 327 /** | 285 * @return {boolean} true if scanning is complete. |
| 328 * @param {!importer.ImportHistory} history | 286 */ |
| 329 * @return {!Promise} | 287 importer.ScanResult.prototype.isFinal; |
| 330 * @this {importer.DefaultMediaScanner} | 288 |
| 331 */ | 289 /** |
| 332 function(history) { | 290 * @return {boolean} true if scanning is invalidated. |
| 333 return Promise.all([ | 291 */ |
| 334 history.wasCopied(entry, importer.Destination.GOOGLE_DRIVE), | 292 importer.ScanResult.prototype.isInvalidated; |
| 335 history.wasImported(entry, importer.Destination.GOOGLE_DRIVE) | 293 |
| 336 ]).then( | 294 /** |
| 337 /** | 295 * Returns all files entries discovered so far. The list will be |
| 338 * @param {!Array.<boolean>} results | 296 * complete only after scanning has completed and {@code isFinal} |
| 339 * @return {!Promise} | 297 * returns {@code true}. |
| 340 * @this {importer.DefaultMediaScanner} | 298 * |
| 341 */ | 299 * @return {!Array<!FileEntry>} |
| 342 function(results) { | 300 */ |
| 343 return results[0] || results[1]; | 301 importer.ScanResult.prototype.getFileEntries; |
| 344 }.bind(this)); | 302 |
| 345 }.bind(this)); | 303 /** |
| 346 }; | 304 * Returns a promise that fires when scanning is complete. |
| 305 * |
| 306 * @return {!Promise<!importer.ScanResult>} |
| 307 */ |
| 308 importer.ScanResult.prototype.whenFinal; |
| 309 |
| 310 /** |
| 311 * @return {!importer.ScanResult.Statistics} |
| 312 */ |
| 313 importer.ScanResult.prototype.getStatistics; |
| 314 |
| 315 /** |
| 316 * @typedef {{ |
| 317 * scanDuration: number, |
| 318 * newFileCount: number, |
| 319 * duplicates: !Object<!importer.Disposition, number>, |
| 320 * sizeBytes: number |
| 321 * }} |
| 322 */ |
| 323 importer.ScanResult.Statistics; |
| 347 | 324 |
| 348 /** | 325 /** |
| 349 * Results of a scan operation. The object is "live" in that data can and | 326 * Results of a scan operation. The object is "live" in that data can and |
| 350 * will change as the scan operation discovers files. | 327 * will change as the scan operation discovers files. |
| 351 * | 328 * |
| 352 * <p>The scan is complete, and the object will become static once the | 329 * <p>The scan is complete, and the object will become static once the |
| 353 * {@code whenFinal} promise resolves. | 330 * {@code whenFinal} promise resolves. |
| 354 * | 331 * |
| 355 * @constructor | 332 * @constructor |
| 356 * @struct | 333 * @struct |
| (...skipping 17 matching lines...) Expand all Loading... |
| 374 * Hashcodes of all files included captured by this result object so-far. | 351 * Hashcodes of all files included captured by this result object so-far. |
| 375 * Used to dedupe newly discovered files against other files withing | 352 * Used to dedupe newly discovered files against other files withing |
| 376 * the ScanResult. | 353 * the ScanResult. |
| 377 * @private {!Object.<string, !FileEntry>} | 354 * @private {!Object.<string, !FileEntry>} |
| 378 */ | 355 */ |
| 379 this.fileHashcodes_ = {}; | 356 this.fileHashcodes_ = {}; |
| 380 | 357 |
| 381 /** @private {number} */ | 358 /** @private {number} */ |
| 382 this.totalBytes_ = 0; | 359 this.totalBytes_ = 0; |
| 383 | 360 |
| 384 /** @private {number} */ | 361 /** @private {!Object<!importer.Disposition, number>} */ |
| 385 this.duplicateFileCount_ = 0; | 362 this.duplicates_ = {}; |
| 386 | 363 |
| 387 /** | 364 /** |
| 388 * The point in time when the scan was started. | 365 * The point in time when the scan was started. |
| 389 * @type {Date} | 366 * @type {Date} |
| 390 */ | 367 */ |
| 391 this.scanStarted_ = new Date(); | 368 this.scanStarted_ = new Date(); |
| 392 | 369 |
| 393 /** | 370 /** |
| 394 * The point in time when the last scan activity occured. | 371 * The point in time when the last scan activity occured. |
| 395 * @type {Date} | 372 * @type {Date} |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 462 return this.createHashcode_(entry) | 439 return this.createHashcode_(entry) |
| 463 .then( | 440 .then( |
| 464 /** | 441 /** |
| 465 * @param {string} hashcode | 442 * @param {string} hashcode |
| 466 * @this {importer.DefaultScanResult} | 443 * @this {importer.DefaultScanResult} |
| 467 */ | 444 */ |
| 468 function(hashcode) { | 445 function(hashcode) { |
| 469 this.lastScanActivity_ = new Date(); | 446 this.lastScanActivity_ = new Date(); |
| 470 | 447 |
| 471 if (hashcode in this.fileHashcodes_) { | 448 if (hashcode in this.fileHashcodes_) { |
| 472 this.addDuplicateEntry(entry); | 449 this.addDuplicateEntry( |
| 450 entry, |
| 451 importer.Disposition.SCAN_DUPLICATE); |
| 473 return false; | 452 return false; |
| 474 } | 453 } |
| 475 | 454 |
| 476 entry.size = metadata.size; | 455 entry.size = metadata.size; |
| 477 this.totalBytes_ += metadata.size; | 456 this.totalBytes_ += metadata.size; |
| 478 this.fileHashcodes_[hashcode] = entry; | 457 this.fileHashcodes_[hashcode] = entry; |
| 479 this.fileEntries_.push(entry); | 458 this.fileEntries_.push(entry); |
| 480 return true; | 459 return true; |
| 481 }.bind(this)); | 460 }.bind(this)); |
| 482 | 461 |
| 483 }.bind(this)); | 462 }.bind(this)); |
| 484 }; | 463 }; |
| 485 | 464 |
| 486 /** | 465 /** |
| 487 * Logs the fact that a duplicate file entry was discovered during the scan. | 466 * Logs the fact that a duplicate file entry was discovered during the scan. |
| 488 * @param {!FileEntry} entry | 467 * @param {!FileEntry} entry |
| 468 * @param {!importer.Disposition} disposition |
| 489 */ | 469 */ |
| 490 importer.DefaultScanResult.prototype.addDuplicateEntry = function(entry) { | 470 importer.DefaultScanResult.prototype.addDuplicateEntry = |
| 491 this.duplicateFileCount_++; | 471 function(entry, disposition) { |
| 472 if (!(disposition in this.duplicates_)) { |
| 473 this.duplicates_[disposition] = 0; |
| 474 } |
| 475 this.duplicates_[disposition]++; |
| 492 }; | 476 }; |
| 493 | 477 |
| 494 /** @override */ | 478 /** @override */ |
| 495 importer.DefaultScanResult.prototype.getStatistics = function() { | 479 importer.DefaultScanResult.prototype.getStatistics = function() { |
| 496 return { | 480 return { |
| 497 scanDuration: | 481 scanDuration: |
| 498 this.lastScanActivity_.getTime() - this.scanStarted_.getTime(), | 482 this.lastScanActivity_.getTime() - this.scanStarted_.getTime(), |
| 499 newFileCount: this.fileEntries_.length, | 483 newFileCount: this.fileEntries_.length, |
| 500 duplicateFileCount: this.duplicateFileCount_, | 484 duplicates: this.duplicates_, |
| 501 sizeBytes: this.totalBytes_ | 485 sizeBytes: this.totalBytes_ |
| 502 }; | 486 }; |
| 503 }; | 487 }; |
| 504 | 488 |
| 505 /** | 489 /** |
| 506 * Watcher for directories. | 490 * Watcher for directories. |
| 507 * @interface | 491 * @interface |
| 508 */ | 492 */ |
| 509 importer.DirectoryWatcher = function() {}; | 493 importer.DirectoryWatcher = function() {}; |
| 510 | 494 |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 572 if (!this.watchedDirectories_[event.entry.toURL()]) | 556 if (!this.watchedDirectories_[event.entry.toURL()]) |
| 573 return; | 557 return; |
| 574 this.triggered = true; | 558 this.triggered = true; |
| 575 for (var url in this.watchedDirectories_) { | 559 for (var url in this.watchedDirectories_) { |
| 576 chrome.fileManagerPrivate.removeFileWatch(url, function() {}); | 560 chrome.fileManagerPrivate.removeFileWatch(url, function() {}); |
| 577 } | 561 } |
| 578 chrome.fileManagerPrivate.onDirectoryChanged.removeListener( | 562 chrome.fileManagerPrivate.onDirectoryChanged.removeListener( |
| 579 assert(this.listener_)); | 563 assert(this.listener_)); |
| 580 this.callback_(); | 564 this.callback_(); |
| 581 }; | 565 }; |
| OLD | NEW |