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 */ 15function 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 */ 32FileWatcher.prototype.__proto__ = cr.EventTarget.prototype; 33 34/** 35 * Stops watching (must be called before page unload). 36 */ 37FileWatcher.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 */ 49FileWatcher.prototype.onDirectoryChanged_ = function(event) { 50 if (this.watchedDirectoryEntry_ && 51 event.entry.toURL() === this.watchedDirectoryEntry_.toURL()) { 52 var e = new Event('watcher-directory-changed'); 53 this.dispatchEvent(e); 54 } 55}; 56 57/** 58 * Called when general metadata in the watched directory has been changed. 59 * 60 * @param {Array.<Entry>} entries Array of entries. 61 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 62 * properties. 63 * @private 64 */ 65FileWatcher.prototype.onFilesystemMetadataChanged_ = function( 66 entries, properties) { 67 this.dispatchMetadataEvent_('filesystem', entries, properties); 68}; 69 70/** 71 * Called when thumbnail metadata in the watched directory has been changed. 72 * 73 * @param {Array.<Entry>} entries Arrray of entries. 74 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 75 * properties. 76 * @private 77 */ 78FileWatcher.prototype.onThumbnailMetadataChanged_ = function( 79 entries, properties) { 80 this.dispatchMetadataEvent_('thumbnail', entries, properties); 81}; 82 83/** 84 * Called when drive metadata in the watched directory has been changed. 85 * 86 * @param {Array.<Entry>} entries Array of entries. 87 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 88 * properties. 89 * @private 90 */ 91FileWatcher.prototype.onDriveMetadataChanged_ = function( 92 entries, properties) { 93 this.dispatchMetadataEvent_('drive', entries, properties); 94}; 95 96/** 97 * Dispatches an event about detected change in metadata within the tracked 98 * directory. 99 * 100 * @param {string} type Type of the metadata change. 101 * @param {Array.<Entry>} entries Array of entries. 102 * @param {Object.<string, Object>} properties Map from entry URLs to metadata 103 * properties. 104 * @private 105 */ 106FileWatcher.prototype.dispatchMetadataEvent_ = function( 107 type, entries, properties) { 108 var e = new Event('watcher-metadata-changed'); 109 e.metadataType = type; 110 e.entries = entries; 111 e.properties = properties; 112 this.dispatchEvent(e); 113}; 114 115/** 116 * Changes the watched directory. In case of a fake entry, the watch is 117 * just released, since there is no reason to track a fake directory. 118 * 119 * @param {!DirectoryEntry|!Object} entry Directory entry to be tracked, or the 120 * fake entry. 121 * @param {function()} callback Completion callback. 122 */ 123FileWatcher.prototype.changeWatchedDirectory = function(entry, callback) { 124 if (entry && entry.toURL) { 125 this.changeWatchedEntry_( 126 entry, 127 callback, 128 function() { 129 console.error( 130 'Unable to change the watched directory to: ' + entry.toURL()); 131 callback(); 132 }); 133 } else { 134 this.resetWatchedEntry_( 135 callback, 136 function() { 137 console.error('Unable to reset the watched directory.'); 138 callback(); 139 }); 140 } 141}; 142 143/** 144 * Resets the watched entry to the passed directory. 145 * 146 * @param {function()} onSuccess Success callback. 147 * @param {function()} onError Error callback. 148 * @private 149 */ 150FileWatcher.prototype.resetWatchedEntry_ = function(onSuccess, onError) { 151 // Run the tasks in the queue to avoid races. 152 this.queue_.run(function(callback) { 153 // Release the watched directory. 154 if (this.watchedDirectoryEntry_) { 155 chrome.fileBrowserPrivate.removeFileWatch( 156 this.watchedDirectoryEntry_.toURL(), 157 function(result) { 158 this.watchedDirectoryEntry_ = null; 159 if (result) 160 onSuccess(); 161 else 162 onError(); 163 callback(); 164 }.bind(this)); 165 this.metadataCache_.removeObserver(this.filesystemMetadataObserverId_); 166 this.metadataCache_.removeObserver(this.thumbnailMetadataObserverId_); 167 this.metadataCache_.removeObserver(this.driveMetadataObserverId_); 168 } else { 169 onSuccess(); 170 callback(); 171 } 172 }.bind(this)); 173}; 174 175/** 176 * Sets the watched entry to the passed directory. 177 * 178 * @param {!DirectoryEntry} entry Directory to be watched. 179 * @param {function()} onSuccess Success callback. 180 * @param {function()} onError Error callback. 181 * @private 182 */ 183FileWatcher.prototype.changeWatchedEntry_ = function( 184 entry, onSuccess, onError) { 185 var setEntryClosure = function() { 186 // Run the tasks in the queue to avoid races. 187 this.queue_.run(function(callback) { 188 chrome.fileBrowserPrivate.addFileWatch( 189 entry.toURL(), 190 function(result) { 191 if (!result) { 192 this.watchedDirectoryEntry_ = null; 193 onError(); 194 } else { 195 this.watchedDirectoryEntry_ = entry; 196 onSuccess(); 197 } 198 callback(); 199 }.bind(this)); 200 this.filesystemMetadataObserverId_ = this.metadataCache_.addObserver( 201 entry, 202 MetadataCache.CHILDREN, 203 'filesystem', 204 this.onFilesystemMetadataChanged_.bind(this)); 205 this.thumbnailMetadataObserverId_ = this.metadataCache_.addObserver( 206 entry, 207 MetadataCache.CHILDREN, 208 'thumbnail', 209 this.onThumbnailMetadataChanged_.bind(this)); 210 this.driveMetadataObserverId_ = this.metadataCache_.addObserver( 211 entry, 212 MetadataCache.CHILDREN, 213 'drive', 214 this.onDriveMetadataChanged_.bind(this)); 215 }.bind(this)); 216 }.bind(this); 217 218 // Reset the watched directory first, then set the new watched directory. 219 this.resetWatchedEntry_(setEntryClosure, onError); 220}; 221 222/** 223 * @return {DirectoryEntry} Current watched directory entry. 224 */ 225FileWatcher.prototype.getWatchedDirectoryEntry = function() { 226 return this.watchedDirectoryEntry_; 227}; 228