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

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

Issue 7745051: Added refresh on filesystem change (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Flacky test Created 9 years, 1 month 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
« no previous file with comments | « chrome/browser/chromeos/extensions/file_browser_event_router.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 // Setting the src of an img to an empty string can crash the browser, so we 5 // Setting the src of an img to an empty string can crash the browser, so we
6 // use an empty 1x1 gif instead. 6 // use an empty 1x1 gif instead.
7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,' 7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,'
8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D'; 8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D';
9 9
10 var g_slideshow_data = null; 10 var g_slideshow_data = null;
11 11
12 const GALLERY_ENABLED = true;
13
14 // If directory files changes too often, don't rescan directory more than once
15 // per specified interval
16 const SIMULTANEOUS_RESCAN_INTERVAL = 1000;
17
12 /** 18 /**
13 * FileManager constructor. 19 * FileManager constructor.
14 * 20 *
15 * FileManager objects encapsulate the functionality of the file selector 21 * FileManager objects encapsulate the functionality of the file selector
16 * dialogs, as well as the full screen file manager application (though the 22 * dialogs, as well as the full screen file manager application (though the
17 * latter is not yet implemented). 23 * latter is not yet implemented).
18 * 24 *
19 * @param {HTMLElement} dialogDom The DOM node containing the prototypical 25 * @param {HTMLElement} dialogDom The DOM node containing the prototypical
20 * dialog UI. 26 * dialog UI.
21 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing 27 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing
(...skipping 21 matching lines...) Expand all
43 49
44 this.selection = null; 50 this.selection = null;
45 51
46 this.clipboard_ = null; // Current clipboard, or null if empty. 52 this.clipboard_ = null; // Current clipboard, or null if empty.
47 53
48 this.butterTimer_ = null; 54 this.butterTimer_ = null;
49 this.currentButter_ = null; 55 this.currentButter_ = null;
50 56
51 // True if we should filter out files that start with a dot. 57 // True if we should filter out files that start with a dot.
52 this.filterFiles_ = true; 58 this.filterFiles_ = true;
59 this.subscribedOnDirectoryChanges_ = false;
60 this.pendingRescanQueue_ = [];
61 this.rescanRunning_ = false;
53 62
54 this.commands_ = {}; 63 this.commands_ = {};
55 64
56 this.document_ = dialogDom.ownerDocument; 65 this.document_ = dialogDom.ownerDocument;
57 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE; 66 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE;
58 67
59 metrics.recordEnum('Create', this.dialogType_, 68 metrics.recordEnum('Create', this.dialogType_,
60 [FileManager.DialogType.SELECT_FOLDER, 69 [FileManager.DialogType.SELECT_FOLDER,
61 FileManager.DialogType.SELECT_SAVEAS_FILE, 70 FileManager.DialogType.SELECT_SAVEAS_FILE,
62 FileManager.DialogType.SELECT_OPEN_FILE, 71 FileManager.DialogType.SELECT_OPEN_FILE,
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
104 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE); 113 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE);
105 114
106 // DirectoryEntry representing the current directory of the dialog. 115 // DirectoryEntry representing the current directory of the dialog.
107 this.currentDirEntry_ = null; 116 this.currentDirEntry_ = null;
108 117
109 this.copyManager_ = new FileCopyManager(); 118 this.copyManager_ = new FileCopyManager();
110 this.copyManager_.addEventListener('copy-progress', 119 this.copyManager_.addEventListener('copy-progress',
111 this.onCopyProgress_.bind(this)); 120 this.onCopyProgress_.bind(this));
112 121
113 window.addEventListener('popstate', this.onPopState_.bind(this)); 122 window.addEventListener('popstate', this.onPopState_.bind(this));
123 window.addEventListener('unload', this.onUnload_.bind(this));
124
114 this.addEventListener('directory-changed', 125 this.addEventListener('directory-changed',
115 this.onDirectoryChanged_.bind(this)); 126 this.onDirectoryChanged_.bind(this));
116 this.addEventListener('selection-summarized', 127 this.addEventListener('selection-summarized',
117 this.onSelectionSummarized_.bind(this)); 128 this.onSelectionSummarized_.bind(this));
118 129
119 // The list of archives requested to mount. We will show contents once 130 // The list of archives requested to mount. We will show contents once
120 // archive is mounted, but only for mounts from within this filebrowser tab. 131 // archive is mounted, but only for mounts from within this filebrowser tab.
121 this.mountRequests_ = []; 132 this.mountRequests_ = [];
122 chrome.fileBrowserPrivate.onMountCompleted.addListener( 133 chrome.fileBrowserPrivate.onMountCompleted.addListener(
123 this.onMountCompleted_.bind(this)); 134 this.onMountCompleted_.bind(this));
124 135
136 chrome.fileBrowserPrivate.onFileChanged.addListener(
137 this.onFileChanged_.bind(this));
138
125 var self = this; 139 var self = this;
126 140
127 // The list of callbacks to be invoked during the directory rescan after 141 // The list of callbacks to be invoked during the directory rescan after
128 // all paste tasks are complete. 142 // all paste tasks are complete.
129 this.pasteSuccessCallbacks_ = []; 143 this.pasteSuccessCallbacks_ = [];
130 144
131 // The list of active mount points to distinct them from other directories. 145 // The list of active mount points to distinct them from other directories.
132 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) { 146 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) {
133 self.mountPoints_ = mountPoints; 147 self.mountPoints_ = mountPoints;
134 }); 148 });
(...skipping 2711 matching lines...) Expand 10 before | Expand all | Expand 10 after
2846 this.updateOkButton_(); 2860 this.updateOkButton_();
2847 2861
2848 this.checkFreeSpace_(this.currentDirEntry_.fullPath); 2862 this.checkFreeSpace_(this.currentDirEntry_.fullPath);
2849 2863
2850 // New folder should never be enabled in the root or media/ directories. 2864 // New folder should never be enabled in the root or media/ directories.
2851 this.newFolderButton_.disabled = isSystemDirEntry(this.currentDirEntry_); 2865 this.newFolderButton_.disabled = isSystemDirEntry(this.currentDirEntry_);
2852 2866
2853 this.document_.title = this.currentDirEntry_.fullPath; 2867 this.document_.title = this.currentDirEntry_.fullPath;
2854 2868
2855 var self = this; 2869 var self = this;
2870
2871 if (this.subscribedOnDirectoryChanges_) {
2872 chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(),
2873 function(result) {
2874 if (!result) {
2875 console.log('Failed to remove file watch');
2876 }
2877 });
2878 }
2879
2880 if (event.newDirEntry.fullPath != '/') {
2881 this.subscribedOnDirectoryChanges_ = true;
2882 chrome.fileBrowserPrivate.addFileWatch(event.newDirEntry.toURL(),
2883 function(result) {
2884 if (!result) {
2885 console.log('Failed to add file watch');
2886 }
2887 });
2888 }
2889
2856 this.rescanDirectory_(function() { 2890 this.rescanDirectory_(function() {
2857 if (event.selectedEntry) 2891 if (event.selectedEntry)
2858 self.selectEntry(event.selectedEntry); 2892 self.selectEntry(event.selectedEntry);
2859 if (event.opt_callback) { 2893 if (event.opt_callback) {
2860 try { 2894 try {
2861 event.opt_callback(); 2895 event.opt_callback();
2862 } catch (ex) { 2896 } catch (ex) {
2863 console.error('Caught exception while inovking callback: ', ex); 2897 console.error('Caught exception while inovking callback: ', ex);
2864 } 2898 }
2865 } 2899 }
2866 // For tests that open the dialog to empty directories, everything 2900 // For tests that open the dialog to empty directories, everything
2867 // is loaded at this point. 2901 // is loaded at this point.
2868 chrome.test.sendMessage('directory-change-complete'); 2902 chrome.test.sendMessage('directory-change-complete');
2869 // PyAuto tests monitor this state by polling this variable 2903 // PyAuto tests monitor this state by polling this variable
2870 self.directoryChanged_ = true; 2904 self.directoryChanged_ = true;
2871 }); 2905 });
2872 }; 2906 };
2873 2907
2874 /** 2908 /**
2909 * Rescans directory later.
2910 * This method should be used if we just want rescan but not actually now.
2911 * This helps us not to flood queue with rescan requests.
2912 *
2913 * @param opt_callback
2914 * @param opt_onError
2915 */
2916 FileManager.prototype.rescanDirectoryLater_ = function(opt_callback,
2917 opt_onError) {
2918 // It might be massive change, so let's note somehow, that we need
2919 // rescanning and then wait some time
2920
2921 if (this.pendingRescanQueue_.length == 0) {
2922 this.pendingRescanQueue_.push({onSuccess:opt_callback,
2923 onError:opt_onError});
2924
2925 // If rescan isn't going to run without
2926 // our interruption, then say that we need to run it
2927 if (!this.rescanRunning_) {
2928 setTimeout(this.rescanDirectory_.bind(this),
2929 SIMULTANEOUS_RESCAN_INTERVAL);
2930 }
2931 }
2932 }
2933
2934
2935 /**
2875 * Rescans the current directory, refreshing the list. It decreases the 2936 * Rescans the current directory, refreshing the list. It decreases the
2876 * probability that two such calls are pending simultaneously. 2937 * probability that two such calls are pending simultaneously.
2877 * 2938 *
2878 * @param {function()} opt_callback Optional function to invoke when the 2939 * This method tries to queue request if rescan is already running, and
2879 * rescan is complete. 2940 * processes this request later. Anyway callback would be called after
2880 * @param {string} delayMS Delay during which next rescanDirectory calls 2941 * processing.
2881 * can happen. 2942 *
2882 */ 2943 * If no rescan is running, then method starts rescanning immediately.
2883
2884 FileManager.prototype.rescanDirectory_ = function(opt_callback, delayMS) {
2885 var self = this;
2886 function done(count) {
2887 // Variable count is introduced because we only want to do callbacks, that
2888 // were in the queue at the moment of rescanDirectoryNow invocation.
2889 while (count--) {
2890 var callback = self.rescanDirectory_.callbacks.shift();
2891 callback();
2892 }
2893 }
2894
2895 // callbacks is a queue of callbacks that need to be called after rescaning
2896 // directory is done. We push callback to the end and pop form the front.
2897 // When we get to actually call rescanDirectoryNow_ we need to remember how
2898 // many callbacks were in a queue at the time, not to call callbacks that
2899 // arrived in the middle of execution (they may depend on rescaning being
2900 // done after they called rescanDirectory_).
2901 if (!this.rescanDirectory_.callbacks)
2902 this.rescanDirectory_.callbacks = [];
2903
2904 if (opt_callback)
2905 this.rescanDirectory_.callbacks.push(opt_callback);
2906
2907 delayMS = delayMS || 100;
2908
2909 // Assumes rescanDirectoryNow_ takes less than 100 ms. If not there is
2910 // a possible overlap between two calls.
2911 if (delayMS < 100)
2912 delayMS = 100;
2913
2914 if (this.rescanDirectory_.handle)
2915 clearTimeout(this.rescanDirectory_.handle);
2916 var currentQueueLength = self.rescanDirectory_.callbacks.length;
2917 this.rescanDirectory_.handle = setTimeout(function () {
2918 self.rescanDirectoryNow_(function() {
2919 done(currentQueueLength);
2920 });
2921 }, delayMS);
2922 };
2923
2924 /**
2925 * Rescans the current directory immediately, refreshing the list. Should NOT
2926 * be used in most cases. Instead use rescanDirectory_.
2927 * 2944 *
2928 * @param {function()} opt_callback Optional function to invoke when the 2945 * @param {function()} opt_callback Optional function to invoke when the
2929 * rescan is complete. 2946 * rescan is complete.
2947 *
2948 * @param {function()} opt_onError Optional function to invoke when the
2949 * rescan fails.
2930 */ 2950 */
2931 FileManager.prototype.rescanDirectoryNow_ = function(opt_callback) { 2951 FileManager.prototype.rescanDirectory_ = function(opt_callback, opt_onError) {
2932 var self = this;
2933 var reader;
2934
2935 function rescanDone() {
2936 metrics.recordTime('ScanDirectory');
2937 if (self.currentDirEntry_.fullPath == DOWNLOADS_DIRECTORY)
2938 metrics.reportCount("DownloadsCount", self.dataModel_.length);
2939 if (opt_callback)
2940 opt_callback();
2941 }
2942
2943 metrics.startInterval('ScanDirectory');
2944
2945 function onReadSome(entries) {
2946 if (entries.length == 0) {
2947 rescanDone();
2948 return;
2949 }
2950
2951 // Splice takes the to-be-spliced-in array as individual parameters,
2952 // rather than as an array, so we need to perform some acrobatics...
2953 var spliceArgs = [].slice.call(entries);
2954
2955 // Hide files that start with a dot ('.').
2956 // TODO(rginda): User should be able to override this. Support for other
2957 // commonly hidden patterns might be nice too.
2958 if (self.filterFiles_) {
2959 spliceArgs = spliceArgs.filter(function(e) {
2960 return e.name.substr(0, 1) != '.';
2961 });
2962 }
2963
2964 spliceArgs.unshift(0, 0); // index, deleteCount
2965 self.dataModel_.splice.apply(self.dataModel_, spliceArgs);
2966
2967 // Keep reading until entries.length is 0.
2968 reader.readEntries(onReadSome);
2969 };
2970
2971 // Updated when a user clicks on the label of a file, used to detect 2952 // Updated when a user clicks on the label of a file, used to detect
2972 // when a click is eligible to trigger a rename. Can be null, or 2953 // when a click is eligible to trigger a rename. Can be null, or
2973 // an object with 'path' and 'date' properties. 2954 // an object with 'path' and 'date' properties.
2974 this.lastLabelClick_ = null; 2955 this.lastLabelClick_ = null;
2975 2956
2976 // Clear the table first. 2957 // Clear the table first.
2977 this.dataModel_.splice(0, this.dataModel_.length); 2958 this.dataModel_.splice(0, this.dataModel_.length);
2978 this.currentList_.selectionModel.clear(); 2959 this.currentList_.selectionModel.clear();
2979 2960
2980 this.updateBreadcrumbs_(); 2961 this.updateBreadcrumbs_();
2981 2962
2982 if (this.currentDirEntry_.fullPath != '/') { 2963 if (this.currentDirEntry_.fullPath != '/') {
2964 // Add current request to pending result list
2965 this.pendingRescanQueue_.push({
2966 onSuccess:opt_callback,
2967 onError:opt_onError
2968 });
2969
2970 if (this.rescanRunning_)
2971 return;
2972
2973 this.rescanRunning_ = true;
2974
2975 // The current list of callbacks is saved and reset. Subsequent
2976 // calls to rescanDirectory_ while we're still pending will be
2977 // saved and will cause an additional rescan to happen after a delay.
2978 var callbacks = this.pendingRescanQueue_;
2979
2980 this.pendingRescanQueue_ = [];
2981
2982 var self = this;
2983 var reader;
2984
2985 function onError() {
2986 if (self.pendingRescanQueue_.length > 0) {
2987 setTimeout(self.rescanDirectory_.bind(self),
2988 SIMULTANEOUS_RESCAN_INTERVAL);
2989 }
2990
2991 self.rescanRunning_ = false;
2992
2993 for (var i= 0; i < callbacks.length; i++) {
2994 if (callbacks[i].onError)
2995 try {
2996 callbacks[i].onError();
2997 } catch (ex) {
2998 console.error('Caught exception while notifying about error: ' +
2999 name, ex);
3000 }
3001 }
3002 }
3003
3004 function onReadSome(entries) {
3005 if (entries.length == 0) {
3006 if (self.pendingRescanQueue_.length > 0) {
3007 setTimeout(self.rescanDirectory_.bind(self),
3008 SIMULTANEOUS_RESCAN_INTERVAL);
3009 }
3010
3011 self.rescanRunning_ = false;
3012 for (var i= 0; i < callbacks.length; i++) {
3013 if (callbacks[i].onSuccess)
3014 try {
3015 callbacks[i].onSuccess();
3016 } catch (ex) {
3017 console.error('Caught exception while notifying about error: ' +
3018 name, ex);
3019 }
3020 }
3021
3022 return;
3023 }
3024
3025 // Splice takes the to-be-spliced-in array as individual parameters,
3026 // rather than as an array, so we need to perform some acrobatics...
3027 var spliceArgs = [].slice.call(entries);
3028
3029 // Hide files that start with a dot ('.').
3030 // TODO(rginda): User should be able to override this. Support for other
3031 // commonly hidden patterns might be nice too.
3032 if (self.filterFiles_) {
3033 spliceArgs = spliceArgs.filter(function(e) {
3034 return e.name.substr(0, 1) != '.';
3035 });
3036 }
3037
3038 spliceArgs.unshift(0, 0); // index, deleteCount
3039 self.dataModel_.splice.apply(self.dataModel_, spliceArgs);
3040
3041 // Keep reading until entries.length is 0.
3042 reader.readEntries(onReadSome, onError);
3043 };
3044
2983 // If not the root directory, just read the contents. 3045 // If not the root directory, just read the contents.
2984 reader = this.currentDirEntry_.createReader(); 3046 reader = this.currentDirEntry_.createReader();
2985 reader.readEntries(onReadSome); 3047 reader.readEntries(onReadSome, onError);
2986 return; 3048 return;
2987 } 3049 }
2988 3050
2989 // Otherwise, use the provided list of root subdirectories, since the 3051 // Otherwise, use the provided list of root subdirectories, since the
2990 // real local filesystem root directory (the one we use outside the 3052 // real local filesystem root directory (the one we use outside the
2991 // harness) can't be enumerated yet. 3053 // harness) can't be enumerated yet.
2992 var spliceArgs = [].slice.call(this.rootEntries_); 3054 var spliceArgs = [].slice.call(this.rootEntries_);
2993 spliceArgs.unshift(0, 0); // index, deleteCount 3055 spliceArgs.unshift(0, 0); // index, deleteCount
2994 self.dataModel_.splice.apply(self.dataModel_, spliceArgs); 3056 this.dataModel_.splice.apply(this.dataModel_, spliceArgs);
2995 3057
2996 rescanDone(); 3058 rescanDone();
2997 }; 3059 };
2998 3060
2999 FileManager.prototype.findListItem_ = function(event) { 3061 FileManager.prototype.findListItem_ = function(event) {
3000 var node = event.srcElement; 3062 var node = event.srcElement;
3001 while (node) { 3063 while (node) {
3002 if (node.tagName == 'LI') 3064 if (node.tagName == 'LI')
3003 break; 3065 break;
3004 node = node.parentNode; 3066 node = node.parentNode;
(...skipping 30 matching lines...) Expand all
3035 if (event.button != 1) 3097 if (event.button != 1)
3036 return; 3098 return;
3037 3099
3038 var li = this.findListItem_(event); 3100 var li = this.findListItem_(event);
3039 if (!li) { 3101 if (!li) {
3040 console.log('li not found', event); 3102 console.log('li not found', event);
3041 return; 3103 return;
3042 } 3104 }
3043 }; 3105 };
3044 3106
3107 FileManager.prototype.onUnload_ = function(event) {
3108 if (this.subscribedOnDirectoryChanges_) {
3109 chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(),
3110 function(result) {
3111 if (!result) {
3112 console.log('Failed to remove file watch');
3113 }
3114 });
3115 }
3116 };
3117
3118 FileManager.prototype.onFileChanged_ = function(event) {
3119 // We receive a lot of events even in folders we are not interested in.
3120 if (event.fileUrl == this.currentDirEntry_.toURL())
3121 this.rescanDirectoryLater_();
3122 };
3123
3045 /** 3124 /**
3046 * Determine whether or not a click should initiate a rename. 3125 * Determine whether or not a click should initiate a rename.
3047 * 3126 *
3048 * Renames can happen on mouse click if the user clicks on a label twice, 3127 * Renames can happen on mouse click if the user clicks on a label twice,
3049 * at least a half second apart. 3128 * at least a half second apart.
3050 */ 3129 */
3051 FileManager.prototype.allowRenameClick_ = function(event, row) { 3130 FileManager.prototype.allowRenameClick_ = function(event, row) {
3052 if (this.dialogType_ != FileManager.DialogType.FULL_PAGE || 3131 if (this.dialogType_ != FileManager.DialogType.FULL_PAGE ||
3053 this.currentDirEntry_.name == '' || 3132 this.currentDirEntry_.name == '' ||
3054 isSystemDirEntry(this.currentDirEntry_)) { 3133 isSystemDirEntry(this.currentDirEntry_)) {
(...skipping 587 matching lines...) Expand 10 before | Expand all | Expand 10 after
3642 }); 3721 });
3643 }, onError); 3722 }, onError);
3644 3723
3645 function onError(err) { 3724 function onError(err) {
3646 console.log('Error while checking free space: ' + err); 3725 console.log('Error while checking free space: ' + err);
3647 setTimeout(doCheck, 1000 * 60); 3726 setTimeout(doCheck, 1000 * 60);
3648 } 3727 }
3649 } 3728 }
3650 } 3729 }
3651 })(); 3730 })();
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/extensions/file_browser_event_router.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698