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

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: Also, cancel scans when window closes...and add test coverage. 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.log(scan.name + ': Scanning directory ' + directory.fullPath);
mtomasz 2015/04/01 02:28:45 nit: Are these logs necessary? If not, let's remov
Steve McKay 2015/04/01 03:52:52 Useful. Changed to console.info and console.debug.
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.log(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.log(
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() {
151 this.notify_(importer.ScanEvent.FINALIZED, scan); 158 this.notify_(importer.ScanEvent.FINALIZED, scan);
152 }.bind(this)); 159 }.bind(this));
153 160
154 return scan; 161 return scan;
155 }; 162 };
156 163
157 /** @const {number} */ 164 /** @const {number} */
158 importer.DefaultMediaScanner.SCAN_BATCH_SIZE = 1; 165 importer.DefaultMediaScanner.SCAN_BATCH_SIZE = 1;
159 166
160 /** 167 /**
161 * @param {!importer.DefaultScanResult} scan 168 * @param {!importer.DefaultScanResult} scan
162 * @param {!Array<!FileEntry>} entries 169 * @param {!Array<!FileEntry>} entries
163 * @return {!Promise} Resolves when scanning is finished. 170 * @return {!Promise} Resolves when scanning is finished.
mtomasz 2015/04/01 02:28:45 nit: ... or cancelled. Shall we add?
Steve McKay 2015/04/01 03:52:52 Done.
164 * @private 171 * @private
165 */ 172 */
166 importer.DefaultMediaScanner.prototype.scanMediaFiles_ = 173 importer.DefaultMediaScanner.prototype.scanMediaFiles_ =
167 function(scan, entries) { 174 function(scan, entries) {
168 var handleFileEntry = this.onFileEntryFound_.bind(this, scan); 175 var handleFileEntry = this.onFileEntryFound_.bind(this, scan);
169 176
170 /** 177 /**
171 * @param {number} begin The beginning offset in the list of entries 178 * @param {number} begin The beginning offset in the list of entries
172 * to process. 179 * to process.
173 * @return {!Promise} 180 * @return {!Promise}
174 */ 181 */
175 var scanChunk = function(begin) { 182 var scanBatch = function(begin) {
183 if (scan.canceled()) {
184 console.log(
185 scan.name + ': Skipping remaining ' +
186 (entries.length - begin) +
187 ' entries. Scan was canceled.');
188 return Promise.resolve();
189 }
190
176 // the second arg to slice is an exclusive end index, so we +1 batch size. 191 // 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; 192 var end = begin + importer.DefaultMediaScanner.SCAN_BATCH_SIZE ;
193 console.log(scan.name + ': Processing batch ' + begin + '-' + (end - 1));
194 var batch = entries.slice(begin, end);
195
178 return Promise.all( 196 return Promise.all(
179 entries.slice(begin, end).map(handleFileEntry)) 197 batch.map(handleFileEntry))
180 .then( 198 .then(
199 /** @this {importer.DefaultMediaScanner} */
181 function() { 200 function() {
182 if (end < entries.length) { 201 if (end < entries.length) {
183 return scanChunk(end); 202 return scanBatch(end);
184 } 203 }
185 }); 204 });
186 }; 205 };
187 206
188 return scanChunk(0); 207 return scanBatch(0);
189 }; 208 };
190 209
191 /** 210 /**
192 * Notifies all listeners at some point in the near future. 211 * Notifies all listeners at some point in the near future.
193 * 212 *
194 * @param {!importer.ScanEvent} event 213 * @param {!importer.ScanEvent} event
195 * @param {!importer.DefaultScanResult} result 214 * @param {!importer.DefaultScanResult} result
196 * @private 215 * @private
197 */ 216 */
198 importer.DefaultMediaScanner.prototype.notify_ = function(event, result) { 217 importer.DefaultMediaScanner.prototype.notify_ = function(event, result) {
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 * @interface 333 * @interface
315 */ 334 */
316 importer.ScanResult = function() {}; 335 importer.ScanResult = function() {};
317 336
318 /** 337 /**
319 * @return {boolean} true if scanning is complete. 338 * @return {boolean} true if scanning is complete.
320 */ 339 */
321 importer.ScanResult.prototype.isFinal; 340 importer.ScanResult.prototype.isFinal;
322 341
323 /** 342 /**
324 * @return {boolean} true if scanning is invalidated. 343 * Notifies the scan to stop working. Some in progress work
344 * may continue, but no new work will be undertaken.
325 */ 345 */
326 importer.ScanResult.prototype.isInvalidated; 346 importer.ScanResult.prototype.cancel;
347
348 /**
349 * @return {boolean} True if the scan has been canceled. Some
350 * work started prior to cancelation may still be ongoing.
351 */
352 importer.ScanResult.prototype.canceled;
327 353
328 /** 354 /**
329 * Returns all files entries discovered so far. The list will be 355 * Returns all files entries discovered so far. The list will be
330 * complete only after scanning has completed and {@code isFinal} 356 * complete only after scanning has completed and {@code isFinal}
331 * returns {@code true}. 357 * returns {@code true}.
332 * 358 *
333 * @return {!Array<!FileEntry>} 359 * @return {!Array<!FileEntry>}
334 */ 360 */
335 importer.ScanResult.prototype.getFileEntries; 361 importer.ScanResult.prototype.getFileEntries;
336 362
337 /** 363 /**
338 * Returns a promise that fires when scanning is complete. 364 * Returns a promise that fires when scanning is finished
365 * normally or has been canceled.
339 * 366 *
340 * @return {!Promise<!importer.ScanResult>} 367 * @return {!Promise<!importer.ScanResult>}
341 */ 368 */
342 importer.ScanResult.prototype.whenFinal; 369 importer.ScanResult.prototype.whenFinal;
343 370
344 /** 371 /**
345 * @return {!importer.ScanResult.Statistics} 372 * @return {!importer.ScanResult.Statistics}
346 */ 373 */
347 importer.ScanResult.prototype.getStatistics; 374 importer.ScanResult.prototype.getStatistics;
348 375
349 /** 376 /**
350 * @typedef {{ 377 * @typedef {{
351 * scanDuration: number, 378 * scanDuration: number,
352 * newFileCount: number, 379 * newFileCount: number,
353 * duplicates: !Object<!importer.Disposition, number>, 380 * duplicates: !Object<!importer.Disposition, number>,
354 * sizeBytes: number 381 * sizeBytes: number
355 * }} 382 * }}
356 */ 383 */
357 importer.ScanResult.Statistics; 384 importer.ScanResult.Statistics;
358 385
359 /** 386 /**
360 * Results of a scan operation. The object is "live" in that data can and 387 * Results of a scan operation. The object is "live" in that data can and
361 * will change as the scan operation discovers files. 388 * will change as the scan operation discovers files.
362 * 389 *
363 * <p>The scan is complete, and the object will become static once the 390 * <p>The scan is complete, and the object will become static once the
364 * {@code whenFinal} promise resolves. 391 * {@code whenFinal} promise resolves.
365 * 392 *
393 * Note that classes implementing this should provide a read-only
394 * {@code name} field.
395 *
366 * @constructor 396 * @constructor
367 * @struct 397 * @struct
368 * @implements {importer.ScanResult} 398 * @implements {importer.ScanResult}
369 * 399 *
370 * @param {function(!FileEntry): !Promise.<string>} hashGenerator Hash-code 400 * @param {function(!FileEntry): !Promise.<string>} hashGenerator Hash-code
371 * generator used to dedupe within the scan results itself. 401 * generator used to dedupe within the scan results itself.
372 */ 402 */
373 importer.DefaultScanResult = function(hashGenerator) { 403 importer.DefaultScanResult = function(hashGenerator) {
374 404
mtomasz 2015/04/01 02:28:45 nit: Remove \n.
Steve McKay 2015/04/01 03:52:52 Done.
405 /** @private {number} */
406 this.scanId_ = importer.generateId();
407
375 /** @private {function(!FileEntry): !Promise.<string>} */ 408 /** @private {function(!FileEntry): !Promise.<string>} */
376 this.createHashcode_ = hashGenerator; 409 this.createHashcode_ = hashGenerator;
377 410
378 /** 411 /**
379 * List of file entries found while scanning. 412 * List of file entries found while scanning.
380 * @private {!Array<!FileEntry>} 413 * @private {!Array<!FileEntry>}
381 */ 414 */
382 this.fileEntries_ = []; 415 this.fileEntries_ = [];
383 416
384 /** 417 /**
(...skipping 18 matching lines...) Expand all
403 436
404 /** 437 /**
405 * The point in time when the last scan activity occured. 438 * The point in time when the last scan activity occured.
406 * @type {Date} 439 * @type {Date}
407 */ 440 */
408 this.lastScanActivity_ = this.scanStarted_; 441 this.lastScanActivity_ = this.scanStarted_;
409 442
410 /** 443 /**
411 * @private {boolean} 444 * @private {boolean}
412 */ 445 */
413 this.invalidated_ = false; 446 this.canceled_ = false;
414 447
415 /** @private {!importer.Resolver.<!importer.ScanResult>} */ 448 /** @private {!importer.Resolver.<!importer.ScanResult>} */
416 this.resolver_ = new importer.Resolver(); 449 this.resolver_ = new importer.Resolver();
417 }; 450 };
418 451
419 /** @struct */ 452 /** @struct */
420 importer.DefaultScanResult.prototype = { 453 importer.DefaultScanResult.prototype = {
421 454
mtomasz 2015/04/01 02:28:45 nit: Remove \n.
Steve McKay 2015/04/01 03:52:52 Done.
455 /** @return {string} */
456 get name() { return 'ScanResult(' + this.scanId_ + ')' },
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