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 operaitons. 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.code); 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.processingEntry.name; 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.progressValue = event.status.processedBytes; 196 progressCenter.updateItem(item); 197 break; 198 199 case 'SUCCESS': 200 case 'CANCELED': 201 case 'ERROR': 202 item = progressCenter.getItemById(event.taskId); 203 if (!item) { 204 // ERROR events can be dispatched before BEGIN events. 205 item = new ProgressCenterItem(); 206 item.type = FileOperationHandler.getType_(event.status.operationType); 207 item.id = event.taskId; 208 item.progressMax = 1; 209 } 210 if (event.reason === 'SUCCESS') { 211 item.message = ''; 212 item.state = ProgressItemState.COMPLETED; 213 item.progressValue = item.progressMax; 214 } else if (event.reason === 'CANCELED') { 215 item.message = ''; 216 item.state = ProgressItemState.CANCELED; 217 } else { 218 item.message = FileOperationHandler.getMessage_(event); 219 item.state = ProgressItemState.ERROR; 220 } 221 progressCenter.updateItem(item); 222 break; 223 } 224}; 225 226/** 227 * Handles the delete event. 228 * @param {Event} event The delete event. 229 * @private 230 */ 231FileOperationHandler.prototype.onDeleteProgress_ = function(event) { 232 // If the copy is finished, may be we can close the background page. 233 if (event.reason !== 'BEGIN' && event.reason !== 'PROGRESS') 234 this.background_.tryClose(); 235 236 // Update progress center. 237 var progressCenter = this.progressCenter_; 238 var item; 239 var pending; 240 switch (event.reason) { 241 case 'BEGIN': 242 item = new ProgressCenterItem(); 243 item.id = event.taskId; 244 item.type = ProgressItemType.DELETE; 245 item.message = FileOperationHandler.getDeleteMessage_(event); 246 item.progressMax = event.totalBytes; 247 item.progressValue = event.processedBytes; 248 item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( 249 this.fileOperationManager_, 250 event.taskId); 251 this.pendingItems_[item.id] = item; 252 setTimeout(this.showPendingItem_.bind(this, item), 253 FileOperationHandler.PENDING_TIME_MS_); 254 break; 255 256 case 'PROGRESS': 257 pending = event.taskId in this.pendingItems_; 258 item = this.pendingItems_[event.taskId] || 259 progressCenter.getItemById(event.taskId); 260 if (!item) { 261 console.error('Cannot find deleting item.'); 262 return; 263 } 264 item.message = FileOperationHandler.getDeleteMessage_(event); 265 item.progressMax = event.totalBytes; 266 item.progressValue = event.processedBytes; 267 if (!pending) 268 progressCenter.updateItem(item); 269 break; 270 271 case 'SUCCESS': 272 case 'CANCELED': 273 case 'ERROR': 274 // Obtain working variable. 275 pending = event.taskId in this.pendingItems_; 276 item = this.pendingItems_[event.taskId] || 277 progressCenter.getItemById(event.taskId); 278 if (!item) { 279 console.error('Cannot find deleting item.'); 280 return; 281 } 282 283 // Update the item. 284 item.message = FileOperationHandler.getDeleteMessage_(event); 285 if (event.reason === 'SUCCESS') { 286 item.state = ProgressItemState.COMPLETED; 287 item.progressValue = item.progressMax; 288 } else if (event.reason === 'CANCELED') { 289 item.state = ProgressItemState.CANCELED; 290 } else { 291 item.state = ProgressItemState.ERROR; 292 } 293 294 // Apply the change. 295 if (!pending || event.reason === 'ERROR') 296 progressCenter.updateItem(item); 297 if (pending) 298 delete this.pendingItems_[event.taskId]; 299 break; 300 } 301}; 302 303/** 304 * Shows the pending item. 305 * 306 * @param {ProgressCenterItem} item Pending item. 307 * @private 308 */ 309FileOperationHandler.prototype.showPendingItem_ = function(item) { 310 // The item is already gone. 311 if (!this.pendingItems_[item.id]) 312 return; 313 delete this.pendingItems_[item.id]; 314 this.progressCenter_.updateItem(item); 315}; 316