Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Unified Diff: chrome/browser/resources/file_manager/js/directory_model.js

Issue 10310163: Refactoring file manager: moving volume mounting related code to a separate class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge. Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/file_manager/js/directory_model.js
diff --git a/chrome/browser/resources/file_manager/js/directory_model.js b/chrome/browser/resources/file_manager/js/directory_model.js
index 650f296b43191eb23dc5b1085b403c693f1a57a6..71e0f1bba3311678bf30df63392cab94166145af 100644
--- a/chrome/browser/resources/file_manager/js/directory_model.js
+++ b/chrome/browser/resources/file_manager/js/directory_model.js
@@ -16,8 +16,11 @@ var SHORT_RESCAN_INTERVAL = 100;
* @param {boolean} singleSelection True if only one file could be selected
* at the time.
* @param {MetadataCache} metadataCache The metadata cache service.
+ * @param {VolumeManager} volumeManager The volume manager.
+ * @param {boolean} isGDataEnabled True if GDATA enabled (initial value).
*/
-function DirectoryModel(root, singleSelection, metadataCache) {
+function DirectoryModel(root, singleSelection,
+ metadataCache, volumeManager, isGDataEnabled) {
this.root_ = root;
this.metadataCache_ = metadataCache;
this.fileList_ = new cr.ui.ArrayDataModel([]);
@@ -28,6 +31,7 @@ function DirectoryModel(root, singleSelection, metadataCache) {
this.pendingScan_ = null;
this.rescanTimeout_ = undefined;
this.scanFailures_ = 0;
+ this.gDataEnabled_ = isGDataEnabled;
// DirectoryEntry representing the current directory of the dialog.
this.currentDirEntry_ = root;
@@ -50,11 +54,7 @@ function DirectoryModel(root, singleSelection, metadataCache) {
this.filters_ = {};
this.setFilterHidden(true);
- /**
- * @private
- * @type {Object.<string, boolean>}
- */
- this.volumeReadOnlyStatus_ = {};
+ this.volumeManager_ = volumeManager;
/**
* Directory in which search results are displayed. Not null iff search
@@ -106,19 +106,13 @@ DirectoryModel.DOWNLOADS_DIRECTORY = 'Downloads';
DirectoryModel.GDATA_DIRECTORY = 'drive';
/**
- * GData access mode: disabled (no GData root displayed in the list).
- */
-DirectoryModel.GDATA_ACCESS_DISABLED = 0;
-
-/**
- * GData access mode: lazy (GData root displayed, no content is fetched yet).
- */
-DirectoryModel.GDATA_ACCESS_LAZY = 1;
-
-/**
- * GData access mode: full (GData root displayed, content is available).
+ * Fake entry to be used in currentDirEntry_ when current directory is
+ * unmounted GDATA.
+ * @private
*/
-DirectoryModel.GDATA_ACCESS_FULL = 2;
+DirectoryModel.fakeGDataEntry_ = {
+ fullPath: '/' + DirectoryModel.GDATA_DIRECTORY
+};
/**
* Root path used for displaying gdata content search results.
@@ -141,6 +135,15 @@ DirectoryModel.GDATA_SEARCH_ROOT_COMPONENTS = ['', 'drive', '.search'];
DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype;
/**
+ * Fills the root list and starts tracking changes.
+ */
+DirectoryModel.prototype.start = function() {
+ var volumesChangeHandler = this.onMountChanged_.bind(this);
+ this.volumeManager_.addEventListener('change', volumesChangeHandler);
+ this.updateRoots_();
+};
+
+/**
* @return {cr.ui.ArrayDataModel} Files in the current directory.
*/
DirectoryModel.prototype.getFileList = function() {
@@ -155,6 +158,20 @@ DirectoryModel.prototype.getMetadataCache = function() {
};
/**
+ * Sets whether GDATA appears in the roots list and
+ * if it could be used as current directory.
+ * @param {boolead} enabled True if GDATA enabled.
+ */
+DirectoryModel.prototype.setGDataEnabled = function(enabled) {
+ if (this.gDataEnabled_ == enabled)
+ return;
+ this.gDataEnabled_ = enabled;
+ this.updateRoots_();
+ if (!enabled && this.getCurrentRootType() == DirectoryModel.RootType.GDATA)
+ this.changeDirectory(this.getDefaultDirectory());
+};
+
+/**
* Sort the file list.
* @param {string} sortField Sort field.
* @param {string} sortDirection "asc" or "desc".
@@ -227,7 +244,7 @@ DirectoryModel.prototype.isOnGDataSearchDir = function() {
DirectoryModel.prototype.isPathReadOnly = function(path) {
switch (DirectoryModel.getRootType(path)) {
case DirectoryModel.RootType.REMOVABLE:
- return !!this.volumeReadOnlyStatus_[DirectoryModel.getRootPath(path)];
+ return !!this.volumeManager_.isReadOnly(DirectoryModel.getRootPath(path));
case DirectoryModel.RootType.ARCHIVE:
return true;
case DirectoryModel.RootType.DOWNLOADS:
@@ -369,6 +386,13 @@ DirectoryModel.prototype.getCurrentRootPath = function() {
};
/**
+ * @return {DirectoryModel.RootType} A root type.
+ */
+DirectoryModel.prototype.getCurrentRootType = function() {
+ return DirectoryModel.getRootType(this.currentDirEntry_.fullPath);
+};
+
+/**
* @return {cr.ui.ListSingleSelectionModel} Root list selection model.
*/
DirectoryModel.prototype.getRootsListSelectionModel = function() {
@@ -544,10 +568,6 @@ DirectoryModel.prototype.scan_ = function(callback) {
// Clear the table first.
this.fileList_.splice(0, this.fileList_.length);
cr.dispatchSimpleEvent(this, 'scan-started');
- if (this.currentDirEntry_ == this.unmountedGDataEntry_) {
- onDone();
- return;
- }
this.runningScan_ = this.createScanner_(this.fileList_, onDone);
this.runningScan_.run();
};
@@ -799,16 +819,14 @@ DirectoryModel.prototype.changeDirectory = function(path) {
*/
DirectoryModel.prototype.resolveDirectory = function(path, successCallback,
errorCallback) {
- if (this.unmountedGDataEntry_ &&
- DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) {
- // TODO(kaznacheeev): Currently if path points to some GData subdirectory
- // and GData is not mounted we will change to the fake GData root and
- // ignore the rest of the path. Consider remembering the path and
- // changing to it once GDdata is mounted. This is only relevant for cases
- // when we open the File Manager with an URL pointing to GData (e.g. via
- // a bookmark).
- successCallback(this.unmountedGDataEntry_);
- return;
+ if (DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) {
+ if (!this.isGDataMounted_()) {
+ if (path == DirectoryModel.fakeGDataEntry_.fullPath)
+ successCallback(DirectoryModel.fakeGDataEntry_);
+ else // Subdirectory.
+ errorCallback({ code: FileError.NOT_FOUND_ERR });
+ return;
+ }
}
if (path == '/') {
@@ -866,6 +884,9 @@ DirectoryModel.prototype.changeRoot = function(path) {
*/
DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry,
opt_callback) {
+ if (dirEntry == DirectoryModel.fakeGDataEntry_)
+ this.volumeManager_.mountGData(function() {}, function() {});
+
this.clearSearch_();
var previous = this.currentDirEntry_;
this.currentDirEntry_ = dirEntry;
@@ -888,6 +909,47 @@ DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry,
};
/**
+ * Creates an object wich could say wether directory has changed while it has
+ * been active or not. Designed for long operations that should be canncelled
+ * if the used change current directory.
+ * @return {Object} Created object.
+ */
+DirectoryModel.prototype.createDirectoryChangeTracker = function() {
+ var tracker = {
+ dm_: this,
+ active_: false,
+ hasChanged: false,
+ exceptInitialChange: false,
+
+ start: function() {
+ if (!this.active_) {
+ this.dm_.addEventListener('directory-changed',
+ this.onDirectoryChange_);
+ this.active_ = true;
+ this.hasChanged = false;
+ }
+ },
+
+ stop: function() {
+ if (this.active_) {
+ this.dm_.removeEventListener('directory-changed',
+ this.onDirectoryChange_);
+ active_ = false;
+ }
+ },
+
+ onDirectoryChange_: function(event) {
+ // this == tracker.dm_ here.
+ if (tracker.exceptInitialChange && event.initial)
+ return;
+ tracker.stop();
+ tracker.hasChanged = true;
+ }
+ };
+ return tracker;
+};
+
+/**
* Change the state of the model to reflect the specified path (either a
* file or directory).
*
@@ -905,103 +967,82 @@ DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry,
*/
DirectoryModel.prototype.setupPath = function(path, opt_loadedCallback,
opt_pathResolveCallback) {
- var overridden = false;
- function onExternalDirChange() { overridden = true }
- this.addEventListener('directory-changed', onExternalDirChange);
-
- var resolveCallback = function(exists) {
- this.removeEventListener('directory-changed', onExternalDirChange);
- if (opt_pathResolveCallback)
- opt_pathResolveCallback(baseName, leafName, exists && !overridden);
- }.bind(this);
+ var tracker = this.createDirectoryChangeTracker();
+ tracker.start();
- var changeDirectoryEntry = function(entry, initial, exists, opt_callback) {
- resolveCallback(exists);
- if (!overridden)
- this.changeDirectoryEntry_(initial, entry, opt_callback);
- }.bind(this);
+ var self = this;
+ function resolveCallback(directoryPath, fileName, exists) {
+ tracker.stop();
+ if (!opt_pathResolveCallback)
+ return;
+ opt_pathResolveCallback(directoryPath, fileName,
+ exists && !tracker.hasChanged);
+ }
+
+ function changeDirectoryEntry(directoryEntry, initial, opt_callback) {
+ tracker.stop();
+ if (!tracker.hasChanged)
+ self.changeDirectoryEntry_(initial, directoryEntry, opt_callback);
+ }
var INITIAL = true;
var EXISTS = true;
- // Split the dirname from the basename.
- var ary = path.match(/^(?:(.*)\/)?([^\/]*)$/);
-
- if (!ary) {
- console.warn('Unable to split default path: ' + path);
- changeDirectoryEntry(this.root_, INITIAL, !EXISTS);
- return;
+ function changeToDefault() {
+ var def = self.getDefaultDirectory();
+ self.resolveDirectory(def, function(directoryEntry) {
+ resolveCallback(def, '', !EXISTS);
+ changeDirectoryEntry(directoryEntry, INITIAL);
+ }, function(error) {
+ console.error('Failed to resolve default directory: ' + def, error);
+ resolveCallback('/', '', !EXISTS);
+ });
}
- var baseName = ary[1];
- var leafName = ary[2];
-
- function onLeafFound(baseDirEntry, leafEntry) {
- if (leafEntry.isDirectory) {
- baseName = path;
- leafName = '';
- changeDirectoryEntry(leafEntry, INITIAL, EXISTS);
- return;
- }
-
- // Leaf is an existing file, cd to its parent directory and select it.
- changeDirectoryEntry(baseDirEntry,
- !INITIAL /*HACK*/,
- EXISTS,
- function() {
- this.selectEntry(leafEntry.name);
- if (opt_loadedCallback)
- opt_loadedCallback();
- }.bind(this));
- // TODO(kaznacheev): Fix history.replaceState for the File Browser and
- // change !INITIAL to INITIAL. Passing |false| makes things
- // less ugly for now.
+ function noParentDirectory(error) {
+ console.log('Can\'t resolve parent directory: ' + path, error);
+ changeToDefault();
}
- function onLeafError(baseDirEntry, err) {
- // Usually, leaf does not exist, because it's just a suggested file name.
- if (err.code != FileError.NOT_FOUND_ERR)
- console.log('Unexpected error resolving default leaf: ' + err);
- // |baseDirEntry| would point to a system directory if we are trying
- // to change to a non-existing removable drive or an archive.
- // Try to change to the default directory then.
- if (DirectoryModel.isSystemDirectory(baseDirEntry.fullPath))
- onBaseError(err);
- else
- changeDirectoryEntry(baseDirEntry, INITIAL, !EXISTS);
+ if (DirectoryModel.isSystemDirectory(path)) {
+ changeToDefault();
+ return;
}
- var onBaseError = function(err) {
- console.log('Unexpected error resolving default base "' +
- baseName + '": ' + err);
- if (path != this.getDefaultDirectory()) {
- // Can't find the provided path, let's go to default one instead.
- resolveCallback(!EXISTS);
- if (!overridden)
- this.setupDefaultPath(opt_loadedCallback);
+ this.resolveDirectory(path, function(directoryEntry) {
+ resolveCallback(directoryEntry.fullPath, '', !EXISTS);
+ changeDirectoryEntry(directoryEntry, INITIAL);
+ }, function(error) {
+ // Usually, leaf does not exist, because it's just a suggested file name.
+ var fileExists = error.code == FileError.TYPE_MISMATCH_ERR;
+ if (fileExists || error.code == FileError.NOT_FOUND_ERR) {
+ var nameDelimiter = path.lastIndexOf('/');
+ var parentDirectoryPath = path.substr(0, nameDelimiter);
+ if (DirectoryModel.isSystemDirectory(parentDirectoryPath)) {
+ changeToDefault();
+ return;
+ }
+ self.resolveDirectory(parentDirectoryPath,
+ function(parentDirectoryEntry) {
+ var fileName = path.substr(nameDelimiter + 1);
+ resolveCallback(parentDirectoryEntry.fullPath, fileName, fileExists);
+ changeDirectoryEntry(parentDirectoryEntry,
+ !INITIAL /*HACK*/,
+ function() {
+ self.selectEntry(fileName);
+ if (opt_loadedCallback)
+ opt_loadedCallback();
+ });
+ // TODO(kaznacheev): Fix history.replaceState for the File Browser and
+ // change !INITIAL to INITIAL. Passing |false| makes things
+ // less ugly for now.
+ }, noParentDirectory);
} else {
- // Well, we can't find the downloads dir. Let's just show something,
- // or we will get an infinite recursion.
- changeDirectoryEntry(this.root_, opt_loadedCallback, INITIAL, !EXISTS);
- }
- }.bind(this);
-
- var onBaseFound = function(baseDirEntry) {
- if (!leafName) {
- // Default path is just a directory, cd to it and we're done.
- changeDirectoryEntry(baseDirEntry, INITIAL, !EXISTS);
- return;
+ // Unexpected errors.
+ console.error('Directory resolving error: ', error);
+ changeToDefault();
}
-
- util.resolvePath(this.root_, path,
- onLeafFound.bind(this, baseDirEntry),
- onLeafError.bind(this, baseDirEntry));
- }.bind(this);
-
- var root = this.root_;
- if (!baseName)
- baseName = this.getDefaultDirectory();
- root.getDirectory(baseName, {create: false}, onBaseFound, onBaseError);
+ });
};
/**
@@ -1073,9 +1114,8 @@ DirectoryModel.prototype.prepareSortEntries_ = function(entries, field,
* Get root entries asynchronously.
* @private
* @param {function(Array.<Entry>)} callback Called when roots are resolved.
- * @param {number} gdataAccess One of GDATA_ACCESS_* constants.
*/
-DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) {
+DirectoryModel.prototype.resolveRoots_ = function(callback) {
var groups = {
downloads: null,
archives: null,
@@ -1090,7 +1130,6 @@ DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) {
if (!groups[i])
return;
- self.updateVolumeReadOnlyStatus_(groups.removables);
callback(groups.downloads.
concat(groups.gdata).
concat(groups.archives).
@@ -1103,58 +1142,45 @@ DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) {
done();
}
- function onDownloads(entry) {
- groups.downloads = [entry];
- done();
- }
-
- function onDownloadsError(error) {
- groups.downloads = [];
+ function appendSingle(index, entry) {
+ groups[index] = [entry];
done();
}
- function onGDataMounted(entry) {
- console.log('GData mounted:', entry);
- self.unmountedGDataEntry_ = null;
- groups.gdata = [entry];
+ function onSingleError(index, error, defaultValue) {
+ groups[index] = defailtValue || [];
done();
}
- function onGDataNotMounted(error) {
- console.log('GData not mounted: ' + (error || 'lazy'));
- self.unmountedGDataEntry_ = {
- unmounted: true, // Clients use this field to distinguish a fake root.
- error: error,
- toURL: function() { return '' },
- fullPath: '/' + DirectoryModel.GDATA_DIRECTORY
- };
- groups.gdata = [self.unmountedGDataEntry_];
- done();
+ var root = this.root_;
+ function readSingle(dir, index, opt_defaultValue) {
+ root.getDirectory(dir, { create: false },
+ appendSingle.bind(this, index),
+ onSingleError.bind(this, index, opt_defaultValue));
}
- var root = this.root_;
- root.getDirectory(DirectoryModel.DOWNLOADS_DIRECTORY, { create: false },
- onDownloads, onDownloadsError);
+ readSingle(DirectoryModel.DOWNLOADS_DIRECTORY, 'downloads');
util.readDirectory(root, DirectoryModel.ARCHIVE_DIRECTORY,
append.bind(this, 'archives'));
util.readDirectory(root, DirectoryModel.REMOVABLE_DIRECTORY,
append.bind(this, 'removables'));
- if (gdataAccess == DirectoryModel.GDATA_ACCESS_FULL) {
- root.getDirectory(DirectoryModel.GDATA_DIRECTORY, { create: false },
- onGDataMounted, onGDataNotMounted);
- } else if (gdataAccess == DirectoryModel.GDATA_ACCESS_LAZY) {
- onGDataNotMounted();
+ if (this.gDataEnabled_) {
+ var fake = [DirectoryModel.fakeGDataEntry_];
+ if (this.isGDataMounted_())
+ readSingle(DirectoryModel.GDATA_DIRECTORY, 'gdata', fake);
+ else
+ groups.gdata = fake;
} else {
groups.gdata = [];
}
};
/**
- * @param {function} callback Called when all roots are resolved.
- * @param {number} gdataAccess One of GDATA_ACCESS_* constants.
+ * Updates the roots list.
+ * @private
*/
-DirectoryModel.prototype.updateRoots = function(callback, gdataAccess) {
+DirectoryModel.prototype.updateRoots_ = function() {
var self = this;
this.resolveRoots_(function(rootEntries) {
var dm = self.rootsList_;
@@ -1162,9 +1188,7 @@ DirectoryModel.prototype.updateRoots = function(callback, gdataAccess) {
dm.splice.apply(dm, args);
self.updateRootsListSelection_();
-
- callback();
- }, gdataAccess);
+ });
};
/**
@@ -1192,44 +1216,46 @@ DirectoryModel.prototype.updateRootsListSelection_ = function() {
};
/**
- * @param {Array.<DirectoryEntry>} roots Removable volumes entries.
+ * @return {true} True if GDATA mounted.
* @private
*/
-DirectoryModel.prototype.updateVolumeReadOnlyStatus_ = function(roots) {
- var status = this.volumeReadOnlyStatus_ = {};
- for (var i = 0; i < roots.length; i++) {
- status[roots[i].fullPath] = false;
- chrome.fileBrowserPrivate.getVolumeMetadata(roots[i].toURL(),
- function(systemMetadata, path) {
- status[path] = !!(systemMetadata && systemMetadata.isReadOnly);
- }.bind(null, roots[i].fullPath));
- }
+DirectoryModel.prototype.isGDataMounted_ = function() {
+ return this.volumeManager_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY);
};
/**
- * Prepare the root for the unmount.
- *
- * @param {string} rootPath The path to the root.
+ * Handler for the VolumeManager's event.
+ * @private
*/
-DirectoryModel.prototype.prepareUnmount = function(rootPath) {
- var index = this.findRootsListItem_(rootPath);
- if (index == -1) {
- console.error('Unknown root entry', rootPath);
- return;
- }
- var entry = this.rootsList_.item(index);
+DirectoryModel.prototype.onMountChanged_ = function() {
+ this.updateRoots_();
- // We never need to remove this attribute because even if the unmount fails
- // the onMountCompleted handler calls updateRoots which creates a new entry
- // object for this volume.
- entry.unmounting = true;
-
- // Re-place the entry into the roots data model to force re-rendering.
- this.rootsList_.splice(index, 1, entry);
+ if (this.getCurrentRootType() != DirectoryModel.RootType.GDATA)
+ return;
- if (rootPath == this.rootPath) {
- // TODO(kaznacheev): Consider changing to the most recently used root.
- this.changeDirectory(this.getDefaultDirectory());
+ var mounted = this.isGDataMounted_();
+ if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) {
+ if (mounted) {
+ // Change fake entry to real one and rescan.
+ function onGotDirectory(entry) {
+ if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) {
+ this.currentDirEntry_ = entry;
+ this.rescan();
+ }
+ }
+ this.root_.getDirectory('/' + DirectoryModel.GDATA_DIRECTORY, {},
+ onGotDirectory.bind(this));
+ }
+ } else if (!mounted) {
+ // Current entry unmounted. replace with fake one.
+ if (this.currentDirEntry_.fullPath ==
+ DirectoryModel.fakeGDataEntry_.fullPath) {
+ // Replace silently and rescan.
+ this.currentDirEntry_ = DirectoryModel.fakeGDataEntry_;
+ this.rescan();
+ } else {
+ this.changeDirectoryEntry_(false, DirectoryModel.fakeGDataEntry_);
+ }
}
};
@@ -1238,6 +1264,7 @@ DirectoryModel.prototype.prepareUnmount = function(rootPath) {
* @return {boolean} If current directory is system.
*/
DirectoryModel.isSystemDirectory = function(path) {
+ path = path.replace(/\/+$/, '');
return path == '/' + DirectoryModel.REMOVABLE_DIRECTORY ||
path == '/' + DirectoryModel.ARCHIVE_DIRECTORY;
};
@@ -1359,7 +1386,7 @@ DirectoryModel.getRootName = function(path) {
/**
* @param {string} path A path.
- * @return {string} A root type.
+ * @return {DirectoryModel.RootType} A root type.
*/
DirectoryModel.getRootType = function(path) {
function isTop(dir) {
@@ -1450,6 +1477,12 @@ DirectoryModel.Scanner.prototype.cancel = function() {
* Start scanner.
*/
DirectoryModel.Scanner.prototype.run = function() {
+ if (this.dir_ == DirectoryModel.fakeGDataEntry_) {
+ if (!this.cancelled_)
+ this.successCallback_();
+ return;
+ }
+
metrics.startInterval('DirectoryScan');
this.reader_ = this.dir_.createReader();
@@ -1506,3 +1539,128 @@ DirectoryModel.Scanner.prototype.recordMetrics_ = function() {
metrics.recordMediumCount('DownloadsCount', this.list_.length);
}
};
+
+/**
+ * @constructor
+ * @param {DirectoryEntry} root Root entry.
+ * @param {DirectoryModel} directoryModel Model to watch.
+ * @param {VolumeManager} volumeManager Manager to watch.
+ */
+function FileWatcher(root, directoryModel, volumeManager) {
+ this.root_ = root;
+ this.dm_ = directoryModel;
+ this.vm_ = volumeManager;
+ this.watchedDirectoryEntry_ = null;
+ this.updateWatchedDirectoryBound_ =
+ this.updateWatchedDirectory_.bind(this);
+ this.onFileChangedBound_ =
+ this.onFileChanged_.bind(this);
+}
+
+/**
+ * Starts watching.
+ */
+FileWatcher.prototype.start = function() {
+ chrome.fileBrowserPrivate.onFileChanged.addListener(
+ this.onFileChangedBound_);
+
+ this.dm_.addEventListener('directory-changed',
+ this.updateWatchedDirectoryBound_);
+ this.vm_.addEventListener('changed',
+ this.updateWatchedDirectoryBound_);
+
+ this.updateWatchedDirectory_();
+};
+
+/**
+ * Stops watching (must be called before page unload).
+ */
+FileWatcher.prototype.stop = function() {
+ chrome.fileBrowserPrivate.onFileChanged.removeListener(
+ this.onFileChangedBound_);
+
+ this.dm_.removeEventListener('directory-changed',
+ this.updateWatchedDirectoryBound_);
+ this.vm_.removeEventListener('changed',
+ this.updateWatchedDirectoryBound_);
+
+ if (this.watchedDirectoryEntry_)
+ this.changeWatchedEntry(null);
+};
+
+/**
+ * @param {Object} event chrome.fileBrowserPrivate.onFileChanged event.
+ * @private
+ */
+FileWatcher.prototype.onFileChanged_ = function(event) {
+ if (encodeURI(event.fileUrl) == this.watchedDirectoryEntry_.toURL())
+ this.onFileInWatchedDirectoryChanged();
+};
+
+/**
+ * Called when file in the watched directory changed.
+ */
+FileWatcher.prototype.onFileInWatchedDirectoryChanged = function() {
+ this.dm_.rescanLater();
+};
+
+/**
+ * Called when directory changed or volumes mounted/unmounted.
+ * @private
+ */
+FileWatcher.prototype.updateWatchedDirectory_ = function() {
+ var current = this.watchedDirectoryEntry_;
+ switch (this.dm_.getCurrentRootType()) {
+ case DirectoryModel.RootType.GDATA:
+ if (!this.vm_.isMounted('/' + DirectoryModel.GDATA_DIRECTORY))
+ break;
+ case DirectoryModel.RootType.DOWNLOADS:
+ case DirectoryModel.RootType.REMOVABLE:
+ if (!current || current.fullPath != this.dm_.getCurrentDirPath()) {
+ // TODO(serya): Changed in readonly removable directoried don't
+ // need to be tracked.
+ this.root_.getDirectory(this.dm_.getCurrentDirPath(), {},
+ this.changeWatchedEntry.bind(this),
+ this.changeWatchedEntry.bind(this, null));
+ }
+ return;
+ }
+ if (current)
+ this.changeWatchedEntry(null);
+};
+
+/**
+ * @param {Entry?} entry Null if no directory need to be watched or
+ * directory to watch.
+ */
+FileWatcher.prototype.changeWatchedEntry = function(entry) {
+ if (this.watchedDirectoryEntry_) {
+ chrome.fileBrowserPrivate.removeFileWatch(
+ this.watchedDirectoryEntry_.toURL(),
+ function(result) {
+ if (!result) {
+ console.log('Failed to remove file watch');
+ }
+ });
+ }
+ this.watchedDirectoryEntry_ = entry;
+
+ if (this.watchedDirectoryEntry_) {
+ chrome.fileBrowserPrivate.addFileWatch(
+ this.watchedDirectoryEntry_.toURL(),
+ function(result) {
+ if (!result) {
+ console.log('Failed to add file watch');
+ if (this.watchedDirectoryEntry_ == entry)
+ this.watchedDirectoryEntry_ = null;
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * @return {DirectoryEntry} Current watched directory entry.
+ */
+FileWatcher.prototype.getWatchedDirectoryEntry = function() {
+ return this.watchedDirectoryEntry_;
+};

Powered by Google App Engine
This is Rietveld 408576698