| 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} volumeId ID of the volume. | |
| 13 * @param {DOMFileSystem} fileSystem The file system object for 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 * @param {!{displayName:string, isCurrentProfile:boolean}} profile Profile | |
| 20 * information. | |
| 21 * @param {string} label Label of the volume. | |
| 22 * @constructor | |
| 23 */ | |
| 24 function VolumeInfo( | |
| 25 volumeType, | |
| 26 volumeId, | |
| 27 fileSystem, | |
| 28 error, | |
| 29 deviceType, | |
| 30 isReadOnly, | |
| 31 profile, | |
| 32 label) { | |
| 33 this.volumeType_ = volumeType; | |
| 34 this.volumeId_ = volumeId; | |
| 35 this.fileSystem_ = fileSystem; | |
| 36 this.label_ = label; | |
| 37 this.displayRoot_ = null; | |
| 38 this.fakeEntries_ = {}; | |
| 39 this.displayRoot_ = null; | |
| 40 this.displayRootPromise_ = null; | |
| 41 | |
| 42 if (volumeType === util.VolumeType.DRIVE) { | |
| 43 // TODO(mtomasz): Convert fake entries to DirectoryProvider. | |
| 44 this.fakeEntries_[RootType.DRIVE_OFFLINE] = { | |
| 45 isDirectory: true, | |
| 46 rootType: RootType.DRIVE_OFFLINE, | |
| 47 toURL: function() { return 'fake-entry://drive_offline' } | |
| 48 }; | |
| 49 this.fakeEntries_[RootType.DRIVE_SHARED_WITH_ME] = { | |
| 50 isDirectory: true, | |
| 51 rootType: RootType.DRIVE_SHARED_WITH_ME, | |
| 52 toURL: function() { return 'fake-entry://drive_shared_with_me'; } | |
| 53 }; | |
| 54 this.fakeEntries_[RootType.DRIVE_RECENT] = { | |
| 55 isDirectory: true, | |
| 56 rootType: RootType.DRIVE_RECENT, | |
| 57 toURL: function() { return 'fake-entry://drive_recent'; } | |
| 58 }; | |
| 59 } | |
| 60 | |
| 61 // Note: This represents if the mounting of the volume is successfully done | |
| 62 // or not. (If error is empty string, the mount is successfully done). | |
| 63 // TODO(hidehiko): Rename to make this more understandable. | |
| 64 this.error_ = error; | |
| 65 this.deviceType_ = deviceType; | |
| 66 this.isReadOnly_ = isReadOnly; | |
| 67 this.profile_ = Object.freeze(profile); | |
| 68 | |
| 69 Object.seal(this); | |
| 70 } | |
| 71 | |
| 72 VolumeInfo.prototype = { | |
| 73 /** | |
| 74 * @return {util.VolumeType} Volume type. | |
| 75 */ | |
| 76 get volumeType() { | |
| 77 return this.volumeType_; | |
| 78 }, | |
| 79 /** | |
| 80 * @return {string} Volume ID. | |
| 81 */ | |
| 82 get volumeId() { | |
| 83 return this.volumeId_; | |
| 84 }, | |
| 85 /** | |
| 86 * @return {DOMFileSystem} File system object. | |
| 87 */ | |
| 88 get fileSystem() { | |
| 89 return this.fileSystem_; | |
| 90 }, | |
| 91 /** | |
| 92 * @return {DirectoryEntry} Display root path. It is null before finishing to | |
| 93 * resolve the entry. | |
| 94 */ | |
| 95 get displayRoot() { | |
| 96 return this.displayRoot_; | |
| 97 }, | |
| 98 /** | |
| 99 * @return {Object.<string, Object>} Fake entries. | |
| 100 */ | |
| 101 get fakeEntries() { | |
| 102 return this.fakeEntries_; | |
| 103 }, | |
| 104 /** | |
| 105 * @return {string} Error identifier. | |
| 106 */ | |
| 107 get error() { | |
| 108 return this.error_; | |
| 109 }, | |
| 110 /** | |
| 111 * @return {string} Device type identifier. | |
| 112 */ | |
| 113 get deviceType() { | |
| 114 return this.deviceType_; | |
| 115 }, | |
| 116 /** | |
| 117 * @return {boolean} Whether read only or not. | |
| 118 */ | |
| 119 get isReadOnly() { | |
| 120 return this.isReadOnly_; | |
| 121 }, | |
| 122 /** | |
| 123 * @return {!{displayName:string, isCurrentProfile:boolean}} Profile data. | |
| 124 */ | |
| 125 get profile() { | |
| 126 return this.profile_; | |
| 127 }, | |
| 128 /** | |
| 129 * @return {string} Label for the volume. | |
| 130 */ | |
| 131 get label() { | |
| 132 return this.label_; | |
| 133 } | |
| 134 }; | |
| 135 | |
| 136 /** | |
| 137 * Starts resolving the display root and obtains it. It may take long time for | |
| 138 * Drive. Once resolved, it is cached. | |
| 139 * | |
| 140 * @param {function(DirectoryEntry)} onSuccess Success callback with the | |
| 141 * display root directory as an argument. | |
| 142 * @param {function(FileError)} onFailure Failure callback. | |
| 143 */ | |
| 144 VolumeInfo.prototype.resolveDisplayRoot = function(onSuccess, onFailure) { | |
| 145 if (!this.displayRootPromise_) { | |
| 146 // TODO(mtomasz): Do not add VolumeInfo which failed to resolve root, and | |
| 147 // remove this if logic. Call onSuccess() always, instead. | |
| 148 if (this.volumeType !== util.VolumeType.DRIVE) { | |
| 149 if (this.fileSystem_) | |
| 150 this.displayRootPromise_ = Promise.resolve(this.fileSystem_.root); | |
| 151 else | |
| 152 this.displayRootPromise_ = Promise.reject(this.error); | |
| 153 } else { | |
| 154 // For Drive, we need to resolve. | |
| 155 var displayRootURL = this.fileSystem_.root.toURL() + '/root'; | |
| 156 this.displayRootPromise_ = new Promise( | |
| 157 webkitResolveLocalFileSystemURL.bind(null, displayRootURL)); | |
| 158 } | |
| 159 | |
| 160 // Store the obtained displayRoot. | |
| 161 this.displayRootPromise_.then(function(displayRoot) { | |
| 162 this.displayRoot_ = displayRoot; | |
| 163 }.bind(this)); | |
| 164 } | |
| 165 this.displayRootPromise_.then(onSuccess, onFailure); | |
| 166 }; | |
| 167 | |
| 168 /** | |
| 169 * Utilities for volume manager implementation. | |
| 170 */ | |
| 171 var volumeManagerUtil = {}; | |
| 172 | |
| 173 /** | |
| 174 * Throws an Error when the given error is not in util.VolumeError. | |
| 175 * @param {util.VolumeError} error Status string usually received from APIs. | |
| 176 */ | |
| 177 volumeManagerUtil.validateError = function(error) { | |
| 178 for (var key in util.VolumeError) { | |
| 179 if (error === util.VolumeError[key]) | |
| 180 return; | |
| 181 } | |
| 182 | |
| 183 throw new Error('Invalid mount error: ' + error); | |
| 184 }; | |
| 185 | |
| 186 /** | |
| 187 * Builds the VolumeInfo data from VolumeMetadata. | |
| 188 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume. | |
| 189 * @param {function(VolumeInfo)} callback Called on completion. | |
| 190 */ | |
| 191 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) { | |
| 192 var localizedLabel; | |
| 193 switch (volumeMetadata.volumeType) { | |
| 194 case util.VolumeType.DOWNLOADS: | |
| 195 localizedLabel = str('DOWNLOADS_DIRECTORY_LABEL'); | |
| 196 break; | |
| 197 case util.VolumeType.DRIVE: | |
| 198 localizedLabel = str('DRIVE_DIRECTORY_LABEL'); | |
| 199 break; | |
| 200 default: | |
| 201 localizedLabel = volumeMetadata.volumeId.split(':', 2)[1]; | |
| 202 break; | |
| 203 } | |
| 204 | |
| 205 chrome.fileBrowserPrivate.requestFileSystem( | |
| 206 volumeMetadata.volumeId, | |
| 207 function(fileSystem) { | |
| 208 // TODO(mtomasz): chrome.runtime.lastError should have error reason. | |
| 209 if (!fileSystem) { | |
| 210 console.error('File system not found: ' + volumeMetadata.volumeId); | |
| 211 callback(new VolumeInfo( | |
| 212 volumeMetadata.volumeType, | |
| 213 volumeMetadata.volumeId, | |
| 214 null, // File system is not found. | |
| 215 volumeMetadata.mountCondition, | |
| 216 volumeMetadata.deviceType, | |
| 217 volumeMetadata.isReadOnly, | |
| 218 volumeMetadata.profile, | |
| 219 localizedLabel)); | |
| 220 return; | |
| 221 } | |
| 222 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) { | |
| 223 // After file system is mounted, we "read" drive grand root | |
| 224 // entry at first. This triggers full feed fetch on background. | |
| 225 // Note: we don't need to handle errors here, because even if | |
| 226 // it fails, accessing to some path later will just become | |
| 227 // a fast-fetch and it re-triggers full-feed fetch. | |
| 228 fileSystem.root.createReader().readEntries( | |
| 229 function() { /* do nothing */ }, | |
| 230 function(error) { | |
| 231 console.error( | |
| 232 'Triggering full feed fetch is failed: ' + error.name); | |
| 233 }); | |
| 234 } | |
| 235 callback(new VolumeInfo( | |
| 236 volumeMetadata.volumeType, | |
| 237 volumeMetadata.volumeId, | |
| 238 fileSystem, | |
| 239 volumeMetadata.mountCondition, | |
| 240 volumeMetadata.deviceType, | |
| 241 volumeMetadata.isReadOnly, | |
| 242 volumeMetadata.profile, | |
| 243 localizedLabel)); | |
| 244 }); | |
| 245 }; | |
| 246 | |
| 247 /** | |
| 248 * The order of the volume list based on root type. | |
| 249 * @type {Array.<util.VolumeType>} | |
| 250 * @const | |
| 251 * @private | |
| 252 */ | |
| 253 volumeManagerUtil.volumeListOrder_ = [ | |
| 254 util.VolumeType.DRIVE, | |
| 255 util.VolumeType.DOWNLOADS, | |
| 256 util.VolumeType.ARCHIVE, | |
| 257 util.VolumeType.REMOVABLE, | |
| 258 util.VolumeType.MTP, | |
| 259 util.VolumeType.CLOUD_DEVICE | |
| 260 ]; | |
| 261 | |
| 262 /** | |
| 263 * Orders two volumes by volumeType and volumeId. | |
| 264 * | |
| 265 * The volumes at first are compared by volume type in the order of | |
| 266 * volumeListOrder_. Then they are compared by volume ID. | |
| 267 * | |
| 268 * @param {VolumeInfo} volumeInfo1 Volume info to be compared. | |
| 269 * @param {VolumeInfo} volumeInfo2 Volume info to be compared. | |
| 270 * @return {number} Returns -1 if volume1 < volume2, returns 1 if volume2 > | |
| 271 * volume1, returns 0 if volume1 === volume2. | |
| 272 * @private | |
| 273 */ | |
| 274 volumeManagerUtil.compareVolumeInfo_ = function(volumeInfo1, volumeInfo2) { | |
| 275 var typeIndex1 = | |
| 276 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo1.volumeType); | |
| 277 var typeIndex2 = | |
| 278 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo2.volumeType); | |
| 279 if (typeIndex1 !== typeIndex2) | |
| 280 return typeIndex1 < typeIndex2 ? -1 : 1; | |
| 281 if (volumeInfo1.volumeId !== volumeInfo2.volumeId) | |
| 282 return volumeInfo1.volumeId < volumeInfo2.volumeId ? -1 : 1; | |
| 283 return 0; | |
| 284 }; | |
| 285 | |
| 286 /** | |
| 287 * The container of the VolumeInfo for each mounted volume. | |
| 288 * @constructor | |
| 289 */ | |
| 290 function VolumeInfoList() { | |
| 291 var field = 'volumeType,volumeId'; | |
| 292 | |
| 293 /** | |
| 294 * Holds VolumeInfo instances. | |
| 295 * @type {cr.ui.ArrayDataModel} | |
| 296 * @private | |
| 297 */ | |
| 298 this.model_ = new cr.ui.ArrayDataModel([]); | |
| 299 this.model_.setCompareFunction(field, volumeManagerUtil.compareVolumeInfo_); | |
| 300 this.model_.sort(field, 'asc'); | |
| 301 | |
| 302 Object.freeze(this); | |
| 303 } | |
| 304 | |
| 305 VolumeInfoList.prototype = { | |
| 306 get length() { return this.model_.length; } | |
| 307 }; | |
| 308 | |
| 309 /** | |
| 310 * Adds the event listener to listen the change of volume info. | |
| 311 * @param {string} type The name of the event. | |
| 312 * @param {function(Event)} handler The handler for the event. | |
| 313 */ | |
| 314 VolumeInfoList.prototype.addEventListener = function(type, handler) { | |
| 315 this.model_.addEventListener(type, handler); | |
| 316 }; | |
| 317 | |
| 318 /** | |
| 319 * Removes the event listener. | |
| 320 * @param {string} type The name of the event. | |
| 321 * @param {function(Event)} handler The handler to be removed. | |
| 322 */ | |
| 323 VolumeInfoList.prototype.removeEventListener = function(type, handler) { | |
| 324 this.model_.removeEventListener(type, handler); | |
| 325 }; | |
| 326 | |
| 327 /** | |
| 328 * Adds the volumeInfo to the appropriate position. If there already exists, | |
| 329 * just replaces it. | |
| 330 * @param {VolumeInfo} volumeInfo The information of the new volume. | |
| 331 */ | |
| 332 VolumeInfoList.prototype.add = function(volumeInfo) { | |
| 333 var index = this.findIndex(volumeInfo.volumeId); | |
| 334 if (index !== -1) | |
| 335 this.model_.splice(index, 1, volumeInfo); | |
| 336 else | |
| 337 this.model_.push(volumeInfo); | |
| 338 }; | |
| 339 | |
| 340 /** | |
| 341 * Removes the VolumeInfo having the given ID. | |
| 342 * @param {string} volumeId ID of the volume. | |
| 343 */ | |
| 344 VolumeInfoList.prototype.remove = function(volumeId) { | |
| 345 var index = this.findIndex(volumeId); | |
| 346 if (index !== -1) | |
| 347 this.model_.splice(index, 1); | |
| 348 }; | |
| 349 | |
| 350 /** | |
| 351 * Obtains an index from the volume ID. | |
| 352 * @param {string} volumeId Volume ID. | |
| 353 * @return {number} Index of the volume. | |
| 354 */ | |
| 355 VolumeInfoList.prototype.findIndex = function(volumeId) { | |
| 356 for (var i = 0; i < this.model_.length; i++) { | |
| 357 if (this.model_.item(i).volumeId === volumeId) | |
| 358 return i; | |
| 359 } | |
| 360 return -1; | |
| 361 }; | |
| 362 | |
| 363 /** | |
| 364 * Searches the information of the volume that contains the passed entry. | |
| 365 * @param {Entry|Object} entry Entry on the volume to be found. | |
| 366 * @return {VolumeInfo} The volume's information, or null if not found. | |
| 367 */ | |
| 368 VolumeInfoList.prototype.findByEntry = function(entry) { | |
| 369 for (var i = 0; i < this.length; i++) { | |
| 370 var volumeInfo = this.item(i); | |
| 371 if (volumeInfo.fileSystem && | |
| 372 util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) { | |
| 373 return volumeInfo; | |
| 374 } | |
| 375 // Additionally, check fake entries. | |
| 376 for (var key in volumeInfo.fakeEntries_) { | |
| 377 var fakeEntry = volumeInfo.fakeEntries_[key]; | |
| 378 if (util.isSameEntry(fakeEntry, entry)) | |
| 379 return volumeInfo; | |
| 380 } | |
| 381 } | |
| 382 return null; | |
| 383 }; | |
| 384 | |
| 385 /** | |
| 386 * @param {number} index The index of the volume in the list. | |
| 387 * @return {VolumeInfo} The VolumeInfo instance. | |
| 388 */ | |
| 389 VolumeInfoList.prototype.item = function(index) { | |
| 390 return this.model_.item(index); | |
| 391 }; | |
| 392 | |
| 393 /** | |
| 394 * VolumeManager is responsible for tracking list of mounted volumes. | |
| 395 * | |
| 396 * @constructor | |
| 397 * @extends {cr.EventTarget} | |
| 398 */ | |
| 399 function VolumeManager() { | |
| 400 /** | |
| 401 * The list of archives requested to mount. We will show contents once | |
| 402 * archive is mounted, but only for mounts from within this filebrowser tab. | |
| 403 * @type {Object.<string, Object>} | |
| 404 * @private | |
| 405 */ | |
| 406 this.requests_ = {}; | |
| 407 | |
| 408 /** | |
| 409 * The list of VolumeInfo instances for each mounted volume. | |
| 410 * @type {VolumeInfoList} | |
| 411 */ | |
| 412 this.volumeInfoList = new VolumeInfoList(); | |
| 413 | |
| 414 /** | |
| 415 * Queue for mounting. | |
| 416 * @type {AsyncUtil.Queue} | |
| 417 * @private | |
| 418 */ | |
| 419 this.mountQueue_ = new AsyncUtil.Queue(); | |
| 420 | |
| 421 // The status should be merged into VolumeManager. | |
| 422 // TODO(hidehiko): Remove them after the migration. | |
| 423 this.driveConnectionState_ = { | |
| 424 type: util.DriveConnectionType.OFFLINE, | |
| 425 reason: util.DriveConnectionReason.NO_SERVICE | |
| 426 }; | |
| 427 | |
| 428 chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener( | |
| 429 this.onDriveConnectionStatusChanged_.bind(this)); | |
| 430 this.onDriveConnectionStatusChanged_(); | |
| 431 } | |
| 432 | |
| 433 /** | |
| 434 * Invoked when the drive connection status is changed. | |
| 435 * @private_ | |
| 436 */ | |
| 437 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() { | |
| 438 chrome.fileBrowserPrivate.getDriveConnectionState(function(state) { | |
| 439 this.driveConnectionState_ = state; | |
| 440 cr.dispatchSimpleEvent(this, 'drive-connection-changed'); | |
| 441 }.bind(this)); | |
| 442 }; | |
| 443 | |
| 444 /** | |
| 445 * Returns the drive connection state. | |
| 446 * @return {util.DriveConnectionType} Connection type. | |
| 447 */ | |
| 448 VolumeManager.prototype.getDriveConnectionState = function() { | |
| 449 return this.driveConnectionState_; | |
| 450 }; | |
| 451 | |
| 452 /** | |
| 453 * VolumeManager extends cr.EventTarget. | |
| 454 */ | |
| 455 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype; | |
| 456 | |
| 457 /** | |
| 458 * Time in milliseconds that we wait a response for. If no response on | |
| 459 * mount/unmount received the request supposed failed. | |
| 460 */ | |
| 461 VolumeManager.TIMEOUT = 15 * 60 * 1000; | |
| 462 | |
| 463 /** | |
| 464 * Queue to run getInstance sequentially. | |
| 465 * @type {AsyncUtil.Queue} | |
| 466 * @private | |
| 467 */ | |
| 468 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue(); | |
| 469 | |
| 470 /** | |
| 471 * The singleton instance of VolumeManager. Initialized by the first invocation | |
| 472 * of getInstance(). | |
| 473 * @type {VolumeManager} | |
| 474 * @private | |
| 475 */ | |
| 476 VolumeManager.instance_ = null; | |
| 477 | |
| 478 /** | |
| 479 * Returns the VolumeManager instance asynchronously. If it is not created or | |
| 480 * under initialization, it will waits for the finish of the initialization. | |
| 481 * @param {function(VolumeManager)} callback Called with the VolumeManager | |
| 482 * instance. | |
| 483 */ | |
| 484 VolumeManager.getInstance = function(callback) { | |
| 485 VolumeManager.getInstanceQueue_.run(function(continueCallback) { | |
| 486 if (VolumeManager.instance_) { | |
| 487 callback(VolumeManager.instance_); | |
| 488 continueCallback(); | |
| 489 return; | |
| 490 } | |
| 491 | |
| 492 VolumeManager.instance_ = new VolumeManager(); | |
| 493 VolumeManager.instance_.initialize_(function() { | |
| 494 callback(VolumeManager.instance_); | |
| 495 continueCallback(); | |
| 496 }); | |
| 497 }); | |
| 498 }; | |
| 499 | |
| 500 /** | |
| 501 * Initializes mount points. | |
| 502 * @param {function()} callback Called upon the completion of the | |
| 503 * initialization. | |
| 504 * @private | |
| 505 */ | |
| 506 VolumeManager.prototype.initialize_ = function(callback) { | |
| 507 chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) { | |
| 508 // We must subscribe to the mount completed event in the callback of | |
| 509 // getVolumeMetadataList. crbug.com/330061. | |
| 510 // But volumes reported by onMountCompleted events must be added after the | |
| 511 // volumes in the volumeMetadataList are mounted. crbug.com/135477. | |
| 512 this.mountQueue_.run(function(inCallback) { | |
| 513 // Create VolumeInfo for each volume. | |
| 514 var group = new AsyncUtil.Group(); | |
| 515 for (var i = 0; i < volumeMetadataList.length; i++) { | |
| 516 group.add(function(volumeMetadata, continueCallback) { | |
| 517 volumeManagerUtil.createVolumeInfo( | |
| 518 volumeMetadata, | |
| 519 function(volumeInfo) { | |
| 520 this.volumeInfoList.add(volumeInfo); | |
| 521 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) | |
| 522 this.onDriveConnectionStatusChanged_(); | |
| 523 continueCallback(); | |
| 524 }.bind(this)); | |
| 525 }.bind(this, volumeMetadataList[i])); | |
| 526 } | |
| 527 group.run(function() { | |
| 528 // Call the callback of the initialize function. | |
| 529 callback(); | |
| 530 // Call the callback of AsyncQueue. Maybe it invokes callbacks | |
| 531 // registered by mountCompleted events. | |
| 532 inCallback(); | |
| 533 }); | |
| 534 }.bind(this)); | |
| 535 | |
| 536 chrome.fileBrowserPrivate.onMountCompleted.addListener( | |
| 537 this.onMountCompleted_.bind(this)); | |
| 538 }.bind(this)); | |
| 539 }; | |
| 540 | |
| 541 /** | |
| 542 * Event handler called when some volume was mounted or unmounted. | |
| 543 * @param {MountCompletedEvent} event Received event. | |
| 544 * @private | |
| 545 */ | |
| 546 VolumeManager.prototype.onMountCompleted_ = function(event) { | |
| 547 this.mountQueue_.run(function(callback) { | |
| 548 switch (event.eventType) { | |
| 549 case 'mount': | |
| 550 var requestKey = this.makeRequestKey_( | |
| 551 'mount', | |
| 552 event.volumeMetadata.sourcePath); | |
| 553 | |
| 554 var error = event.status === 'success' ? '' : event.status; | |
| 555 if (!error || event.status === 'error_unknown_filesystem') { | |
| 556 volumeManagerUtil.createVolumeInfo( | |
| 557 event.volumeMetadata, | |
| 558 function(volumeInfo) { | |
| 559 this.volumeInfoList.add(volumeInfo); | |
| 560 this.finishRequest_(requestKey, event.status, volumeInfo); | |
| 561 | |
| 562 if (volumeInfo.volumeType === util.VolumeType.DRIVE) { | |
| 563 // Update the network connection status, because until the | |
| 564 // drive is initialized, the status is set to not ready. | |
| 565 // TODO(mtomasz): The connection status should be migrated | |
| 566 // into VolumeMetadata. | |
| 567 this.onDriveConnectionStatusChanged_(); | |
| 568 } | |
| 569 callback(); | |
| 570 }.bind(this)); | |
| 571 } else { | |
| 572 console.warn('Failed to mount a volume: ' + event.status); | |
| 573 this.finishRequest_(requestKey, event.status); | |
| 574 callback(); | |
| 575 } | |
| 576 break; | |
| 577 | |
| 578 case 'unmount': | |
| 579 var volumeId = event.volumeMetadata.volumeId; | |
| 580 var status = event.status; | |
| 581 if (status === util.VolumeError.PATH_UNMOUNTED) { | |
| 582 console.warn('Volume already unmounted: ', volumeId); | |
| 583 status = 'success'; | |
| 584 } | |
| 585 var requestKey = this.makeRequestKey_('unmount', volumeId); | |
| 586 var requested = requestKey in this.requests_; | |
| 587 var volumeInfoIndex = | |
| 588 this.volumeInfoList.findIndex(volumeId); | |
| 589 var volumeInfo = volumeInfoIndex !== -1 ? | |
| 590 this.volumeInfoList.item(volumeInfoIndex) : null; | |
| 591 if (event.status === 'success' && !requested && volumeInfo) { | |
| 592 console.warn('Mounted volume without a request: ' + volumeId); | |
| 593 var e = new Event('externally-unmounted'); | |
| 594 e.volumeInfo = volumeInfo; | |
| 595 this.dispatchEvent(e); | |
| 596 } | |
| 597 | |
| 598 this.finishRequest_(requestKey, status); | |
| 599 if (event.status === 'success') | |
| 600 this.volumeInfoList.remove(event.volumeMetadata.volumeId); | |
| 601 callback(); | |
| 602 break; | |
| 603 } | |
| 604 }.bind(this)); | |
| 605 }; | |
| 606 | |
| 607 /** | |
| 608 * Creates string to match mount events with requests. | |
| 609 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by | |
| 610 * enum. | |
| 611 * @param {string} argument Argument describing the request, eg. source file | |
| 612 * path of the archive to be mounted, or a volumeId for unmounting. | |
| 613 * @return {string} Key for |this.requests_|. | |
| 614 * @private | |
| 615 */ | |
| 616 VolumeManager.prototype.makeRequestKey_ = function(requestType, argument) { | |
| 617 return requestType + ':' + argument; | |
| 618 }; | |
| 619 | |
| 620 /** | |
| 621 * @param {string} fileUrl File url to the archive file. | |
| 622 * @param {function(VolumeInfo)} successCallback Success callback. | |
| 623 * @param {function(util.VolumeError)} errorCallback Error callback. | |
| 624 */ | |
| 625 VolumeManager.prototype.mountArchive = function( | |
| 626 fileUrl, successCallback, errorCallback) { | |
| 627 chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) { | |
| 628 console.info( | |
| 629 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath); | |
| 630 var requestKey = this.makeRequestKey_('mount', sourcePath); | |
| 631 this.startRequest_(requestKey, successCallback, errorCallback); | |
| 632 }.bind(this)); | |
| 633 }; | |
| 634 | |
| 635 /** | |
| 636 * Unmounts volume. | |
| 637 * @param {!VolumeInfo} volumeInfo Volume to be unmounted. | |
| 638 * @param {function()} successCallback Success callback. | |
| 639 * @param {function(util.VolumeError)} errorCallback Error callback. | |
| 640 */ | |
| 641 VolumeManager.prototype.unmount = function(volumeInfo, | |
| 642 successCallback, | |
| 643 errorCallback) { | |
| 644 chrome.fileBrowserPrivate.removeMount(volumeInfo.volumeId); | |
| 645 var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId); | |
| 646 this.startRequest_(requestKey, successCallback, errorCallback); | |
| 647 }; | |
| 648 | |
| 649 /** | |
| 650 * Obtains a volume info containing the passed entry. | |
| 651 * @param {Entry|Object} entry Entry on the volume to be returned. Can be fake. | |
| 652 * @return {VolumeInfo} The VolumeInfo instance or null if not found. | |
| 653 */ | |
| 654 VolumeManager.prototype.getVolumeInfo = function(entry) { | |
| 655 return this.volumeInfoList.findByEntry(entry); | |
| 656 }; | |
| 657 | |
| 658 /** | |
| 659 * Obtains volume information of the current profile. | |
| 660 * | |
| 661 * @param {util.VolumeType} volumeType Volume type. | |
| 662 * @return {VolumeInfo} Volume info. | |
| 663 */ | |
| 664 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) { | |
| 665 for (var i = 0; i < this.volumeInfoList.length; i++) { | |
| 666 var volumeInfo = this.volumeInfoList.item(i); | |
| 667 if (volumeInfo.profile.isCurrentProfile && | |
| 668 volumeInfo.volumeType === volumeType) | |
| 669 return volumeInfo; | |
| 670 } | |
| 671 return null; | |
| 672 }; | |
| 673 | |
| 674 /** | |
| 675 * Obtains location information from an entry. | |
| 676 * | |
| 677 * @param {Entry|Object} entry File or directory entry. It can be a fake entry. | |
| 678 * @return {EntryLocation} Location information. | |
| 679 */ | |
| 680 VolumeManager.prototype.getLocationInfo = function(entry) { | |
| 681 var volumeInfo = this.volumeInfoList.findByEntry(entry); | |
| 682 if (!volumeInfo) | |
| 683 return null; | |
| 684 | |
| 685 if (util.isFakeEntry(entry)) { | |
| 686 return new EntryLocation( | |
| 687 volumeInfo, | |
| 688 entry.rootType, | |
| 689 true /* the entry points a root directory. */, | |
| 690 true /* fake entries are read only. */); | |
| 691 } | |
| 692 | |
| 693 var rootType; | |
| 694 var isReadOnly; | |
| 695 var isRootEntry; | |
| 696 if (volumeInfo.volumeType === util.VolumeType.DRIVE) { | |
| 697 // For Drive, the roots are /root and /other, instead of /. Root URLs | |
| 698 // contain trailing slashes. | |
| 699 if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) { | |
| 700 rootType = RootType.DRIVE; | |
| 701 isReadOnly = volumeInfo.isReadOnly; | |
| 702 isRootEntry = entry.fullPath === '/root'; | |
| 703 } else if (entry.fullPath == '/other' || | |
| 704 entry.fullPath.indexOf('/other/') === 0) { | |
| 705 rootType = RootType.DRIVE_OTHER; | |
| 706 isReadOnly = true; | |
| 707 isRootEntry = entry.fullPath === '/other'; | |
| 708 } else { | |
| 709 // Accessing Drive files outside of /drive/root and /drive/other is not | |
| 710 // allowed, but can happen. Therefore returning null. | |
| 711 return null; | |
| 712 } | |
| 713 } else { | |
| 714 switch (volumeInfo.volumeType) { | |
| 715 case util.VolumeType.DOWNLOADS: | |
| 716 rootType = RootType.DOWNLOADS; | |
| 717 break; | |
| 718 case util.VolumeType.REMOVABLE: | |
| 719 rootType = RootType.REMOVABLE; | |
| 720 break; | |
| 721 case util.VolumeType.ARCHIVE: | |
| 722 rootType = RootType.ARCHIVE; | |
| 723 break; | |
| 724 case util.VolumeType.CLOUD_DEVICE: | |
| 725 rootType = RootType.CLOUD_DEVICE; | |
| 726 break; | |
| 727 case util.VolumeType.MTP: | |
| 728 rootType = RootType.MTP; | |
| 729 break; | |
| 730 default: | |
| 731 // Programming error, throw an exception. | |
| 732 throw new Error('Invalid volume type: ' + volumeInfo.volumeType); | |
| 733 } | |
| 734 isReadOnly = volumeInfo.isReadOnly; | |
| 735 isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root); | |
| 736 } | |
| 737 | |
| 738 return new EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly); | |
| 739 }; | |
| 740 | |
| 741 /** | |
| 742 * @param {string} key Key produced by |makeRequestKey_|. | |
| 743 * @param {function(VolumeInfo)} successCallback To be called when request | |
| 744 * finishes successfully. | |
| 745 * @param {function(util.VolumeError)} errorCallback To be called when | |
| 746 * request fails. | |
| 747 * @private | |
| 748 */ | |
| 749 VolumeManager.prototype.startRequest_ = function(key, | |
| 750 successCallback, errorCallback) { | |
| 751 if (key in this.requests_) { | |
| 752 var request = this.requests_[key]; | |
| 753 request.successCallbacks.push(successCallback); | |
| 754 request.errorCallbacks.push(errorCallback); | |
| 755 } else { | |
| 756 this.requests_[key] = { | |
| 757 successCallbacks: [successCallback], | |
| 758 errorCallbacks: [errorCallback], | |
| 759 | |
| 760 timeout: setTimeout(this.onTimeout_.bind(this, key), | |
| 761 VolumeManager.TIMEOUT) | |
| 762 }; | |
| 763 } | |
| 764 }; | |
| 765 | |
| 766 /** | |
| 767 * Called if no response received in |TIMEOUT|. | |
| 768 * @param {string} key Key produced by |makeRequestKey_|. | |
| 769 * @private | |
| 770 */ | |
| 771 VolumeManager.prototype.onTimeout_ = function(key) { | |
| 772 this.invokeRequestCallbacks_(this.requests_[key], | |
| 773 util.VolumeError.TIMEOUT); | |
| 774 delete this.requests_[key]; | |
| 775 }; | |
| 776 | |
| 777 /** | |
| 778 * @param {string} key Key produced by |makeRequestKey_|. | |
| 779 * @param {util.VolumeError|'success'} status Status received from the API. | |
| 780 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume. | |
| 781 * @private | |
| 782 */ | |
| 783 VolumeManager.prototype.finishRequest_ = function(key, status, opt_volumeInfo) { | |
| 784 var request = this.requests_[key]; | |
| 785 if (!request) | |
| 786 return; | |
| 787 | |
| 788 clearTimeout(request.timeout); | |
| 789 this.invokeRequestCallbacks_(request, status, opt_volumeInfo); | |
| 790 delete this.requests_[key]; | |
| 791 }; | |
| 792 | |
| 793 /** | |
| 794 * @param {Object} request Structure created in |startRequest_|. | |
| 795 * @param {util.VolumeError|string} status If status === 'success' | |
| 796 * success callbacks are called. | |
| 797 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume. | |
| 798 * @private | |
| 799 */ | |
| 800 VolumeManager.prototype.invokeRequestCallbacks_ = function( | |
| 801 request, status, opt_volumeInfo) { | |
| 802 var callEach = function(callbacks, self, args) { | |
| 803 for (var i = 0; i < callbacks.length; i++) { | |
| 804 callbacks[i].apply(self, args); | |
| 805 } | |
| 806 }; | |
| 807 if (status === 'success') { | |
| 808 callEach(request.successCallbacks, this, [opt_volumeInfo]); | |
| 809 } else { | |
| 810 volumeManagerUtil.validateError(status); | |
| 811 callEach(request.errorCallbacks, this, [status]); | |
| 812 } | |
| 813 }; | |
| 814 | |
| 815 /** | |
| 816 * Location information which shows where the path points in FileManager's | |
| 817 * file system. | |
| 818 * | |
| 819 * @param {!VolumeInfo} volumeInfo Volume information. | |
| 820 * @param {RootType} rootType Root type. | |
| 821 * @param {boolean} isRootEntry Whether the entry is root entry or not. | |
| 822 * @param {boolean} isReadOnly Whether the entry is read only or not. | |
| 823 * @constructor | |
| 824 */ | |
| 825 function EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly) { | |
| 826 /** | |
| 827 * Volume information. | |
| 828 * @type {!VolumeInfo} | |
| 829 */ | |
| 830 this.volumeInfo = volumeInfo; | |
| 831 | |
| 832 /** | |
| 833 * Root type. | |
| 834 * @type {RootType} | |
| 835 */ | |
| 836 this.rootType = rootType; | |
| 837 | |
| 838 /** | |
| 839 * Whether the entry is root entry or not. | |
| 840 * @type {boolean} | |
| 841 */ | |
| 842 this.isRootEntry = isRootEntry; | |
| 843 | |
| 844 /** | |
| 845 * Whether the location obtained from the fake entry correspond to special | |
| 846 * searches. | |
| 847 * @type {boolean} | |
| 848 */ | |
| 849 this.isSpecialSearchRoot = | |
| 850 this.rootType === RootType.DRIVE_OFFLINE || | |
| 851 this.rootType === RootType.DRIVE_SHARED_WITH_ME || | |
| 852 this.rootType === RootType.DRIVE_RECENT; | |
| 853 | |
| 854 /** | |
| 855 * Whether the location is under Google Drive or a special search root which | |
| 856 * represents a special search from Google Drive. | |
| 857 * @type {boolean} | |
| 858 */ | |
| 859 this.isDriveBased = | |
| 860 this.rootType === RootType.DRIVE || | |
| 861 this.rootType === RootType.DRIVE_OTHER || | |
| 862 this.rootType === RootType.DRIVE_SHARED_WITH_ME || | |
| 863 this.rootType === RootType.DRIVE_RECENT || | |
| 864 this.rootType === RootType.DRIVE_OFFLINE; | |
| 865 | |
| 866 /** | |
| 867 * Whether the given path can be a target path of folder shortcut. | |
| 868 * @type {boolean} | |
| 869 */ | |
| 870 this.isEligibleForFolderShortcut = | |
| 871 !this.isSpecialSearchRoot && | |
| 872 !this.isRootEntry && | |
| 873 this.isDriveBased; | |
| 874 | |
| 875 /** | |
| 876 * Whether the entry is read only or not. | |
| 877 * @type {boolean} | |
| 878 */ | |
| 879 this.isReadOnly = isReadOnly; | |
| 880 | |
| 881 Object.freeze(this); | |
| 882 } | |
| OLD | NEW |