OLD | NEW |
---|---|
(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 }; | |
OLD | NEW |