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 'use strict'; | |
6 | |
7 /** | |
8 * Represents each volume, such as "drive", "download directory", each "USB | |
9 * flush storage", or "mounted zip archive" etc. | |
10 * | |
11 * @param {util.VolumeType} volumeType The type of the volume. | |
12 * @param {string} mountPath Where the volume is mounted. | |
13 * @param {DirectoryEntry} root The root directory entry of this volume. | |
14 * @param {string} error The error if an error is found. | |
15 * @param {string} deviceType The type of device ('usb'|'sd'|'optical'|'mobile' | |
16 * |'unknown') (as defined in chromeos/disks/disk_mount_manager.cc). | |
17 * Can be null. | |
18 * @param {boolean} isReadOnly True if the volume is read only. | |
19 * @constructor | |
20 */ | |
21 function VolumeInfo( | |
22 volumeType, mountPath, root, error, deviceType, isReadOnly) { | |
23 this.volumeType = volumeType; | |
24 // TODO(hidehiko): This should include FileSystem instance. | |
25 this.mountPath = mountPath; | |
26 this.root = root; | |
27 | |
28 // Note: This represents if the mounting of the volume is successfully done | |
29 // or not. (If error is empty string, the mount is successfully done). | |
30 // TODO(hidehiko): Rename to make this more understandable. | |
31 this.error = error; | |
32 this.deviceType = deviceType; | |
33 this.isReadOnly = isReadOnly; | |
34 | |
35 // VolumeInfo is immutable. | |
36 Object.freeze(this); | |
37 } | |
38 | |
39 /** | |
40 * Utilities for volume manager implementation. | |
41 */ | |
42 var volumeManagerUtil = {}; | |
43 | |
44 /** | |
45 * Throws an Error when the given error is not in util.VolumeError. | |
46 * @param {util.VolumeError} error Status string usually received from APIs. | |
47 */ | |
48 volumeManagerUtil.validateError = function(error) { | |
49 for (var key in util.VolumeError) { | |
50 if (error == util.VolumeError[key]) | |
51 return; | |
52 } | |
53 | |
54 throw new Error('Invalid mount error: ' + error); | |
55 }; | |
56 | |
57 /** | |
58 * The regex pattern which matches valid mount paths. | |
59 * The valid paths are: | |
60 * - Either of '/drive', '/drive_shared_with_me', '/drive_offline', | |
61 * '/drive_recent' or '/Download' | |
62 * - For archive, drive, removable can have (exactly one) sub directory in the | |
63 * root path. E.g. '/archive/foo', '/removable/usb1' etc. | |
64 * | |
65 * @type {RegExp} | |
66 * @private | |
67 */ | |
68 volumeManagerUtil.validateMountPathRegExp_ = new RegExp( | |
69 '^/(drive|drive_shared_with_me|drive_offline|drive_recent|Downloads|' + | |
70 '((archive|drive|removable)\/[^/]+))$'); | |
71 | |
72 /** | |
73 * Throws an Error if the validation fails. | |
74 * @param {string} mountPath The target path of the validation. | |
75 */ | |
76 volumeManagerUtil.validateMountPath = function(mountPath) { | |
77 if (!volumeManagerUtil.validateMountPathRegExp_.test(mountPath)) | |
78 throw new Error('Invalid mount path: ' + mountPath); | |
79 }; | |
80 | |
81 /** | |
82 * Returns the root entry of a volume mounted at mountPath. | |
83 * | |
84 * @param {string} mountPath The mounted path of the volume. | |
85 * @param {function(DirectoryEntry)} successCallback Called when the root entry | |
86 * is found. | |
87 * @param {function(FileError)} errorCallback Called when an error is found. | |
88 * @private | |
89 */ | |
90 volumeManagerUtil.getRootEntry_ = function( | |
91 mountPath, successCallback, errorCallback) { | |
92 // We always request FileSystem here, because requestFileSystem() grants | |
93 // permissions if necessary, especially for Drive File System at first mount | |
94 // time. | |
95 // Note that we actually need to request FileSystem after multi file system | |
96 // support, so this will be more natural code then. | |
97 chrome.fileBrowserPrivate.requestFileSystem( | |
98 'compatible', | |
99 function(fileSystem) { | |
100 // TODO(hidehiko): chrome.runtime.lastError should have error reason. | |
101 if (!fileSystem) { | |
102 errorCallback(util.createFileError(FileError.NOT_FOUND_ERR)); | |
103 return; | |
104 } | |
105 | |
106 fileSystem.root.getDirectory( | |
107 mountPath.substring(1), // Strip leading '/'. | |
108 {create: false}, successCallback, errorCallback); | |
109 }); | |
110 }; | |
111 | |
112 /** | |
113 * Builds the VolumeInfo data from VolumeMetadata. | |
114 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume. | |
115 * @param {function(VolumeInfo)} callback Called on completion. | |
116 */ | |
117 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) { | |
118 volumeManagerUtil.getRootEntry_( | |
119 volumeMetadata.mountPath, | |
120 function(entry) { | |
121 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) { | |
122 // After file system is mounted, we "read" drive grand root | |
123 // entry at first. This triggers full feed fetch on background. | |
124 // Note: we don't need to handle errors here, because even if | |
125 // it fails, accessing to some path later will just become | |
126 // a fast-fetch and it re-triggers full-feed fetch. | |
127 entry.createReader().readEntries( | |
128 function() { /* do nothing */ }, | |
129 function(error) { | |
130 console.error( | |
131 'Triggering full feed fetch is failed: ' + | |
132 util.getFileErrorMnemonic(error.code)); | |
133 }); | |
134 } | |
135 callback(new VolumeInfo( | |
136 volumeMetadata.volumeType, | |
137 volumeMetadata.mountPath, | |
138 entry, | |
139 volumeMetadata.mountCondition, | |
140 volumeMetadata.deviceType, | |
141 volumeMetadata.isReadOnly)); | |
142 }, | |
143 function(fileError) { | |
144 console.error('Root entry is not found: ' + | |
145 mountPath + ', ' + util.getFileErrorMnemonic(fileError.code)); | |
146 callback(new VolumeInfo( | |
147 volumeMetadata.volumeType, | |
148 volumeMetadata.mountPath, | |
149 null, // Root entry is not found. | |
150 volumeMetadata.mountCondition, | |
151 volumeMetadata.deviceType, | |
152 volumeMetadata.isReadOnly)); | |
153 }); | |
154 }; | |
155 | |
156 /** | |
157 * The order of the volume list based on root type. | |
158 * @type {Array.<string>} | |
159 * @const | |
160 * @private | |
161 */ | |
162 volumeManagerUtil.volumeListOrder_ = [ | |
163 RootType.DRIVE, RootType.DOWNLOADS, RootType.ARCHIVE, RootType.REMOVABLE | |
164 ]; | |
165 | |
166 /** | |
167 * Compares mount paths to sort the volume list order. | |
168 * @param {string} mountPath1 The mount path for the first volume. | |
169 * @param {string} mountPath2 The mount path for the second volume. | |
170 * @return {number} 0 if mountPath1 and mountPath2 are same, -1 if VolumeInfo | |
171 * mounted at mountPath1 should be listed before the one mounted at | |
172 * mountPath2, otherwise 1. | |
173 */ | |
174 volumeManagerUtil.compareMountPath = function(mountPath1, mountPath2) { | |
175 var order1 = volumeManagerUtil.volumeListOrder_.indexOf( | |
176 PathUtil.getRootType(mountPath1)); | |
177 var order2 = volumeManagerUtil.volumeListOrder_.indexOf( | |
178 PathUtil.getRootType(mountPath2)); | |
179 if (order1 != order2) | |
180 return order1 < order2 ? -1 : 1; | |
181 | |
182 if (mountPath1 != mountPath2) | |
183 return mountPath1 < mountPath2 ? -1 : 1; | |
184 | |
185 // The path is same. | |
186 return 0; | |
187 }; | |
188 | |
189 /** | |
190 * The container of the VolumeInfo for each mounted volume. | |
191 * @constructor | |
192 */ | |
193 function VolumeInfoList() { | |
194 /** | |
195 * Holds VolumeInfo instances. | |
196 * @type {cr.ui.ArrayDataModel} | |
197 * @private | |
198 */ | |
199 this.model_ = new cr.ui.ArrayDataModel([]); | |
200 | |
201 Object.freeze(this); | |
202 } | |
203 | |
204 VolumeInfoList.prototype = { | |
205 get length() { return this.model_.length; } | |
206 }; | |
207 | |
208 /** | |
209 * Adds the event listener to listen the change of volume info. | |
210 * @param {string} type The name of the event. | |
211 * @param {function(Event)} handler The handler for the event. | |
212 */ | |
213 VolumeInfoList.prototype.addEventListener = function(type, handler) { | |
214 this.model_.addEventListener(type, handler); | |
215 }; | |
216 | |
217 /** | |
218 * Removes the event listener. | |
219 * @param {string} type The name of the event. | |
220 * @param {function(Event)} handler The handler to be removed. | |
221 */ | |
222 VolumeInfoList.prototype.removeEventListener = function(type, handler) { | |
223 this.model_.removeEventListener(type, handler); | |
224 }; | |
225 | |
226 /** | |
227 * Adds the volumeInfo to the appropriate position. If there already exists, | |
228 * just replaces it. | |
229 * @param {VolumeInfo} volumeInfo The information of the new volume. | |
230 */ | |
231 VolumeInfoList.prototype.add = function(volumeInfo) { | |
232 var index = this.findLowerBoundIndex_(volumeInfo.mountPath); | |
233 if (index < this.length && | |
234 this.item(index).mountPath == volumeInfo.mountPath) { | |
235 // Replace the VolumeInfo. | |
236 this.model_.splice(index, 1, volumeInfo); | |
237 } else { | |
238 // Insert the VolumeInfo. | |
239 this.model_.splice(index, 0, volumeInfo); | |
240 } | |
241 }; | |
242 | |
243 /** | |
244 * Removes the VolumeInfo of the volume mounted at mountPath. | |
245 * @param {string} mountPath The path to the location where the volume is | |
246 * mounted. | |
247 */ | |
248 VolumeInfoList.prototype.remove = function(mountPath) { | |
249 var index = this.findLowerBoundIndex_(mountPath); | |
250 if (index < this.length && this.item(index).mountPath == mountPath) | |
251 this.model_.splice(index, 1); | |
252 }; | |
253 | |
254 /** | |
255 * Searches the information of the volume mounted at mountPath. | |
256 * @param {string} mountPath The path to the location where the volume is | |
257 * mounted. | |
258 * @return {VolumeInfo} The volume's information, or null if not found. | |
259 */ | |
260 VolumeInfoList.prototype.find = function(mountPath) { | |
261 var index = this.findLowerBoundIndex_(mountPath); | |
262 if (index < this.length && this.item(index).mountPath == mountPath) | |
263 return this.item(index); | |
264 | |
265 // Not found. | |
266 return null; | |
267 }; | |
268 | |
269 /** | |
270 * @param {string} mountPath The mount path of searched volume. | |
271 * @return {number} The index of the volume if found, or the inserting | |
272 * position of the volume. | |
273 * @private | |
274 */ | |
275 VolumeInfoList.prototype.findLowerBoundIndex_ = function(mountPath) { | |
276 // Assuming the number of elements in the array data model is very small | |
277 // in most cases, use simple linear search, here. | |
278 for (var i = 0; i < this.length; i++) { | |
279 if (volumeManagerUtil.compareMountPath( | |
280 this.item(i).mountPath, mountPath) >= 0) | |
281 return i; | |
282 } | |
283 return this.length; | |
284 }; | |
285 | |
286 /** | |
287 * @param {number} index The index of the volume in the list. | |
288 * @return {VolumeInfo} The VolumeInfo instance. | |
289 */ | |
290 VolumeInfoList.prototype.item = function(index) { | |
291 return this.model_.item(index); | |
292 }; | |
293 | |
294 /** | |
295 * VolumeManager is responsible for tracking list of mounted volumes. | |
296 * | |
297 * @constructor | |
298 * @extends {cr.EventTarget} | |
299 */ | |
300 function VolumeManager() { | |
301 /** | |
302 * The list of archives requested to mount. We will show contents once | |
303 * archive is mounted, but only for mounts from within this filebrowser tab. | |
304 * @type {Object.<string, Object>} | |
305 * @private | |
306 */ | |
307 this.requests_ = {}; | |
308 | |
309 /** | |
310 * The list of VolumeInfo instances for each mounted volume. | |
311 * @type {VolumeInfoList} | |
312 */ | |
313 this.volumeInfoList = new VolumeInfoList(); | |
314 | |
315 // The status should be merged into VolumeManager. | |
316 // TODO(hidehiko): Remove them after the migration. | |
317 this.driveConnectionState_ = { | |
318 type: util.DriveConnectionType.OFFLINE, | |
319 reasons: [util.DriveConnectionReason.NO_SERVICE] | |
320 }; | |
321 | |
322 chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener( | |
323 this.onDriveConnectionStatusChanged_.bind(this)); | |
324 this.onDriveConnectionStatusChanged_(); | |
325 } | |
326 | |
327 /** | |
328 * Invoked when the drive connection status is changed. | |
329 * @private_ | |
330 */ | |
331 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() { | |
332 chrome.fileBrowserPrivate.getDriveConnectionState(function(state) { | |
333 this.driveConnectionState_ = state; | |
334 cr.dispatchSimpleEvent(this, 'drive-connection-changed'); | |
335 }.bind(this)); | |
336 }; | |
337 | |
338 /** | |
339 * Returns the drive connection state. | |
340 * @return {util.DriveConnectionType} Connection type. | |
341 */ | |
342 VolumeManager.prototype.getDriveConnectionState = function() { | |
343 return this.driveConnectionState_; | |
344 }; | |
345 | |
346 /** | |
347 * VolumeManager extends cr.EventTarget. | |
348 */ | |
349 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype; | |
350 | |
351 /** | |
352 * Time in milliseconds that we wait a response for. If no response on | |
353 * mount/unmount received the request supposed failed. | |
354 */ | |
355 VolumeManager.TIMEOUT = 15 * 60 * 1000; | |
356 | |
357 /** | |
358 * Queue to run getInstance sequentially. | |
359 * @type {AsyncUtil.Queue} | |
360 * @private | |
361 */ | |
362 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue(); | |
363 | |
364 /** | |
365 * The singleton instance of VolumeManager. Initialized by the first invocation | |
366 * of getInstance(). | |
367 * @type {VolumeManager} | |
368 * @private | |
369 */ | |
370 VolumeManager.instance_ = null; | |
371 | |
372 /** | |
373 * Returns the VolumeManager instance asynchronously. If it is not created or | |
374 * under initialization, it will waits for the finish of the initialization. | |
375 * @param {function(VolumeManager)} callback Called with the VolumeManager | |
376 * instance. | |
377 */ | |
378 VolumeManager.getInstance = function(callback) { | |
379 VolumeManager.getInstanceQueue_.run(function(continueCallback) { | |
380 if (VolumeManager.instance_) { | |
381 callback(VolumeManager.instance_); | |
382 continueCallback(); | |
383 return; | |
384 } | |
385 | |
386 VolumeManager.instance_ = new VolumeManager(); | |
387 VolumeManager.instance_.initialize_(function() { | |
388 callback(VolumeManager.instance_); | |
389 continueCallback(); | |
390 }); | |
391 }); | |
392 }; | |
393 | |
394 /** | |
395 * Initializes mount points. | |
396 * @param {function()} callback Called upon the completion of the | |
397 * initialization. | |
398 * @private | |
399 */ | |
400 VolumeManager.prototype.initialize_ = function(callback) { | |
401 chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) { | |
402 // Create VolumeInfo for each volume. | |
403 var group = new AsyncUtil.Group(); | |
404 for (var i = 0; i < volumeMetadataList.length; i++) { | |
405 group.add(function(volumeMetadata, continueCallback) { | |
406 volumeManagerUtil.createVolumeInfo( | |
407 volumeMetadata, | |
408 function(volumeInfo) { | |
409 this.volumeInfoList.add(volumeInfo); | |
410 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) | |
411 this.onDriveConnectionStatusChanged_(); | |
412 continueCallback(); | |
413 }.bind(this)); | |
414 }.bind(this, volumeMetadataList[i])); | |
415 } | |
416 | |
417 // Then, finalize the initialization. | |
418 group.run(function() { | |
419 // Subscribe to the mount completed event when mount points initialized. | |
420 chrome.fileBrowserPrivate.onMountCompleted.addListener( | |
421 this.onMountCompleted_.bind(this)); | |
422 callback(); | |
423 }.bind(this)); | |
424 }.bind(this)); | |
425 }; | |
426 | |
427 /** | |
428 * Event handler called when some volume was mounted or unmounted. | |
429 * @param {MountCompletedEvent} event Received event. | |
430 * @private | |
431 */ | |
432 VolumeManager.prototype.onMountCompleted_ = function(event) { | |
433 if (event.eventType === 'mount') { | |
434 if (event.volumeMetadata.mountPath) { | |
435 var requestKey = this.makeRequestKey_( | |
436 'mount', | |
437 event.volumeMetadata.sourcePath); | |
438 | |
439 var error = event.status === 'success' ? '' : event.status; | |
440 | |
441 volumeManagerUtil.createVolumeInfo( | |
442 event.volumeMetadata, | |
443 function(volumeInfo) { | |
444 this.volumeInfoList.add(volumeInfo); | |
445 this.finishRequest_(requestKey, event.status, volumeInfo.mountPath); | |
446 | |
447 if (volumeInfo.volumeType === util.VolumeType.DRIVE) { | |
448 // Update the network connection status, because until the | |
449 // drive is initialized, the status is set to not ready. | |
450 // TODO(hidehiko): The connection status should be migrated into | |
451 // VolumeMetadata. | |
452 this.onDriveConnectionStatusChanged_(); | |
453 } | |
454 }.bind(this)); | |
455 } else { | |
456 console.warn('No mount path.'); | |
457 this.finishRequest_(requestKey, event.status); | |
458 } | |
459 } else if (event.eventType === 'unmount') { | |
460 var mountPath = event.volumeMetadata.mountPath; | |
461 volumeManagerUtil.validateMountPath(mountPath); | |
462 var status = event.status; | |
463 if (status === util.VolumeError.PATH_UNMOUNTED) { | |
464 console.warn('Volume already unmounted: ', mountPath); | |
465 status = 'success'; | |
466 } | |
467 var requestKey = this.makeRequestKey_('unmount', mountPath); | |
468 var requested = requestKey in this.requests_; | |
469 if (event.status === 'success' && !requested && | |
470 this.volumeInfoList.find(mountPath)) { | |
471 console.warn('Mounted volume without a request: ', mountPath); | |
472 var e = new Event('externally-unmounted'); | |
473 e.mountPath = mountPath; | |
474 this.dispatchEvent(e); | |
475 } | |
476 this.finishRequest_(requestKey, status); | |
477 | |
478 if (event.status === 'success') | |
479 this.volumeInfoList.remove(mountPath); | |
480 } | |
481 }; | |
482 | |
483 /** | |
484 * Creates string to match mount events with requests. | |
485 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by | |
486 * enum. | |
487 * @param {string} path Source path provided by API for mount request, or | |
488 * mount path for unmount request. | |
489 * @return {string} Key for |this.requests_|. | |
490 * @private | |
491 */ | |
492 VolumeManager.prototype.makeRequestKey_ = function(requestType, path) { | |
493 return requestType + ':' + path; | |
494 }; | |
495 | |
496 /** | |
497 * @param {string} fileUrl File url to the archive file. | |
498 * @param {function(string)} successCallback Success callback. | |
499 * @param {function(util.VolumeError)} errorCallback Error callback. | |
500 */ | |
501 VolumeManager.prototype.mountArchive = function( | |
502 fileUrl, successCallback, errorCallback) { | |
503 chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) { | |
504 console.info( | |
505 'Mount request: url=' + fileUrl + '; sourceUrl=' + sourcePath); | |
506 var requestKey = this.makeRequestKey_('mount', sourcePath); | |
507 this.startRequest_(requestKey, successCallback, errorCallback); | |
508 }.bind(this)); | |
509 }; | |
510 | |
511 /** | |
512 * Unmounts volume. | |
513 * @param {string} mountPath Volume mounted path. | |
514 * @param {function(string)} successCallback Success callback. | |
515 * @param {function(util.VolumeError)} errorCallback Error callback. | |
516 */ | |
517 VolumeManager.prototype.unmount = function(mountPath, | |
518 successCallback, | |
519 errorCallback) { | |
520 volumeManagerUtil.validateMountPath(mountPath); | |
521 var volumeInfo = this.volumeInfoList.find(mountPath); | |
522 if (!volumeInfo) { | |
523 errorCallback(util.VolumeError.NOT_MOUNTED); | |
524 return; | |
525 } | |
526 | |
527 chrome.fileBrowserPrivate.removeMount(util.makeFilesystemUrl(mountPath)); | |
528 var requestKey = this.makeRequestKey_('unmount', volumeInfo.mountPath); | |
529 this.startRequest_(requestKey, successCallback, errorCallback); | |
530 }; | |
531 | |
532 /** | |
533 * Resolve the path to its entry. | |
534 * @param {string} path The path to be resolved. | |
535 * @param {function(Entry)} successCallback Called with the resolved entry on | |
536 * success. | |
537 * @param {function(FileError)} errorCallback Called on error. | |
538 */ | |
539 VolumeManager.prototype.resolvePath = function( | |
540 path, successCallback, errorCallback) { | |
541 // Make sure the path is in the mounted volume. | |
542 var mountPath = PathUtil.isDriveBasedPath(path) ? | |
543 RootDirectory.DRIVE : PathUtil.getRootPath(path); | |
544 var volumeInfo = this.getVolumeInfo(mountPath); | |
545 if (!volumeInfo || !volumeInfo.root) { | |
546 errorCallback(util.createFileError(FileError.NOT_FOUND_ERR)); | |
547 return; | |
548 } | |
549 | |
550 webkitResolveLocalFileSystemURL( | |
551 util.makeFilesystemUrl(path), successCallback, errorCallback); | |
552 }; | |
553 | |
554 /** | |
555 * @param {string} mountPath Volume mounted path. | |
556 * @return {VolumeInfo} The data about the volume. | |
557 */ | |
558 VolumeManager.prototype.getVolumeInfo = function(mountPath) { | |
559 volumeManagerUtil.validateMountPath(mountPath); | |
560 return this.volumeInfoList.find(mountPath); | |
561 }; | |
562 | |
563 /** | |
564 * @param {string} key Key produced by |makeRequestKey_|. | |
565 * @param {function(string)} successCallback To be called when request finishes | |
566 * successfully. | |
567 * @param {function(util.VolumeError)} errorCallback To be called when | |
568 * request fails. | |
569 * @private | |
570 */ | |
571 VolumeManager.prototype.startRequest_ = function(key, | |
572 successCallback, errorCallback) { | |
573 if (key in this.requests_) { | |
574 var request = this.requests_[key]; | |
575 request.successCallbacks.push(successCallback); | |
576 request.errorCallbacks.push(errorCallback); | |
577 } else { | |
578 this.requests_[key] = { | |
579 successCallbacks: [successCallback], | |
580 errorCallbacks: [errorCallback], | |
581 | |
582 timeout: setTimeout(this.onTimeout_.bind(this, key), | |
583 VolumeManager.TIMEOUT) | |
584 }; | |
585 } | |
586 }; | |
587 | |
588 /** | |
589 * Called if no response received in |TIMEOUT|. | |
590 * @param {string} key Key produced by |makeRequestKey_|. | |
591 * @private | |
592 */ | |
593 VolumeManager.prototype.onTimeout_ = function(key) { | |
594 this.invokeRequestCallbacks_(this.requests_[key], | |
595 util.VolumeError.TIMEOUT); | |
596 delete this.requests_[key]; | |
597 }; | |
598 | |
599 /** | |
600 * @param {string} key Key produced by |makeRequestKey_|. | |
601 * @param {util.VolumeError|'success'} status Status received from the API. | |
602 * @param {string=} opt_mountPath Mount path. | |
603 * @private | |
604 */ | |
605 VolumeManager.prototype.finishRequest_ = function(key, status, opt_mountPath) { | |
606 var request = this.requests_[key]; | |
607 if (!request) | |
608 return; | |
609 | |
610 clearTimeout(request.timeout); | |
611 this.invokeRequestCallbacks_(request, status, opt_mountPath); | |
612 delete this.requests_[key]; | |
613 }; | |
614 | |
615 /** | |
616 * @param {Object} request Structure created in |startRequest_|. | |
617 * @param {util.VolumeError|string} status If status == 'success' | |
618 * success callbacks are called. | |
619 * @param {string=} opt_mountPath Mount path. Required if success. | |
620 * @private | |
621 */ | |
622 VolumeManager.prototype.invokeRequestCallbacks_ = function(request, status, | |
623 opt_mountPath) { | |
624 var callEach = function(callbacks, self, args) { | |
625 for (var i = 0; i < callbacks.length; i++) { | |
626 callbacks[i].apply(self, args); | |
627 } | |
628 }; | |
629 if (status == 'success') { | |
630 callEach(request.successCallbacks, this, [opt_mountPath]); | |
631 } else { | |
632 volumeManagerUtil.validateError(status); | |
633 callEach(request.errorCallbacks, this, [status]); | |
634 } | |
635 }; | |
OLD | NEW |