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 |