Index: chrome/browser/resources/file_manager/js/file_manager.js |
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js |
index f2a8921aa2601d327710729222e4de961116744a..79d35318dc977aec74be226882eef84eabdf526d 100644 |
--- a/chrome/browser/resources/file_manager/js/file_manager.js |
+++ b/chrome/browser/resources/file_manager/js/file_manager.js |
@@ -554,7 +554,27 @@ FileManager.prototype = { |
// all paste tasks are complete. |
this.pasteSuccessCallbacks_ = []; |
- this.setupCurrentDirectory_(); |
+ var path = this.getPathFromUrlOrParams_(); |
+ if (path && |
+ DirectoryModel.getRootType(path) == DirectoryModel.RootType.GDATA) { |
+ // We are opening on a GData path. Mount GData and show |
+ // "Loading Google Docs" message until the directory content loads. |
+ this.dialogContainer_.setAttribute('unmounted', true); |
+ this.initGData_(true /* dirChanged */); |
+ // This is a one-time handler (will be nulled out on the first call). |
+ this.setupCurrentDirectoryPostponed_ = function(event) { |
+ this.directoryModel_.removeEventListener('directory-changed', |
+ this.setupCurrentDirectoryPostponed_); |
+ this.setupCurrentDirectoryPostponed_ = null; |
+ if (event) // If called as an event handler just exit silently. |
+ return; |
+ this.setupCurrentDirectory_(false /* blankWhileOpeningAFile */); |
+ }.bind(this); |
+ this.directoryModel_.addEventListener('directory-changed', |
+ this.setupCurrentDirectoryPostponed_); |
+ } else { |
+ this.setupCurrentDirectory_(true /* blankWhileOpeningAFile */); |
+ } |
this.summarizeSelection_(); |
@@ -637,6 +657,7 @@ FileManager.prototype = { |
this.spinner_ = this.dialogDom_.querySelector('.spinner'); |
this.showSpinner_(false); |
this.butter_ = this.dialogDom_.querySelector('.butter-bar'); |
+ this.unmountedPanel_ = this.dialogDom_.querySelector('.unmounted-panel'); |
cr.ui.Table.decorate(this.table_); |
cr.ui.Grid.decorate(this.grid_); |
@@ -802,22 +823,98 @@ FileManager.prototype = { |
this.rootsList_.dataModel = this.directoryModel_.rootsList; |
this.directoryModel_.updateRoots(function() { |
self.rootsList_.endBatchUpdates(); |
- }); |
+ }, false); |
}; |
- FileManager.prototype.initGData_ = function() { |
+ /** |
+ * @param {boolean} dirChanged True if we just changed to GData directory, |
+ * False if "Retry" button clicked. |
+ */ |
+ FileManager.prototype.initGData_ = function(dirChanged) { |
+ this.initGDataUnmountedPanel_(); |
+ |
+ this.unmountedPanel_.removeAttribute('error'); |
+ if (dirChanged) { |
+ // When changing to GData directory we want to see a clear panel. |
+ this.unmountedPanel_.removeAttribute('retry'); |
+ if (this.gdataLoadingTimer_ ) { // Show immediately if already loading. |
+ this.unmountedPanel_.setAttribute('loading', true); |
+ } else { |
+ this.unmountedPanel_.removeAttribute('loading'); |
+ setTimeout(function() { |
+ if (this.gdataLoadingTimer_) { // Still loading. |
+ this.unmountedPanel_.setAttribute('loading', true); |
+ } |
+ }.bind(this), 500); |
+ } |
+ } else { |
+ // When retrying we do not hide "Retry" and "Learn more". |
+ this.unmountedPanel_.setAttribute('loading', true); |
+ } |
+ |
+ // If the user changed to another directory and then back to GData we |
+ // re-enter this method while the timer is still active. In this case |
+ // we only update the UI but do not request the mount again. |
+ if (this.gdataLoadingTimer_) |
+ return; |
+ |
metrics.startInterval('Load.GData'); |
chrome.fileBrowserPrivate.addMount('', 'gdata', {}); |
- if (this.gdataMountTimer_) { |
- clearTimeout(this.gdataMountTimer_); |
+ |
+ // This timer could fire before the mount succeeds. We will silently |
+ // replace the error message with the correct directory contents. |
+ this.gdataLoadingTimer_ = setTimeout(function() { |
+ this.gdataLoadingTimer_ = null; |
+ this.onGDataUnreachable_('GData load timeout'); |
+ }.bind(this), |
+ 10 * 1000) ; |
+ }; |
+ |
+ FileManager.prototype.clearGDataLoadingTimer_ = function(message) { |
+ if (this.gdataLoadingTimer_) { |
+ clearTimeout(this.gdataLoadingTimer_); |
+ this.gdataLoadingTimer_ = null; |
} |
- this.gdataMountTimer_ = setTimeout(function() { |
- this.gdataMountTimer_ = null; |
- if (this.isOnGData()) { |
- // TODO(kaznacheev): show the message in the file list space. |
- this.alert.show('Could not connect to GData'); |
- } |
- }.bind(this), 10 * 1000); |
+ }; |
+ |
+ FileManager.prototype.onGDataUnreachable_ = function(message) { |
+ console.warn(message); |
+ if (this.isOnGData()) { |
+ this.unmountedPanel_.removeAttribute('loading'); |
+ this.unmountedPanel_.setAttribute('error', true); |
+ this.unmountedPanel_.setAttribute('retry', true); |
+ } |
+ }; |
+ |
+ FileManager.prototype.initGDataUnmountedPanel_ = function() { |
+ if (this.unmountedPanel_.firstElementChild) |
+ return; |
+ |
+ var loading = this.document_.createElement('div'); |
+ loading.className = 'gdata loading'; |
+ loading.textContent = strf('GDATA_LOADING', str('GDATA_PRODUCT_NAME')); |
+ this.unmountedPanel_.appendChild(loading); |
+ |
+ var error = this.document_.createElement('div'); |
+ error.className = 'gdata error'; |
+ error.textContent = strf('GDATA_CANNOT_REACH', str('GDATA_PRODUCT_NAME')); |
+ this.unmountedPanel_.appendChild(error); |
+ |
+ var retry = this.document_.createElement('button'); |
+ retry.className = 'gdata retry'; |
+ retry.textContent = str('GDATA_RETRY'); |
+ retry.onclick = this.initGData_.bind(this, false /* retry */); |
+ this.unmountedPanel_.appendChild(retry); |
+ |
+ var learnMore = this.document_.createElement('div'); |
+ learnMore.className = 'gdata learn-more'; |
+ this.unmountedPanel_.appendChild(learnMore); |
+ |
+ var learnMoreLink = this.document_.createElement('a'); |
+ learnMoreLink.textContent = str('GDATA_LEARN_MORE'); |
+ learnMoreLink.href = 'javascript://'; // TODO: Set a proper link URL. |
+ learnMoreLink.className = 'gdata learn-more'; |
+ learnMore.appendChild(learnMoreLink); |
}; |
/** |
@@ -1518,6 +1615,12 @@ FileManager.prototype = { |
errorCallback); |
}; |
+ FileManager.prototype.getPathFromUrlOrParams_ = function() { |
+ return location.hash ? // Location hash has the highest priority. |
+ decodeURI(location.hash.substr(1)) : |
+ this.params_.defaultPath; |
+ }; |
+ |
/** |
* Restores current directory and may be a selected item after page load (or |
* reload) or popping a state (after click on back/forward). If location.hash |
@@ -1525,84 +1628,86 @@ FileManager.prototype = { |
* will be restored. defaultPath primarily is used with save/open dialogs. |
* Default path may also contain a file name. Freshly opened file manager |
* window has neither. |
+ * |
+ * @param {boolean} blankWhileOpeningAFile |
*/ |
- FileManager.prototype.setupCurrentDirectory_ = function() { |
- |
- if (location.hash) { |
- // Location hash has the highest priority. |
- var path = decodeURI(location.hash.substr(1)); |
- |
- // In the FULL_PAGE mode if the path points to a file we might have |
- // to invoke a task after selecting it. |
- if (this.dialogType_ == FileManager.DialogType.FULL_PAGE) { |
- // To prevent the file list flickering for a moment before the action |
- // is executed we hide it under a white div. |
- var shade = this.document_.createElement('div'); |
+ FileManager.prototype.setupCurrentDirectory_ = |
+ function(blankWhileOpeningAFile) { |
+ var path = this.getPathFromUrlOrParams_(); |
+ |
+ if (!path) { |
+ this.directoryModel_.setupDefaultPath(); |
+ return; |
+ } |
+ |
+ // In the FULL_PAGE mode if the hash path points to a file we might have |
+ // to invoke a task after selecting it. |
+ // If the file path is in params_ we only want to select the file. |
+ if (location.hash && |
+ this.dialogType_ == FileManager.DialogType.FULL_PAGE) { |
+ // To prevent the file list flickering for a moment before the action |
+ // is executed we hide it under a white div. |
+ var shade; |
+ if (blankWhileOpeningAFile) { |
+ shade = this.document_.createElement('div'); |
shade.className = 'overlay-pane'; |
shade.style.backgroundColor = 'white'; |
this.document_.body.appendChild(shade); |
- function removeShade() { shade.parentNode.removeChild(shade) } |
- |
- // Keep track of whether the path is identified as an existing leaf |
- // node. Note that onResolve is guaranteed to be called (exactly once) |
- // before onLoadedActivateLeaf. |
- var foundLeaf = true; |
- function onResolve(baseName, leafName, exists) { |
- if (!exists || leafName == '') { |
- // Non-existent file or a directory. Remove the shade immediately. |
- removeShade(); |
- foundLeaf = false; |
- } |
+ } |
+ function removeShade() { |
+ if (shade) |
+ shade.parentNode.removeChild(shade); |
+ } |
+ |
+ // Keep track of whether the path is identified as an existing leaf |
+ // node. Note that onResolve is guaranteed to be called (exactly once) |
+ // before onLoadedActivateLeaf. |
+ var foundLeaf = true; |
+ function onResolve(baseName, leafName, exists) { |
+ if (!exists || leafName == '') { |
+ // Non-existent file or a directory. Remove the shade immediately. |
+ removeShade(); |
+ foundLeaf = false; |
} |
+ } |
- // TODO(kaznacheev): refactor dispatchDefaultTask to accept an array |
- // of urls instead of a selection. This will remove the need to wait |
- // until the selection is done. |
- var self = this; |
- function onLoadedActivateLeaf() { |
- if (foundLeaf) { |
- // There are 3 ways we can get here: |
- // 1. Invoked from file_manager_util::ViewFile. This can only |
- // happen for 'gallery' and 'mount-archive' actions. |
- // 2. Reloading a Gallery page. Must be an image or a video file. |
- // 3. A user manually entered a URL pointing to a file. |
- if (FileType.isImageOrVideo(path)) { |
- self.dispatchInternalTask_('gallery', self.selection.urls); |
- } else if (FileType.getMediaType(path) == 'archive') { |
- self.dispatchInternalTask_('mount-archive', self.selection.urls); |
- } else { |
- // Manually entered path, do nothing, remove the shade ASAP. |
- removeShade(); |
- return; |
- } |
- setTimeout(removeShade, 1000); |
+ // TODO(kaznacheev): refactor dispatchDefaultTask to accept an array |
+ // of urls instead of a selection. This will remove the need to wait |
+ // until the selection is done. |
+ var self = this; |
+ function onLoadedActivateLeaf() { |
+ if (foundLeaf) { |
+ // There are 3 ways we can get here: |
+ // 1. Invoked from file_manager_util::ViewFile. This can only |
+ // happen for 'gallery' and 'mount-archive' actions. |
+ // 2. Reloading a Gallery page. Must be an image or a video file. |
+ // 3. A user manually entered a URL pointing to a file. |
+ if (FileType.isImageOrVideo(path)) { |
+ self.dispatchInternalTask_('gallery', self.selection.urls); |
+ } else if (FileType.getMediaType(path) == 'archive') { |
+ self.dispatchInternalTask_('mount-archive', self.selection.urls); |
+ } else { |
+ // Manually entered path, do nothing, remove the shade ASAP. |
+ removeShade(); |
+ return; |
} |
+ setTimeout(removeShade, 1000); |
} |
- this.directoryModel_.setupPath(path, onLoadedActivateLeaf, onResolve); |
- |
- return; |
} |
- |
- this.directoryModel_.setupPath(path); |
+ this.directoryModel_.setupPath(path, onLoadedActivateLeaf, onResolve); |
return; |
} |
- if (this.params_.defaultPath) { |
- var path = this.params_.defaultPath; |
- if (this.dialogType_ == FileManager.DialogType.SELECT_SAVEAS_FILE) { |
- this.directoryModel_.setupPath(path, undefined, |
- function(basePath, leafName) { |
- this.filenameInput_.value = leafName; |
- this.selectDefaultPathInFilenameInput_(); |
- }.bind(this)); |
- return; |
- } |
- |
- this.directoryModel_.setupPath(path); |
+ if (this.dialogType_ == FileManager.DialogType.SELECT_SAVEAS_FILE) { |
+ this.directoryModel_.setupPath(path, undefined, |
+ function(basePath, leafName) { |
+ this.filenameInput_.value = leafName; |
+ this.selectDefaultPathInFilenameInput_(); |
+ }.bind(this)); |
return; |
} |
- this.directoryModel_.setupDefaultPath(); |
+ this.directoryModel_.setupPath(path); |
}; |
/** |
@@ -2516,17 +2621,16 @@ FileManager.prototype = { |
// We don't have tasks, so try the default browser action. |
// We only do that for single selection to avoid confusion. |
- function callback(success) { |
+ var callback = function(success) { |
if (!success && selection.entries.length == 1) |
this.alert.showHtml( |
unescape(selection.entries[0].name), |
strf('NO_ACTION_FOR_FILE', NO_ACTION_FOR_FILE_URL), |
function() {}); |
- } |
+ }.bind(this); |
this.executeIfAvailable_(selection.urls, function(urls) { |
- chrome.fileBrowserPrivate.viewFiles(urls, 'default', |
- callback.bind(this)); |
+ chrome.fileBrowserPrivate.viewFiles(urls, 'default', callback); |
}); |
} |
}; |
@@ -2598,10 +2702,7 @@ FileManager.prototype = { |
if (event && event.mountType == 'gdata') { |
metrics.recordInterval('Load.GData'); |
- if (this.gdataMountTimer_) { |
- clearTimeout(this.gdataMountTimer_); |
- this.gdataMountTimer_ = null; |
- } |
+ console.log("GData mounted"); |
if (event.status == 'success') { |
this.gdataMounted_ = true; |
this.gdataMountInfo_ = { |
@@ -2610,13 +2711,27 @@ FileManager.prototype = { |
"mountType": event.mountType, |
"mountCondition": event.status |
}; |
- if (this.isOnGData()) { |
+ // Not calling clearGDataLoadingTimer_ here because we want to keep |
+ // "Loading Google Docs" message until the directory loads. It is OK if |
+ // the timer fires after the mount because onDirectoryChanged_ will hide |
+ // the unmounted panel. |
+ if (this.setupCurrentDirectoryPostponed_) { |
+ this.setupCurrentDirectoryPostponed_(false /* execute */); |
+ } else if (this.isOnGData() && |
+ this.directoryModel_.currentEntry.unmounted) { |
// We are currently on an unmounted GData directory, force a rescan. |
changeDirectoryTo = this.directoryModel_.rootPath; |
} |
} else { |
this.gdataMounted_ = false; |
this.gdataMountInfo_ = null; |
+ this.clearGDataLoadingTimer_(); |
+ this.onGDataUnreachable_('GData mount failed: ' + event.status); |
+ if (this.setupCurrentDirectoryPostponed_) { |
+ this.setupCurrentDirectoryPostponed_(true /* cancel */); |
+ // Change to unmounted GData root. |
+ changeDirectoryTo = '/' + DirectoryModel.GDATA_DIRECTORY; |
+ } |
} |
} |
@@ -2672,7 +2787,7 @@ FileManager.prototype = { |
if (changeDirectoryTo) { |
self.directoryModel_.changeDirectory(changeDirectoryTo); |
} |
- }); |
+ }, self.gdataMounted_); |
}); |
}; |
@@ -3562,7 +3677,7 @@ FileManager.prototype = { |
this.watchedDirectoryUrl_ = null; |
} |
- if (event.newDirEntry.fullPath != '/') { |
+ if (event.newDirEntry.fullPath != '/' && !event.newDirEntry.unmounted) { |
this.watchedDirectoryUrl_ = event.newDirEntry.toURL(); |
chrome.fileBrowserPrivate.addFileWatch(this.watchedDirectoryUrl_, |
function(result) { |
@@ -3575,11 +3690,15 @@ FileManager.prototype = { |
this.updateVolumeMetadata_(); |
+ if (event.newDirEntry.unmounted) |
+ this.dialogContainer_.setAttribute('unmounted', true); |
+ else |
+ this.dialogContainer_.removeAttribute('unmounted'); |
+ |
if (this.isOnGData()) { |
this.dialogContainer_.setAttribute('gdata', true); |
- if (!this.requestedGDataMount_) { // Request GData mount only once. |
- this.requestedGDataMount_ = true; |
- this.initGData_(); |
+ if (event.newDirEntry.unmounted) { |
+ this.initGData_(true /* directory changed */); |
} |
} else { |
this.dialogContainer_.removeAttribute('gdata'); |