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

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: Fixed gallery opening on GDATA at startup. 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 whet we wait a respone for. If no response on
dgozman 2012/05/18 14:17:19 typo: whet -> that
SeRya 2012/05/21 08:06:36 Done.
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 extpected initially mounted');
dgozman 2012/05/18 14:17:19 typo: extpected
SeRya 2012/05/21 08:06:36 Done.
126 var error = info.mountCondition ? 'error_' + info.mountCondition : '';
127 self.makeVolumeInfo_('/' + info.mountPath, error,
128 function(volume) {
dgozman 2012/05/18 14:17:19 strange indentation
SeRya 2012/05/21 08:06:36 Done.
129 mountedVolumes.push(volume);
130 index++;
131 step(mountPoints);
132 });
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.sourcePath);
175 var expectedly = requestKey in this.requests_;
176 if (!expectedly && mountPath in this.mountedVolumes_) {
dgozman 2012/05/18 14:17:19 Check for status=success here.
SeRya 2012/05/21 08:06:36 Done.
177 console.log('Mounted volume without a request: ', mountPath);
178 var e = new cr.Event('unexpectedly-unmounted');
dgozman 2012/05/18 14:17:19 I'd suggest to rename this to 'externally-unmounte
SeRya 2012/05/21 08:06:36 Done.
179 e.mountPath = mountPath;
180 this.dispatchEvent(e);
181 }
182 this.finishRequest_(requestKey, status);
183
184 if (event.status == 'success') {
185 delete this.mountedVolumes_[mountPath];
186 cr.dispatchSimpleEvent(this, 'change');
187 }
188 }
189
190 if (event.mountType == 'gdata') {
191 if (event.status == 'success') {
192 if (event.eventType == 'mount')
193 this.setGDataStatus_(VolumeManager.GDataStatus.MOUNTED);
194 else if (event.eventType == 'unmount' && event.status == 'success')
dgozman 2012/05/18 14:17:19 Already checked for success 3 lines above.
SeRya 2012/05/21 08:06:36 Done.
195 this.setGDataStatus_(VolumeManager.GDataStatus.UMOUNTED);
196 }
197 }
198 };
199
200 /**
201 * @param {string} mountPath Path to the volume.
202 * @param {VolumeManager?} error Mounting error if any.
203 * @param {function(Object)} callback Result acceptor.
204 * @private
205 */
206 VolumeManager.prototype.makeVolumeInfo_ = function(
207 mountPath, error, callback) {
208 if (error)
209 this.validateError_(error);
210 this.validateMountPath_(mountPath);
211 chrome.fileBrowserPrivate.getVolumeMetadata(this.makeUrl_(mountPath),
212 function(metadata) {
dgozman 2012/05/18 14:17:19 strange indentation
SeRya 2012/05/21 08:06:36 Done.
213 callback({
214 mountPath: mountPath,
215 error: error,
216 readonly: !!metadata && metadata.isReadOnly
217 });
218 }.bind(this));
219 };
220
221 /**
222 * Creates string to match mount events with requests.
223 * @param {string} requestType 'mount' | 'unmount'.
224 * @param {string} mountType 'device' | 'file' | 'network' | 'gdata'.
225 * @param {string} sourcePath Source path provided by API after
226 * resolving mount request.
227 * @return {string} Key for |this.requests_|.
228 * @private
229 */
230 VolumeManager.prototype.makeRequestKey_ = function(requestType,
231 mountType,
232 sourcePath) {
233 return requestType + ':' + mountType + ':' + sourcePath;
234 };
235
236
237 /**
238 * @param {Function} successCallback Success callback.
239 * @param {Function} errorCallback Error callback.
240 */
241 VolumeManager.prototype.mountGData = function(successCallback, errorCallback) {
242 if (this.getGDataStatus() == VolumeManager.GDataStatus.ERROR) {
243 this.setGDataStatus_(VolumeManager.GDataStatus.UNMOUNTED);
244 }
245 var self = this;
246 var timeout = setTimeout(function() {
247 if (self.getGDataStatus() == VolumeManager.GDataStatus.UNMOUNTED)
248 self.setGDataStatus_(VolumeManager.GDataStatus.MOUNTING);
249 timeout = null;
250 }, VolumeManager.MOUNTING_DELAY);
251 this.mount_('', 'gdata', function(mountPath) {
252 if (timeout !== null)
253 clearTimeout(timeout);
254 successCallback(mountPath);
255 }, function(error) {
256 if (self.getGDataStatus() != VolumeManager.GDataStatus.MOUNTED)
257 self.setGDataStatus_(VolumeManager.GDataStatus.ERROR);
258 if (timeout != null)
259 clearTimeout(timeout);
260 errorCallback(error);
261 });
262 };
263
264 /**
265 * @param {string} fullPath Path to the archive file.
266 * @param {Function} successCallback Success callback.
267 * @param {Function} errorCallback Error callback.
268 */
269 VolumeManager.prototype.mountArchive = function(fullPath, successCallback,
270 errorCallback) {
271 this.mount_(this.makeUrl_(fullPath),
272 'file', successCallback, errorCallback);
273 };
274
275 /**
276 * Unmounts volume.
277 * @param {string} mountPath Volume mounted path.
278 * @param {Function} successCallback Success callback.
279 * @param {Function} errorCallback Error callback.
280 */
281 VolumeManager.prototype.unmount = function(mountPath,
282 successCallback,
283 errorCallback) {
284 this.validateMountPath_(mountPath);
285 var volumeInfo = this.mountedVolumes_[mountPath];
286 if (!volumeInfo) {
287 errorCallback(VolumeManager.Error.NOT_NOUNTED);
dgozman 2012/05/18 14:17:19 typo: NOT_NOUNTED
SeRya 2012/05/21 08:06:36 Done.
288 return;
289 }
290
291 chrome.fileBrowserPrivate.removeMount(this.makeUrl_(mountPath));
292 this.startRequest_(this.makeRequestKey_('unmount', '',
293 volumeInfo.sourcePath));
294 };
295
296 /**
297 * @param {string} mountPath Volume mounted path.
298 * @return {VolumeManager.Error?} Retuns mount error code
dgozman 2012/05/18 14:17:19 typo: Retuns
SeRya 2012/05/21 08:06:36 Done.
299 * or undefined if no error.
300 */
301 VolumeManager.prototype.getMountError = function(mountPath) {
302 this.validateMountPath_(mountPath);
303 return this.getVolumeInfo_(mountPath).status;
dgozman 2012/05/18 14:17:19 status -> error
SeRya 2012/05/21 08:06:36 Done.
304 };
305
306 /**
307 * @param {string} mountPath Volume mounted path.
308 * @return {boolean} True if volume at |mountedPath| is mounted but not usable.
309 */
310 VolumeManager.prototype.isUnreadable = function(mountPath) {
311 this.validateMountPath_(mountPath);
312 var status = this.getMountError(mountPath);
dgozman 2012/05/18 14:17:19 status -> error
SeRya 2012/05/21 08:06:36 Done.
313 return status == VolumeManager.Error.UNKNOWN_FILESYSTEM ||
314 status == 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 this.validateMountPath_(mountPath);
dgozman 2012/05/18 14:17:19 You can remove most of these checks, and just leav
SeRya 2012/05/21 08:06:36 Done.
SeRya 2012/05/21 08:06:36 Done.
323 return !!this.getVolumeInfo_(mountPath).readonly;
324 };
325
326 /**
327 * Helper method.
328 * @param {string} mountPath Volume mounted path.
329 * @return {Object} Structure created in |startRequest_|.
330 * @private
331 */
332 VolumeManager.prototype.getVolumeInfo_ = function(mountPath) {
333 this.validateMountPath_(mountPath);
334 return this.mountedVolumes_[mountPath] || {};
335 };
336
337 /**
338 * Makes filesystem: URL from directory path.
339 * @param {string} path Directory path.
340 * @return {string} URL.
341 * @private
342 */
343 VolumeManager.prototype.makeUrl_ = function(path) {
dgozman 2012/05/18 14:17:19 Move to util.js
SeRya 2012/05/21 08:06:36 Done.
344 return 'filesystem:' + chrome.extension.getURL('external' + path);
345 };
346
347 /**
348 * @param {string} url URL for for |fileBrowserPrivate.addMount|.
349 * @param {'gdata'|'file'} mountType Mount type for
350 * |fileBrowserPrivate.addMount|.
351 * @param {Function} successCallback Success callback.
352 * @param {Function} errorCallback Error callback.
353 * @private
354 */
355 VolumeManager.prototype.mount_ = function(url, mountType,
356 successCallback, errorCallback) {
357 chrome.fileBrowserPrivate.addMount(url, mountType, {},
358 function(sourcePath) {
359 console.log('Mount request: url=' + url + '; mountType=' + mountType +
360 '; sourceUrl=' + sourcePath);
361 var requestKey = this.makeRequestKey_('mount', mountType, sourcePath);
362 this.startRequest_(requestKey, successCallback, errorCallback);
363 }.bind(this));
364 };
365
366 /**
367 * @param {sting} key Key produced by |makeRequestKey_|.
368 * @param {Function} successCallback To be called when request finishes
369 * successfully.
370 * @param {Function} errorCallback To be called when request fails.
371 * @private
372 */
373 VolumeManager.prototype.startRequest_ = function(key,
374 successCallback, errorCallback) {
375 if (key in this.requests_) {
376 var request = this.requests_[key];
377 request.successCallbacks.push(successCallback);
378 request.errorCallbacks.push(errorCallback);
379 } else {
380 this.requests_[key] = {
381 successCallbacks: [successCallback],
382 errorCallbacks: [errorCallback],
383
384 timeout: setTimeout(this.onTimeout_.bind(this, key),
385 VolumeManager.TIMEOUT)
386 };
387 }
388 };
389
390 /**
391 * Called if not response received in |TIMEOUT|.
dgozman 2012/05/18 14:17:19 typo: not -> no
SeRya 2012/05/21 08:06:36 Done.
392 * @param {sting} key Key produced by |makeRequestKey_|.
393 * @private
394 */
395 VolumeManager.prototype.onTimeout_ = function(key) {
396 this.invokeRequestCallbacks_(this.requests_[key],
397 VolumeManager.Error.TIMEOUT);
398 delete this.requests_[key];
399 };
400
401 /**
402 * @param {sting} key Key produced by |makeRequestKey_|.
403 * @param {VolumeManager.Error|'success'} status Status received from the API.
404 * @param {string} opt_mountPath Mount path.
405 * @private
406 */
407 VolumeManager.prototype.finishRequest_ = function(key, status, opt_mountPath) {
408 var request = this.requests_[key];
409 if (!request)
410 return;
411
412 clearTimeout(request.timeout);
413 this.invokeRequestCallbacks_(request, status, opt_mountPath);
414 delete this.requests_[key];
415 };
416
417 /**
418 * @param {object} request Structure created in |startRequest_|.
419 * @param {VolumeManager.Error|string} status If status == 'success'
420 * success callbacks are called.
421 * @param {string} opt_mountPath Mount path. Required if success.
422 * @private
423 */
424 VolumeManager.prototype.invokeRequestCallbacks_ = function(request, status,
425 opt_mountPath) {
426 function callEach(callbacks, self, args) {
427 for (var i = 0; i < callbacks.length; i++) {
428 callbacks[i].apply(self, args);
429 }
430 }
431 if (status == 'success') {
432 callEach(request.successCallbacks, this, [opt_mountPath]);
433 } else {
434 this.validateError_(status);
435 callEach(request.errorCallbacks, this, [status]);
436 }
437 };
438
439 /**
440 * @param {VolumeManager.Error} error Status string iusually received from API.
441 * @private
442 */
443 VolumeManager.prototype.validateError_ = function(error) {
444 for (var i in VolumeManager.Error) {
445 if (error == VolumeManager.Error[i])
446 return;
447 }
448 throw new Error('Invalid mount error: ', error);
449 };
450
451 /**
452 * @param {string} mountPath Mount path.
453 * @private
454 */
455 VolumeManager.prototype.validateMountPath_ = function(mountPath) {
456 if (!/^\/(((archive|removable)\/[^\/]+)|drive|Downloads)$/.test(mountPath))
457 throw new Error('Invalid mount path: ', mountPath);
458 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698