1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 'use strict'; 6 7 /** 8 * Watches for changes in the tracked directory, including local metadata 9 * changes. 10 * 11 * @param {MetadataCache} metadataCache Instance of MetadataCache. 12 * @extends {cr.EventTarget} 13 * @constructor 14 */ 15 function FileWatcher(metadataCache) { 16 this.queue_ = new AsyncUtil.Queue(); 17 this.metadataCache_ = metadataCache; 18 this.watchedDirectoryEntry_ = null; 19 20 this.onDirectoryChangedBound_ = this.onDirectoryChanged_.bind(this); 21 chrome.fileBrowserPrivate.onDirectoryChanged.addListener( 22 this.onDirectoryChangedBound_); 23 24 this.filesystemMetadataObserverId_ = null; 25 this.thumbnailMetadataObserverId_ = null; 26 this.driveMetadataObserverId_ = null; 27 } 28 29 /** 30 * FileWatcher extends cr.EventTarget. 31 */ 32 FileWatcher.prototype.__proto__ = cr.EventTarget.prototype; 33 34 /** 35 * Stops watching (must be called before page unload). 36 */ 37 FileWatcher.prototype.dispose = function() { 38 chrome.fileBrowserPrivate.onDirectoryChanged.removeListener( 39 this.onDirectoryChangedBound_); 40 if (this.watchedDirectoryEntry_) 41 this.resetWatchedEntry_(function() {}, function() {}); 42 }; 43 44 /** 45 * Called when a file in the watched directory is changed. 46 * @param {Event} event Change event. 47 * @private 48 */ 49 FileWatcher.prototype.onDirectoryChanged_ = function(event) { 50 if (event.directoryUrl == this.watchedDirectoryEntry_.toURL()) { 51 var e = new cr.Event('watcher-directory-changed'); 52 this.dispatchEvent(e); 53 } 54 }; 55 56 /** 57 * Called when general metadata in the watched directory has been changed. 58 * 59 * @param {Array.<string>} urls Array of urls. 60 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 61 * properties. 62 * @private 63 */ 64 FileWatcher.prototype.onFilesystemMetadataChanged_ = function( 65 urls, properties) { 66 this.dispatchMetadataEvent_('filesystem', urls, properties); 67 }; 68 69 /** 70 * Called when thumbnail metadata in the watched directory has been changed. 71 * 72 * @param {Array.<string>} urls Array of urls. 73 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 74 * properties. 75 * @private 76 */ 77 FileWatcher.prototype.onThumbnailMetadataChanged_ = function( 78 urls, properties) { 79 this.dispatchMetadataEvent_('thumbnail', urls, properties); 80 }; 81 82 /** 83 * Called when drive metadata in the watched directory has been changed. 84 * 85 * @param {Array.<string>} urls Array of urls. 86 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 87 * properties. 88 * @private 89 */ 90 FileWatcher.prototype.onDriveMetadataChanged_ = function( 91 urls, properties) { 92 this.dispatchMetadataEvent_('drive', urls, properties); 93 }; 94 95 /** 96 * Dispatches an event about detected change in metadata within the tracked 97 * directory. 98 * 99 * @param {string} type Type of the metadata change. 100 * @param {Array.<string>} urls Array of urls. 101 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 102 * properties. 103 * @private 104 */ 105 FileWatcher.prototype.dispatchMetadataEvent_ = function( 106 type, urls, properties) { 107 var e = new cr.Event('watcher-metadata-changed'); 108 e.metadataType = type; 109 e.urls = urls; 110 e.properties = properties; 111 this.dispatchEvent(e); 112 }; 113 114 /** 115 * Changes the watched directory. In case of a fake entry, the watch is 116 * just released, since there is no reason to track a fake directory. 117 * 118 * @param {!DirectoryEntry|!Object} entry Directory entry to be tracked, or the 119 * fake entry. 120 * @param {function()} callback Completion callback. 121 */ 122 FileWatcher.prototype.changeWatchedDirectory = function(entry, callback) { 123 if (entry && entry.toURL) { 124 this.changeWatchedEntry_( 125 entry, 126 callback, 127 function() { 128 console.error( 129 'Unable to change the watched directory to: ' + entry.toURL()); 130 callback(); 131 }); 132 } else { 133 this.resetWatchedEntry_( 134 callback, 135 function() { 136 console.error('Unable to reset the watched directory.'); 137 callback(); 138 }); 139 } 140 }; 141 142 /** 143 * Resets the watched entry to the passed directory. 144 * 145 * @param {function()} onSuccess Success callback. 146 * @param {function()} onError Error callback. 147 * @private 148 */ 149 FileWatcher.prototype.resetWatchedEntry_ = function(onSuccess, onError) { 150 // Run the tasks in the queue to avoid races. 151 this.queue_.run(function(callback) { 152 // Release the watched directory. 153 if (this.watchedDirectoryEntry_) { 154 chrome.fileBrowserPrivate.removeFileWatch( 155 this.watchedDirectoryEntry_.toURL(), 156 function(result) { 157 this.watchedDirectoryEntry_ = null; 158 if (result) 159 onSuccess(); 160 else 161 onError(); 162 callback(); 163 }); 164 this.metadataCache_.removeObserver(this.filesystemMetadataObserverId_); 165 this.metadataCache_.removeObserver(this.thumbnailMetadataObserverId_); 166 this.metadataCache_.removeObserver(this.driveMetadataObserverId_); 167 } else { 168 onSuccess(); 169 callback(); 170 } 171 }.bind(this)); 172 }; 173 174 /** 175 * Sets the watched entry to the passed directory. 176 * 177 * @param {!DirectoryEntry} entry Directory to be watched. 178 * @param {function()} onSuccess Success callback. 179 * @param {function()} onError Error callback. 180 * @private 181 */ 182 FileWatcher.prototype.changeWatchedEntry_ = function( 183 entry, onSuccess, onError) { 184 var setEntryClosure = function() { 185 // Run the tasks in the queue to avoid races. 186 this.queue_.run(function(callback) { 187 chrome.fileBrowserPrivate.addFileWatch( 188 entry.toURL(), 189 function(result) { 190 if (!result) { 191 this.watchedDirectoryEntry_ = null; 192 onError(); 193 } else { 194 this.watchedDirectoryEntry_ = entry; 195 onSuccess(); 196 } 197 callback(); 198 }.bind(this)); 199 this.filesystemMetadataObserverId_ = this.metadataCache_.addObserver( 200 entry, 201 MetadataCache.CHILDREN, 202 'filesystem', 203 this.onFilesystemMetadataChanged_.bind(this)); 204 this.thumbnailMetadataObserverId_ = this.metadataCache_.addObserver( 205 entry, 206 MetadataCache.CHILDREN, 207 'thumbnail', 208 this.onThumbnailMetadataChanged_.bind(this)); 209 this.driveMetadataObserverId_ = this.metadataCache_.addObserver( 210 entry, 211 MetadataCache.CHILDREN, 212 'drive', 213 this.onDriveMetadataChanged_.bind(this)); 214 }.bind(this)); 215 }.bind(this); 216 217 // Reset the watched directory first, then set the new watched directory. 218 this.resetWatchedEntry_(setEntryClosure, onError); 219 }; 220 221 /** 222 * @return {DirectoryEntry} Current watched directory entry. 223 */ 224 FileWatcher.prototype.getWatchedDirectoryEntry = function() { 225 return this.watchedDirectoryEntry_; 226 }; 227