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

Unified Diff: chrome/browser/resources/file_manager/js/volume_manager.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/volume_manager.js
diff --git a/chrome/browser/resources/file_manager/js/volume_manager.js b/chrome/browser/resources/file_manager/js/volume_manager.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6633fa0f3972b043874aaa7279e97289370199a
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/volume_manager.js
@@ -0,0 +1,447 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * VolumeManager is responsible for tracking list of mounted volumes.
+ *
+ * @constructor
+ * @extends {cr.EventTarget}
+ */
+function VolumeManager() {
+ /**
+ * The list of archives requested to mount. We will show contents once
+ * archive is mounted, but only for mounts from within this filebrowser tab.
+ * @type {Object.<string, Object>}
+ * @private
+ */
+ this.requests_ = {};
+
+ /**
+ * @type {Object.<string, Object>}
+ * @private
+ */
+ this.mountedVolumes_ = {};
+
+ this.initMountPoints_();
+ chrome.fileBrowserPrivate.onMountCompleted.addListener(
+ this.onMountCompleted_.bind(this));
+ this.gDataStatus_ = VolumeManager.GDataStatus.UNMOUNTED;
+}
+
+/**
+ * VolumeManager extends cr.EventTarget.
+ */
+VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
+
+/**
+ * @enum
+ */
+VolumeManager.Error = {
+ /* Internal errors */
+ NOT_MOUNTED: 'not_mounted',
+ TIMEOUT: 'timeout',
+
+ /* System events */
+ UNKNOWN: 'error_unknown',
+ INTERNAL: 'error_internal',
+ UNKNOWN_FILESYSTEM: 'error_unknown_filesystem',
+ UNSUPPORTED_FILESYSTEM: 'error_unsuported_filesystem',
+ INVALID_ARCHIVE: 'error_invalid_archive',
+ LIBCROS_MISSING: 'error_libcros_missing',
+ AUTHENTICATION: 'error_authentication',
+ PATH_UNMOUNTED: 'error_path_unmounted'
+};
+
+/**
+ * @enum
+ */
+VolumeManager.GDataStatus = {
+ UNMOUNTED: 'unmounted',
+ MOUNTING: 'mounting',
+ ERROR: 'error',
+ MOUNTED: 'mounted'
+};
+
+/**
+ * Time in milliseconds that we wait a respone for. If no response on
+ * mount/unmount received the request supposed failed.
+ */
+VolumeManager.TIMEOUT = 15 * 60 * 1000;
+
+/**
+ * Delay in milliseconds GDATA changes its state from |UNMOUNTED| to
+ * |MOUNTING|. Used to display progress in the UI.
+ */
+VolumeManager.MOUNTING_DELAY = 500;
+
+/**
+ * @return {VolumeManager} Singleton instance.
+ */
+VolumeManager.getInstance = function() {
+ return VolumeManager.instance_ = VolumeManager.instance_ ||
+ new VolumeManager();
+};
+
+/**
+ * @param {VolumeManager.GDataStatus} newStatus New GDATA status.
+ * @private
+ */
+VolumeManager.prototype.setGDataStatus_ = function(newStatus) {
+ if (this.gDataStatus_ != newStatus) {
+ this.gDataStatus_ = newStatus;
+ cr.dispatchSimpleEvent(this, 'gdata-status-changed');
+ }
+};
+
+/**
+ * @return {VolumeManager.GDataStatus} Current GDATA status.
+ */
+VolumeManager.prototype.getGDataStatus = function() {
+ return this.gDataStatus_;
+};
+
+/**
+ * @param {string} mountPath Volume root path.
+ * @return {boolean} True if mounted.
+ */
+VolumeManager.prototype.isMounted = function(mountPath) {
+ this.validateMountPath_(mountPath);
+ return mountPath in this.mountedVolumes_;
+};
+
+/**
+ * Initialized mount points.
+ * @private
+ */
+VolumeManager.prototype.initMountPoints_ = function() {
+ var mountedVolumes = [];
+ var self = this;
+ var index = 0;
+ function step(mountPoints) {
+ if (index < mountPoints.length) {
+ var info = mountPoints[index];
+ if (info.mountType == 'gdata')
+ console.error('GData is not expected initially mounted');
+ var error = info.mountCondition ? 'error_' + info.mountCondition : '';
+ function onVolumeInfo(volume) {
+ mountedVolumes.push(volume);
+ index++;
+ step(mountPoints);
+ }
+ self.makeVolumeInfo_('/' + info.mountPath, error, onVolumeInfo);
+ } else {
+ for (var i = 0; i < mountedVolumes.length; i++) {
+ var volume = mountedVolumes[i];
+ self.mountedVolumes_[volume.mountPath] = volume;
+ }
+ if (mountedVolumes.length > 0)
+ cr.dispatchSimpleEvent(self, 'change');
+ }
+ }
+
+ chrome.fileBrowserPrivate.getMountPoints(step);
+};
+
+/**
+ * Event handler called when some volume was mounted or unmouted.
+ * @param {MountCompletedEvent} event Received event.
+ * @private
+ */
+VolumeManager.prototype.onMountCompleted_ = function(event) {
+ if (event.eventType == 'mount') {
+ if (event.mountPath) {
+ var requestKey = this.makeRequestKey_(
+ 'mount', event.mountType, event.sourcePath);
+ var error = event.status == 'success' ? '' : event.status;
+ this.makeVolumeInfo_(event.mountPath, error, function(volume) {
+ this.mountedVolumes_[volume.mountPath] = volume;
+ this.finishRequest_(requestKey, event.status, event.mountPath);
+ cr.dispatchSimpleEvent(this, 'change');
+ }.bind(this));
+ } else {
+ console.log('No mount path');
+ this.finishRequest_(requestKey, event.status);
+ }
+ } else if (event.eventType == 'unmount') {
+ var mountPath = event.mountPath;
+ this.validateMountPath_(mountPath);
+ var status = event.status;
+ if (status == VolumeManager.Error.PATH_UNMOUNTED) {
+ console.log('Volume already unmounted: ', mountPath);
+ status = 'success';
+ }
+ var requestKey = this.makeRequestKey_('unmount', '', event.mountPath);
+ var requested = requestKey in this.requests_;
+ if (event.status == 'success' && !requested &&
+ mountPath in this.mountedVolumes_) {
+ console.log('Mounted volume without a request: ', mountPath);
+ var e = new cr.Event('externally-unmounted');
+ e.mountPath = mountPath;
+ this.dispatchEvent(e);
+ }
+ this.finishRequest_(requestKey, status);
+
+ if (event.status == 'success') {
+ delete this.mountedVolumes_[mountPath];
+ cr.dispatchSimpleEvent(this, 'change');
+ }
+ }
+
+ if (event.mountType == 'gdata') {
+ if (event.status == 'success') {
+ if (event.eventType == 'mount')
+ this.setGDataStatus_(VolumeManager.GDataStatus.MOUNTED);
+ else if (event.eventType == 'unmount')
+ this.setGDataStatus_(VolumeManager.GDataStatus.UMOUNTED);
+ }
+ }
+};
+
+/**
+ * @param {string} mountPath Path to the volume.
+ * @param {VolumeManager?} error Mounting error if any.
+ * @param {function(Object)} callback Result acceptor.
+ * @private
+ */
+VolumeManager.prototype.makeVolumeInfo_ = function(
+ mountPath, error, callback) {
+ if (error)
+ this.validateError_(error);
+ this.validateMountPath_(mountPath);
+ function onVolumeMetadata(metadata) {
+ callback({
+ mountPath: mountPath,
+ error: error,
+ readonly: !!metadata && metadata.isReadOnly
+ });
+ }
+ chrome.fileBrowserPrivate.getVolumeMetadata(
+ util.makeFilesystemUrl(mountPath), onVolumeMetadata);
+};
+
+/**
+ * Creates string to match mount events with requests.
+ * @param {string} requestType 'mount' | 'unmount'.
+ * @param {string} mountType 'device' | 'file' | 'network' | 'gdata'.
+ * @param {string} mountOrSourcePath Source path provided by API after
+ * resolving mount request or mountPath for unmount request.
+ * @return {string} Key for |this.requests_|.
+ * @private
+ */
+VolumeManager.prototype.makeRequestKey_ = function(requestType,
+ mountType,
+ mountOrSourcePath) {
+ return requestType + ':' + mountType + ':' + mountOrSourcePath;
+};
+
+
+/**
+ * @param {Function} successCallback Success callback.
+ * @param {Function} errorCallback Error callback.
+ */
+VolumeManager.prototype.mountGData = function(successCallback, errorCallback) {
+ if (this.getGDataStatus() == VolumeManager.GDataStatus.ERROR) {
+ this.setGDataStatus_(VolumeManager.GDataStatus.UNMOUNTED);
+ }
+ var self = this;
+ var timeout = setTimeout(function() {
+ if (self.getGDataStatus() == VolumeManager.GDataStatus.UNMOUNTED)
+ self.setGDataStatus_(VolumeManager.GDataStatus.MOUNTING);
+ timeout = null;
+ }, VolumeManager.MOUNTING_DELAY);
+ this.mount_('', 'gdata', function(mountPath) {
+ if (timeout !== null)
+ clearTimeout(timeout);
+ successCallback(mountPath);
+ }, function(error) {
+ if (self.getGDataStatus() != VolumeManager.GDataStatus.MOUNTED)
+ self.setGDataStatus_(VolumeManager.GDataStatus.ERROR);
+ if (timeout != null)
+ clearTimeout(timeout);
+ errorCallback(error);
+ });
+};
+
+/**
+ * @param {string} fullPath Path to the archive file.
+ * @param {Function} successCallback Success callback.
+ * @param {Function} errorCallback Error callback.
+ */
+VolumeManager.prototype.mountArchive = function(fullPath, successCallback,
+ errorCallback) {
+ this.mount_(util.makeFilesystemUrl(fullPath),
+ 'file', successCallback, errorCallback);
+};
+
+/**
+ * Unmounts volume.
+ * @param {string} mountPath Volume mounted path.
+ * @param {Function} successCallback Success callback.
+ * @param {Function} errorCallback Error callback.
+ */
+VolumeManager.prototype.unmount = function(mountPath,
+ successCallback,
+ errorCallback) {
+ this.validateMountPath_(mountPath);
+ var volumeInfo = this.mountedVolumes_[mountPath];
+ if (!volumeInfo) {
+ errorCallback(VolumeManager.Error.NOT_MOUNTED);
+ return;
+ }
+
+ chrome.fileBrowserPrivate.removeMount(util.makeFilesystemUrl(mountPath));
+ var requestKey = this.makeRequestKey_('unmount', '', volumeInfo.mountPath);
+ this.startRequest_(requestKey, successCallback, errorCallback);
+};
+
+/**
+ * @param {string} mountPath Volume mounted path.
+ * @return {VolumeManager.Error?} Returns mount error code
+ * or undefined if no error.
+ */
+VolumeManager.prototype.getMountError = function(mountPath) {
+ return this.getVolumeInfo_(mountPath).error;
+};
+
+/**
+ * @param {string} mountPath Volume mounted path.
+ * @return {boolean} True if volume at |mountedPath| is mounted but not usable.
+ */
+VolumeManager.prototype.isUnreadable = function(mountPath) {
+ var error = this.getMountError(mountPath);
+ return error == VolumeManager.Error.UNKNOWN_FILESYSTEM ||
+ error == VolumeManager.Error.UNSUPPORTED_FILESYSTEM;
+};
+
+/**
+ * @param {string} mountPath Volume mounted path.
+ * @return {boolean} True if volume at |mountedPath| is read only.
+ */
+VolumeManager.prototype.isReadOnly = function(mountPath) {
+ return !!this.getVolumeInfo_(mountPath).readonly;
+};
+
+/**
+ * Helper method.
+ * @param {string} mountPath Volume mounted path.
+ * @return {Object} Structure created in |startRequest_|.
+ * @private
+ */
+VolumeManager.prototype.getVolumeInfo_ = function(mountPath) {
+ this.validateMountPath_(mountPath);
+ return this.mountedVolumes_[mountPath] || {};
+};
+
+/**
+ * @param {string} url URL for for |fileBrowserPrivate.addMount|.
+ * @param {'gdata'|'file'} mountType Mount type for
+ * |fileBrowserPrivate.addMount|.
+ * @param {Function} successCallback Success callback.
+ * @param {Function} errorCallback Error callback.
+ * @private
+ */
+VolumeManager.prototype.mount_ = function(url, mountType,
+ successCallback, errorCallback) {
+ chrome.fileBrowserPrivate.addMount(url, mountType, {},
+ function(sourcePath) {
+ console.log('Mount request: url=' + url + '; mountType=' + mountType +
+ '; sourceUrl=' + sourcePath);
+ var requestKey = this.makeRequestKey_('mount', mountType, sourcePath);
+ this.startRequest_(requestKey, successCallback, errorCallback);
+ }.bind(this));
+};
+
+/**
+ * @param {sting} key Key produced by |makeRequestKey_|.
+ * @param {Function} successCallback To be called when request finishes
+ * successfully.
+ * @param {Function} errorCallback To be called when request fails.
+ * @private
+ */
+VolumeManager.prototype.startRequest_ = function(key,
+ successCallback, errorCallback) {
+ if (key in this.requests_) {
+ var request = this.requests_[key];
+ request.successCallbacks.push(successCallback);
+ request.errorCallbacks.push(errorCallback);
+ } else {
+ this.requests_[key] = {
+ successCallbacks: [successCallback],
+ errorCallbacks: [errorCallback],
+
+ timeout: setTimeout(this.onTimeout_.bind(this, key),
+ VolumeManager.TIMEOUT)
+ };
+ }
+};
+
+/**
+ * Called if no response received in |TIMEOUT|.
+ * @param {sting} key Key produced by |makeRequestKey_|.
+ * @private
+ */
+VolumeManager.prototype.onTimeout_ = function(key) {
+ this.invokeRequestCallbacks_(this.requests_[key],
+ VolumeManager.Error.TIMEOUT);
+ delete this.requests_[key];
+};
+
+/**
+ * @param {sting} key Key produced by |makeRequestKey_|.
+ * @param {VolumeManager.Error|'success'} status Status received from the API.
+ * @param {string} opt_mountPath Mount path.
+ * @private
+ */
+VolumeManager.prototype.finishRequest_ = function(key, status, opt_mountPath) {
+ var request = this.requests_[key];
+ if (!request)
+ return;
+
+ clearTimeout(request.timeout);
+ this.invokeRequestCallbacks_(request, status, opt_mountPath);
+ delete this.requests_[key];
+};
+
+/**
+ * @param {object} request Structure created in |startRequest_|.
+ * @param {VolumeManager.Error|string} status If status == 'success'
+ * success callbacks are called.
+ * @param {string} opt_mountPath Mount path. Required if success.
+ * @private
+ */
+VolumeManager.prototype.invokeRequestCallbacks_ = function(request, status,
+ opt_mountPath) {
+ function callEach(callbacks, self, args) {
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i].apply(self, args);
+ }
+ }
+ if (status == 'success') {
+ callEach(request.successCallbacks, this, [opt_mountPath]);
+ } else {
+ this.validateError_(status);
+ callEach(request.errorCallbacks, this, [status]);
+ }
+};
+
+/**
+ * @param {VolumeManager.Error} error Status string iusually received from API.
+ * @private
+ */
+VolumeManager.prototype.validateError_ = function(error) {
+ for (var i in VolumeManager.Error) {
+ if (error == VolumeManager.Error[i])
+ return;
+ }
+ throw new Error('Invalid mount error: ', error);
+};
+
+/**
+ * @param {string} mountPath Mount path.
+ * @private
+ */
+VolumeManager.prototype.validateMountPath_ = function(mountPath) {
+ if (!/^\/(((archive|removable)\/[^\/]+)|drive|Downloads)$/.test(mountPath))
+ throw new Error('Invalid mount path: ', mountPath);
+};

Powered by Google App Engine
This is Rietveld 408576698