Chromium Code Reviews| 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..56b24dfd03d02c17b5ad65a80598e537f6eae593 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,67 @@ DirectoryModel.prototype.changeDirectoryEntry_ = function(initial, dirEntry, |
| */ |
| DirectoryModel.prototype.setupPath = function(path, opt_loadedCallback, |
|
dgozman
2012/05/18 14:17:19
Two problems here:
1. (optional) This method still
SeRya
2012/05/21 08:06:36
Fixed. Tested open and save dialogs with existing
|
| 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); |
| + opt_pathResolveCallback = null; |
| + } |
| + |
| + function changeDirectoryEntry(directoryEntry, initial, exists, opt_callback) { |
| + tracker.stop(); |
| + resolveCallback(directoryEntry.fillPath, '', true); |
|
dgozman
2012/05/18 14:17:19
typo: fillPath
|
| + if (!tracker.hasChanged) |
| + self.changeDirectoryEntry_(initial, directoryEntry, opt_callback); |
| + } |
| var INITIAL = true; |
|
dgozman
2012/05/18 14:17:19
Add todo to rename initial to something like put_i
SeRya
2012/05/21 08:06:36
I don't think this is a good idea. For now we inde
|
| 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) { |
| + changeDirectoryEntry(directoryEntry, INITIAL, !EXISTS); |
| + }, function(error) { |
| + console.error('Failed to resolve default directory: ' + def, error); |
| + resolveCallback(path, '', false); |
|
dgozman
2012/05/18 14:17:19
false -> !EXISTS
SeRya
2012/05/21 08:06:36
Done.
|
| + }); |
| } |
| - 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 netherFileNorEntry(error) { |
|
dgozman
2012/05/18 14:17:19
typo: nether -> neither
|
| + console.log('Can\'t get file not entry: ' + path, error); |
| + changeToDefault(); |
| } |
| - function onLeafError(baseDirEntry, err) { |
| - // Usually, leaf does not exist, because it's just a suggested file name. |
|
dgozman
2012/05/18 14:17:19
See this comment.
|
| - 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); |
| + function getParentFiled(error) { |
|
dgozman
2012/05/18 14:17:19
typo: getParentFiled -> getParentFailed
|
| + console.error('Failed to get file parent: ', error); |
| + changeToDefault(); |
| } |
| - 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); |
| - } 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; |
| - } |
| - |
| - 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); |
| + this.resolveDirectory(path, function(directoryEntry) { |
| + changeDirectoryEntry(directoryEntry, INITIAL, EXISTS); |
| + }, function(error) { |
| + self.root_.getFile(path, {}, function(fileEntry) { |
| + fileEntry.getParent(function(parentDirectoryEntry) { |
| + // Path is a file and parent directory resolved successfully. |
| + resolveCallback(parentDirectoryEntry.fullPath, fileEntry.name, EXISTS); |
| + changeDirectoryEntry(parentDirectoryEntry, |
| + !INITIAL /*HACK*/, |
| + EXISTS, |
| + function() { |
| + self.selectEntry(fileEntry.name); |
| + if (opt_loadedCallback) |
| + opt_loadedCallback(); |
| + }); |
| + }, getParentFiled); |
| + }, netherFileNorEntry); |
| + }); |
| }; |
| /** |
| @@ -1073,9 +1099,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 +1115,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 +1127,45 @@ DirectoryModel.prototype.resolveRoots_ = function(callback, gdataAccess) { |
| 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 onGDataMounted(entry) { |
| - console.log('GData mounted:', entry); |
| - self.unmountedGDataEntry_ = null; |
| - groups.gdata = [entry]; |
| - 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 +1173,7 @@ DirectoryModel.prototype.updateRoots = function(callback, gdataAccess) { |
| dm.splice.apply(dm, args); |
| self.updateRootsListSelection_(); |
| - |
| - callback(); |
| - }, gdataAccess); |
| + }); |
| }; |
| /** |
| @@ -1192,44 +1201,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_); |
| + } |
| } |
| }; |
| @@ -1359,7 +1370,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 +1461,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 +1523,114 @@ 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.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)); |
| + } |
| +}; |