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