1// Copyright 2013 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 * Thin wrapper for VolumeManager. This should be an interface proxy to talk 7 * to VolumeManager. This class also filters Drive related data/events if 8 * driveEnabled is set to false. 9 * 10 * @param {VolumeManagerWrapper.DriveEnabledStatus} driveEnabled DRIVE_ENABLED 11 * if drive should be available. DRIVE_DISABLED if drive related 12 * data/events should be hidden. 13 * @param {DOMWindow=} opt_backgroundPage Window object of the background 14 * page. If this is specified, the class skips to get background page. 15 * TOOD(hirono): Let all clients of the class pass the background page and 16 * make the argument not optional. 17 * @constructor 18 * @extends {cr.EventTarget} 19 */ 20function VolumeManagerWrapper(driveEnabled, opt_backgroundPage) { 21 cr.EventTarget.call(this); 22 23 this.driveEnabled_ = driveEnabled; 24 this.volumeInfoList = new cr.ui.ArrayDataModel([]); 25 26 this.volumeManager_ = null; 27 this.pendingTasks_ = []; 28 this.onEventBound_ = this.onEvent_.bind(this); 29 this.onVolumeInfoListUpdatedBound_ = 30 this.onVolumeInfoListUpdated_.bind(this); 31 32 this.disposed_ = false; 33 34 // Start initialize the VolumeManager. 35 var queue = new AsyncUtil.Queue(); 36 37 if (opt_backgroundPage) { 38 this.backgroundPage_ = opt_backgroundPage; 39 } else { 40 queue.run(function(callNextStep) { 41 chrome.runtime.getBackgroundPage(function(backgroundPage) { 42 this.backgroundPage_ = backgroundPage; 43 callNextStep(); 44 }.bind(this)); 45 }.bind(this)); 46 } 47 48 queue.run(function(callNextStep) { 49 this.backgroundPage_.VolumeManager.getInstance(function(volumeManager) { 50 this.onReady_(volumeManager); 51 callNextStep(); 52 }.bind(this)); 53 }.bind(this)); 54} 55 56/** 57 * If the drive is enabled on the wrapper. 58 * @enum {boolean} 59 */ 60VolumeManagerWrapper.DriveEnabledStatus = { 61 DRIVE_ENABLED: true, 62 DRIVE_DISABLED: false 63}; 64 65/** 66 * Extends cr.EventTarget. 67 */ 68VolumeManagerWrapper.prototype.__proto__ = cr.EventTarget.prototype; 69 70/** 71 * Called when the VolumeManager gets ready for post initialization. 72 * @param {VolumeManager} volumeManager The initialized VolumeManager instance. 73 * @private 74 */ 75VolumeManagerWrapper.prototype.onReady_ = function(volumeManager) { 76 if (this.disposed_) 77 return; 78 79 this.volumeManager_ = volumeManager; 80 81 // Subscribe to VolumeManager. 82 this.volumeManager_.addEventListener( 83 'drive-connection-changed', this.onEventBound_); 84 this.volumeManager_.addEventListener( 85 'externally-unmounted', this.onEventBound_); 86 87 // Cache volumeInfoList. 88 var volumeInfoList = []; 89 for (var i = 0; i < this.volumeManager_.volumeInfoList.length; i++) { 90 var volumeInfo = this.volumeManager_.volumeInfoList.item(i); 91 // TODO(hidehiko): Filter mounted volumes located on Drive File System. 92 if (!this.driveEnabled_ && volumeInfo.volumeType === 93 VolumeManagerCommon.VolumeType.DRIVE) 94 continue; 95 volumeInfoList.push(volumeInfo); 96 } 97 this.volumeInfoList.splice.apply( 98 this.volumeInfoList, 99 [0, this.volumeInfoList.length].concat(volumeInfoList)); 100 101 // Subscribe to VolumeInfoList. 102 // In VolumeInfoList, we only use 'splice' event. 103 this.volumeManager_.volumeInfoList.addEventListener( 104 'splice', this.onVolumeInfoListUpdatedBound_); 105 106 // Run pending tasks. 107 var pendingTasks = this.pendingTasks_; 108 this.pendingTasks_ = null; 109 for (var i = 0; i < pendingTasks.length; i++) 110 pendingTasks[i](); 111}; 112 113/** 114 * Disposes the instance. After the invocation of this method, any other 115 * method should not be called. 116 */ 117VolumeManagerWrapper.prototype.dispose = function() { 118 this.disposed_ = true; 119 120 if (!this.volumeManager_) 121 return; 122 this.volumeManager_.removeEventListener( 123 'drive-connection-changed', this.onEventBound_); 124 this.volumeManager_.removeEventListener( 125 'externally-unmounted', this.onEventBound_); 126 this.volumeManager_.volumeInfoList.removeEventListener( 127 'splice', this.onVolumeInfoListUpdatedBound_); 128}; 129 130/** 131 * Called on events sent from VolumeManager. This has responsibility to 132 * re-dispatch the event to the listeners. 133 * @param {Event} event Event object sent from VolumeManager. 134 * @private 135 */ 136VolumeManagerWrapper.prototype.onEvent_ = function(event) { 137 if (!this.driveEnabled_) { 138 // If the drive is disabled, ignore all drive related events. 139 if (event.type === 'drive-connection-changed' || 140 (event.type === 'externally-unmounted' && 141 event.volumeInfo.volumeType === 142 VolumeManagerCommon.VolumeType.DRIVE)) { 143 return; 144 } 145 } 146 147 this.dispatchEvent(event); 148}; 149 150/** 151 * Called on events of modifying VolumeInfoList. 152 * @param {Event} event Event object sent from VolumeInfoList. 153 * @private 154 */ 155VolumeManagerWrapper.prototype.onVolumeInfoListUpdated_ = function(event) { 156 if (this.driveEnabled_) { 157 // Apply the splice as is. 158 this.volumeInfoList.splice.apply( 159 this.volumeInfoList, 160 [event.index, event.removed.length].concat(event.added)); 161 } else { 162 // Filters drive related volumes. 163 var index = event.index; 164 for (var i = 0; i < event.index; i++) { 165 if (this.volumeManager_.volumeInfoList.item(i).volumeType === 166 VolumeManagerCommon.VolumeType.DRIVE) 167 index--; 168 } 169 170 var numRemovedVolumes = 0; 171 for (var i = 0; i < event.removed.length; i++) { 172 if (event.removed[i].volumeType !== VolumeManagerCommon.VolumeType.DRIVE) 173 numRemovedVolumes++; 174 } 175 176 var addedVolumes = []; 177 for (var i = 0; i < event.added.length; i++) { 178 var volumeInfo = event.added[i]; 179 if (volumeInfo.volumeType !== VolumeManagerCommon.VolumeType.DRIVE) 180 addedVolumes.push(volumeInfo); 181 } 182 183 this.volumeInfoList.splice.apply( 184 this.volumeInfoList, 185 [index, numRemovedVolumes].concat(addedVolumes)); 186 } 187}; 188 189/** 190 * Ensures the VolumeManager is initialized, and then invokes callback. 191 * If the VolumeManager is already initialized, callback will be called 192 * immediately. 193 * @param {function()} callback Called on initialization completion. 194 */ 195VolumeManagerWrapper.prototype.ensureInitialized = function(callback) { 196 if (this.pendingTasks_) { 197 this.pendingTasks_.push(this.ensureInitialized.bind(this, callback)); 198 return; 199 } 200 201 callback(); 202}; 203 204/** 205 * @return {VolumeManagerCommon.DriveConnectionType} Current drive connection 206 * state. 207 */ 208VolumeManagerWrapper.prototype.getDriveConnectionState = function() { 209 if (!this.driveEnabled_ || !this.volumeManager_) { 210 return { 211 type: VolumeManagerCommon.DriveConnectionType.OFFLINE, 212 reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE 213 }; 214 } 215 216 return this.volumeManager_.getDriveConnectionState(); 217}; 218 219/** 220 * Obtains a volume info containing the passed entry. 221 * @param {Entry} entry Entry on the volume to be returned. 222 * @return {VolumeInfo} The VolumeInfo instance or null if not found. 223 */ 224VolumeManagerWrapper.prototype.getVolumeInfo = function(entry) { 225 return this.filterDisabledDriveVolume_( 226 this.volumeManager_ && this.volumeManager_.getVolumeInfo(entry)); 227}; 228 229/** 230 * Obtains a volume information of the current profile. 231 * @param {VolumeManagerCommon.VolumeType} volumeType Volume type. 232 * @return {VolumeInfo} Found volume info. 233 */ 234VolumeManagerWrapper.prototype.getCurrentProfileVolumeInfo = 235 function(volumeType) { 236 return this.filterDisabledDriveVolume_( 237 this.volumeManager_ && 238 this.volumeManager_.getCurrentProfileVolumeInfo(volumeType)); 239}; 240 241/** 242 * Obtains the default display root entry. 243 * @param {function(Entry)} callback Callback passed the default display root. 244 */ 245VolumeManagerWrapper.prototype.getDefaultDisplayRoot = 246 function(callback) { 247 this.ensureInitialized(function() { 248 var defaultVolume = this.getCurrentProfileVolumeInfo( 249 VolumeManagerCommon.VolumeType.DOWNLOADS); 250 defaultVolume.resolveDisplayRoot(callback, function() { 251 // defaultVolume is DOWNLOADS and resolveDisplayRoot should succeed. 252 throw new Error( 253 'Unexpectedly failed to obtain the default display root.'); 254 }); 255 }.bind(this)); 256}; 257 258/** 259 * Obtains location information from an entry. 260 * 261 * @param {Entry} entry File or directory entry. 262 * @return {EntryLocation} Location information. 263 */ 264VolumeManagerWrapper.prototype.getLocationInfo = function(entry) { 265 var locationInfo = 266 this.volumeManager_ && this.volumeManager_.getLocationInfo(entry); 267 if (!locationInfo) 268 return null; 269 if (!this.filterDisabledDriveVolume_(locationInfo.volumeInfo)) 270 return null; 271 return locationInfo; 272}; 273 274/** 275 * Requests to mount the archive file. 276 * @param {string} fileUrl The path to the archive file to be mounted. 277 * @param {function(VolumeInfo)} successCallback Called with the VolumeInfo 278 * instance. 279 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called when 280 * an error occurs. 281 */ 282VolumeManagerWrapper.prototype.mountArchive = function( 283 fileUrl, successCallback, errorCallback) { 284 if (this.pendingTasks_) { 285 this.pendingTasks_.push( 286 this.mountArchive.bind(this, fileUrl, successCallback, errorCallback)); 287 return; 288 } 289 290 this.volumeManager_.mountArchive(fileUrl, successCallback, errorCallback); 291}; 292 293/** 294 * Requests unmount the specified volume. 295 * @param {!VolumeInfo} volumeInfo Volume to be unmounted. 296 * @param {function()} successCallback Called on success. 297 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called when 298 * an error occurs. 299 */ 300VolumeManagerWrapper.prototype.unmount = function( 301 volumeInfo, successCallback, errorCallback) { 302 if (this.pendingTasks_) { 303 this.pendingTasks_.push( 304 this.unmount.bind(this, volumeInfo, successCallback, errorCallback)); 305 return; 306 } 307 308 this.volumeManager_.unmount(volumeInfo, successCallback, errorCallback); 309}; 310 311/** 312 * Filters volume info by referring driveEnabled. 313 * 314 * @param {VolumeInfo} volumeInfo Volume info. 315 * @return {VolumeInfo} Null if the drive is disabled and the given volume is 316 * drive. Otherwise just returns the volume. 317 * @private 318 */ 319VolumeManagerWrapper.prototype.filterDisabledDriveVolume_ = 320 function(volumeInfo) { 321 var isDrive = volumeInfo && volumeInfo.volumeType === 322 VolumeManagerCommon.VolumeType.DRIVE; 323 return this.driveEnabled_ || !isDrive ? volumeInfo : null; 324}; 325