Home | History | Annotate | Download | only in js
      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