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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * VolumeManager is responsible for tracking list of mounted volumes.
7 *
8 * @constructor
9 * @extends {cr.EventTarget}
10 */
11 function VolumeManager() {
12 /**
13 * The list of archives requested to mount. We will show contents once
14 * archive is mounted, but only for mounts from within this filebrowser tab.
15 * @type {Object.<string, Object>}
16 * @private
17 */
18 this.requests_ = {};
19
20 /**
21 * @type {Object.<string, Object>}
22 * @private
23 */
24 this.mountedVolumes_ = {};
25
26 this.initMountPoints_();
27 chrome.fileBrowserPrivate.onMountCompleted.addListener(
28 this.onMountCompleted_.bind(this));
29 this.gDataStatus_ = VolumeManager.GDataStatus.UNMOUNTED;
30 }
31
32 /**
33 * VolumeManager extends cr.EventTarget.
34 */
35 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
36
37 /**
38 * @enum
39 */
40 VolumeManager.Error = {
41 /* Internal errors */
42 NOT_MOUNTED: 'not_mounted',
43 TIMEOUT: 'timeout',
44
45 /* System events */
46 UNKNOWN: 'error_unknown',
47 INTERNAL: 'error_internal',
48 UNKNOWN_FILESYSTEM: 'error_unknown_filesystem',
49 UNSUPPORTED_FILESYSTEM: 'error_unsuported_filesystem',
50 INVALID_ARCHIVE: 'error_invalid_archive',
51 LIBCROS_MISSING: 'error_libcros_missing',
52 AUTHENTICATION: 'error_authentication',
53 PATH_UNMOUNTED: 'error_path_unmounted'
54 };
55
56 /**
57 * @enum
58 */
59 VolumeManager.GDataStatus = {
60 UNMOUNTED: 'unmounted',
61 MOUNTING: 'mounting',
62 ERROR: 'error',
63 MOUNTED: 'mounted'
64 };
65
66 /**
67 * Time in milliseconds that we wait a respone for. If no response on
68 * mount/unmount received the request supposed failed.
69 */
70 VolumeManager.TIMEOUT = 15 * 60 * 1000;
71
72 /**
73 * Delay in milliseconds GDATA changes its state from |UNMOUNTED| to
74 * |MOUNTING|. Used to display progress in the UI.
75 */
76 VolumeManager.MOUNTING_DELAY = 500;
77
78 /**
79 * @return {VolumeManager} Singleton instance.
80 */
81 VolumeManager.getInstance = function() {
82 return VolumeManager.instance_ = VolumeManager.instance_ ||
83 new VolumeManager();
84 };
85
86 /**
87 * @param {VolumeManager.GDataStatus} newStatus New GDATA status.
88 * @private
89 */
90 VolumeManager.prototype.setGDataStatus_ = function(newStatus) {
91 if (this.gDataStatus_ != newStatus) {
92 this.gDataStatus_ = newStatus;
93 cr.dispatchSimpleEvent(this, 'gdata-status-changed');
94 }
95 };
96
97 /**
98 * @return {VolumeManager.GDataStatus} Current GDATA status.
99 */
100 VolumeManager.prototype.getGDataStatus = function() {
101 return this.gDataStatus_;
102 };
103
104 /**
105 * @param {string} mountPath Volume root path.
106 * @return {boolean} True if mounted.
107 */
108 VolumeManager.prototype.isMounted = function(mountPath) {
109 this.validateMountPath_(mountPath);
110 return mountPath in this.mountedVolumes_;
111 };
112
113 /**
114 * Initialized mount points.
115 * @private
116 */
117 VolumeManager.prototype.initMountPoints_ = function() {
118 var mountedVolumes = [];
119 var self = this;
120 var index = 0;
121 function step(mountPoints) {
122 if (index < mountPoints.length) {
123 var info = mountPoints[index];
124 if (info.mountType == 'gdata')
125 console.error('GData is not expected initially mounted');
126 var error = info.mountCondition ? 'error_' + info.mountCondition : '';
127 function onVolumeInfo(volume) {
128 mountedVolumes.push(volume);
129 index++;
130 step(mountPoints);
131 }
132 self.makeVolumeInfo_('/' + info.mountPath, error, onVolumeInfo);
133 } else {
134 for (var i = 0; i < mountedVolumes.length; i++) {
135 var volume = mountedVolumes[i];
136 self.mountedVolumes_[volume.mountPath] = volume;
137 }
138 if (mountedVolumes.length > 0)
139 cr.dispatchSimpleEvent(self, 'change');
140 }
141 }
142
143 chrome.fileBrowserPrivate.getMountPoints(step);
144 };
145
146 /**
147 * Event handler called when some volume was mounted or unmouted.
148 * @param {MountCompletedEvent} event Received event.
149 * @private
150 */
151 VolumeManager.prototype.onMountCompleted_ = function(event) {
152 if (event.eventType == 'mount') {
153 if (event.mountPath) {
154 var requestKey = this.makeRequestKey_(
155 'mount', event.mountType, event.sourcePath);
156 var error = event.status == 'success' ? '' : event.status;
157 this.makeVolumeInfo_(event.mountPath, error, function(volume) {
158 this.mountedVolumes_[volume.mountPath] = volume;
159 this.finishRequest_(requestKey, event.status, event.mountPath);
160 cr.dispatchSimpleEvent(this, 'change');
161 }.bind(this));
162 } else {
163 console.log('No mount path');
164 this.finishRequest_(requestKey, event.status);
165 }
166 } else if (event.eventType == 'unmount') {
167 var mountPath = event.mountPath;
168 this.validateMountPath_(mountPath);
169 var status = event.status;
170 if (status == VolumeManager.Error.PATH_UNMOUNTED) {
171 console.log('Volume already unmounted: ', mountPath);
172 status = 'success';
173 }
174 var requestKey = this.makeRequestKey_('unmount', '', event.mountPath);
175 var requested = requestKey in this.requests_;
176 if (event.status == 'success' && !requested &&
177 mountPath in this.mountedVolumes_) {
178 console.log('Mounted volume without a request: ', mountPath);
179 var e = new cr.Event('externally-unmounted');
180 e.mountPath = mountPath;
181 this.dispatchEvent(e);
182 }
183 this.finishRequest_(requestKey, status);
184
185 if (event.status == 'success') {
186 delete this.mountedVolumes_[mountPath];
187 cr.dispatchSimpleEvent(this, 'change');
188 }
189 }
190
191 if (event.mountType == 'gdata') {
192 if (event.status == 'success') {
193 if (event.eventType == 'mount')
194 this.setGDataStatus_(VolumeManager.GDataStatus.MOUNTED);
195 else if (event.eventType == 'unmount')
196 this.setGDataStatus_(VolumeManager.GDataStatus.UMOUNTED);
197 }
198 }
199 };
200
201 /**
202 * @param {string} mountPath Path to the volume.
203 * @param {VolumeManager?} error Mounting error if any.
204 * @param {function(Object)} callback Result acceptor.
205 * @private
206 */
207 VolumeManager.prototype.makeVolumeInfo_ = function(
208 mountPath, error, callback) {
209 if (error)
210 this.validateError_(error);
211 this.validateMountPath_(mountPath);
212 function onVolumeMetadata(metadata) {
213 callback({
214 mountPath: mountPath,
215 error: error,
216 readonly: !!metadata && metadata.isReadOnly
217 });
218 }
219 chrome.fileBrowserPrivate.getVolumeMetadata(
220 util.makeFilesystemUrl(mountPath), onVolumeMetadata);
221 };
222
223 /**
224 * Creates string to match mount events with requests.
225 * @param {string} requestType 'mount' | 'unmount'.
226 * @param {string} mountType 'device' | 'file' | 'network' | 'gdata'.
227 * @param {string} mountOrSourcePath Source path provided by API after
228 * resolving mount request or mountPath for unmount request.
229 * @return {string} Key for |this.requests_|.
230 * @private
231 */
232 VolumeManager.prototype.makeRequestKey_ = function(requestType,
233 mountType,
234 mountOrSourcePath) {
235 return requestType + ':' + mountType + ':' + mountOrSourcePath;
236 };
237
238
239 /**
240 * @param {Function} successCallback Success callback.
241 * @param {Function} errorCallback Error callback.
242 */
243 VolumeManager.prototype.mountGData = function(successCallback, errorCallback) {
244 if (this.getGDataStatus() == VolumeManager.GDataStatus.ERROR) {
245 this.setGDataStatus_(VolumeManager.GDataStatus.UNMOUNTED);
246 }
247 var self = this;
248 var timeout = setTimeout(function() {
249 if (self.getGDataStatus() == VolumeManager.GDataStatus.UNMOUNTED)
250 self.setGDataStatus_(VolumeManager.GDataStatus.MOUNTING);
251 timeout = null;
252 }, VolumeManager.MOUNTING_DELAY);
253 this.mount_('', 'gdata', function(mountPath) {
254 if (timeout !== null)
255 clearTimeout(timeout);
256 successCallback(mountPath);
257 }, function(error) {
258 if (self.getGDataStatus() != VolumeManager.GDataStatus.MOUNTED)
259 self.setGDataStatus_(VolumeManager.GDataStatus.ERROR);
260 if (timeout != null)
261 clearTimeout(timeout);
262 errorCallback(error);
263 });
264 };
265
266 /**
267 * @param {string} fullPath Path to the archive file.
268 * @param {Function} successCallback Success callback.
269 * @param {Function} errorCallback Error callback.
270 */
271 VolumeManager.prototype.mountArchive = function(fullPath, successCallback,
272 errorCallback) {
273 this.mount_(util.makeFilesystemUrl(fullPath),
274 'file', successCallback, errorCallback);
275 };
276
277 /**
278 * Unmounts volume.
279 * @param {string} mountPath Volume mounted path.
280 * @param {Function} successCallback Success callback.
281 * @param {Function} errorCallback Error callback.
282 */
283 VolumeManager.prototype.unmount = function(mountPath,
284 successCallback,
285 errorCallback) {
286 this.validateMountPath_(mountPath);
287 var volumeInfo = this.mountedVolumes_[mountPath];
288 if (!volumeInfo) {
289 errorCallback(VolumeManager.Error.NOT_MOUNTED);
290 return;
291 }
292
293 chrome.fileBrowserPrivate.removeMount(util.makeFilesystemUrl(mountPath));
294 var requestKey = this.makeRequestKey_('unmount', '', volumeInfo.mountPath);
295 this.startRequest_(requestKey, successCallback, errorCallback);
296 };
297
298 /**
299 * @param {string} mountPath Volume mounted path.
300 * @return {VolumeManager.Error?} Returns mount error code
301 * or undefined if no error.
302 */
303 VolumeManager.prototype.getMountError = function(mountPath) {
304 return this.getVolumeInfo_(mountPath).error;
305 };
306
307 /**
308 * @param {string} mountPath Volume mounted path.
309 * @return {boolean} True if volume at |mountedPath| is mounted but not usable.
310 */
311 VolumeManager.prototype.isUnreadable = function(mountPath) {
312 var error = this.getMountError(mountPath);
313 return error == VolumeManager.Error.UNKNOWN_FILESYSTEM ||
314 error == VolumeManager.Error.UNSUPPORTED_FILESYSTEM;
315 };
316
317 /**
318 * @param {string} mountPath Volume mounted path.
319 * @return {boolean} True if volume at |mountedPath| is read only.
320 */
321 VolumeManager.prototype.isReadOnly = function(mountPath) {
322 return !!this.getVolumeInfo_(mountPath).readonly;
323 };
324
325 /**
326 * Helper method.
327 * @param {string} mountPath Volume mounted path.
328 * @return {Object} Structure created in |startRequest_|.
329 * @private
330 */
331 VolumeManager.prototype.getVolumeInfo_ = function(mountPath) {
332 this.validateMountPath_(mountPath);
333 return this.mountedVolumes_[mountPath] || {};
334 };
335
336 /**
337 * @param {string} url URL for for |fileBrowserPrivate.addMount|.
338 * @param {'gdata'|'file'} mountType Mount type for
339 * |fileBrowserPrivate.addMount|.
340 * @param {Function} successCallback Success callback.
341 * @param {Function} errorCallback Error callback.
342 * @private
343 */
344 VolumeManager.prototype.mount_ = function(url, mountType,
345 successCallback, errorCallback) {
346 chrome.fileBrowserPrivate.addMount(url, mountType, {},
347 function(sourcePath) {
348 console.log('Mount request: url=' + url + '; mountType=' + mountType +
349 '; sourceUrl=' + sourcePath);
350 var requestKey = this.makeRequestKey_('mount', mountType, sourcePath);
351 this.startRequest_(requestKey, successCallback, errorCallback);
352 }.bind(this));
353 };
354
355 /**
356 * @param {sting} key Key produced by |makeRequestKey_|.
357 * @param {Function} successCallback To be called when request finishes
358 * successfully.
359 * @param {Function} errorCallback To be called when request fails.
360 * @private
361 */
362 VolumeManager.prototype.startRequest_ = function(key,
363 successCallback, errorCallback) {
364 if (key in this.requests_) {
365 var request = this.requests_[key];
366 request.successCallbacks.push(successCallback);
367 request.errorCallbacks.push(errorCallback);
368 } else {
369 this.requests_[key] = {
370 successCallbacks: [successCallback],
371 errorCallbacks: [errorCallback],
372
373 timeout: setTimeout(this.onTimeout_.bind(this, key),
374 VolumeManager.TIMEOUT)
375 };
376 }
377 };
378
379 /**
380 * Called if no response received in |TIMEOUT|.
381 * @param {sting} key Key produced by |makeRequestKey_|.
382 * @private
383 */
384 VolumeManager.prototype.onTimeout_ = function(key) {
385 this.invokeRequestCallbacks_(this.requests_[key],
386 VolumeManager.Error.TIMEOUT);
387 delete this.requests_[key];
388 };
389
390 /**
391 * @param {sting} key Key produced by |makeRequestKey_|.
392 * @param {VolumeManager.Error|'success'} status Status received from the API.
393 * @param {string} opt_mountPath Mount path.
394 * @private
395 */
396 VolumeManager.prototype.finishRequest_ = function(key, status, opt_mountPath) {
397 var request = this.requests_[key];
398 if (!request)
399 return;
400
401 clearTimeout(request.timeout);
402 this.invokeRequestCallbacks_(request, status, opt_mountPath);
403 delete this.requests_[key];
404 };
405
406 /**
407 * @param {object} request Structure created in |startRequest_|.
408 * @param {VolumeManager.Error|string} status If status == 'success'
409 * success callbacks are called.
410 * @param {string} opt_mountPath Mount path. Required if success.
411 * @private
412 */
413 VolumeManager.prototype.invokeRequestCallbacks_ = function(request, status,
414 opt_mountPath) {
415 function callEach(callbacks, self, args) {
416 for (var i = 0; i < callbacks.length; i++) {
417 callbacks[i].apply(self, args);
418 }
419 }
420 if (status == 'success') {
421 callEach(request.successCallbacks, this, [opt_mountPath]);
422 } else {
423 this.validateError_(status);
424 callEach(request.errorCallbacks, this, [status]);
425 }
426 };
427
428 /**
429 * @param {VolumeManager.Error} error Status string iusually received from API.
430 * @private
431 */
432 VolumeManager.prototype.validateError_ = function(error) {
433 for (var i in VolumeManager.Error) {
434 if (error == VolumeManager.Error[i])
435 return;
436 }
437 throw new Error('Invalid mount error: ', error);
438 };
439
440 /**
441 * @param {string} mountPath Mount path.
442 * @private
443 */
444 VolumeManager.prototype.validateMountPath_ = function(mountPath) {
445 if (!/^\/(((archive|removable)\/[^\/]+)|drive|Downloads)$/.test(mountPath))
446 throw new Error('Invalid mount path: ', mountPath);
447 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698