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

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

Issue 1045663002: Cancel scans when directory changed. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Respond to review comments. Created 5 years, 8 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 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698