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 89747a4c7b3822cf40c6b3c83dc1c1cb2342bc9f..4ba537ff14b5442ba98b916df5cab70ff2578181 100644 |
--- a/chrome/browser/resources/file_manager/js/directory_model.js |
+++ b/chrome/browser/resources/file_manager/js/directory_model.js |
@@ -19,7 +19,8 @@ var SHORT_RESCAN_INTERVAL = 100; |
* (regardless of its mounts status). |
* @param {MetadataCache} metadataCache The metadata cache service. |
*/ |
-function DirectoryModel(root, singleSelection, showGData, metadataCache) { |
+function DirectoryModel(root, singleSelection, showGData, |
+ metadataCache, volumeManager) { |
this.root_ = root; |
this.metadataCache_ = metadataCache; |
this.fileList_ = new cr.ui.ArrayDataModel([]); |
@@ -54,11 +55,18 @@ function DirectoryModel(root, singleSelection, showGData, metadataCache) { |
this.filters_ = {}; |
this.setFilterHidden(true); |
- /** |
- * @private |
- * @type {Object.<string, boolean>} |
- */ |
- this.volumeReadOnlyStatus_ = {}; |
+ if (this.showGData_) { |
+ this.unmountedGDataEntry_ = { |
+ unmouted: true, |
+ fullPath: '/' + DirectoryModel.GDATA_DIRECTORY |
+ }; |
+ } |
+ |
+ this.volumeManager_ = volumeManager; |
+ var volumesChangeHandler = this.onMountChanged_.bind(this); |
+ this.updateRoots(function() { |
Vladislav Kaznacheev
2012/05/15 11:11:54
Calling updateRoots from the constructor somehow f
|
+ volumeManager.addEventListener('change', volumesChangeHandler); |
+ }); |
} |
/** |
@@ -92,7 +100,11 @@ DirectoryModel.DOWNLOADS_DIRECTORY = 'Downloads'; |
/** |
* The name of the gdata provider directory. |
*/ |
-DirectoryModel.GDATA_DIRECTORY = 'drive'; |
+DirectoryModel.GDATA_DIRECTORY = 'gdata'; // TODO(serya): return 'drive' |
+ |
+DirectoryModel.fakeGDataEntry_ = { |
+ fullPath: '/' + DirectoryModel.GDATA_DIRECTORY |
+}; |
/** |
* DirectoryModel extends cr.EventTarget. |
@@ -172,7 +184,7 @@ DirectoryModel.prototype.isReadOnly = 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: |
@@ -313,6 +325,10 @@ DirectoryModel.prototype.getCurrentRootPath = function() { |
return DirectoryModel.getRootPath(this.currentDirEntry_.fullPath); |
}; |
+DirectoryModel.prototype.getCurrentRootType = function() { |
+ return DirectoryModel.getRootType(this.currentDirEntry_.fullPath); |
+}; |
+ |
/** |
* @return {cr.ui.ListSingleSelectionModel} Root list selection model. |
*/ |
@@ -488,7 +504,7 @@ 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_) { |
+ if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { |
onDone(); |
return; |
} |
@@ -691,16 +707,20 @@ DirectoryModel.prototype.changeDirectory = function(path) { |
*/ |
DirectoryModel.prototype.resolveDirectory = function(path, successCallback, |
errorCallback) { |
- if (this.unmountedGDataEntry_ && |
- DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) { |
+ if (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 (!this.isGDataMounted_()) { |
+ if (path == DirectoryModel.fakeGDataEntry_.fullPath) |
+ successCallback(DirectoryModel.fakeGDataEntry_); |
+ else // Subdirectory. |
+ errorCallback({ code: FileError.NOT_FOUND_ERR }); |
+ return; |
+ } |
} |
if (path == '/') { |
@@ -776,6 +796,51 @@ DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry, |
e.newDirEntry = dirEntry; |
e.initial = initial; |
this.dispatchEvent(e); |
+ |
+ if (dirEntry == DirectoryModel.fakeGDataEntry_) { |
+ this.volumeManager_.mountGData(function() {}, function() {}); |
+ } |
+}; |
+ |
+/** |
+ * Creates an object wich could cay wether directory has changed while it has |
Vladislav Kaznacheev
2012/05/15 11:11:54
"cay", "canncelled" = typo?
|
+ * 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) { |
+ if (tracker.exceptInitialChange && event.initial) |
+ return; |
+ // This is incorrect here. |
+ tracker.stop(); |
+ tracker.hasChange = true; |
+ } |
+ }; |
+ return tracker; |
}; |
/** |
@@ -796,19 +861,21 @@ 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 tracker = this.createDirectoryChangeTracker(); |
+ tracker.start(); |
var resolveCallback = function(exists) { |
- this.removeEventListener('directory-changed', onExternalDirChange); |
- if (opt_pathResolveCallback) |
- opt_pathResolveCallback(baseName, leafName, exists && !overridden); |
+ tracker.stop(); |
+ if (opt_pathResolveCallback) { |
+ opt_pathResolveCallback(baseName, leafName, |
+ exists && !tracker.hasChanged); |
+ } |
}.bind(this); |
var changeDirectoryEntry = function(entry, initial, exists, opt_callback) { |
+ tracker.stop(); |
resolveCallback(exists); |
- if (!overridden) |
+ if (!tracker.hasChanged) |
this.changeDirectoryEntry_(initial, entry, opt_callback); |
}.bind(this); |
@@ -862,7 +929,7 @@ DirectoryModel.prototype.setupPath = function(path, opt_loadedCallback, |
if (path != this.getDefaultDirectory()) { |
// Can't find the provided path, let's go to default one instead. |
resolveCallback(!EXISTS); |
- if (!overridden) |
+ if (!tracker.hasChanged) |
this.setupDefaultPath(opt_loadedCallback); |
} else { |
// Well, we can't find the downloads dir. Let's just show something, |
@@ -958,9 +1025,8 @@ DirectoryModel.prototype.prepareSortEntries_ = function(entries, field, |
* Get root entries asynchronously. |
* @private |
* @param {function(Array.<Entry>)} callback Called when roots are resolved. |
- * @param {boolean} resolveGData See comment for updateRoots. |
*/ |
-DirectoryModel.prototype.resolveRoots_ = function(callback, resolveGData) { |
+DirectoryModel.prototype.resolveRoots_ = function(callback) { |
var groups = { |
downloads: null, |
archives: null, |
@@ -975,7 +1041,6 @@ DirectoryModel.prototype.resolveRoots_ = function(callback, resolveGData) { |
if (!groups[i]) |
return; |
- self.updateVolumeReadOnlyStatus_(groups.removables); |
callback(groups.downloads. |
concat(groups.gdata). |
concat(groups.archives). |
@@ -988,60 +1053,43 @@ DirectoryModel.prototype.resolveRoots_ = function(callback, resolveGData) { |
done(); |
} |
- function onDownloads(entry) { |
- groups.downloads = [entry]; |
+ function appendSingle(index, entry) { |
+ groups[index] = [entry]; |
done(); |
} |
- function onDownloadsError(error) { |
- groups.downloads = []; |
+ function onSingleError(index, error, defaultValue) { |
+ groups[index] = defailtValue || []; |
done(); |
} |
- function onGData(entry) { |
- console.log('GData found:', entry); |
- self.unmountedGDataEntry_ = null; |
- groups.gdata = [entry]; |
- done(); |
- } |
- |
- function onGDataError(error) { |
- console.log('GData error: ' + error); |
- self.unmountedGDataEntry_ = { |
- unmounted: true, // Clients use this field to distinguish a fake root. |
- toURL: function() { return '' }, |
- fullPath: '/' + DirectoryModel.GDATA_DIRECTORY |
- }; |
- groups.gdata = [self.unmountedGDataEntry_]; |
- done(); |
+ var root = this.root_; |
+ function readSingle(dir, index, defaultValue) { |
+ root.getDirectory(dir, { create: false }, |
+ appendSingle.bind(this, index), |
+ onSingleError.bind(this, index, 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 (this.showGData_) { |
- if (resolveGData) { |
- root.getDirectory(DirectoryModel.GDATA_DIRECTORY, { create: false }, |
- onGData, onGDataError); |
- } else { |
- onGDataError('lazy mount'); |
- } |
+ var fake = [DirectoryModel.fakeGDataEntry_]; |
+ if (this.isGDataMounted_()) |
+ readSingle(DirectoryModel.GDATA_DIRECTORY, 'gdata', fake); |
+ else |
+ groups.gdata = fake; |
} else { |
groups.gdata = []; |
} |
}; |
/** |
-* @param {function} opt_callback Called when all roots are resolved. |
-* @param {boolean} opt_resolveGData If true GData should be resolved for real, |
-* If false a stub entry should be created. |
+* @param {functioni?} opt_callback Called when all roots are resolved. |
*/ |
-DirectoryModel.prototype.updateRoots = function(opt_callback, |
- opt_resolveGData) { |
+DirectoryModel.prototype.updateRoots = function(opt_callback) { |
var self = this; |
this.resolveRoots_(function(rootEntries) { |
var dm = self.rootsList_; |
@@ -1049,10 +1097,8 @@ DirectoryModel.prototype.updateRoots = function(opt_callback, |
dm.splice.apply(dm, args); |
self.updateRootsListSelection_(); |
- |
- if (opt_callback) |
- opt_callback(); |
- }, opt_resolveGData); |
+ opt_callback && opt_callback(); |
+ }); |
}; |
/** |
@@ -1079,45 +1125,38 @@ DirectoryModel.prototype.updateRootsListSelection_ = function() { |
this.rootsListSelection_.selectedIndex = this.findRootsListItem_(rootPath); |
}; |
-/** |
- * @param {Array.<DirectoryEntry>} roots Removable volumes entries. |
- * @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. |
- */ |
-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. |
+ this.root_.getDirectory('/' + DirectoryModel.GDATA_DIRECTORY, {}, |
+ function(entry) { |
+ if (this.currentDirEntry_ == DirectoryModel.fakeGDataEntry_) { |
+ this.currentDirEntry_ = entry; |
+ this.rescan(); |
+ } |
+ }.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_); |
+ } |
} |
}; |
@@ -1282,3 +1321,86 @@ DirectoryModel.Scanner.prototype.recordMetrics_ = function() { |
metrics.recordMediumCount('DownloadsCount', this.list_.length); |
} |
}; |
+ |
+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); |
+} |
+ |
+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_(); |
+}; |
+ |
+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); |
+}; |
+ |
+FileWatcher.prototype.onFileChanged_ = function(event) { |
+ if (encodeURI(event.fileUrl) == this.watchedDirectoryEntry_.toURL()) |
+ this.dm_.rescanLater(); |
+}; |
+ |
+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()) { |
+ this.root_.getDirectory(this.dm_.getCurrentDirPath(), {}, |
+ this.changeWatchedEntry.bind(this)); |
+ } |
+ break; |
+ } |
+ if (current) |
+ this.changeWatchedEntry(null); |
+}; |
+ |
+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'); |
+ this.watchedDirectoryEntry_ = null; |
+ } |
+ }.bind(this)); |
+ } |
+}; |