• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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