OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 'use strict'; | 5 'use strict'; |
6 | 6 |
7 // If directory files changes too often, don't rescan directory more than once | 7 // If directory files changes too often, don't rescan directory more than once |
8 // per specified interval | 8 // per specified interval |
9 var SIMULTANEOUS_RESCAN_INTERVAL = 1000; | 9 var SIMULTANEOUS_RESCAN_INTERVAL = 1000; |
10 // Used for operations that require almost instant rescan. | 10 // Used for operations that require almost instant rescan. |
(...skipping 14 matching lines...) Expand all Loading... | |
25 metadataCache, volumeManager) { | 25 metadataCache, volumeManager) { |
26 this.fileListSelection_ = singleSelection ? | 26 this.fileListSelection_ = singleSelection ? |
27 new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel(); | 27 new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel(); |
28 | 28 |
29 this.runningScan_ = null; | 29 this.runningScan_ = null; |
30 this.pendingScan_ = null; | 30 this.pendingScan_ = null; |
31 this.rescanTime_ = null; | 31 this.rescanTime_ = null; |
32 this.scanFailures_ = 0; | 32 this.scanFailures_ = 0; |
33 this.changeDirectorySequence_ = 0; | 33 this.changeDirectorySequence_ = 0; |
34 | 34 |
35 this.directoryChangeQueue_ = new AsyncUtil.Queue(); | |
36 | |
35 this.fileFilter_ = fileFilter; | 37 this.fileFilter_ = fileFilter; |
36 this.fileFilter_.addEventListener('changed', | 38 this.fileFilter_.addEventListener('changed', |
37 this.onFilterChanged_.bind(this)); | 39 this.onFilterChanged_.bind(this)); |
38 | 40 |
39 this.currentFileListContext_ = new FileListContext( | 41 this.currentFileListContext_ = new FileListContext( |
40 fileFilter, metadataCache); | 42 fileFilter, metadataCache); |
41 this.currentDirContents_ = | 43 this.currentDirContents_ = |
42 DirectoryContents.createForDirectory(this.currentFileListContext_, null); | 44 DirectoryContents.createForDirectory(this.currentFileListContext_, null); |
43 | 45 |
44 this.metadataCache_ = metadataCache; | 46 this.metadataCache_ = metadataCache; |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
271 * a single refresh. | 273 * a single refresh. |
272 * @param {number} delay Delay in ms after which the rescan will be performed. | 274 * @param {number} delay Delay in ms after which the rescan will be performed. |
273 */ | 275 */ |
274 DirectoryModel.prototype.scheduleRescan = function(delay) { | 276 DirectoryModel.prototype.scheduleRescan = function(delay) { |
275 if (this.rescanTime_) { | 277 if (this.rescanTime_) { |
276 if (this.rescanTime_ <= Date.now() + delay) | 278 if (this.rescanTime_ <= Date.now() + delay) |
277 return; | 279 return; |
278 clearTimeout(this.rescanTimeoutId_); | 280 clearTimeout(this.rescanTimeoutId_); |
279 } | 281 } |
280 | 282 |
283 var sequence = this.changeDirectorySequence_; | |
284 | |
281 this.rescanTime_ = Date.now() + delay; | 285 this.rescanTime_ = Date.now() + delay; |
282 this.rescanTimeoutId_ = setTimeout(this.rescan.bind(this), delay); | 286 this.rescanTimeoutId_ = setTimeout(function() { |
287 this.rescanTimeoutId_ = null; | |
288 tracker.stop(); | |
hirono
2014/06/02 11:52:07
Remaining line.
yoshiki
2014/06/04 13:46:41
Done.
| |
289 if (sequence !== this.changeDirectorySequence_) | |
290 this.rescan(); | |
291 }.bind(this), delay); | |
283 }; | 292 }; |
284 | 293 |
285 /** | 294 /** |
286 * Cancel a rescan on timeout if it is scheduled. | 295 * Cancel a rescan on timeout if it is scheduled. |
287 * @private | 296 * @private |
288 */ | 297 */ |
289 DirectoryModel.prototype.clearRescanTimeout_ = function() { | 298 DirectoryModel.prototype.clearRescanTimeout_ = function() { |
290 this.rescanTime_ = null; | 299 this.rescanTime_ = null; |
291 if (this.rescanTimeoutId_) { | 300 if (this.rescanTimeoutId_) { |
292 clearTimeout(this.rescanTimeoutId_); | 301 clearTimeout(this.rescanTimeoutId_); |
(...skipping 12 matching lines...) Expand all Loading... | |
305 DirectoryModel.prototype.rescan = function() { | 314 DirectoryModel.prototype.rescan = function() { |
306 this.clearRescanTimeout_(); | 315 this.clearRescanTimeout_(); |
307 if (this.runningScan_) { | 316 if (this.runningScan_) { |
308 this.pendingRescan_ = true; | 317 this.pendingRescan_ = true; |
309 return; | 318 return; |
310 } | 319 } |
311 | 320 |
312 var dirContents = this.currentDirContents_.clone(); | 321 var dirContents = this.currentDirContents_.clone(); |
313 dirContents.setFileList([]); | 322 dirContents.setFileList([]); |
314 | 323 |
324 var sequence = this.changeDirectorySequence_; | |
325 | |
315 var successCallback = (function() { | 326 var successCallback = (function() { |
316 this.replaceDirectoryContents_(dirContents); | 327 if (sequence !== this.changeDirectorySequence_) |
328 this.replaceDirectoryContents_(dirContents); | |
317 cr.dispatchSimpleEvent(this, 'rescan-completed'); | 329 cr.dispatchSimpleEvent(this, 'rescan-completed'); |
318 }).bind(this); | 330 }).bind(this); |
319 | 331 |
320 this.scan_(dirContents, | 332 this.scan_(dirContents, |
321 successCallback, function() {}, function() {}, function() {}); | 333 successCallback, function() {}, function() {}, function() {}); |
322 }; | 334 }; |
323 | 335 |
324 /** | 336 /** |
325 * Run scan on the current DirectoryContents. The active fileList is cleared and | 337 * Run scan on the current DirectoryContents. The active fileList is cleared and |
326 * the entries are added directly. | 338 * the entries are added directly. |
327 * | 339 * |
328 * This should be used when changing directory or initiating a new search. | 340 * This should be used when changing directory or initiating a new search. |
329 * | 341 * |
330 * @param {DirectoryContentes} newDirContents New DirectoryContents instance to | 342 * @param {DirectoryContentes} newDirContents New DirectoryContents instance to |
331 * replace currentDirContents_. | 343 * replace currentDirContents_. |
332 * @param {function()=} opt_callback Called on success. | 344 * @param {function(boolean)} callback Callback with result. True if the scan |
345 * is completed successfully, false if the scan is failed. | |
333 * @private | 346 * @private |
334 */ | 347 */ |
335 DirectoryModel.prototype.clearAndScan_ = function(newDirContents, | 348 DirectoryModel.prototype.clearAndScan_ = function(newDirContents, |
336 opt_callback) { | 349 callback) { |
337 if (this.currentDirContents_.isScanning()) | 350 if (this.currentDirContents_.isScanning()) |
338 this.currentDirContents_.cancelScan(); | 351 this.currentDirContents_.cancelScan(); |
339 this.currentDirContents_ = newDirContents; | 352 this.currentDirContents_ = newDirContents; |
340 this.clearRescanTimeout_(); | 353 this.clearRescanTimeout_(); |
341 | 354 |
342 if (this.pendingScan_) | 355 if (this.pendingScan_) |
343 this.pendingScan_ = false; | 356 this.pendingScan_ = false; |
344 | 357 |
345 if (this.runningScan_) { | 358 if (this.runningScan_) { |
346 if (this.runningScan_.isScanning()) | 359 if (this.runningScan_.isScanning()) |
347 this.runningScan_.cancelScan(); | 360 this.runningScan_.cancelScan(); |
348 this.runningScan_ = null; | 361 this.runningScan_ = null; |
349 } | 362 } |
350 | 363 |
364 var sequence = this.changeDirectorySequence_; | |
365 var cancelled = false; | |
366 | |
351 var onDone = function() { | 367 var onDone = function() { |
368 if (cancelled) | |
369 return; | |
370 | |
352 cr.dispatchSimpleEvent(this, 'scan-completed'); | 371 cr.dispatchSimpleEvent(this, 'scan-completed'); |
353 if (opt_callback) | 372 callback(true); |
354 opt_callback(); | |
355 }.bind(this); | 373 }.bind(this); |
356 | 374 |
357 var onFailed = function() { | 375 var onFailed = function() { |
376 if (cancelled) | |
377 return; | |
378 | |
358 cr.dispatchSimpleEvent(this, 'scan-failed'); | 379 cr.dispatchSimpleEvent(this, 'scan-failed'); |
380 callback(false); | |
359 }.bind(this); | 381 }.bind(this); |
360 | 382 |
361 var onUpdated = function() { | 383 var onUpdated = function() { |
384 if (cancelled) | |
385 return; | |
386 | |
387 if (this.changeDirectorySequence_ !== sequence) { | |
388 cancelled = true; | |
389 cr.dispatchSimpleEvent(this, 'scan-cancelled'); | |
390 callback(false); | |
391 return; | |
392 } | |
393 | |
362 cr.dispatchSimpleEvent(this, 'scan-updated'); | 394 cr.dispatchSimpleEvent(this, 'scan-updated'); |
363 }.bind(this); | 395 }.bind(this); |
364 | 396 |
365 var onCancelled = function() { | 397 var onCancelled = function() { |
398 if (cancelled) | |
399 return; | |
400 | |
401 cancelled = true; | |
366 cr.dispatchSimpleEvent(this, 'scan-cancelled'); | 402 cr.dispatchSimpleEvent(this, 'scan-cancelled'); |
403 callback(false); | |
367 }.bind(this); | 404 }.bind(this); |
368 | 405 |
369 // Clear the table, and start scanning. | 406 // Clear the table, and start scanning. |
370 cr.dispatchSimpleEvent(this, 'scan-started'); | 407 cr.dispatchSimpleEvent(this, 'scan-started'); |
371 var fileList = this.getFileList(); | 408 var fileList = this.getFileList(); |
372 fileList.splice(0, fileList.length); | 409 fileList.splice(0, fileList.length); |
373 this.scan_(this.currentDirContents_, | 410 this.scan_(this.currentDirContents_, |
374 onDone, onFailed, onUpdated, onCancelled); | 411 onDone, onFailed, onUpdated, onCancelled); |
375 }; | 412 }; |
376 | 413 |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
595 errorCallback, | 632 errorCallback, |
596 abortCallback) { | 633 abortCallback) { |
597 // Obtain and check the current directory. | 634 // Obtain and check the current directory. |
598 var entry = this.getCurrentDirEntry(); | 635 var entry = this.getCurrentDirEntry(); |
599 if (!entry || this.isSearching()) { | 636 if (!entry || this.isSearching()) { |
600 errorCallback(util.createDOMError( | 637 errorCallback(util.createDOMError( |
601 util.FileError.INVALID_MODIFICATION_ERR)); | 638 util.FileError.INVALID_MODIFICATION_ERR)); |
602 return; | 639 return; |
603 } | 640 } |
604 | 641 |
605 var tracker = this.createDirectoryChangeTracker(); | 642 var sequence = this.changeDirectorySequence_; |
606 tracker.start(); | |
607 | 643 |
608 new Promise(entry.getDirectory.bind( | 644 new Promise(entry.getDirectory.bind( |
609 entry, name, {create: true, exclusive: true})). | 645 entry, name, {create: true, exclusive: true})). |
610 | 646 |
611 then(function(newEntry) { | 647 then(function(newEntry) { |
612 // Refresh the cache. | 648 // Refresh the cache. |
613 this.metadataCache_.clear([newEntry], '*'); | 649 this.metadataCache_.clear([newEntry], '*'); |
614 return new Promise(function(onFulfilled, onRejected) { | 650 return new Promise(function(onFulfilled, onRejected) { |
615 this.metadataCache_.get([newEntry], | 651 this.metadataCache_.get([newEntry], |
616 'filesystem', | 652 'filesystem', |
617 onFulfilled.bind(null, newEntry)); | 653 onFulfilled.bind(null, newEntry)); |
618 }.bind(this)); | 654 }.bind(this)); |
619 }.bind(this)). | 655 }.bind(this)). |
620 | 656 |
621 then(function(newEntry) { | 657 then(function(newEntry) { |
622 // Do not change anything or call the callback if current | 658 // Do not change anything or call the callback if current |
623 // directory changed. | 659 // directory changed. |
624 tracker.stop(); | 660 if (this.changeDirectorySequence_ !== sequence) { |
625 if (tracker.hasChanged) { | |
626 abortCallback(); | 661 abortCallback(); |
627 return; | 662 return; |
628 } | 663 } |
629 | 664 |
630 // If target directory is already in the list, just select it. | 665 // If target directory is already in the list, just select it. |
631 var existing = this.getFileList().slice().filter( | 666 var existing = this.getFileList().slice().filter( |
632 function(e) { return e.name === name; }); | 667 function(e) { return e.name === name; }); |
633 if (existing.length) { | 668 if (existing.length) { |
634 this.selectEntry(newEntry); | 669 this.selectEntry(newEntry); |
635 successCallback(existing[0]); | 670 successCallback(existing[0]); |
636 } else { | 671 } else { |
637 this.fileListSelection_.beginChange(); | 672 this.fileListSelection_.beginChange(); |
638 this.getFileList().splice(0, 0, newEntry); | 673 this.getFileList().splice(0, 0, newEntry); |
639 this.selectEntry(newEntry); | 674 this.selectEntry(newEntry); |
640 this.fileListSelection_.endChange(); | 675 this.fileListSelection_.endChange(); |
641 successCallback(newEntry); | 676 successCallback(newEntry); |
642 } | 677 } |
643 }.bind(this), function(reason) { | 678 }.bind(this), function(reason) { |
644 tracker.stop(); | 679 tracker.stop(); |
hirono
2014/06/02 11:52:07
ditto.
yoshiki
2014/06/04 13:46:41
Done.
| |
645 errorCallback(reason); | 680 errorCallback(reason); |
646 }); | 681 }); |
647 }; | 682 }; |
648 | 683 |
649 /** | 684 /** |
650 * Changes the current directory to the directory represented by | 685 * Changes the current directory to the directory represented by |
651 * a DirectoryEntry or a fake entry. | 686 * a DirectoryEntry or a fake entry. |
652 * | 687 * |
653 * Dispatches the 'directory-changed' event when the directory is successfully | 688 * Dispatches the 'directory-changed' event when the directory is successfully |
654 * changed. | 689 * changed. |
655 * | 690 * |
656 * Note : if this is called from UI, please consider to use DirectoryModel. | 691 * Note : if this is called from UI, please consider to use DirectoryModel. |
657 * activateDirectoryEntry instead of this, which is higher-level function and | 692 * activateDirectoryEntry instead of this, which is higher-level function and |
658 * cares about the selection. | 693 * cares about the selection. |
659 * | 694 * |
660 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to | 695 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to |
661 * be opened. | 696 * be opened. |
662 * @param {function()=} opt_callback Executed if the directory loads | 697 * @param {function()=} opt_callback Executed if the directory loads |
663 * successfully. | 698 * successfully. |
664 */ | 699 */ |
665 DirectoryModel.prototype.changeDirectoryEntry = function( | 700 DirectoryModel.prototype.changeDirectoryEntry = function( |
666 dirEntry, opt_callback) { | 701 dirEntry, opt_callback) { |
667 // Increment the sequence value. | 702 // Increment the sequence value. |
668 this.changeDirectorySequence_++; | 703 this.changeDirectorySequence_++; |
669 this.clearSearch_(); | 704 this.clearSearch_(); |
670 | 705 |
671 var promise = new Promise( | 706 this.directoryChangeQueue_.run(function(sequence, queueTaskCallback) { |
672 function(onFulfilled, onRejected) { | 707 this.fileWatcher_.changeWatchedDirectory( |
673 this.fileWatcher_.changeWatchedDirectory(dirEntry, onFulfilled); | 708 dirEntry, |
674 }.bind(this)). | 709 function() { |
675 | 710 if (this.changeDirectorySequence_ !== sequence) { |
676 then(function(sequence) { | 711 callback(); |
677 return new Promise(function(onFulfilled, onRejected) { | |
678 if (this.changeDirectorySequence_ !== sequence) | |
679 return; | 712 return; |
713 } | |
680 | 714 |
681 var newDirectoryContents = this.createDirectoryContents_( | 715 var newDirectoryContents = this.createDirectoryContents_( |
682 this.currentFileListContext_, dirEntry, ''); | 716 this.currentFileListContext_, dirEntry, ''); |
683 if (!newDirectoryContents) | 717 if (!newDirectoryContents) { |
718 callback(); | |
684 return; | 719 return; |
720 } | |
685 | 721 |
686 var previousDirEntry = this.currentDirContents_.getDirectoryEntry(); | 722 var previousDirEntry = |
687 this.clearAndScan_(newDirectoryContents, opt_callback); | 723 this.currentDirContents_.getDirectoryEntry(); |
724 this.clearAndScan_( | |
725 newDirectoryContents, | |
726 function(result) { | |
727 // Calls the callback of the method when successful. | |
728 if (result && opt_callback) | |
729 opt_callback(); | |
688 | 730 |
689 // For tests that open the dialog to empty directories, everything is | 731 // Notify that the current task of this.directoryChangeQueue_ |
690 // loaded at this point. | 732 // is completed. |
733 setTimeout(queueTaskCallback); | |
734 }); | |
735 | |
736 // For tests that open the dialog to empty directories, everything | |
737 // is loaded at this point. | |
691 util.testSendMessage('directory-change-complete'); | 738 util.testSendMessage('directory-change-complete'); |
692 | 739 |
693 var event = new Event('directory-changed'); | 740 var event = new Event('directory-changed'); |
694 event.previousDirEntry = previousDirEntry; | 741 event.previousDirEntry = previousDirEntry; |
695 event.newDirEntry = dirEntry; | 742 event.newDirEntry = dirEntry; |
696 this.dispatchEvent(event); | 743 this.dispatchEvent(event); |
697 }.bind(this)); | 744 }.bind(this)); |
698 }.bind(this, this.changeDirectorySequence_)); | 745 }.bind(this, this.changeDirectorySequence_)); |
699 }; | 746 }; |
700 | 747 |
701 /** | 748 /** |
702 * Activates the given directry. | 749 * Activates the given directry. |
703 * This method: | 750 * This method: |
704 * - Changes the current directory, if the given directory is the current | 751 * - Changes the current directory, if the given directory is the current |
705 * directory. | 752 * directory. |
706 * - Clears the selection, if the given directory is the current directory. | 753 * - Clears the selection, if the given directory is the current directory. |
707 * | 754 * |
708 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to | 755 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
896 DirectoryModel.prototype.search = function(query, | 943 DirectoryModel.prototype.search = function(query, |
897 onSearchRescan, | 944 onSearchRescan, |
898 onClearSearch) { | 945 onClearSearch) { |
899 this.clearSearch_(); | 946 this.clearSearch_(); |
900 var currentDirEntry = this.getCurrentDirEntry(); | 947 var currentDirEntry = this.getCurrentDirEntry(); |
901 if (!currentDirEntry) { | 948 if (!currentDirEntry) { |
902 // Not yet initialized. Do nothing. | 949 // Not yet initialized. Do nothing. |
903 return; | 950 return; |
904 } | 951 } |
905 | 952 |
906 if (!(query || '').trimLeft()) { | 953 this.changeDirectorySequence_++; |
907 if (this.isSearching()) { | 954 this.directoryChangeQueue_.run(function(sequence, callback) { |
908 var newDirContents = this.createDirectoryContents_( | 955 if (this.changeDirectorySequence_ !== sequence) { |
909 this.currentFileListContext_, | 956 callback(); |
910 currentDirEntry); | 957 return; |
911 this.clearAndScan_(newDirContents); | |
912 } | 958 } |
913 return; | |
914 } | |
915 | 959 |
916 var newDirContents = this.createDirectoryContents_( | 960 if (!(query || '').trimLeft()) { |
917 this.currentFileListContext_, currentDirEntry, query); | 961 if (this.isSearching()) { |
918 if (!newDirContents) | 962 var newDirContents = this.createDirectoryContents_( |
919 return; | 963 this.currentFileListContext_, |
964 currentDirEntry); | |
965 this.clearAndScan_(newDirContents, | |
966 sequence, | |
967 callback); | |
968 } else { | |
969 callback(); | |
970 } | |
971 return; | |
972 } | |
920 | 973 |
921 this.onSearchCompleted_ = onSearchRescan; | 974 var newDirContents = this.createDirectoryContents_( |
922 this.onClearSearch_ = onClearSearch; | 975 this.currentFileListContext_, currentDirEntry, query); |
923 this.addEventListener('scan-completed', this.onSearchCompleted_); | 976 if (!newDirContents) { |
924 this.clearAndScan_(newDirContents); | 977 callback(); |
978 return; | |
979 } | |
980 | |
981 this.onSearchCompleted_ = onSearchRescan; | |
982 this.onClearSearch_ = onClearSearch; | |
983 this.addEventListener('scan-completed', this.onSearchCompleted_); | |
984 this.clearAndScan_(newDirContents, | |
985 sequence, | |
986 callback); | |
987 }.bind(this, this.changeDirectorySequence_)); | |
925 }; | 988 }; |
926 | 989 |
927 /** | 990 /** |
928 * In case the search was active, remove listeners and send notifications on | 991 * In case the search was active, remove listeners and send notifications on |
929 * its canceling. | 992 * its canceling. |
930 * @private | 993 * @private |
931 */ | 994 */ |
932 DirectoryModel.prototype.clearSearch_ = function() { | 995 DirectoryModel.prototype.clearSearch_ = function() { |
933 if (!this.isSearching()) | 996 if (!this.isSearching()) |
934 return; | 997 return; |
935 | 998 |
936 if (this.onSearchCompleted_) { | 999 if (this.onSearchCompleted_) { |
937 this.removeEventListener('scan-completed', this.onSearchCompleted_); | 1000 this.removeEventListener('scan-completed', this.onSearchCompleted_); |
938 this.onSearchCompleted_ = null; | 1001 this.onSearchCompleted_ = null; |
939 } | 1002 } |
940 | 1003 |
941 if (this.onClearSearch_) { | 1004 if (this.onClearSearch_) { |
942 this.onClearSearch_(); | 1005 this.onClearSearch_(); |
943 this.onClearSearch_ = null; | 1006 this.onClearSearch_ = null; |
944 } | 1007 } |
945 }; | 1008 }; |
OLD | NEW |