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 // If directory files changes too often, don't rescan directory more than once | 5 // If directory files changes too often, don't rescan directory more than once |
6 // per specified interval | 6 // per specified interval |
7 var SIMULTANEOUS_RESCAN_INTERVAL = 1000; | 7 var SIMULTANEOUS_RESCAN_INTERVAL = 1000; |
8 // Used for operations that require almost instant rescan. | 8 // Used for operations that require almost instant rescan. |
9 var SHORT_RESCAN_INTERVAL = 100; | 9 var SHORT_RESCAN_INTERVAL = 100; |
10 | 10 |
11 /** | 11 /** |
12 * Data model of the file manager. | 12 * Data model of the file manager. |
13 * | 13 * |
14 * @constructor | 14 * @constructor |
15 * @param {DirectoryEntry} root File system root. | 15 * @param {DirectoryEntry} root File system root. |
16 * @param {boolean} singleSelection True if only one file could be selected | 16 * @param {boolean} singleSelection True if only one file could be selected |
17 * at the time. | 17 * at the time. |
18 * @param {MetadataCache} metadataCache The metadata cache service. | 18 * @param {MetadataCache} metadataCache The metadata cache service. |
| 19 * @param {VolumeManager} volumeManager The volume manager. |
| 20 * @param {boolean} isGDataEnabled True if GDATA enabled (initial value). |
19 */ | 21 */ |
20 function DirectoryModel(root, singleSelection, metadataCache) { | 22 function DirectoryModel(root, singleSelection, |
| 23 metadataCache, volumeManager, isGDataEnabled) { |
21 this.root_ = root; | 24 this.root_ = root; |
22 this.metadataCache_ = metadataCache; | 25 this.metadataCache_ = metadataCache; |
23 this.fileList_ = new cr.ui.ArrayDataModel([]); | 26 this.fileList_ = new cr.ui.ArrayDataModel([]); |
24 this.fileListSelection_ = singleSelection ? | 27 this.fileListSelection_ = singleSelection ? |
25 new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel(); | 28 new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel(); |
26 | 29 |
27 this.runningScan_ = null; | 30 this.runningScan_ = null; |
28 this.pendingScan_ = null; | 31 this.pendingScan_ = null; |
29 this.rescanTimeout_ = undefined; | 32 this.rescanTimeout_ = undefined; |
30 this.scanFailures_ = 0; | 33 this.scanFailures_ = 0; |
| 34 this.gDataEnabled_ = isGDataEnabled; |
31 | 35 |
32 // DirectoryEntry representing the current directory of the dialog. | 36 // DirectoryEntry representing the current directory of the dialog. |
33 this.currentDirEntry_ = root; | 37 this.currentDirEntry_ = root; |
34 | 38 |
35 this.fileList_.prepareSort = this.prepareSort_.bind(this); | 39 this.fileList_.prepareSort = this.prepareSort_.bind(this); |
36 | 40 |
37 this.rootsList_ = new cr.ui.ArrayDataModel([]); | 41 this.rootsList_ = new cr.ui.ArrayDataModel([]); |
38 this.rootsListSelection_ = new cr.ui.ListSingleSelectionModel(); | 42 this.rootsListSelection_ = new cr.ui.ListSingleSelectionModel(); |
39 this.rootsListSelection_.addEventListener( | 43 this.rootsListSelection_.addEventListener( |
40 'change', this.onRootChange_.bind(this)); | 44 'change', this.onRootChange_.bind(this)); |
41 | 45 |
42 /** | 46 /** |
43 * A map root.fullPath -> currentDirectory.fullPath. | 47 * A map root.fullPath -> currentDirectory.fullPath. |
44 * @private | 48 * @private |
45 * @type {Object.<string, string>} | 49 * @type {Object.<string, string>} |
46 */ | 50 */ |
47 this.currentDirByRoot_ = {}; | 51 this.currentDirByRoot_ = {}; |
48 | 52 |
49 // The map 'name' -> callback. Callbacks are function(entry) -> boolean. | 53 // The map 'name' -> callback. Callbacks are function(entry) -> boolean. |
50 this.filters_ = {}; | 54 this.filters_ = {}; |
51 this.setFilterHidden(true); | 55 this.setFilterHidden(true); |
52 | 56 |
53 /** | 57 this.volumeManager_ = volumeManager; |
54 * @private | |
55 * @type {Object.<string, boolean>} | |
56 */ | |
57 this.volumeReadOnlyStatus_ = {}; | |
58 | 58 |
59 /** | 59 /** |
60 * Directory in which search results are displayed. Not null iff search | 60 * Directory in which search results are displayed. Not null iff search |
61 * results are being displayed. | 61 * results are being displayed. |
62 * @private | 62 * @private |
63 * @type {Entry} | 63 * @type {Entry} |
64 */ | 64 */ |
65 this.searchDirEntry_ = null; | 65 this.searchDirEntry_ = null; |
66 | 66 |
67 /** | 67 /** |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
99 * The name of the downloads directory. | 99 * The name of the downloads directory. |
100 */ | 100 */ |
101 DirectoryModel.DOWNLOADS_DIRECTORY = 'Downloads'; | 101 DirectoryModel.DOWNLOADS_DIRECTORY = 'Downloads'; |
102 | 102 |
103 /** | 103 /** |
104 * The name of the gdata provider directory. | 104 * The name of the gdata provider directory. |
105 */ | 105 */ |
106 DirectoryModel.GDATA_DIRECTORY = 'drive'; | 106 DirectoryModel.GDATA_DIRECTORY = 'drive'; |
107 | 107 |
108 /** | 108 /** |
109 * GData access mode: disabled (no GData root displayed in the list). | 109 * Fake entry to be used in currentDirEntry_ when current directory is |
| 110 * unmounted GDATA. |
| 111 * @private |
110 */ | 112 */ |
111 DirectoryModel.GDATA_ACCESS_DISABLED = 0; | 113 DirectoryModel.fakeGDataEntry_ = { |
112 | 114 fullPath: '/' + DirectoryModel.GDATA_DIRECTORY |
113 /** | 115 }; |
114 * GData access mode: lazy (GData root displayed, no content is fetched yet). | |
115 */ | |
116 DirectoryModel.GDATA_ACCESS_LAZY = 1; | |
117 | |
118 /** | |
119 * GData access mode: full (GData root displayed, content is available). | |
120 */ | |
121 DirectoryModel.GDATA_ACCESS_FULL = 2; | |
122 | 116 |
123 /** | 117 /** |
124 * Root path used for displaying gdata content search results. | 118 * Root path used for displaying gdata content search results. |
125 * Search results will be shown in directory 'GDATA_SEARCH_ROOT_PATH/query'. | 119 * Search results will be shown in directory 'GDATA_SEARCH_ROOT_PATH/query'. |
126 * | 120 * |
127 * @const | 121 * @const |
128 * @type {string} | 122 * @type {string} |
129 */ | 123 */ |
130 DirectoryModel.GDATA_SEARCH_ROOT_PATH = '/drive/.search'; | 124 DirectoryModel.GDATA_SEARCH_ROOT_PATH = '/drive/.search'; |
131 | 125 |
132 /** | 126 /** |
133 * @const | 127 * @const |
134 * @type {Array.<string>} | 128 * @type {Array.<string>} |
135 */ | 129 */ |
136 DirectoryModel.GDATA_SEARCH_ROOT_COMPONENTS = ['', 'drive', '.search']; | 130 DirectoryModel.GDATA_SEARCH_ROOT_COMPONENTS = ['', 'drive', '.search']; |
137 | 131 |
138 /** | 132 /** |
139 * DirectoryModel extends cr.EventTarget. | 133 * DirectoryModel extends cr.EventTarget. |
140 */ | 134 */ |
141 DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype; | 135 DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype; |
142 | 136 |
143 /** | 137 /** |
| 138 * Fills the root list and starts tracking changes. |
| 139 */ |
| 140 DirectoryModel.prototype.start = function() { |
| 141 var volumesChangeHandler = this.onMountChanged_.bind(this); |
| 142 this.volumeManager_.addEventListener('change', volumesChangeHandler); |
| 143 this.updateRoots_(); |
| 144 }; |
| 145 |
| 146 /** |
144 * @return {cr.ui.ArrayDataModel} Files in the current directory. | 147 * @return {cr.ui.ArrayDataModel} Files in the current directory. |
145 */ | 148 */ |
146 DirectoryModel.prototype.getFileList = function() { | 149 DirectoryModel.prototype.getFileList = function() { |
147 return this.fileList_; | 150 return this.fileList_; |
148 }; | 151 }; |
149 | 152 |
150 /** | 153 /** |
151 * @return {MetadataCache} Metadata cache. | 154 * @return {MetadataCache} Metadata cache. |
152 */ | 155 */ |
153 DirectoryModel.prototype.getMetadataCache = function() { | 156 DirectoryModel.prototype.getMetadataCache = function() { |
154 return this.metadataCache_; | 157 return this.metadataCache_; |
155 }; | 158 }; |
156 | 159 |
157 /** | 160 /** |
| 161 * Sets whether GDATA appears in the roots list and |
| 162 * if it could be used as current directory. |
| 163 * @param {boolead} enabled True if GDATA enabled. |
| 164 */ |
| 165 DirectoryModel.prototype.setGDataEnabled = function(enabled) { |
| 166 if (this.gDataEnabled_ == enabled) |
| 167 return; |
| 168 this.gDataEnabled_ = enabled; |
| 169 this.updateRoots_(); |
| 170 if (!enabled && this.getCurrentRootType() == DirectoryModel.RootType.GDATA) |
| 171 this.changeDirectory(this.getDefaultDirectory()); |
| 172 }; |
| 173 |
| 174 /** |
158 * Sort the file list. | 175 * Sort the file list. |
159 * @param {string} sortField Sort field. | 176 * @param {string} sortField Sort field. |
160 * @param {string} sortDirection "asc" or "desc". | 177 * @param {string} sortDirection "asc" or "desc". |
161 */ | 178 */ |
162 DirectoryModel.prototype.sortFileList = function(sortField, sortDirection) { | 179 DirectoryModel.prototype.sortFileList = function(sortField, sortDirection) { |
163 this.fileList_.sort(sortField, sortDirection); | 180 this.fileList_.sort(sortField, sortDirection); |
164 }; | 181 }; |
165 | 182 |
166 /** | 183 /** |
167 * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection | 184 * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
220 return this.getSearchOrCurrentDirEntry() != this.getCurrentDirEntry(); | 237 return this.getSearchOrCurrentDirEntry() != this.getCurrentDirEntry(); |
221 }; | 238 }; |
222 | 239 |
223 /** | 240 /** |
224 * @param {strin} path Path to check. | 241 * @param {strin} path Path to check. |
225 * @return {boolean} True if the |path| is read only. | 242 * @return {boolean} True if the |path| is read only. |
226 */ | 243 */ |
227 DirectoryModel.prototype.isPathReadOnly = function(path) { | 244 DirectoryModel.prototype.isPathReadOnly = function(path) { |
228 switch (DirectoryModel.getRootType(path)) { | 245 switch (DirectoryModel.getRootType(path)) { |
229 case DirectoryModel.RootType.REMOVABLE: | 246 case DirectoryModel.RootType.REMOVABLE: |
230 return !!this.volumeReadOnlyStatus_[DirectoryModel.getRootPath(path)]; | 247 return !!this.volumeManager_.isReadOnly(DirectoryModel.getRootPath(path)); |
231 case DirectoryModel.RootType.ARCHIVE: | 248 case DirectoryModel.RootType.ARCHIVE: |
232 return true; | 249 return true; |
233 case DirectoryModel.RootType.DOWNLOADS: | 250 case DirectoryModel.RootType.DOWNLOADS: |
234 return false; | 251 return false; |
235 case DirectoryModel.RootType.GDATA: | 252 case DirectoryModel.RootType.GDATA: |
236 return this.isOffline(); | 253 return this.isOffline(); |
237 default: | 254 default: |
238 return true; | 255 return true; |
239 } | 256 } |
240 }; | 257 }; |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
362 | 379 |
363 /** | 380 /** |
364 * @return {string} Root path for the current directory (parent directory is | 381 * @return {string} Root path for the current directory (parent directory is |
365 * not navigatable for the user). | 382 * not navigatable for the user). |
366 */ | 383 */ |
367 DirectoryModel.prototype.getCurrentRootPath = function() { | 384 DirectoryModel.prototype.getCurrentRootPath = function() { |
368 return DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); | 385 return DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); |
369 }; | 386 }; |
370 | 387 |
371 /** | 388 /** |
| 389 * @return {DirectoryModel.RootType} A root type. |
| 390 */ |
| 391 DirectoryModel.prototype.getCurrentRootType = function() { |
| 392 return DirectoryModel.getRootType(this.currentDirEntry_.fullPath); |
| 393 }; |
| 394 |
| 395 /** |
372 * @return {cr.ui.ListSingleSelectionModel} Root list selection model. | 396 * @return {cr.ui.ListSingleSelectionModel} Root list selection model. |
373 */ | 397 */ |
374 DirectoryModel.prototype.getRootsListSelectionModel = function() { | 398 DirectoryModel.prototype.getRootsListSelectionModel = function() { |
375 return this.rootsListSelection_; | 399 return this.rootsListSelection_; |
376 }; | 400 }; |
377 | 401 |
378 /** | 402 /** |
379 * Add a filter for directory contents. | 403 * Add a filter for directory contents. |
380 * @param {string} name An identifier of the filter (used when removing it). | 404 * @param {string} name An identifier of the filter (used when removing it). |
381 * @param {function(Entry):boolean} filter Hide file if false. | 405 * @param {function(Entry):boolean} filter Hide file if false. |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
537 } | 561 } |
538 | 562 |
539 var onDone = function() { | 563 var onDone = function() { |
540 cr.dispatchSimpleEvent(this, 'scan-completed'); | 564 cr.dispatchSimpleEvent(this, 'scan-completed'); |
541 callback(); | 565 callback(); |
542 }.bind(this); | 566 }.bind(this); |
543 | 567 |
544 // Clear the table first. | 568 // Clear the table first. |
545 this.fileList_.splice(0, this.fileList_.length); | 569 this.fileList_.splice(0, this.fileList_.length); |
546 cr.dispatchSimpleEvent(this, 'scan-started'); | 570 cr.dispatchSimpleEvent(this, 'scan-started'); |
547 if (this.currentDirEntry_ == this.unmountedGDataEntry_) { | |
548 onDone(); | |
549 return; | |
550 } | |
551 this.runningScan_ = this.createScanner_(this.fileList_, onDone); | 571 this.runningScan_ = this.createScanner_(this.fileList_, onDone); |
552 this.runningScan_.run(); | 572 this.runningScan_.run(); |
553 }; | 573 }; |
554 | 574 |
555 /** | 575 /** |
556 * @private | 576 * @private |
557 * @param {Array.<Entry>} entries Files. | 577 * @param {Array.<Entry>} entries Files. |
558 * @param {function} callback Callback on done. | 578 * @param {function} callback Callback on done. |
559 */ | 579 */ |
560 DirectoryModel.prototype.prefetchCacheForSorting_ = function(entries, | 580 DirectoryModel.prototype.prefetchCacheForSorting_ = function(entries, |
(...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
792 }; | 812 }; |
793 | 813 |
794 /** | 814 /** |
795 * Resolves absolute directory path. Handles GData stub. | 815 * Resolves absolute directory path. Handles GData stub. |
796 * @param {string} path Path to the directory. | 816 * @param {string} path Path to the directory. |
797 * @param {function(DirectoryEntry} successCallback Success callback. | 817 * @param {function(DirectoryEntry} successCallback Success callback. |
798 * @param {function(FileError} errorCallback Error callback. | 818 * @param {function(FileError} errorCallback Error callback. |
799 */ | 819 */ |
800 DirectoryModel.prototype.resolveDirectory = function(path, successCallback, | 820 DirectoryModel.prototype.resolveDirectory = function(path, successCallback, |
801 errorCallback) { | 821 errorCallback) { |
802 if (this.unmountedGDataEntry_ && | 822 if (DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) { |
803 DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) { | 823 if (!this.isGDataMounted_()) { |
804 // TODO(kaznacheeev): Currently if path points to some GData subdirectory | 824 if (path == DirectoryModel.fakeGDataEntry_.fullPath) |
805 // and GData is not mounted we will change to the fake GData root and | 825 successCallback(DirectoryModel.fakeGDataEntry_); |
806 // ignore the rest of the path. Consider remembering the path and | 826 else // Subdirectory. |
807 // changing to it once GDdata is mounted. This is only relevant for cases | 827 errorCallback({ code: FileError.NOT_FOUND_ERR }); |
808 // when we open the File Manager with an URL pointing to GData (e.g. via | 828 return; |
809 // a bookmark). | 829 } |
810 successCallback(this.unmountedGDataEntry_); | |
811 return; | |
812 } | 830 } |
813 | 831 |
814 if (path == '/') { | 832 if (path == '/') { |
815 successCallback(this.root_); | 833 successCallback(this.root_); |
816 return; | 834 return; |
817 } | 835 } |
818 | 836 |
819 this.root_.getDirectory(path, {create: false}, | 837 this.root_.getDirectory(path, {create: false}, |
820 successCallback, errorCallback); | 838 successCallback, errorCallback); |
821 }; | 839 }; |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
859 * changed. | 877 * changed. |
860 * | 878 * |
861 * @private | 879 * @private |
862 * @param {boolean} initial True if it comes from setupPath and | 880 * @param {boolean} initial True if it comes from setupPath and |
863 * false if caused by an user action. | 881 * false if caused by an user action. |
864 * @param {DirectoryEntry} dirEntry The absolute path to the new directory. | 882 * @param {DirectoryEntry} dirEntry The absolute path to the new directory. |
865 * @param {function} opt_callback Executed if the directory loads successfully. | 883 * @param {function} opt_callback Executed if the directory loads successfully. |
866 */ | 884 */ |
867 DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry, | 885 DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry, |
868 opt_callback) { | 886 opt_callback) { |
| 887 if (dirEntry == DirectoryModel.fakeGDataEntry_) |
| 888 this.volumeManager_.mountGData(function() {}, function() {}); |
| 889 |
869 this.clearSearch_(); | 890 this.clearSearch_(); |
870 var previous = this.currentDirEntry_; | 891 var previous = this.currentDirEntry_; |
871 this.currentDirEntry_ = dirEntry; | 892 this.currentDirEntry_ = dirEntry; |
872 function onRescanComplete() { | 893 function onRescanComplete() { |
873 if (opt_callback) | 894 if (opt_callback) |
874 opt_callback(); | 895 opt_callback(); |
875 // For tests that open the dialog to empty directories, everything | 896 // For tests that open the dialog to empty directories, everything |
876 // is loaded at this point. | 897 // is loaded at this point. |
877 chrome.test.sendMessage('directory-change-complete'); | 898 chrome.test.sendMessage('directory-change-complete'); |
878 } | 899 } |
879 this.updateRootsListSelection_(); | 900 this.updateRootsListSelection_(); |
880 this.scan_(onRescanComplete); | 901 this.scan_(onRescanComplete); |
881 this.currentDirByRoot_[this.getCurrentRootPath()] = dirEntry.fullPath; | 902 this.currentDirByRoot_[this.getCurrentRootPath()] = dirEntry.fullPath; |
882 | 903 |
883 var e = new cr.Event('directory-changed'); | 904 var e = new cr.Event('directory-changed'); |
884 e.previousDirEntry = previous; | 905 e.previousDirEntry = previous; |
885 e.newDirEntry = dirEntry; | 906 e.newDirEntry = dirEntry; |
886 e.initial = initial; | 907 e.initial = initial; |
887 this.dispatchEvent(e); | 908 this.dispatchEvent(e); |
888 }; | 909 }; |
889 | 910 |
890 /** | 911 /** |
| 912 * Creates an object wich could say wether directory has changed while it has |
| 913 * been active or not. Designed for long operations that should be canncelled |
| 914 * if the used change current directory. |
| 915 * @return {Object} Created object. |
| 916 */ |
| 917 DirectoryModel.prototype.createDirectoryChangeTracker = function() { |
| 918 var tracker = { |
| 919 dm_: this, |
| 920 active_: false, |
| 921 hasChanged: false, |
| 922 exceptInitialChange: false, |
| 923 |
| 924 start: function() { |
| 925 if (!this.active_) { |
| 926 this.dm_.addEventListener('directory-changed', |
| 927 this.onDirectoryChange_); |
| 928 this.active_ = true; |
| 929 this.hasChanged = false; |
| 930 } |
| 931 }, |
| 932 |
| 933 stop: function() { |
| 934 if (this.active_) { |
| 935 this.dm_.removeEventListener('directory-changed', |
| 936 this.onDirectoryChange_); |
| 937 active_ = false; |
| 938 } |
| 939 }, |
| 940 |
| 941 onDirectoryChange_: function(event) { |
| 942 // this == tracker.dm_ here. |
| 943 if (tracker.exceptInitialChange && event.initial) |
| 944 return; |
| 945 tracker.stop(); |
| 946 tracker.hasChanged = true; |
| 947 } |
| 948 }; |
| 949 return tracker; |
| 950 }; |
| 951 |
| 952 /** |
891 * Change the state of the model to reflect the specified path (either a | 953 * Change the state of the model to reflect the specified path (either a |
892 * file or directory). | 954 * file or directory). |
893 * | 955 * |
894 * @param {string} path The root path to use | 956 * @param {string} path The root path to use |
895 * @param {function=} opt_loadedCallback Invoked when the entire directory | 957 * @param {function=} opt_loadedCallback Invoked when the entire directory |
896 * has been loaded and any default file selected. If there are any | 958 * has been loaded and any default file selected. If there are any |
897 * errors loading the directory this will not get called (even if the | 959 * errors loading the directory this will not get called (even if the |
898 * directory loads OK on retry later). Will NOT be called if another | 960 * directory loads OK on retry later). Will NOT be called if another |
899 * directory change happened while setupPath was in progress. | 961 * directory change happened while setupPath was in progress. |
900 * @param {function=} opt_pathResolveCallback Invoked as soon as the path has | 962 * @param {function=} opt_pathResolveCallback Invoked as soon as the path has |
901 * been resolved, and called with the base and leaf portions of the path | 963 * been resolved, and called with the base and leaf portions of the path |
902 * name, and a flag indicating if the entry exists. Will be called even | 964 * name, and a flag indicating if the entry exists. Will be called even |
903 * if another directory change happened while setupPath was in progress, | 965 * if another directory change happened while setupPath was in progress, |
904 * but will pass |false| as |exist| parameter. | 966 * but will pass |false| as |exist| parameter. |
905 */ | 967 */ |
906 DirectoryModel.prototype.setupPath = function(path, opt_loadedCallback, | 968 DirectoryModel.prototype.setupPath = function(path, opt_loadedCallback, |
907 opt_pathResolveCallback) { | 969 opt_pathResolveCallback) { |
908 var overridden = false; | 970 var tracker = this.createDirectoryChangeTracker(); |
909 function onExternalDirChange() { overridden = true } | 971 tracker.start(); |
910 this.addEventListener('directory-changed', onExternalDirChange); | |
911 | 972 |
912 var resolveCallback = function(exists) { | 973 var self = this; |
913 this.removeEventListener('directory-changed', onExternalDirChange); | 974 function resolveCallback(directoryPath, fileName, exists) { |
914 if (opt_pathResolveCallback) | 975 tracker.stop(); |
915 opt_pathResolveCallback(baseName, leafName, exists && !overridden); | 976 if (!opt_pathResolveCallback) |
916 }.bind(this); | 977 return; |
| 978 opt_pathResolveCallback(directoryPath, fileName, |
| 979 exists && !tracker.hasChanged); |
| 980 } |
917 | 981 |
918 var changeDirectoryEntry = function(entry, initial, exists, opt_callback) { | 982 function changeDirectoryEntry(directoryEntry, initial, opt_callback) { |
919 resolveCallback(exists); | 983 tracker.stop(); |
920 if (!overridden) | 984 if (!tracker.hasChanged) |
921 this.changeDirectoryEntry_(initial, entry, opt_callback); | 985 self.changeDirectoryEntry_(initial, directoryEntry, opt_callback); |
922 }.bind(this); | 986 } |
923 | 987 |
924 var INITIAL = true; | 988 var INITIAL = true; |
925 var EXISTS = true; | 989 var EXISTS = true; |
926 | 990 |
927 // Split the dirname from the basename. | 991 function changeToDefault() { |
928 var ary = path.match(/^(?:(.*)\/)?([^\/]*)$/); | 992 var def = self.getDefaultDirectory(); |
| 993 self.resolveDirectory(def, function(directoryEntry) { |
| 994 resolveCallback(def, '', !EXISTS); |
| 995 changeDirectoryEntry(directoryEntry, INITIAL); |
| 996 }, function(error) { |
| 997 console.error('Failed to resolve default directory: ' + def, error); |
| 998 resolveCallback('/', '', !EXISTS); |
| 999 }); |
| 1000 } |
929 | 1001 |
930 if (!ary) { | 1002 function noParentDirectory(error) { |
931 console.warn('Unable to split default path: ' + path); | 1003 console.log('Can\'t resolve parent directory: ' + path, error); |
932 changeDirectoryEntry(this.root_, INITIAL, !EXISTS); | 1004 changeToDefault(); |
| 1005 } |
| 1006 |
| 1007 if (DirectoryModel.isSystemDirectory(path)) { |
| 1008 changeToDefault(); |
933 return; | 1009 return; |
934 } | 1010 } |
935 | 1011 |
936 var baseName = ary[1]; | 1012 this.resolveDirectory(path, function(directoryEntry) { |
937 var leafName = ary[2]; | 1013 resolveCallback(directoryEntry.fullPath, '', !EXISTS); |
938 | 1014 changeDirectoryEntry(directoryEntry, INITIAL); |
939 function onLeafFound(baseDirEntry, leafEntry) { | 1015 }, function(error) { |
940 if (leafEntry.isDirectory) { | 1016 // Usually, leaf does not exist, because it's just a suggested file name. |
941 baseName = path; | 1017 var fileExists = error.code == FileError.TYPE_MISMATCH_ERR; |
942 leafName = ''; | 1018 if (fileExists || error.code == FileError.NOT_FOUND_ERR) { |
943 changeDirectoryEntry(leafEntry, INITIAL, EXISTS); | 1019 var nameDelimiter = path.lastIndexOf('/'); |
944 return; | 1020 var parentDirectoryPath = path.substr(0, nameDelimiter); |
| 1021 if (DirectoryModel.isSystemDirectory(parentDirectoryPath)) { |
| 1022 changeToDefault(); |
| 1023 return; |
| 1024 } |
| 1025 self.resolveDirectory(parentDirectoryPath, |
| 1026 function(parentDirectoryEntry) { |
| 1027 var fileName = path.substr(nameDelimiter + 1); |
| 1028 resolveCallback(parentDirectoryEntry.fullPath, fileName, fileExists); |
| 1029 changeDirectoryEntry(parentDirectoryEntry, |
| 1030 !INITIAL /*HACK*/, |
| 1031 function() { |
| 1032 self.selectEntry(fileName); |
| 1033 if (opt_loadedCallback) |
| 1034 opt_loadedCallback(); |
| 1035 }); |
| 1036 // TODO(kaznacheev): Fix history.replaceState for the File Browser and |
| 1037 // change !INITIAL to INITIAL. Passing |false| makes things |
| 1038 // less ugly for now. |
| 1039 }, noParentDirectory); |
| 1040 } else { |
| 1041 // Unexpected errors. |
| 1042 console.error('Directory resolving error: ', error); |
| 1043 changeToDefault(); |
945 } | 1044 } |
946 | 1045 }); |
947 // Leaf is an existing file, cd to its parent directory and select it. | |
948 changeDirectoryEntry(baseDirEntry, | |
949 !INITIAL /*HACK*/, | |
950 EXISTS, | |
951 function() { | |
952 this.selectEntry(leafEntry.name); | |
953 if (opt_loadedCallback) | |
954 opt_loadedCallback(); | |
955 }.bind(this)); | |
956 // TODO(kaznacheev): Fix history.replaceState for the File Browser and | |
957 // change !INITIAL to INITIAL. Passing |false| makes things | |
958 // less ugly for now. | |
959 } | |
960 | |
961 function onLeafError(baseDirEntry, err) { | |
962 // Usually, leaf does not exist, because it's just a suggested file name. | |
963 if (err.code != FileError.NOT_FOUND_ERR) | |
964 console.log('Unexpected error resolving default leaf: ' + err); | |
965 // |baseDirEntry| would point to a system directory if we are trying | |
966 // to change to a non-existing removable drive or an archive. | |
967 // Try to change to the default directory then. | |
968 if (DirectoryModel.isSystemDirectory(baseDirEntry.fullPath)) | |
969 onBaseError(err); | |
970 else | |
971 changeDirectoryEntry(baseDirEntry, INITIAL, !EXISTS); | |
972 } | |
973 | |
974 var onBaseError = function(err) { | |
975 console.log('Unexpected error resolving default base "' + | |
976 baseName + '": ' + err); | |
977 if (path != this.getDefaultDirectory()) { | |
978 // Can't find the provided path, let's go to default one instead. | |
979 resolveCallback(!EXISTS); | |
980 if (!overridden) | |
981 this.setupDefaultPath(opt_loadedCallback); | |
982 } else { | |
983 // Well, we can't find the downloads dir. Let's just show something, | |
984 // or we will get an infinite recursion. | |
985 changeDirectoryEntry(this.root_, opt_loadedCallback, INITIAL, !EXISTS); | |
986 } | |
987 }.bind(this); | |
988 | |
989 var onBaseFound = function(baseDirEntry) { | |
990 if (!leafName) { | |
991 // Default path is just a directory, cd to it and we're done. | |
992 changeDirectoryEntry(baseDirEntry, INITIAL, !EXISTS); | |
993 return; | |
994 } | |
995 | |
996 util.resolvePath(this.root_, path, | |
997 onLeafFound.bind(this, baseDirEntry), | |
998 onLeafError.bind(this, baseDirEntry)); | |
999 }.bind(this); | |
1000 | |
1001 var root = this.root_; | |
1002 if (!baseName) | |
1003 baseName = this.getDefaultDirectory(); | |
1004 root.getDirectory(baseName, {create: false}, onBaseFound, onBaseError); | |
1005 }; | 1046 }; |
1006 | 1047 |
1007 /** | 1048 /** |
1008 * @param {function} opt_callback Callback on done. | 1049 * @param {function} opt_callback Callback on done. |
1009 */ | 1050 */ |
1010 DirectoryModel.prototype.setupDefaultPath = function(opt_callback) { | 1051 DirectoryModel.prototype.setupDefaultPath = function(opt_callback) { |
1011 this.setupPath(this.getDefaultDirectory(), opt_callback); | 1052 this.setupPath(this.getDefaultDirectory(), opt_callback); |
1012 }; | 1053 }; |
1013 | 1054 |
1014 /** | 1055 /** |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1066 callback) { | 1107 callback) { |
1067 this.metadataCache_.get(entries, 'filesystem', function(properties) { | 1108 this.metadataCache_.get(entries, 'filesystem', function(properties) { |
1068 callback(); | 1109 callback(); |
1069 }); | 1110 }); |
1070 }; | 1111 }; |
1071 | 1112 |
1072 /** | 1113 /** |
1073 * Get root entries asynchronously. | 1114 * Get root entries asynchronously. |
1074 * @private | 1115 * @private |
1075 * @param {function(Array.<Entry>)} callback Called when roots are resolved. | 1116 * @param {function(Array.<Entry>)} callback Called when roots are resolved. |
1076 * @param {number} gdataAccess One of GDATA_ACCESS_* constants. | |
1077 */ | 1117 */ |
1078 DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) { | 1118 DirectoryModel.prototype.resolveRoots_ = function(callback) { |
1079 var groups = { | 1119 var groups = { |
1080 downloads: null, | 1120 downloads: null, |
1081 archives: null, | 1121 archives: null, |
1082 removables: null, | 1122 removables: null, |
1083 gdata: null | 1123 gdata: null |
1084 }; | 1124 }; |
1085 var self = this; | 1125 var self = this; |
1086 | 1126 |
1087 metrics.startInterval('Load.Roots'); | 1127 metrics.startInterval('Load.Roots'); |
1088 function done() { | 1128 function done() { |
1089 for (var i in groups) | 1129 for (var i in groups) |
1090 if (!groups[i]) | 1130 if (!groups[i]) |
1091 return; | 1131 return; |
1092 | 1132 |
1093 self.updateVolumeReadOnlyStatus_(groups.removables); | |
1094 callback(groups.downloads. | 1133 callback(groups.downloads. |
1095 concat(groups.gdata). | 1134 concat(groups.gdata). |
1096 concat(groups.archives). | 1135 concat(groups.archives). |
1097 concat(groups.removables)); | 1136 concat(groups.removables)); |
1098 metrics.recordInterval('Load.Roots'); | 1137 metrics.recordInterval('Load.Roots'); |
1099 } | 1138 } |
1100 | 1139 |
1101 function append(index, values, opt_error) { | 1140 function append(index, values, opt_error) { |
1102 groups[index] = values; | 1141 groups[index] = values; |
1103 done(); | 1142 done(); |
1104 } | 1143 } |
1105 | 1144 |
1106 function onDownloads(entry) { | 1145 function appendSingle(index, entry) { |
1107 groups.downloads = [entry]; | 1146 groups[index] = [entry]; |
1108 done(); | 1147 done(); |
1109 } | 1148 } |
1110 | 1149 |
1111 function onDownloadsError(error) { | 1150 function onSingleError(index, error, defaultValue) { |
1112 groups.downloads = []; | 1151 groups[index] = defailtValue || []; |
1113 done(); | |
1114 } | |
1115 | |
1116 function onGDataMounted(entry) { | |
1117 console.log('GData mounted:', entry); | |
1118 self.unmountedGDataEntry_ = null; | |
1119 groups.gdata = [entry]; | |
1120 done(); | |
1121 } | |
1122 | |
1123 function onGDataNotMounted(error) { | |
1124 console.log('GData not mounted: ' + (error || 'lazy')); | |
1125 self.unmountedGDataEntry_ = { | |
1126 unmounted: true, // Clients use this field to distinguish a fake root. | |
1127 error: error, | |
1128 toURL: function() { return '' }, | |
1129 fullPath: '/' + DirectoryModel.GDATA_DIRECTORY | |
1130 }; | |
1131 groups.gdata = [self.unmountedGDataEntry_]; | |
1132 done(); | 1152 done(); |
1133 } | 1153 } |
1134 | 1154 |
1135 var root = this.root_; | 1155 var root = this.root_; |
1136 root.getDirectory(DirectoryModel.DOWNLOADS_DIRECTORY, { create: false }, | 1156 function readSingle(dir, index, opt_defaultValue) { |
1137 onDownloads, onDownloadsError); | 1157 root.getDirectory(dir, { create: false }, |
| 1158 appendSingle.bind(this, index), |
| 1159 onSingleError.bind(this, index, opt_defaultValue)); |
| 1160 } |
| 1161 |
| 1162 readSingle(DirectoryModel.DOWNLOADS_DIRECTORY, 'downloads'); |
1138 util.readDirectory(root, DirectoryModel.ARCHIVE_DIRECTORY, | 1163 util.readDirectory(root, DirectoryModel.ARCHIVE_DIRECTORY, |
1139 append.bind(this, 'archives')); | 1164 append.bind(this, 'archives')); |
1140 util.readDirectory(root, DirectoryModel.REMOVABLE_DIRECTORY, | 1165 util.readDirectory(root, DirectoryModel.REMOVABLE_DIRECTORY, |
1141 append.bind(this, 'removables')); | 1166 append.bind(this, 'removables')); |
1142 | 1167 |
1143 if (gdataAccess == DirectoryModel.GDATA_ACCESS_FULL) { | 1168 if (this.gDataEnabled_) { |
1144 root.getDirectory(DirectoryModel.GDATA_DIRECTORY, { create: false }, | 1169 var fake = [DirectoryModel.fakeGDataEntry_]; |
1145 onGDataMounted, onGDataNotMounted); | 1170 if (this.isGDataMounted_()) |
1146 } else if (gdataAccess == DirectoryModel.GDATA_ACCESS_LAZY) { | 1171 readSingle(DirectoryModel.GDATA_DIRECTORY, 'gdata', fake); |
1147 onGDataNotMounted(); | 1172 else |
| 1173 groups.gdata = fake; |
1148 } else { | 1174 } else { |
1149 groups.gdata = []; | 1175 groups.gdata = []; |
1150 } | 1176 } |
1151 }; | 1177 }; |
1152 | 1178 |
1153 /** | 1179 /** |
1154 * @param {function} callback Called when all roots are resolved. | 1180 * Updates the roots list. |
1155 * @param {number} gdataAccess One of GDATA_ACCESS_* constants. | 1181 * @private |
1156 */ | 1182 */ |
1157 DirectoryModel.prototype.updateRoots = function(callback, gdataAccess) { | 1183 DirectoryModel.prototype.updateRoots_ = function() { |
1158 var self = this; | 1184 var self = this; |
1159 this.resolveRoots_(function(rootEntries) { | 1185 this.resolveRoots_(function(rootEntries) { |
1160 var dm = self.rootsList_; | 1186 var dm = self.rootsList_; |
1161 var args = [0, dm.length].concat(rootEntries); | 1187 var args = [0, dm.length].concat(rootEntries); |
1162 dm.splice.apply(dm, args); | 1188 dm.splice.apply(dm, args); |
1163 | 1189 |
1164 self.updateRootsListSelection_(); | 1190 self.updateRootsListSelection_(); |
1165 | 1191 }); |
1166 callback(); | |
1167 }, gdataAccess); | |
1168 }; | 1192 }; |
1169 | 1193 |
1170 /** | 1194 /** |
1171 * Find roots list item by root path. | 1195 * Find roots list item by root path. |
1172 * | 1196 * |
1173 * @param {string} path Root path. | 1197 * @param {string} path Root path. |
1174 * @return {number} Index of the item. | 1198 * @return {number} Index of the item. |
1175 * @private | 1199 * @private |
1176 */ | 1200 */ |
1177 DirectoryModel.prototype.findRootsListItem_ = function(path) { | 1201 DirectoryModel.prototype.findRootsListItem_ = function(path) { |
1178 var roots = this.rootsList_; | 1202 var roots = this.rootsList_; |
1179 for (var index = 0; index < roots.length; index++) { | 1203 for (var index = 0; index < roots.length; index++) { |
1180 if (roots.item(index).fullPath == path) | 1204 if (roots.item(index).fullPath == path) |
1181 return index; | 1205 return index; |
1182 } | 1206 } |
1183 return -1; | 1207 return -1; |
1184 }; | 1208 }; |
1185 | 1209 |
1186 /** | 1210 /** |
1187 * @private | 1211 * @private |
1188 */ | 1212 */ |
1189 DirectoryModel.prototype.updateRootsListSelection_ = function() { | 1213 DirectoryModel.prototype.updateRootsListSelection_ = function() { |
1190 var rootPath = DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); | 1214 var rootPath = DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); |
1191 this.rootsListSelection_.selectedIndex = this.findRootsListItem_(rootPath); | 1215 this.rootsListSelection_.selectedIndex = this.findRootsListItem_(rootPath); |
1192 }; | 1216 }; |
1193 | 1217 |
1194 /** | 1218 /** |
1195 * @param {Array.<DirectoryEntry>} roots Removable volumes entries. | 1219 * @return {true} True if GDATA mounted. |
1196 * @private | 1220 * @private |
1197 */ | 1221 */ |
1198 DirectoryModel.prototype.updateVolumeReadOnlyStatus_ = function(roots) { | 1222 DirectoryModel.prototype.isGDataMounted_ = function() { |
1199 var status = this.volumeReadOnlyStatus_ = {}; | 1223 return this.volumeManager_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY); |
1200 for (var i = 0; i < roots.length; i++) { | 1224 }; |
1201 status[roots[i].fullPath] = false; | 1225 |
1202 chrome.fileBrowserPrivate.getVolumeMetadata(roots[i].toURL(), | 1226 /** |
1203 function(systemMetadata, path) { | 1227 * Handler for the VolumeManager's event. |
1204 status[path] = !!(systemMetadata && systemMetadata.isReadOnly); | 1228 * @private |
1205 }.bind(null, roots[i].fullPath)); | 1229 */ |
| 1230 DirectoryModel.prototype.onMountChanged_ = function() { |
| 1231 this.updateRoots_(); |
| 1232 |
| 1233 if (this.getCurrentRootType() != DirectoryModel.RootType.GDATA) |
| 1234 return; |
| 1235 |
| 1236 var mounted = this.isGDataMounted_(); |
| 1237 if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { |
| 1238 if (mounted) { |
| 1239 // Change fake entry to real one and rescan. |
| 1240 function onGotDirectory(entry) { |
| 1241 if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { |
| 1242 this.currentDirEntry_ = entry; |
| 1243 this.rescan(); |
| 1244 } |
| 1245 } |
| 1246 this.root_.getDirectory('/' + DirectoryModel.GDATA_DIRECTORY, {}, |
| 1247 onGotDirectory.bind(this)); |
| 1248 } |
| 1249 } else if (!mounted) { |
| 1250 // Current entry unmounted. replace with fake one. |
| 1251 if (this.currentDirEntry_.fullPath == |
| 1252 DirectoryModel.fakeGDataEntry_.fullPath) { |
| 1253 // Replace silently and rescan. |
| 1254 this.currentDirEntry_ = DirectoryModel.fakeGDataEntry_; |
| 1255 this.rescan(); |
| 1256 } else { |
| 1257 this.changeDirectoryEntry_(false, DirectoryModel.fakeGDataEntry_); |
| 1258 } |
1206 } | 1259 } |
1207 }; | 1260 }; |
1208 | 1261 |
1209 /** | |
1210 * Prepare the root for the unmount. | |
1211 * | |
1212 * @param {string} rootPath The path to the root. | |
1213 */ | |
1214 DirectoryModel.prototype.prepareUnmount = function(rootPath) { | |
1215 var index = this.findRootsListItem_(rootPath); | |
1216 if (index == -1) { | |
1217 console.error('Unknown root entry', rootPath); | |
1218 return; | |
1219 } | |
1220 var entry = this.rootsList_.item(index); | |
1221 | |
1222 // We never need to remove this attribute because even if the unmount fails | |
1223 // the onMountCompleted handler calls updateRoots which creates a new entry | |
1224 // object for this volume. | |
1225 entry.unmounting = true; | |
1226 | |
1227 // Re-place the entry into the roots data model to force re-rendering. | |
1228 this.rootsList_.splice(index, 1, entry); | |
1229 | |
1230 if (rootPath == this.rootPath) { | |
1231 // TODO(kaznacheev): Consider changing to the most recently used root. | |
1232 this.changeDirectory(this.getDefaultDirectory()); | |
1233 } | |
1234 }; | |
1235 | |
1236 /** | 1262 /** |
1237 * @param {string} path Path | 1263 * @param {string} path Path |
1238 * @return {boolean} If current directory is system. | 1264 * @return {boolean} If current directory is system. |
1239 */ | 1265 */ |
1240 DirectoryModel.isSystemDirectory = function(path) { | 1266 DirectoryModel.isSystemDirectory = function(path) { |
| 1267 path = path.replace(/\/+$/, ''); |
1241 return path == '/' + DirectoryModel.REMOVABLE_DIRECTORY || | 1268 return path == '/' + DirectoryModel.REMOVABLE_DIRECTORY || |
1242 path == '/' + DirectoryModel.ARCHIVE_DIRECTORY; | 1269 path == '/' + DirectoryModel.ARCHIVE_DIRECTORY; |
1243 }; | 1270 }; |
1244 | 1271 |
1245 /** | 1272 /** |
1246 * Performs search and displays results. The search type is dependent on the | 1273 * Performs search and displays results. The search type is dependent on the |
1247 * current directory. If we are currently on gdata, server side content search | 1274 * current directory. If we are currently on gdata, server side content search |
1248 * over gdata mount point. If the current directory is not on the gdata, file | 1275 * over gdata mount point. If the current directory is not on the gdata, file |
1249 * name search over current directory wil be performed. | 1276 * name search over current directory wil be performed. |
1250 * | 1277 * |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1352 * @return {string} The name of the root. | 1379 * @return {string} The name of the root. |
1353 */ | 1380 */ |
1354 DirectoryModel.getRootName = function(path) { | 1381 DirectoryModel.getRootName = function(path) { |
1355 var root = DirectoryModel.getRootPath(path); | 1382 var root = DirectoryModel.getRootPath(path); |
1356 var index = root.lastIndexOf('/'); | 1383 var index = root.lastIndexOf('/'); |
1357 return index == -1 ? root : root.substring(index + 1); | 1384 return index == -1 ? root : root.substring(index + 1); |
1358 }; | 1385 }; |
1359 | 1386 |
1360 /** | 1387 /** |
1361 * @param {string} path A path. | 1388 * @param {string} path A path. |
1362 * @return {string} A root type. | 1389 * @return {DirectoryModel.RootType} A root type. |
1363 */ | 1390 */ |
1364 DirectoryModel.getRootType = function(path) { | 1391 DirectoryModel.getRootType = function(path) { |
1365 function isTop(dir) { | 1392 function isTop(dir) { |
1366 return path.substr(1, dir.length) == dir; | 1393 return path.substr(1, dir.length) == dir; |
1367 } | 1394 } |
1368 | 1395 |
1369 if (isTop(DirectoryModel.DOWNLOADS_DIRECTORY)) | 1396 if (isTop(DirectoryModel.DOWNLOADS_DIRECTORY)) |
1370 return DirectoryModel.RootType.DOWNLOADS; | 1397 return DirectoryModel.RootType.DOWNLOADS; |
1371 if (isTop(DirectoryModel.GDATA_DIRECTORY)) | 1398 if (isTop(DirectoryModel.GDATA_DIRECTORY)) |
1372 return DirectoryModel.RootType.GDATA; | 1399 return DirectoryModel.RootType.GDATA; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1443 * Cancel scanner. | 1470 * Cancel scanner. |
1444 */ | 1471 */ |
1445 DirectoryModel.Scanner.prototype.cancel = function() { | 1472 DirectoryModel.Scanner.prototype.cancel = function() { |
1446 this.cancelled_ = true; | 1473 this.cancelled_ = true; |
1447 }; | 1474 }; |
1448 | 1475 |
1449 /** | 1476 /** |
1450 * Start scanner. | 1477 * Start scanner. |
1451 */ | 1478 */ |
1452 DirectoryModel.Scanner.prototype.run = function() { | 1479 DirectoryModel.Scanner.prototype.run = function() { |
| 1480 if (this.dir_ == DirectoryModel.fakeGDataEntry_) { |
| 1481 if (!this.cancelled_) |
| 1482 this.successCallback_(); |
| 1483 return; |
| 1484 } |
| 1485 |
1453 metrics.startInterval('DirectoryScan'); | 1486 metrics.startInterval('DirectoryScan'); |
1454 | 1487 |
1455 this.reader_ = this.dir_.createReader(); | 1488 this.reader_ = this.dir_.createReader(); |
1456 this.readNextChunk_(); | 1489 this.readNextChunk_(); |
1457 }; | 1490 }; |
1458 | 1491 |
1459 /** | 1492 /** |
1460 * @private | 1493 * @private |
1461 */ | 1494 */ |
1462 DirectoryModel.Scanner.prototype.readNextChunk_ = function() { | 1495 DirectoryModel.Scanner.prototype.readNextChunk_ = function() { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1499 /** | 1532 /** |
1500 * @private | 1533 * @private |
1501 */ | 1534 */ |
1502 DirectoryModel.Scanner.prototype.recordMetrics_ = function() { | 1535 DirectoryModel.Scanner.prototype.recordMetrics_ = function() { |
1503 metrics.recordInterval('DirectoryScan'); | 1536 metrics.recordInterval('DirectoryScan'); |
1504 if (this.dir_.fullPath == | 1537 if (this.dir_.fullPath == |
1505 '/' + DirectoryModel.DOWNLOADS_DIRECTORY) { | 1538 '/' + DirectoryModel.DOWNLOADS_DIRECTORY) { |
1506 metrics.recordMediumCount('DownloadsCount', this.list_.length); | 1539 metrics.recordMediumCount('DownloadsCount', this.list_.length); |
1507 } | 1540 } |
1508 }; | 1541 }; |
| 1542 |
| 1543 /** |
| 1544 * @constructor |
| 1545 * @param {DirectoryEntry} root Root entry. |
| 1546 * @param {DirectoryModel} directoryModel Model to watch. |
| 1547 * @param {VolumeManager} volumeManager Manager to watch. |
| 1548 */ |
| 1549 function FileWatcher(root, directoryModel, volumeManager) { |
| 1550 this.root_ = root; |
| 1551 this.dm_ = directoryModel; |
| 1552 this.vm_ = volumeManager; |
| 1553 this.watchedDirectoryEntry_ = null; |
| 1554 this.updateWatchedDirectoryBound_ = |
| 1555 this.updateWatchedDirectory_.bind(this); |
| 1556 this.onFileChangedBound_ = |
| 1557 this.onFileChanged_.bind(this); |
| 1558 } |
| 1559 |
| 1560 /** |
| 1561 * Starts watching. |
| 1562 */ |
| 1563 FileWatcher.prototype.start = function() { |
| 1564 chrome.fileBrowserPrivate.onFileChanged.addListener( |
| 1565 this.onFileChangedBound_); |
| 1566 |
| 1567 this.dm_.addEventListener('directory-changed', |
| 1568 this.updateWatchedDirectoryBound_); |
| 1569 this.vm_.addEventListener('changed', |
| 1570 this.updateWatchedDirectoryBound_); |
| 1571 |
| 1572 this.updateWatchedDirectory_(); |
| 1573 }; |
| 1574 |
| 1575 /** |
| 1576 * Stops watching (must be called before page unload). |
| 1577 */ |
| 1578 FileWatcher.prototype.stop = function() { |
| 1579 chrome.fileBrowserPrivate.onFileChanged.removeListener( |
| 1580 this.onFileChangedBound_); |
| 1581 |
| 1582 this.dm_.removeEventListener('directory-changed', |
| 1583 this.updateWatchedDirectoryBound_); |
| 1584 this.vm_.removeEventListener('changed', |
| 1585 this.updateWatchedDirectoryBound_); |
| 1586 |
| 1587 if (this.watchedDirectoryEntry_) |
| 1588 this.changeWatchedEntry(null); |
| 1589 }; |
| 1590 |
| 1591 /** |
| 1592 * @param {Object} event chrome.fileBrowserPrivate.onFileChanged event. |
| 1593 * @private |
| 1594 */ |
| 1595 FileWatcher.prototype.onFileChanged_ = function(event) { |
| 1596 if (encodeURI(event.fileUrl) == this.watchedDirectoryEntry_.toURL()) |
| 1597 this.onFileInWatchedDirectoryChanged(); |
| 1598 }; |
| 1599 |
| 1600 /** |
| 1601 * Called when file in the watched directory changed. |
| 1602 */ |
| 1603 FileWatcher.prototype.onFileInWatchedDirectoryChanged = function() { |
| 1604 this.dm_.rescanLater(); |
| 1605 }; |
| 1606 |
| 1607 /** |
| 1608 * Called when directory changed or volumes mounted/unmounted. |
| 1609 * @private |
| 1610 */ |
| 1611 FileWatcher.prototype.updateWatchedDirectory_ = function() { |
| 1612 var current = this.watchedDirectoryEntry_; |
| 1613 switch (this.dm_.getCurrentRootType()) { |
| 1614 case DirectoryModel.RootType.GDATA: |
| 1615 if (!this.vm_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY)) |
| 1616 break; |
| 1617 case DirectoryModel.RootType.DOWNLOADS: |
| 1618 case DirectoryModel.RootType.REMOVABLE: |
| 1619 if (!current || current.fullPath != this.dm_.getCurrentDirPath()) { |
| 1620 // TODO(serya): Changed in readonly removable directoried don't |
| 1621 // need to be tracked. |
| 1622 this.root_.getDirectory(this.dm_.getCurrentDirPath(), {}, |
| 1623 this.changeWatchedEntry.bind(this), |
| 1624 this.changeWatchedEntry.bind(this, null)); |
| 1625 } |
| 1626 return; |
| 1627 } |
| 1628 if (current) |
| 1629 this.changeWatchedEntry(null); |
| 1630 }; |
| 1631 |
| 1632 /** |
| 1633 * @param {Entry?} entry Null if no directory need to be watched or |
| 1634 * directory to watch. |
| 1635 */ |
| 1636 FileWatcher.prototype.changeWatchedEntry = function(entry) { |
| 1637 if (this.watchedDirectoryEntry_) { |
| 1638 chrome.fileBrowserPrivate.removeFileWatch( |
| 1639 this.watchedDirectoryEntry_.toURL(), |
| 1640 function(result) { |
| 1641 if (!result) { |
| 1642 console.log('Failed to remove file watch'); |
| 1643 } |
| 1644 }); |
| 1645 } |
| 1646 this.watchedDirectoryEntry_ = entry; |
| 1647 |
| 1648 if (this.watchedDirectoryEntry_) { |
| 1649 chrome.fileBrowserPrivate.addFileWatch( |
| 1650 this.watchedDirectoryEntry_.toURL(), |
| 1651 function(result) { |
| 1652 if (!result) { |
| 1653 console.log('Failed to add file watch'); |
| 1654 if (this.watchedDirectoryEntry_ == entry) |
| 1655 this.watchedDirectoryEntry_ = null; |
| 1656 } |
| 1657 }.bind(this)); |
| 1658 } |
| 1659 }; |
| 1660 |
| 1661 /** |
| 1662 * @return {DirectoryEntry} Current watched directory entry. |
| 1663 */ |
| 1664 FileWatcher.prototype.getWatchedDirectoryEntry = function() { |
| 1665 return this.watchedDirectoryEntry_; |
| 1666 }; |
OLD | NEW |