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'use strict'; 6 7/** 8 * An event handler of the background page for file operations. 9 * @param {Background} background Background page. 10 * @constructor 11 */ 12var FileOperationHandler = function(background) { 13 /** 14 * Background page. 15 * @type {Background} 16 * @private 17 */ 18 this.background_ = background; 19 20 /** 21 * File operation manager. 22 * @type {FileOperationManager} 23 * @private 24 */ 25 this.fileOperationManager_ = background.fileOperationManager; 26 27 /** 28 * Progress center. 29 * @type {progressCenter} 30 * @private 31 */ 32 this.progressCenter_ = background.progressCenter; 33 34 /** 35 * Pending items of delete operation. 36 * 37 * Delete operations are usually complete quickly. 38 * So we would not like to show the progress bar at first. 39 * If the operation takes more than FileOperationHandler.PENDING_TIME_MS_, 40 * we adds the item to the progress center. 41 * 42 * @type {Object.<string, ProgressCenterItem>}} 43 * @private 44 */ 45 this.pendingItems_ = {}; 46 47 // Register event. 48 this.fileOperationManager_.addEventListener( 49 'copy-progress', 50 this.onCopyProgress_.bind(this)); 51 this.fileOperationManager_.addEventListener( 52 'delete', 53 this.onDeleteProgress_.bind(this)); 54 55 // Seal the object. 56 Object.seal(this); 57}; 58 59/** 60 * Pending time before a delete item is added to the progress center. 61 * 62 * @type {number} 63 * @const 64 * @private 65 */ 66FileOperationHandler.PENDING_TIME_MS_ = 500; 67 68/** 69 * Generate a progress message from the event. 70 * @param {Event} event Progress event. 71 * @return {string} message. 72 * @private 73 */ 74FileOperationHandler.getMessage_ = function(event) { 75 if (event.reason === 'ERROR') { 76 switch (event.error.code) { 77 case util.FileOperationErrorType.TARGET_EXISTS: 78 var name = event.error.data.name; 79 if (event.error.data.isDirectory) 80 name += '/'; 81 switch (event.status.operationType) { 82 case 'COPY': return strf('COPY_TARGET_EXISTS_ERROR', name); 83 case 'MOVE': return strf('MOVE_TARGET_EXISTS_ERROR', name); 84 case 'ZIP': return strf('ZIP_TARGET_EXISTS_ERROR', name); 85 default: return strf('TRANSFER_TARGET_EXISTS_ERROR', name); 86 } 87 88 case util.FileOperationErrorType.FILESYSTEM_ERROR: 89 var detail = util.getFileErrorString(event.error.data.name); 90 switch (event.status.operationType) { 91 case 'COPY': return strf('COPY_FILESYSTEM_ERROR', detail); 92 case 'MOVE': return strf('MOVE_FILESYSTEM_ERROR', detail); 93 case 'ZIP': return strf('ZIP_FILESYSTEM_ERROR', detail); 94 default: return strf('TRANSFER_FILESYSTEM_ERROR', detail); 95 } 96 97 default: 98 switch (event.status.operationType) { 99 case 'COPY': return strf('COPY_UNEXPECTED_ERROR', event.error.code); 100 case 'MOVE': return strf('MOVE_UNEXPECTED_ERROR', event.error.code); 101 case 'ZIP': return strf('ZIP_UNEXPECTED_ERROR', event.error.code); 102 default: return strf('TRANSFER_UNEXPECTED_ERROR', event.error.code); 103 } 104 } 105 } else if (event.status.numRemainingItems === 1) { 106 var name = event.status.processingEntryName; 107 switch (event.status.operationType) { 108 case 'COPY': return strf('COPY_FILE_NAME', name); 109 case 'MOVE': return strf('MOVE_FILE_NAME', name); 110 case 'ZIP': return strf('ZIP_FILE_NAME', name); 111 default: return strf('TRANSFER_FILE_NAME', name); 112 } 113 } else { 114 var remainNumber = event.status.numRemainingItems; 115 switch (event.status.operationType) { 116 case 'COPY': return strf('COPY_ITEMS_REMAINING', remainNumber); 117 case 'MOVE': return strf('MOVE_ITEMS_REMAINING', remainNumber); 118 case 'ZIP': return strf('ZIP_ITEMS_REMAINING', remainNumber); 119 default: return strf('TRANSFER_ITEMS_REMAINING', remainNumber); 120 } 121 } 122}; 123 124/** 125 * Generates a delete message from the event. 126 * @param {Event} event Progress event. 127 * @return {string} message. 128 * @private 129 */ 130FileOperationHandler.getDeleteMessage_ = function(event) { 131 if (event.reason === 'ERROR') { 132 return str('DELETE_ERROR'); 133 } else if (event.entries.length == 1) { 134 var fileName = event.entries[0].name; 135 return strf('DELETE_FILE_NAME', fileName); 136 } else if (event.entries.length > 1) { 137 return strf('DELETE_ITEMS_REMAINING', event.entries.length); 138 } else { 139 return ''; 140 } 141}; 142 143/** 144 * Obtains ProgressItemType from OperationType of FileTransferManager. 145 * @param {string} operationType OperationType of FileTransferManager. 146 * @return {ProgressItemType} ProgreeType corresponding to the specified 147 * operation type. 148 * @private 149 */ 150FileOperationHandler.getType_ = function(operationType) { 151 switch (operationType) { 152 case 'COPY': return ProgressItemType.COPY; 153 case 'MOVE': return ProgressItemType.MOVE; 154 case 'ZIP': return ProgressItemType.ZIP; 155 default: 156 console.error('Unknown operation type.'); 157 return ProgressItemType.TRANSFER; 158 } 159}; 160 161/** 162 * Handles the copy-progress event. 163 * @param {Event} event The copy-progress event. 164 * @private 165 */ 166FileOperationHandler.prototype.onCopyProgress_ = function(event) { 167 // If the copy is finished, may be we can close the background page. 168 if (event.reason !== 'BEGIN' && event.reason !== 'PROGRESS') 169 this.background_.tryClose(); 170 171 // Update progress center. 172 var progressCenter = this.progressCenter_; 173 var item; 174 switch (event.reason) { 175 case 'BEGIN': 176 item = new ProgressCenterItem(); 177 item.id = event.taskId; 178 item.type = FileOperationHandler.getType_(event.status.operationType); 179 item.message = FileOperationHandler.getMessage_(event); 180 item.progressMax = event.status.totalBytes; 181 item.progressValue = event.status.processedBytes; 182 item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( 183 this.fileOperationManager_, 184 event.taskId); 185 progressCenter.updateItem(item); 186 break; 187 188 case 'PROGRESS': 189 item = progressCenter.getItemById(event.taskId); 190 if (!item) { 191 console.error('Cannot find copying item.'); 192 return; 193 } 194 item.message = FileOperationHandler.getMessage_(event); 195 item.progressMax = event.status.totalBytes; 196 item.progressValue = event.status.processedBytes; 197 progressCenter.updateItem(item); 198 break; 199 200 case 'SUCCESS': 201 case 'CANCELED': 202 case 'ERROR': 203 item = progressCenter.getItemById(event.taskId); 204 if (!item) { 205 // ERROR events can be dispatched before BEGIN events. 206 item = new ProgressCenterItem(); 207 item.type = FileOperationHandler.getType_(event.status.operationType); 208 item.id = event.taskId; 209 item.progressMax = 1; 210 } 211 if (event.reason === 'SUCCESS') { 212 item.message = ''; 213 item.state = ProgressItemState.COMPLETED; 214 item.progressValue = item.progressMax; 215 } else if (event.reason === 'CANCELED') { 216 item.message = ''; 217 item.state = ProgressItemState.CANCELED; 218 } else { 219 item.message = FileOperationHandler.getMessage_(event); 220 item.state = ProgressItemState.ERROR; 221 } 222 progressCenter.updateItem(item); 223 break; 224 } 225}; 226 227/** 228 * Handles the delete event. 229 * @param {Event} event The delete event. 230 * @private 231 */ 232FileOperationHandler.prototype.onDeleteProgress_ = function(event) { 233 // If the copy is finished, may be we can close the background page. 234 if (event.reason !== 'BEGIN' && event.reason !== 'PROGRESS') 235 this.background_.tryClose(); 236 237 // Update progress center. 238 var progressCenter = this.progressCenter_; 239 var item; 240 var pending; 241 switch (event.reason) { 242 case 'BEGIN': 243 item = new ProgressCenterItem(); 244 item.id = event.taskId; 245 item.type = ProgressItemType.DELETE; 246 item.message = FileOperationHandler.getDeleteMessage_(event); 247 item.progressMax = event.totalBytes; 248 item.progressValue = event.processedBytes; 249 item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( 250 this.fileOperationManager_, 251 event.taskId); 252 this.pendingItems_[item.id] = item; 253 setTimeout(this.showPendingItem_.bind(this, item), 254 FileOperationHandler.PENDING_TIME_MS_); 255 break; 256 257 case 'PROGRESS': 258 pending = event.taskId in this.pendingItems_; 259 item = this.pendingItems_[event.taskId] || 260 progressCenter.getItemById(event.taskId); 261 if (!item) { 262 console.error('Cannot find deleting item.'); 263 return; 264 } 265 item.message = FileOperationHandler.getDeleteMessage_(event); 266 item.progressMax = event.totalBytes; 267 item.progressValue = event.processedBytes; 268 if (!pending) 269 progressCenter.updateItem(item); 270 break; 271 272 case 'SUCCESS': 273 case 'CANCELED': 274 case 'ERROR': 275 // Obtain working variable. 276 pending = event.taskId in this.pendingItems_; 277 item = this.pendingItems_[event.taskId] || 278 progressCenter.getItemById(event.taskId); 279 if (!item) { 280 console.error('Cannot find deleting item.'); 281 return; 282 } 283 284 // Update the item. 285 item.message = FileOperationHandler.getDeleteMessage_(event); 286 if (event.reason === 'SUCCESS') { 287 item.state = ProgressItemState.COMPLETED; 288 item.progressValue = item.progressMax; 289 } else if (event.reason === 'CANCELED') { 290 item.state = ProgressItemState.CANCELED; 291 } else { 292 item.state = ProgressItemState.ERROR; 293 } 294 295 // Apply the change. 296 if (!pending || event.reason === 'ERROR') 297 progressCenter.updateItem(item); 298 if (pending) 299 delete this.pendingItems_[event.taskId]; 300 break; 301 } 302}; 303 304/** 305 * Shows the pending item. 306 * 307 * @param {ProgressCenterItem} item Pending item. 308 * @private 309 */ 310FileOperationHandler.prototype.showPendingItem_ = function(item) { 311 // The item is already gone. 312 if (!this.pendingItems_[item.id]) 313 return; 314 delete this.pendingItems_[item.id]; 315 this.progressCenter_.updateItem(item); 316}; 317