Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Side by Side Diff: ui/file_manager/file_manager/background/js/media_scanner.js

Issue 980603003: Move content deduplication into the scan process. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Respond to review comments. Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698