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, |
dgozman
2012/05/18 14:17:19
Two problems here:
1. (optional) This method still
SeRya
2012/05/21 08:06:36
Fixed. Tested open and save dialogs with existing
| |
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 opt_pathResolveCallback = null; | |
981 } | |
917 | 982 |
918 var changeDirectoryEntry = function(entry, initial, exists, opt_callback) { | 983 function changeDirectoryEntry(directoryEntry, initial, exists, opt_callback) { |
919 resolveCallback(exists); | 984 tracker.stop(); |
920 if (!overridden) | 985 resolveCallback(directoryEntry.fillPath, '', true); |
dgozman
2012/05/18 14:17:19
typo: fillPath
| |
921 this.changeDirectoryEntry_(initial, entry, opt_callback); | 986 if (!tracker.hasChanged) |
922 }.bind(this); | 987 self.changeDirectoryEntry_(initial, directoryEntry, opt_callback); |
988 } | |
923 | 989 |
924 var INITIAL = true; | 990 var INITIAL = true; |
dgozman
2012/05/18 14:17:19
Add todo to rename initial to something like put_i
SeRya
2012/05/21 08:06:36
I don't think this is a good idea. For now we inde
| |
925 var EXISTS = true; | 991 var EXISTS = true; |
926 | 992 |
927 // Split the dirname from the basename. | 993 function changeToDefault() { |
928 var ary = path.match(/^(?:(.*)\/)?([^\/]*)$/); | 994 var def = self.getDefaultDirectory(); |
929 | 995 self.resolveDirectory(def, function(directoryEntry) { |
930 if (!ary) { | 996 changeDirectoryEntry(directoryEntry, INITIAL, !EXISTS); |
931 console.warn('Unable to split default path: ' + path); | 997 }, function(error) { |
932 changeDirectoryEntry(this.root_, INITIAL, !EXISTS); | 998 console.error('Failed to resolve default directory: ' + def, error); |
933 return; | 999 resolveCallback(path, '', false); |
dgozman
2012/05/18 14:17:19
false -> !EXISTS
SeRya
2012/05/21 08:06:36
Done.
| |
1000 }); | |
934 } | 1001 } |
935 | 1002 |
936 var baseName = ary[1]; | 1003 function netherFileNorEntry(error) { |
dgozman
2012/05/18 14:17:19
typo: nether -> neither
| |
937 var leafName = ary[2]; | 1004 console.log('Can\'t get file not entry: ' + path, error); |
938 | 1005 changeToDefault(); |
939 function onLeafFound(baseDirEntry, leafEntry) { | |
940 if (leafEntry.isDirectory) { | |
941 baseName = path; | |
942 leafName = ''; | |
943 changeDirectoryEntry(leafEntry, INITIAL, EXISTS); | |
944 return; | |
945 } | |
946 | |
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 } | 1006 } |
960 | 1007 |
961 function onLeafError(baseDirEntry, err) { | 1008 function getParentFiled(error) { |
dgozman
2012/05/18 14:17:19
typo: getParentFiled -> getParentFailed
| |
962 // Usually, leaf does not exist, because it's just a suggested file name. | 1009 console.error('Failed to get file parent: ', error); |
dgozman
2012/05/18 14:17:19
See this comment.
| |
963 if (err.code != FileError.NOT_FOUND_ERR) | 1010 changeToDefault(); |
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 } | 1011 } |
973 | 1012 |
974 var onBaseError = function(err) { | 1013 this.resolveDirectory(path, function(directoryEntry) { |
975 console.log('Unexpected error resolving default base "' + | 1014 changeDirectoryEntry(directoryEntry, INITIAL, EXISTS); |
976 baseName + '": ' + err); | 1015 }, function(error) { |
977 if (path != this.getDefaultDirectory()) { | 1016 self.root_.getFile(path, {}, function(fileEntry) { |
978 // Can't find the provided path, let's go to default one instead. | 1017 fileEntry.getParent(function(parentDirectoryEntry) { |
979 resolveCallback(!EXISTS); | 1018 // Path is a file and parent directory resolved successfully. |
980 if (!overridden) | 1019 resolveCallback(parentDirectoryEntry.fullPath, fileEntry.name, EXISTS); |
981 this.setupDefaultPath(opt_loadedCallback); | 1020 changeDirectoryEntry(parentDirectoryEntry, |
982 } else { | 1021 !INITIAL /*HACK*/, |
983 // Well, we can't find the downloads dir. Let's just show something, | 1022 EXISTS, |
984 // or we will get an infinite recursion. | 1023 function() { |
985 changeDirectoryEntry(this.root_, opt_loadedCallback, INITIAL, !EXISTS); | 1024 self.selectEntry(fileEntry.name); |
986 } | 1025 if (opt_loadedCallback) |
987 }.bind(this); | 1026 opt_loadedCallback(); |
988 | 1027 }); |
989 var onBaseFound = function(baseDirEntry) { | 1028 }, getParentFiled); |
990 if (!leafName) { | 1029 }, netherFileNorEntry); |
991 // Default path is just a directory, cd to it and we're done. | 1030 }); |
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 }; | 1031 }; |
1006 | 1032 |
1007 /** | 1033 /** |
1008 * @param {function} opt_callback Callback on done. | 1034 * @param {function} opt_callback Callback on done. |
1009 */ | 1035 */ |
1010 DirectoryModel.prototype.setupDefaultPath = function(opt_callback) { | 1036 DirectoryModel.prototype.setupDefaultPath = function(opt_callback) { |
1011 this.setupPath(this.getDefaultDirectory(), opt_callback); | 1037 this.setupPath(this.getDefaultDirectory(), opt_callback); |
1012 }; | 1038 }; |
1013 | 1039 |
1014 /** | 1040 /** |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1066 callback) { | 1092 callback) { |
1067 this.metadataCache_.get(entries, 'filesystem', function(properties) { | 1093 this.metadataCache_.get(entries, 'filesystem', function(properties) { |
1068 callback(); | 1094 callback(); |
1069 }); | 1095 }); |
1070 }; | 1096 }; |
1071 | 1097 |
1072 /** | 1098 /** |
1073 * Get root entries asynchronously. | 1099 * Get root entries asynchronously. |
1074 * @private | 1100 * @private |
1075 * @param {function(Array.<Entry>)} callback Called when roots are resolved. | 1101 * @param {function(Array.<Entry>)} callback Called when roots are resolved. |
1076 * @param {number} gdataAccess One of GDATA_ACCESS_* constants. | |
1077 */ | 1102 */ |
1078 DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) { | 1103 DirectoryModel.prototype.resolveRoots_ = function(callback) { |
1079 var groups = { | 1104 var groups = { |
1080 downloads: null, | 1105 downloads: null, |
1081 archives: null, | 1106 archives: null, |
1082 removables: null, | 1107 removables: null, |
1083 gdata: null | 1108 gdata: null |
1084 }; | 1109 }; |
1085 var self = this; | 1110 var self = this; |
1086 | 1111 |
1087 metrics.startInterval('Load.Roots'); | 1112 metrics.startInterval('Load.Roots'); |
1088 function done() { | 1113 function done() { |
1089 for (var i in groups) | 1114 for (var i in groups) |
1090 if (!groups[i]) | 1115 if (!groups[i]) |
1091 return; | 1116 return; |
1092 | 1117 |
1093 self.updateVolumeReadOnlyStatus_(groups.removables); | |
1094 callback(groups.downloads. | 1118 callback(groups.downloads. |
1095 concat(groups.gdata). | 1119 concat(groups.gdata). |
1096 concat(groups.archives). | 1120 concat(groups.archives). |
1097 concat(groups.removables)); | 1121 concat(groups.removables)); |
1098 metrics.recordInterval('Load.Roots'); | 1122 metrics.recordInterval('Load.Roots'); |
1099 } | 1123 } |
1100 | 1124 |
1101 function append(index, values, opt_error) { | 1125 function append(index, values, opt_error) { |
1102 groups[index] = values; | 1126 groups[index] = values; |
1103 done(); | 1127 done(); |
1104 } | 1128 } |
1105 | 1129 |
1106 function onDownloads(entry) { | 1130 function appendSingle(index, entry) { |
1107 groups.downloads = [entry]; | 1131 groups[index] = [entry]; |
1108 done(); | 1132 done(); |
1109 } | 1133 } |
1110 | 1134 |
1111 function onDownloadsError(error) { | 1135 function onSingleError(index, error, defaultValue) { |
1112 groups.downloads = []; | 1136 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(); | 1137 done(); |
1133 } | 1138 } |
1134 | 1139 |
1135 var root = this.root_; | 1140 var root = this.root_; |
1136 root.getDirectory(DirectoryModel.DOWNLOADS_DIRECTORY, { create: false }, | 1141 function readSingle(dir, index, opt_defaultValue) { |
1137 onDownloads, onDownloadsError); | 1142 root.getDirectory(dir, { create: false }, |
1143 appendSingle.bind(this, index), | |
1144 onSingleError.bind(this, index, opt_defaultValue)); | |
1145 } | |
1146 | |
1147 readSingle(DirectoryModel.DOWNLOADS_DIRECTORY, 'downloads'); | |
1138 util.readDirectory(root, DirectoryModel.ARCHIVE_DIRECTORY, | 1148 util.readDirectory(root, DirectoryModel.ARCHIVE_DIRECTORY, |
1139 append.bind(this, 'archives')); | 1149 append.bind(this, 'archives')); |
1140 util.readDirectory(root, DirectoryModel.REMOVABLE_DIRECTORY, | 1150 util.readDirectory(root, DirectoryModel.REMOVABLE_DIRECTORY, |
1141 append.bind(this, 'removables')); | 1151 append.bind(this, 'removables')); |
1142 | 1152 |
1143 if (gdataAccess == DirectoryModel.GDATA_ACCESS_FULL) { | 1153 if (this.gDataEnabled_) { |
1144 root.getDirectory(DirectoryModel.GDATA_DIRECTORY, { create: false }, | 1154 var fake = [DirectoryModel.fakeGDataEntry_]; |
1145 onGDataMounted, onGDataNotMounted); | 1155 if (this.isGDataMounted_()) |
1146 } else if (gdataAccess == DirectoryModel.GDATA_ACCESS_LAZY) { | 1156 readSingle(DirectoryModel.GDATA_DIRECTORY, 'gdata', fake); |
1147 onGDataNotMounted(); | 1157 else |
1158 groups.gdata = fake; | |
1148 } else { | 1159 } else { |
1149 groups.gdata = []; | 1160 groups.gdata = []; |
1150 } | 1161 } |
1151 }; | 1162 }; |
1152 | 1163 |
1153 /** | 1164 /** |
1154 * @param {function} callback Called when all roots are resolved. | 1165 * Updates the roots list. |
1155 * @param {number} gdataAccess One of GDATA_ACCESS_* constants. | 1166 * @private |
1156 */ | 1167 */ |
1157 DirectoryModel.prototype.updateRoots = function(callback, gdataAccess) { | 1168 DirectoryModel.prototype.updateRoots_ = function() { |
1158 var self = this; | 1169 var self = this; |
1159 this.resolveRoots_(function(rootEntries) { | 1170 this.resolveRoots_(function(rootEntries) { |
1160 var dm = self.rootsList_; | 1171 var dm = self.rootsList_; |
1161 var args = [0, dm.length].concat(rootEntries); | 1172 var args = [0, dm.length].concat(rootEntries); |
1162 dm.splice.apply(dm, args); | 1173 dm.splice.apply(dm, args); |
1163 | 1174 |
1164 self.updateRootsListSelection_(); | 1175 self.updateRootsListSelection_(); |
1165 | 1176 }); |
1166 callback(); | |
1167 }, gdataAccess); | |
1168 }; | 1177 }; |
1169 | 1178 |
1170 /** | 1179 /** |
1171 * Find roots list item by root path. | 1180 * Find roots list item by root path. |
1172 * | 1181 * |
1173 * @param {string} path Root path. | 1182 * @param {string} path Root path. |
1174 * @return {number} Index of the item. | 1183 * @return {number} Index of the item. |
1175 * @private | 1184 * @private |
1176 */ | 1185 */ |
1177 DirectoryModel.prototype.findRootsListItem_ = function(path) { | 1186 DirectoryModel.prototype.findRootsListItem_ = function(path) { |
1178 var roots = this.rootsList_; | 1187 var roots = this.rootsList_; |
1179 for (var index = 0; index < roots.length; index++) { | 1188 for (var index = 0; index < roots.length; index++) { |
1180 if (roots.item(index).fullPath == path) | 1189 if (roots.item(index).fullPath == path) |
1181 return index; | 1190 return index; |
1182 } | 1191 } |
1183 return -1; | 1192 return -1; |
1184 }; | 1193 }; |
1185 | 1194 |
1186 /** | 1195 /** |
1187 * @private | 1196 * @private |
1188 */ | 1197 */ |
1189 DirectoryModel.prototype.updateRootsListSelection_ = function() { | 1198 DirectoryModel.prototype.updateRootsListSelection_ = function() { |
1190 var rootPath = DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); | 1199 var rootPath = DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); |
1191 this.rootsListSelection_.selectedIndex = this.findRootsListItem_(rootPath); | 1200 this.rootsListSelection_.selectedIndex = this.findRootsListItem_(rootPath); |
1192 }; | 1201 }; |
1193 | 1202 |
1194 /** | 1203 /** |
1195 * @param {Array.<DirectoryEntry>} roots Removable volumes entries. | 1204 * @return {true} True if GDATA mounted. |
1196 * @private | 1205 * @private |
1197 */ | 1206 */ |
1198 DirectoryModel.prototype.updateVolumeReadOnlyStatus_ = function(roots) { | 1207 DirectoryModel.prototype.isGDataMounted_ = function() { |
1199 var status = this.volumeReadOnlyStatus_ = {}; | 1208 return this.volumeManager_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY); |
1200 for (var i = 0; i < roots.length; i++) { | 1209 }; |
1201 status[roots[i].fullPath] = false; | 1210 |
1202 chrome.fileBrowserPrivate.getVolumeMetadata(roots[i].toURL(), | 1211 /** |
1203 function(systemMetadata, path) { | 1212 * Handler for the VolumeManager's event. |
1204 status[path] = !!(systemMetadata && systemMetadata.isReadOnly); | 1213 * @private |
1205 }.bind(null, roots[i].fullPath)); | 1214 */ |
1215 DirectoryModel.prototype.onMountChanged_ = function() { | |
1216 this.updateRoots_(); | |
1217 | |
1218 if (this.getCurrentRootType() != DirectoryModel.RootType.GDATA) | |
1219 return; | |
1220 | |
1221 var mounted = this.isGDataMounted_(); | |
1222 if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { | |
1223 if (mounted) { | |
1224 // Change fake entry to real one and rescan. | |
1225 function onGotDirectory(entry) { | |
1226 if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { | |
1227 this.currentDirEntry_ = entry; | |
1228 this.rescan(); | |
1229 } | |
1230 } | |
1231 this.root_.getDirectory('/' + DirectoryModel.GDATA_DIRECTORY, {}, | |
1232 onGotDirectory.bind(this)); | |
1233 } | |
1234 } else if (!mounted) { | |
1235 // Current entry unmounted. replace with fake one. | |
1236 if (this.currentDirEntry_.fullPath == | |
1237 DirectoryModel.fakeGDataEntry_.fullPath) { | |
1238 // Replace silently and rescan. | |
1239 this.currentDirEntry_ = DirectoryModel.fakeGDataEntry_; | |
1240 this.rescan(); | |
1241 } else { | |
1242 this.changeDirectoryEntry_(false, DirectoryModel.fakeGDataEntry_); | |
1243 } | |
1206 } | 1244 } |
1207 }; | 1245 }; |
1208 | 1246 |
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 /** | 1247 /** |
1237 * @param {string} path Path | 1248 * @param {string} path Path |
1238 * @return {boolean} If current directory is system. | 1249 * @return {boolean} If current directory is system. |
1239 */ | 1250 */ |
1240 DirectoryModel.isSystemDirectory = function(path) { | 1251 DirectoryModel.isSystemDirectory = function(path) { |
1241 return path == '/' + DirectoryModel.REMOVABLE_DIRECTORY || | 1252 return path == '/' + DirectoryModel.REMOVABLE_DIRECTORY || |
1242 path == '/' + DirectoryModel.ARCHIVE_DIRECTORY; | 1253 path == '/' + DirectoryModel.ARCHIVE_DIRECTORY; |
1243 }; | 1254 }; |
1244 | 1255 |
1245 /** | 1256 /** |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1352 * @return {string} The name of the root. | 1363 * @return {string} The name of the root. |
1353 */ | 1364 */ |
1354 DirectoryModel.getRootName = function(path) { | 1365 DirectoryModel.getRootName = function(path) { |
1355 var root = DirectoryModel.getRootPath(path); | 1366 var root = DirectoryModel.getRootPath(path); |
1356 var index = root.lastIndexOf('/'); | 1367 var index = root.lastIndexOf('/'); |
1357 return index == -1 ? root : root.substring(index + 1); | 1368 return index == -1 ? root : root.substring(index + 1); |
1358 }; | 1369 }; |
1359 | 1370 |
1360 /** | 1371 /** |
1361 * @param {string} path A path. | 1372 * @param {string} path A path. |
1362 * @return {string} A root type. | 1373 * @return {DirectoryModel.RootType} A root type. |
1363 */ | 1374 */ |
1364 DirectoryModel.getRootType = function(path) { | 1375 DirectoryModel.getRootType = function(path) { |
1365 function isTop(dir) { | 1376 function isTop(dir) { |
1366 return path.substr(1, dir.length) == dir; | 1377 return path.substr(1, dir.length) == dir; |
1367 } | 1378 } |
1368 | 1379 |
1369 if (isTop(DirectoryModel.DOWNLOADS_DIRECTORY)) | 1380 if (isTop(DirectoryModel.DOWNLOADS_DIRECTORY)) |
1370 return DirectoryModel.RootType.DOWNLOADS; | 1381 return DirectoryModel.RootType.DOWNLOADS; |
1371 if (isTop(DirectoryModel.GDATA_DIRECTORY)) | 1382 if (isTop(DirectoryModel.GDATA_DIRECTORY)) |
1372 return DirectoryModel.RootType.GDATA; | 1383 return DirectoryModel.RootType.GDATA; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1443 * Cancel scanner. | 1454 * Cancel scanner. |
1444 */ | 1455 */ |
1445 DirectoryModel.Scanner.prototype.cancel = function() { | 1456 DirectoryModel.Scanner.prototype.cancel = function() { |
1446 this.cancelled_ = true; | 1457 this.cancelled_ = true; |
1447 }; | 1458 }; |
1448 | 1459 |
1449 /** | 1460 /** |
1450 * Start scanner. | 1461 * Start scanner. |
1451 */ | 1462 */ |
1452 DirectoryModel.Scanner.prototype.run = function() { | 1463 DirectoryModel.Scanner.prototype.run = function() { |
1464 if (this.dir_ == DirectoryModel.fakeGDataEntry_) { | |
1465 if (!this.cancelled_) | |
1466 this.successCallback_(); | |
1467 return; | |
1468 } | |
1469 | |
1453 metrics.startInterval('DirectoryScan'); | 1470 metrics.startInterval('DirectoryScan'); |
1454 | 1471 |
1455 this.reader_ = this.dir_.createReader(); | 1472 this.reader_ = this.dir_.createReader(); |
1456 this.readNextChunk_(); | 1473 this.readNextChunk_(); |
1457 }; | 1474 }; |
1458 | 1475 |
1459 /** | 1476 /** |
1460 * @private | 1477 * @private |
1461 */ | 1478 */ |
1462 DirectoryModel.Scanner.prototype.readNextChunk_ = function() { | 1479 DirectoryModel.Scanner.prototype.readNextChunk_ = function() { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1499 /** | 1516 /** |
1500 * @private | 1517 * @private |
1501 */ | 1518 */ |
1502 DirectoryModel.Scanner.prototype.recordMetrics_ = function() { | 1519 DirectoryModel.Scanner.prototype.recordMetrics_ = function() { |
1503 metrics.recordInterval('DirectoryScan'); | 1520 metrics.recordInterval('DirectoryScan'); |
1504 if (this.dir_.fullPath == | 1521 if (this.dir_.fullPath == |
1505 '/' + DirectoryModel.DOWNLOADS_DIRECTORY) { | 1522 '/' + DirectoryModel.DOWNLOADS_DIRECTORY) { |
1506 metrics.recordMediumCount('DownloadsCount', this.list_.length); | 1523 metrics.recordMediumCount('DownloadsCount', this.list_.length); |
1507 } | 1524 } |
1508 }; | 1525 }; |
1526 | |
1527 /** | |
1528 * @constructor | |
1529 * @param {DirectoryEntry} root Root entry. | |
1530 * @param {DirectoryModel} directoryModel Model to watch. | |
1531 * @param {VolumeManager} volumeManager Manager to watch. | |
1532 */ | |
1533 function FileWatcher(root, directoryModel, volumeManager) { | |
1534 this.root_ = root; | |
1535 this.dm_ = directoryModel; | |
1536 this.vm_ = volumeManager; | |
1537 this.watchedDirectoryEntry_ = null; | |
1538 this.updateWatchedDirectoryBound_ = | |
1539 this.updateWatchedDirectory_.bind(this); | |
1540 this.onFileChangedBound_ = | |
1541 this.onFileChanged_.bind(this); | |
1542 } | |
1543 | |
1544 /** | |
1545 * Starts watching. | |
1546 */ | |
1547 FileWatcher.prototype.start = function() { | |
1548 chrome.fileBrowserPrivate.onFileChanged.addListener( | |
1549 this.onFileChangedBound_); | |
1550 | |
1551 this.dm_.addEventListener('directory-changed', | |
1552 this.updateWatchedDirectoryBound_); | |
1553 this.vm_.addEventListener('changed', | |
1554 this.updateWatchedDirectoryBound_); | |
1555 | |
1556 this.updateWatchedDirectory_(); | |
1557 }; | |
1558 | |
1559 /** | |
1560 * Stops watching (must be called before page unload). | |
1561 */ | |
1562 FileWatcher.prototype.stop = function() { | |
1563 chrome.fileBrowserPrivate.onFileChanged.removeListener( | |
1564 this.onFileChangedBound_); | |
1565 | |
1566 this.dm_.removeEventListener('directory-changed', | |
1567 this.updateWatchedDirectoryBound_); | |
1568 this.vm_.removeEventListener('changed', | |
1569 this.updateWatchedDirectoryBound_); | |
1570 | |
1571 if (this.watchedDirectoryEntry_) | |
1572 this.changeWatchedEntry(null); | |
1573 }; | |
1574 | |
1575 /** | |
1576 * @param {Object} event chrome.fileBrowserPrivate.onFileChanged event. | |
1577 * @private | |
1578 */ | |
1579 FileWatcher.prototype.onFileChanged_ = function(event) { | |
1580 if (encodeURI(event.fileUrl) == this.watchedDirectoryEntry_.toURL()) | |
1581 this.dm_.rescanLater(); | |
1582 }; | |
1583 | |
1584 /** | |
1585 * Called when directory changed or volumes mounted/unmounted. | |
1586 * @private | |
1587 */ | |
1588 FileWatcher.prototype.updateWatchedDirectory_ = function() { | |
1589 var current = this.watchedDirectoryEntry_; | |
1590 switch (this.dm_.getCurrentRootType()) { | |
1591 case DirectoryModel.RootType.GDATA: | |
1592 if (!this.vm_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY)) | |
1593 break; | |
1594 case DirectoryModel.RootType.DOWNLOADS: | |
1595 case DirectoryModel.RootType.REMOVABLE: | |
1596 if (!current || current.fullPath != this.dm_.getCurrentDirPath()) { | |
1597 // TODO(serya): Changed in readonly removable directoried don't | |
1598 // need to be tracked. | |
1599 this.root_.getDirectory(this.dm_.getCurrentDirPath(), {}, | |
1600 this.changeWatchedEntry.bind(this), | |
1601 this.changeWatchedEntry.bind(this, null)); | |
1602 } | |
1603 return; | |
1604 } | |
1605 if (current) | |
1606 this.changeWatchedEntry(null); | |
1607 }; | |
1608 | |
1609 /** | |
1610 * @param {Entry?} entry Null if no directory need to be watched or | |
1611 * directory to watch. | |
1612 */ | |
1613 FileWatcher.prototype.changeWatchedEntry = function(entry) { | |
1614 if (this.watchedDirectoryEntry_) { | |
1615 chrome.fileBrowserPrivate.removeFileWatch( | |
1616 this.watchedDirectoryEntry_.toURL(), | |
1617 function(result) { | |
1618 if (!result) { | |
1619 console.log('Failed to remove file watch'); | |
1620 } | |
1621 }); | |
1622 } | |
1623 this.watchedDirectoryEntry_ = entry; | |
1624 | |
1625 if (this.watchedDirectoryEntry_) { | |
1626 chrome.fileBrowserPrivate.addFileWatch( | |
1627 this.watchedDirectoryEntry_.toURL(), | |
1628 function(result) { | |
1629 if (!result) { | |
1630 console.log('Failed to add file watch'); | |
1631 if (this.watchedDirectoryEntry_ == entry) | |
1632 this.watchedDirectoryEntry_ = null; | |
1633 } | |
1634 }.bind(this)); | |
1635 } | |
1636 }; | |
OLD | NEW |