Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(313)

Side by Side Diff: chrome/browser/resources/file_manager/js/directory_model.js

Issue 10310163: Refactoring file manager: moving volume mounting related code to a separate class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed gallery opening on GDATA at startup. Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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
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
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
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
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698