| 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 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 66 }; | 66 }; |
| 67 | 67 |
| 68 /** | 68 /** |
| 69 * @return {cr.ui.ArrayDataModel} Files in the current directory. | 69 * @return {cr.ui.ArrayDataModel} Files in the current directory. |
| 70 */ | 70 */ |
| 71 DirectoryModel.prototype.getFileList = function() { | 71 DirectoryModel.prototype.getFileList = function() { |
| 72 return this.currentFileListContext_.fileList; | 72 return this.currentFileListContext_.fileList; |
| 73 }; | 73 }; |
| 74 | 74 |
| 75 /** | 75 /** |
| 76 * Sort the file list. | |
| 77 * @param {string} sortField Sort field. | |
| 78 * @param {string} sortDirection "asc" or "desc". | |
| 79 */ | |
| 80 DirectoryModel.prototype.sortFileList = function(sortField, sortDirection) { | |
| 81 this.getFileList().sort(sortField, sortDirection); | |
| 82 }; | |
| 83 | |
| 84 /** | |
| 85 * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection | 76 * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection |
| 86 * in the fileList. | 77 * in the fileList. |
| 87 */ | 78 */ |
| 88 DirectoryModel.prototype.getFileListSelection = function() { | 79 DirectoryModel.prototype.getFileListSelection = function() { |
| 89 return this.fileListSelection_; | 80 return this.fileListSelection_; |
| 90 }; | 81 }; |
| 91 | 82 |
| 92 /** | 83 /** |
| 93 * @return {?RootType} Root type of current root, or null if not found. | 84 * @return {?RootType} Root type of current root, or null if not found. |
| 94 */ | 85 */ |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 188 }; | 179 }; |
| 189 | 180 |
| 190 /** | 181 /** |
| 191 * @return {DirectoryEntry} Current directory. | 182 * @return {DirectoryEntry} Current directory. |
| 192 */ | 183 */ |
| 193 DirectoryModel.prototype.getCurrentDirEntry = function() { | 184 DirectoryModel.prototype.getCurrentDirEntry = function() { |
| 194 return this.currentDirContents_.getDirectoryEntry(); | 185 return this.currentDirContents_.getDirectoryEntry(); |
| 195 }; | 186 }; |
| 196 | 187 |
| 197 /** | 188 /** |
| 198 * @return {Array.<Entry>} Array of selected entries.. | 189 * @return {Array.<Entry>} Array of selected entries. |
| 199 * @private | 190 * @private |
| 200 */ | 191 */ |
| 201 DirectoryModel.prototype.getSelectedEntries_ = function() { | 192 DirectoryModel.prototype.getSelectedEntries_ = function() { |
| 202 var indexes = this.fileListSelection_.selectedIndexes; | 193 var indexes = this.fileListSelection_.selectedIndexes; |
| 203 var fileList = this.getFileList(); | 194 var fileList = this.getFileList(); |
| 204 if (fileList) { | 195 if (fileList) { |
| 205 return indexes.map(function(i) { | 196 return indexes.map(function(i) { |
| 206 return fileList.item(i); | 197 return fileList.item(i); |
| 207 }); | 198 }); |
| 208 } | 199 } |
| (...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 621 this.fileListSelection_.endChange(); | 612 this.fileListSelection_.endChange(); |
| 622 successCallback(newEntry); | 613 successCallback(newEntry); |
| 623 } | 614 } |
| 624 }.bind(this), function(reason) { | 615 }.bind(this), function(reason) { |
| 625 tracker.stop(); | 616 tracker.stop(); |
| 626 errorCallback(reason); | 617 errorCallback(reason); |
| 627 }); | 618 }); |
| 628 }; | 619 }; |
| 629 | 620 |
| 630 /** | 621 /** |
| 631 * @param {DirectoryEntry} dirEntry The entry of the new directory. | |
| 632 * @param {function()=} opt_callback Executed if the directory loads | |
| 633 * successfully. | |
| 634 * @private | |
| 635 */ | |
| 636 DirectoryModel.prototype.changeDirectoryEntrySilent_ = function(dirEntry, | |
| 637 opt_callback) { | |
| 638 var onScanComplete = function() { | |
| 639 if (opt_callback) | |
| 640 opt_callback(); | |
| 641 // For tests that open the dialog to empty directories, everything | |
| 642 // is loaded at this point. | |
| 643 chrome.test.sendMessage('directory-change-complete'); | |
| 644 }; | |
| 645 this.clearAndScan_( | |
| 646 DirectoryContents.createForDirectory(this.currentFileListContext_, | |
| 647 dirEntry), | |
| 648 onScanComplete.bind(this)); | |
| 649 }; | |
| 650 | |
| 651 /** | |
| 652 * Change the current directory to the directory represented by | 622 * Change the current directory to the directory represented by |
| 653 * a DirectoryEntry or a fake entry. | 623 * a DirectoryEntry or a fake entry. |
| 654 * | 624 * |
| 655 * Dispatches the 'directory-changed' event when the directory is successfully | 625 * Dispatches the 'directory-changed' event when the directory is successfully |
| 656 * changed. | 626 * changed. |
| 657 * | 627 * |
| 658 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to | 628 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to |
| 659 * be opened. | 629 * be opened. |
| 660 * @param {function()=} opt_callback Executed if the directory loads | 630 * @param {function()=} opt_callback Executed if the directory loads |
| 661 * successfully. | 631 * successfully. |
| 662 */ | 632 */ |
| 663 DirectoryModel.prototype.changeDirectoryEntry = function( | 633 DirectoryModel.prototype.changeDirectoryEntry = function( |
| 664 dirEntry, opt_callback) { | 634 dirEntry, opt_callback) { |
| 665 if (util.isFakeEntry(dirEntry)) { | |
| 666 this.specialSearch(dirEntry); | |
| 667 if (opt_callback) | |
| 668 opt_callback(); | |
| 669 return; | |
| 670 } | |
| 671 | |
| 672 // Increment the sequence value. | 635 // Increment the sequence value. |
| 673 this.changeDirectorySequence_++; | 636 this.changeDirectorySequence_++; |
| 637 this.clearSearch_(); |
| 674 | 638 |
| 675 this.fileWatcher_.changeWatchedDirectory(dirEntry, function(sequence) { | 639 var promise = new Promise( |
| 676 if (this.changeDirectorySequence_ !== sequence) | 640 function(onFulfilled, onRejected) { |
| 677 return; | 641 this.fileWatcher_.changeWatchedDirectory(dirEntry, onFulfilled); |
| 678 var previous = this.currentDirContents_.getDirectoryEntry(); | 642 }.bind(this)). |
| 679 this.clearSearch_(); | |
| 680 this.changeDirectoryEntrySilent_(dirEntry, opt_callback); | |
| 681 | 643 |
| 682 var e = new Event('directory-changed'); | 644 then(function(sequence) { |
| 683 e.previousDirEntry = previous; | 645 return new Promise(function(onFulfilled, onRejected) { |
| 684 e.newDirEntry = dirEntry; | 646 if (this.changeDirectorySequence_ !== sequence) |
| 685 this.dispatchEvent(e); | 647 return; |
| 686 }.bind(this, this.changeDirectorySequence_)); | 648 |
| 649 var newDirectoryContents = this.createDirectoryContents_( |
| 650 this.currentFileListContext_, dirEntry, ''); |
| 651 if (!newDirectoryContents) |
| 652 return; |
| 653 |
| 654 var previousDirEntry = this.currentDirContents_.getDirectoryEntry(); |
| 655 this.clearAndScan_(newDirectoryContents, opt_callback); |
| 656 |
| 657 // For tests that open the dialog to empty directories, everything is |
| 658 // loaded at this point. |
| 659 chrome.test.sendMessage('directory-change-complete'); |
| 660 |
| 661 var event = new Event('directory-changed'); |
| 662 event.previousDirEntry = previousDirEntry; |
| 663 event.newDirEntry = dirEntry; |
| 664 this.dispatchEvent(event); |
| 665 }.bind(this)); |
| 666 }.bind(this, this.changeDirectorySequence_)); |
| 687 }; | 667 }; |
| 688 | 668 |
| 689 /** | 669 /** |
| 690 * Creates an object which could say whether directory has changed while it has | 670 * Creates an object which could say whether directory has changed while it has |
| 691 * been active or not. Designed for long operations that should be cancelled | 671 * been active or not. Designed for long operations that should be cancelled |
| 692 * if the used change current directory. | 672 * if the used change current directory. |
| 693 * @return {Object} Created object. | 673 * @return {Object} Created object. |
| 694 */ | 674 */ |
| 695 DirectoryModel.prototype.createDirectoryChangeTracker = function() { | 675 DirectoryModel.prototype.createDirectoryChangeTracker = function() { |
| 696 var tracker = { | 676 var tracker = { |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 758 DirectoryModel.prototype.selectIndex = function(index) { | 738 DirectoryModel.prototype.selectIndex = function(index) { |
| 759 // this.focusCurrentList_(); | 739 // this.focusCurrentList_(); |
| 760 if (index >= this.getFileList().length) | 740 if (index >= this.getFileList().length) |
| 761 return; | 741 return; |
| 762 | 742 |
| 763 // If a list bound with the model it will do scrollIndexIntoView(index). | 743 // If a list bound with the model it will do scrollIndexIntoView(index). |
| 764 this.fileListSelection_.selectedIndex = index; | 744 this.fileListSelection_.selectedIndex = index; |
| 765 }; | 745 }; |
| 766 | 746 |
| 767 /** | 747 /** |
| 768 * Called when VolumeInfoList is updated. | 748 * Handles update of VolumeInfoList. |
| 769 * @param {Event} event Event of VolumeInfoList's 'splice'. | 749 * @param {Event} event Event of VolumeInfoList's 'splice'. |
| 770 * @private | 750 * @private |
| 771 */ | 751 */ |
| 772 DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) { | 752 DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) { |
| 773 // When the volume where we are is unmounted, fallback to the default volume's | 753 // When the volume where we are is unmounted, fallback to the default volume's |
| 774 // root. If current directory path is empty, stop the fallback | 754 // root. If current directory path is empty, stop the fallback |
| 775 // since the current directory is initializing now. | 755 // since the current directory is initializing now. |
| 776 if (this.getCurrentDirEntry() && | 756 if (this.getCurrentDirEntry() && |
| 777 !this.volumeManager_.getVolumeInfo(this.getCurrentDirEntry())) { | 757 !this.volumeManager_.getVolumeInfo(this.getCurrentDirEntry())) { |
| 778 this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) { | 758 this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) { |
| 779 this.changeDirectoryEntry(displayRoot); | 759 this.changeDirectoryEntry(displayRoot); |
| 780 }.bind(this)); | 760 }.bind(this)); |
| 781 } | 761 } |
| 782 }; | 762 }; |
| 783 | 763 |
| 784 /** | 764 /** |
| 765 * Creates directory contents for the entry and query. |
| 766 * |
| 767 * @param {FileListContext} context File list context. |
| 768 * @param {DirectoryEntry} entry Current directory. |
| 769 * @param {string=} opt_query Search query string. |
| 770 * @return {DirectoryContents} Directory contents. |
| 771 * @private |
| 772 */ |
| 773 DirectoryModel.prototype.createDirectoryContents_ = |
| 774 function(context, entry, opt_query) { |
| 775 var query = (opt_query || '').trimLeft(); |
| 776 var locationInfo = this.volumeManager_.getLocationInfo(entry); |
| 777 if (!locationInfo) |
| 778 return null; |
| 779 var canUseDriveSearch = this.volumeManager_.getDriveConnectionState().type !== |
| 780 util.DriveConnectionType.OFFLINE && |
| 781 locationInfo.isDriveBased; |
| 782 |
| 783 if (query && canUseDriveSearch) { |
| 784 // Drive search. |
| 785 return DirectoryContents.createForDriveSearch(context, entry, query); |
| 786 } else if (query) { |
| 787 // Local search. |
| 788 return DirectoryContents.createForLocalSearch(context, entry, query); |
| 789 } if (locationInfo.isSpecialSearchRoot) { |
| 790 // Drive special search. |
| 791 var searchType; |
| 792 switch (locationInfo.rootType) { |
| 793 case RootType.DRIVE_OFFLINE: |
| 794 searchType = |
| 795 DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE; |
| 796 break; |
| 797 case RootType.DRIVE_SHARED_WITH_ME: |
| 798 searchType = |
| 799 DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME; |
| 800 break; |
| 801 case RootType.DRIVE_RECENT: |
| 802 searchType = |
| 803 DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES; |
| 804 break; |
| 805 default: |
| 806 // Unknown special search entry. |
| 807 throw new Error('Unknown special search type.'); |
| 808 } |
| 809 return DirectoryContents.createForDriveMetadataSearch( |
| 810 context, |
| 811 entry, |
| 812 searchType); |
| 813 } else { |
| 814 // Local fetch or search. |
| 815 return DirectoryContents.createForDirectory(context, entry); |
| 816 } |
| 817 }; |
| 818 |
| 819 /** |
| 785 * Performs search and displays results. The search type is dependent on the | 820 * Performs search and displays results. The search type is dependent on the |
| 786 * current directory. If we are currently on drive, server side content search | 821 * current directory. If we are currently on drive, server side content search |
| 787 * over drive mount point. If the current directory is not on the drive, file | 822 * over drive mount point. If the current directory is not on the drive, file |
| 788 * name search over current directory will be performed. | 823 * name search over current directory will be performed. |
| 789 * | 824 * |
| 790 * @param {string} query Query that will be searched for. | 825 * @param {string} query Query that will be searched for. |
| 791 * @param {function(Event)} onSearchRescan Function that will be called when the | 826 * @param {function(Event)} onSearchRescan Function that will be called when the |
| 792 * search directory is rescanned (i.e. search results are displayed). | 827 * search directory is rescanned (i.e. search results are displayed). |
| 793 * @param {function()} onClearSearch Function to be called when search state | 828 * @param {function()} onClearSearch Function to be called when search state |
| 794 * gets cleared. | 829 * gets cleared. |
| 795 * TODO(olege): Change callbacks to events. | 830 * TODO(olege): Change callbacks to events. |
| 796 */ | 831 */ |
| 797 DirectoryModel.prototype.search = function(query, | 832 DirectoryModel.prototype.search = function(query, |
| 798 onSearchRescan, | 833 onSearchRescan, |
| 799 onClearSearch) { | 834 onClearSearch) { |
| 800 query = query.trimLeft(); | |
| 801 | |
| 802 this.clearSearch_(); | 835 this.clearSearch_(); |
| 803 | |
| 804 var currentDirEntry = this.getCurrentDirEntry(); | 836 var currentDirEntry = this.getCurrentDirEntry(); |
| 805 if (!currentDirEntry) { | 837 if (!currentDirEntry) { |
| 806 // Not yet initialized. Do nothing. | 838 // Not yet initialized. Do nothing. |
| 807 return; | 839 return; |
| 808 } | 840 } |
| 809 | 841 |
| 810 if (!query) { | 842 if (!(query || '').trimLeft()) { |
| 811 if (this.isSearching()) { | 843 if (this.isSearching()) { |
| 812 var newDirContents = DirectoryContents.createForDirectory( | 844 var newDirContents = DirectoryContents.createForDirectory( |
| 813 this.currentFileListContext_, | 845 this.currentFileListContext_, |
| 814 this.currentDirContents_.getLastNonSearchDirectoryEntry()); | 846 currentDirEntry); |
| 815 this.clearAndScan_(newDirContents); | 847 this.clearAndScan_(newDirContents); |
| 816 } | 848 } |
| 817 return; | 849 return; |
| 818 } | 850 } |
| 819 | 851 |
| 852 var newDirContents = this.createDirectoryContents_( |
| 853 this.currentFileListContext_, currentDirEntry, query); |
| 854 if (!newDirContents) |
| 855 return; |
| 856 |
| 820 this.onSearchCompleted_ = onSearchRescan; | 857 this.onSearchCompleted_ = onSearchRescan; |
| 821 this.onClearSearch_ = onClearSearch; | 858 this.onClearSearch_ = onClearSearch; |
| 822 | |
| 823 this.addEventListener('scan-completed', this.onSearchCompleted_); | 859 this.addEventListener('scan-completed', this.onSearchCompleted_); |
| 824 | |
| 825 // If we are offline, let's fallback to file name search inside dir. | |
| 826 // A search initiated from directories in Drive or special search results | |
| 827 // should trigger Drive search. | |
| 828 var newDirContents; | |
| 829 var isDriveOffline = this.volumeManager_.getDriveConnectionState().type === | |
| 830 util.DriveConnectionType.OFFLINE; | |
| 831 var locationInfo = this.volumeManager_.getLocationInfo(currentDirEntry); | |
| 832 if (!isDriveOffline && locationInfo && locationInfo.isDriveBased) { | |
| 833 // Drive search is performed over the whole drive, so pass drive root as | |
| 834 // |directoryEntry|. | |
| 835 newDirContents = DirectoryContents.createForDriveSearch( | |
| 836 this.currentFileListContext_, | |
| 837 currentDirEntry, | |
| 838 this.currentDirContents_.getLastNonSearchDirectoryEntry(), | |
| 839 query); | |
| 840 } else { | |
| 841 newDirContents = DirectoryContents.createForLocalSearch( | |
| 842 this.currentFileListContext_, currentDirEntry, query); | |
| 843 } | |
| 844 this.clearAndScan_(newDirContents); | 860 this.clearAndScan_(newDirContents); |
| 845 }; | 861 }; |
| 846 | 862 |
| 847 /** | 863 /** |
| 848 * Performs special search and displays results. e.g. Drive files available | |
| 849 * offline, shared-with-me files, recently modified files. | |
| 850 * @param {Object} fakeEntry Fake entry representing a special search. | |
| 851 * @param {string=} opt_query Query string used for the search. | |
| 852 */ | |
| 853 DirectoryModel.prototype.specialSearch = function(fakeEntry, opt_query) { | |
| 854 var query = opt_query || ''; | |
| 855 | |
| 856 // Increment the sequence value. | |
| 857 this.changeDirectorySequence_++; | |
| 858 | |
| 859 this.clearSearch_(); | |
| 860 this.onSearchCompleted_ = null; | |
| 861 this.onClearSearch_ = null; | |
| 862 | |
| 863 // Obtains a volume information. | |
| 864 // TODO(hirono): Obtain the proper profile's volume information. | |
| 865 var volumeInfo = this.volumeManager_.getCurrentProfileVolumeInfo( | |
| 866 util.VolumeType.DRIVE); | |
| 867 if (!volumeInfo) { | |
| 868 // It seems that the volume is already unmounted or drive is disable. | |
| 869 return; | |
| 870 } | |
| 871 | |
| 872 var onDriveDirectoryResolved = function(sequence, driveRoot) { | |
| 873 if (this.changeDirectorySequence_ !== sequence) | |
| 874 return; | |
| 875 | |
| 876 var locationInfo = this.volumeManager_.getLocationInfo(fakeEntry); | |
| 877 if (!locationInfo) | |
| 878 return; | |
| 879 | |
| 880 var searchOption; | |
| 881 switch (locationInfo.rootType) { | |
| 882 case RootType.DRIVE_OFFLINE: | |
| 883 searchOption = | |
| 884 DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE; | |
| 885 break; | |
| 886 case RootType.DRIVE_SHARED_WITH_ME: | |
| 887 searchOption = | |
| 888 DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME; | |
| 889 break; | |
| 890 case RootType.DRIVE_RECENT: | |
| 891 searchOption = | |
| 892 DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES; | |
| 893 break; | |
| 894 default: | |
| 895 // Unknown special search entry. | |
| 896 throw new Error('Unknown special search type.'); | |
| 897 } | |
| 898 | |
| 899 var newDirContents = DirectoryContents.createForDriveMetadataSearch( | |
| 900 this.currentFileListContext_, | |
| 901 fakeEntry, driveRoot, query, searchOption); | |
| 902 var previous = this.currentDirContents_.getDirectoryEntry(); | |
| 903 this.clearAndScan_(newDirContents); | |
| 904 | |
| 905 var e = new Event('directory-changed'); | |
| 906 e.previousDirEntry = previous; | |
| 907 e.newDirEntry = fakeEntry; | |
| 908 this.dispatchEvent(e); | |
| 909 }.bind(this, this.changeDirectorySequence_); | |
| 910 | |
| 911 volumeInfo.resolveDisplayRoot( | |
| 912 onDriveDirectoryResolved /* success */, function() {} /* failed */); | |
| 913 }; | |
| 914 | |
| 915 /** | |
| 916 * In case the search was active, remove listeners and send notifications on | 864 * In case the search was active, remove listeners and send notifications on |
| 917 * its canceling. | 865 * its canceling. |
| 918 * @private | 866 * @private |
| 919 */ | 867 */ |
| 920 DirectoryModel.prototype.clearSearch_ = function() { | 868 DirectoryModel.prototype.clearSearch_ = function() { |
| 921 if (!this.isSearching()) | 869 if (!this.isSearching()) |
| 922 return; | 870 return; |
| 923 | 871 |
| 924 if (this.onSearchCompleted_) { | 872 if (this.onSearchCompleted_) { |
| 925 this.removeEventListener('scan-completed', this.onSearchCompleted_); | 873 this.removeEventListener('scan-completed', this.onSearchCompleted_); |
| 926 this.onSearchCompleted_ = null; | 874 this.onSearchCompleted_ = null; |
| 927 } | 875 } |
| 928 | 876 |
| 929 if (this.onClearSearch_) { | 877 if (this.onClearSearch_) { |
| 930 this.onClearSearch_(); | 878 this.onClearSearch_(); |
| 931 this.onClearSearch_ = null; | 879 this.onClearSearch_ = null; |
| 932 } | 880 } |
| 933 }; | 881 }; |
| OLD | NEW |