OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 })(); |
OLD | NEW |