| OLD | NEW |
| (Empty) |
| 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 * Progress center at the background page. | |
| 9 * @constructor | |
| 10 */ | |
| 11 var ProgressCenter = function() { | |
| 12 cr.EventTarget.call(this); | |
| 13 | |
| 14 /** | |
| 15 * Default container. | |
| 16 * @type {ProgressItemContainer} | |
| 17 * @private | |
| 18 */ | |
| 19 this.targetContainer_ = ProgressItemContainer.CLIENT; | |
| 20 | |
| 21 /** | |
| 22 * Current items managed by the progress center. | |
| 23 * @type {Array.<ProgressItem>} | |
| 24 * @private | |
| 25 */ | |
| 26 this.items_ = []; | |
| 27 | |
| 28 /** | |
| 29 * Timeout callback to remove items. | |
| 30 * @type {TimeoutManager} | |
| 31 * @private | |
| 32 */ | |
| 33 this.resetTimeout_ = new ProgressCenter.TimeoutManager( | |
| 34 this.reset_.bind(this)); | |
| 35 }; | |
| 36 | |
| 37 /** | |
| 38 * The default amount of milliseconds time, before a progress item will reset | |
| 39 * after the last complete. | |
| 40 * @type {number} | |
| 41 * @private | |
| 42 * @const | |
| 43 */ | |
| 44 ProgressCenter.RESET_DELAY_TIME_MS_ = 5000; | |
| 45 | |
| 46 /** | |
| 47 * Utility for timeout callback. | |
| 48 * | |
| 49 * @param {function(*):*} callback Callbakc function. | |
| 50 * @constructor | |
| 51 */ | |
| 52 ProgressCenter.TimeoutManager = function(callback) { | |
| 53 this.callback_ = callback; | |
| 54 this.id_ = null; | |
| 55 Object.seal(this); | |
| 56 }; | |
| 57 | |
| 58 /** | |
| 59 * Requests timeout. Previous request is canceled. | |
| 60 * @param {number} milliseconds Time to invoke the callback function. | |
| 61 */ | |
| 62 ProgressCenter.TimeoutManager.prototype.request = function(milliseconds) { | |
| 63 if (this.id_) | |
| 64 clearTimeout(this.id_); | |
| 65 this.id_ = setTimeout(function() { | |
| 66 this.id_ = null; | |
| 67 this.callback_(); | |
| 68 }.bind(this), milliseconds); | |
| 69 }; | |
| 70 | |
| 71 ProgressCenter.prototype = { | |
| 72 __proto__: cr.EventTarget.prototype, | |
| 73 | |
| 74 /** | |
| 75 * Obtains the items to be displayed in the application window. | |
| 76 * @private | |
| 77 */ | |
| 78 get applicationItems() { | |
| 79 return this.items_.filter(function(item) { | |
| 80 return item.container == ProgressItemContainer.CLIENT; | |
| 81 }); | |
| 82 } | |
| 83 }; | |
| 84 | |
| 85 /** | |
| 86 * Updates the item in the progress center. | |
| 87 * If the item has a new ID, the item is added to the item list. | |
| 88 * | |
| 89 * @param {ProgressCenterItem} item Updated item. | |
| 90 */ | |
| 91 ProgressCenter.prototype.updateItem = function(item) { | |
| 92 var index = this.getItemIndex_(item.id); | |
| 93 if (index === -1) { | |
| 94 item.container = this.targetContainer_; | |
| 95 this.items_.push(item); | |
| 96 } else { | |
| 97 this.items_[index] = item; | |
| 98 } | |
| 99 | |
| 100 if (item.status !== ProgressItemState.PROGRESSING) | |
| 101 this.resetTimeout_.request(ProgressCenter.RESET_DELAY_TIME_MS_); | |
| 102 | |
| 103 var event = new Event(ProgressCenterEvent.ITEM_UPDATED); | |
| 104 event.item = item; | |
| 105 this.dispatchEvent(event); | |
| 106 }; | |
| 107 | |
| 108 /** | |
| 109 * Requests to cancel the progress item. | |
| 110 * @param {string} id Progress ID to be requested to cancel. | |
| 111 */ | |
| 112 ProgressCenter.prototype.requestCancel = function(id) { | |
| 113 var item = this.getItemById(id); | |
| 114 if (item && item.cancelCallback) | |
| 115 item.cancelCallback(); | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Switches the default container. | |
| 120 * @param {ProgressItemContainer} newContainer New value of the default | |
| 121 * container. | |
| 122 */ | |
| 123 ProgressCenter.prototype.switchContainer = function(newContainer) { | |
| 124 if (this.targetContainer_ === newContainer) | |
| 125 return; | |
| 126 | |
| 127 // Current items to be moved to the notification center. | |
| 128 if (newContainer == ProgressItemContainer.NOTIFICATION) { | |
| 129 var items = this.applicationItems; | |
| 130 for (var i = 0; i < items.length; i++) { | |
| 131 items[i].container = ProgressItemContainer.NOTIFICATION; | |
| 132 this.postItemToNotification_(items); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 // The items in the notification center does not come back to the Files.app | |
| 137 // clients. | |
| 138 | |
| 139 // Assign the new value. | |
| 140 this.targetContainer_ = newContainer; | |
| 141 }; | |
| 142 | |
| 143 /** | |
| 144 * Obtains the summarized item to be displayed in the closed progress center | |
| 145 * panel. | |
| 146 * @return {ProgressCenterItem} Summarized item. Returns null if there is no | |
| 147 * item. | |
| 148 */ | |
| 149 ProgressCenter.prototype.getSummarizedItem = function() { | |
| 150 var applicationItems = this.applicationItems; | |
| 151 if (applicationItems.length == 0) | |
| 152 return null; | |
| 153 if (applicationItems.length == 1) | |
| 154 return applicationItems[0]; | |
| 155 var summarizedItem = new ProgressCenterItem(); | |
| 156 summarizedItem.summarized = true; | |
| 157 var completeCount = 0; | |
| 158 var progressingCount = 0; | |
| 159 var canceledCount = 0; | |
| 160 var errorCount = 0; | |
| 161 for (var i = 0; i < applicationItems.length; i++) { | |
| 162 switch (applicationItems[i].state) { | |
| 163 case ProgressItemState.COMPLETE: | |
| 164 completeCount++; | |
| 165 break; | |
| 166 case ProgressItemState.PROGRESSING: | |
| 167 progressingCount++; | |
| 168 break; | |
| 169 case ProgressItemState.ERROR: | |
| 170 errorCount++; | |
| 171 continue; | |
| 172 case ProgressItemState.CANCELED: | |
| 173 canceledCount++; | |
| 174 continue; | |
| 175 } | |
| 176 summarizedItem.progressMax += applicationItems[i].progressMax; | |
| 177 summarizedItem.progressValue += applicationItems[i].progressValue; | |
| 178 } | |
| 179 var messages = []; | |
| 180 if (completeCount) | |
| 181 messages.push(completeCount + ' complete'); | |
| 182 if (progressingCount) | |
| 183 messages.push(progressingCount + ' active'); | |
| 184 if (canceledCount) | |
| 185 messages.push(canceledCount + ' canceled'); | |
| 186 if (errorCount) | |
| 187 messages.push(errorCount + ' error'); | |
| 188 summarizedItem.message = messages.join(', ') + '.'; | |
| 189 summarizedItem.state = | |
| 190 completeCount + progressingCount == 0 ? ProgressItemState.CANCELED : | |
| 191 progressingCount > 0 ? ProgressItemState.PROGRESSING : | |
| 192 ProgressItemState.COMPLETE; | |
| 193 return summarizedItem; | |
| 194 }; | |
| 195 | |
| 196 /** | |
| 197 * Obtains item by ID. | |
| 198 * @param {string} id ID of progress item. | |
| 199 * @return {ProgressCenterItem} Progress center item having the specified | |
| 200 * ID. Null if the item is not found. | |
| 201 */ | |
| 202 ProgressCenter.prototype.getItemById = function(id) { | |
| 203 return this.items_[this.getItemIndex_(id)]; | |
| 204 }; | |
| 205 | |
| 206 /** | |
| 207 * Obtains item index that have the specifying ID. | |
| 208 * @param {string} id Item ID. | |
| 209 * @return {number} Item index. Returns -1 If the item is not found. | |
| 210 * @private | |
| 211 */ | |
| 212 ProgressCenter.prototype.getItemIndex_ = function(id) { | |
| 213 for (var i = 0; i < this.items_.length; i++) { | |
| 214 if (this.items_[i].id === id) | |
| 215 return i; | |
| 216 } | |
| 217 return -1; | |
| 218 }; | |
| 219 | |
| 220 /** | |
| 221 * Passes the item to the ChromeOS's message center. | |
| 222 * | |
| 223 * TODO(hirono): Implement the method. | |
| 224 * | |
| 225 * @private | |
| 226 */ | |
| 227 ProgressCenter.prototype.passItemsToNotification_ = function() { | |
| 228 | |
| 229 }; | |
| 230 | |
| 231 /** | |
| 232 * Hides the progress center if there is no progressing items. | |
| 233 * @private | |
| 234 */ | |
| 235 ProgressCenter.prototype.reset_ = function() { | |
| 236 // If we have a progressing item, stop reset. | |
| 237 for (var i = 0; i < this.items_.length; i++) { | |
| 238 if (this.items_[i].state == ProgressItemState.PROGRESSING) | |
| 239 return; | |
| 240 } | |
| 241 | |
| 242 // Reset items. | |
| 243 this.items_.splice(0, this.items_.length); | |
| 244 | |
| 245 // Dispatch a event. | |
| 246 this.dispatchEvent(new Event(ProgressCenterEvent.RESET)); | |
| 247 }; | |
| 248 | |
| 249 /** | |
| 250 * An event handler for progress center. | |
| 251 * @param {FileOperationManager} fileOperationManager File operation manager. | |
| 252 * @param {ProgressCenter} progressCenter Progress center. | |
| 253 * @constructor | |
| 254 */ | |
| 255 var ProgressCenterHandler = function(fileOperationManager, progressCenter) { | |
| 256 /** | |
| 257 * Number of deleted files. | |
| 258 * @type {number} | |
| 259 * @private | |
| 260 */ | |
| 261 this.totalDeleted_ = 0; | |
| 262 | |
| 263 /** | |
| 264 * File operation manager. | |
| 265 * @type {FileOperationManager} | |
| 266 * @private | |
| 267 */ | |
| 268 this.fileOperationManager_ = fileOperationManager; | |
| 269 | |
| 270 /** | |
| 271 * Progress center. | |
| 272 * @type {progressCenter} | |
| 273 * @private | |
| 274 */ | |
| 275 this.progressCenter_ = progressCenter; | |
| 276 | |
| 277 // Seal the object. | |
| 278 Object.seal(this); | |
| 279 | |
| 280 // Register event. | |
| 281 fileOperationManager.addEventListener('copy-progress', | |
| 282 this.onCopyProgress_.bind(this)); | |
| 283 fileOperationManager.addEventListener('delete', | |
| 284 this.onDeleteProgress_.bind(this)); | |
| 285 }; | |
| 286 | |
| 287 /** | |
| 288 * Generate a progress message from the event. | |
| 289 * @param {Event} event Progress event. | |
| 290 * @return {string} message. | |
| 291 * @private | |
| 292 */ | |
| 293 ProgressCenterHandler.getMessage_ = function(event) { | |
| 294 if (event.reason === 'ERROR') { | |
| 295 switch (event.error.code) { | |
| 296 case util.FileOperationErrorType.TARGET_EXISTS: | |
| 297 var name = event.error.data.name; | |
| 298 if (event.error.data.isDirectory) | |
| 299 name += '/'; | |
| 300 switch (event.status.operationType) { | |
| 301 case 'COPY': return strf('COPY_TARGET_EXISTS_ERROR', name); | |
| 302 case 'MOVE': return strf('MOVE_TARGET_EXISTS_ERROR', name); | |
| 303 case 'ZIP': return strf('ZIP_TARGET_EXISTS_ERROR', name); | |
| 304 default: return strf('TRANSFER_TARGET_EXISTS_ERROR', name); | |
| 305 } | |
| 306 | |
| 307 case util.FileOperationErrorType.FILESYSTEM_ERROR: | |
| 308 var detail = util.getFileErrorString(event.error.data.code); | |
| 309 switch (event.status.operationType) { | |
| 310 case 'COPY': return strf('COPY_FILESYSTEM_ERROR', detail); | |
| 311 case 'MOVE': return strf('MOVE_FILESYSTEM_ERROR', detail); | |
| 312 case 'ZIP': return strf('ZIP_FILESYSTEM_ERROR', detail); | |
| 313 default: return strf('TRANSFER_FILESYSTEM_ERROR', detail); | |
| 314 } | |
| 315 | |
| 316 default: | |
| 317 switch (event.status.operationType) { | |
| 318 case 'COPY': return strf('COPY_UNEXPECTED_ERROR', event.error); | |
| 319 case 'MOVE': return strf('MOVE_UNEXPECTED_ERROR', event.error); | |
| 320 case 'ZIP': return strf('ZIP_UNEXPECTED_ERROR', event.error); | |
| 321 default: return strf('TRANSFER_UNEXPECTED_ERROR', event.error); | |
| 322 } | |
| 323 } | |
| 324 } else if (event.status.numRemainingItems === 1) { | |
| 325 var name = event.status.processingEntry.name; | |
| 326 switch (event.status.operationType) { | |
| 327 case 'COPY': return strf('COPY_FILE_NAME', name); | |
| 328 case 'MOVE': return strf('MOVE_FILE_NAME', name); | |
| 329 case 'ZIP': return strf('ZIP_FILE_NAME', name); | |
| 330 default: return strf('TRANSFER_FILE_NAME', name); | |
| 331 } | |
| 332 } else { | |
| 333 var remainNumber = event.status.numRemainingItems; | |
| 334 switch (event.status.operationType) { | |
| 335 case 'COPY': return strf('COPY_ITEMS_REMAINING', remainNumber); | |
| 336 case 'MOVE': return strf('MOVE_ITEMS_REMAINING', remainNumber); | |
| 337 case 'ZIP': return strf('ZIP_ITEMS_REMAINING', remainNumber); | |
| 338 default: return strf('TRANSFER_ITEMS_REMAINING', remainNumber); | |
| 339 } | |
| 340 } | |
| 341 }; | |
| 342 | |
| 343 /** | |
| 344 * Generate a delete message from the event. | |
| 345 * @param {Event} event Progress event. | |
| 346 * @param {number} totalDeleted Total number of deleted files. | |
| 347 * @return {string} message. | |
| 348 * @private | |
| 349 */ | |
| 350 ProgressCenterHandler.getDeleteMessage_ = function(event, totalDeleted) { | |
| 351 if (totalDeleted === 1) { | |
| 352 var fullPath = util.extractFilePath(event.urls[0]); | |
| 353 var fileName = PathUtil.split(fullPath).pop(); | |
| 354 return strf('DELETED_MESSAGE', fileName); | |
| 355 } else { | |
| 356 return strf('DELETED_MESSAGE_PLURAL', totalDeleted); | |
| 357 } | |
| 358 }; | |
| 359 | |
| 360 /** | |
| 361 * Handles the copy-progress event. | |
| 362 * @param {Event} event The copy-progress event. | |
| 363 * @private | |
| 364 */ | |
| 365 ProgressCenterHandler.prototype.onCopyProgress_ = function(event) { | |
| 366 var progressCenter = this.progressCenter_; | |
| 367 var item; | |
| 368 switch (event.reason) { | |
| 369 case 'BEGIN': | |
| 370 item = new ProgressCenterItem(); | |
| 371 item.id = event.taskId; | |
| 372 item.message = ProgressCenterHandler.getMessage_(event); | |
| 373 item.progressMax = event.status.totalBytes; | |
| 374 item.progressValue = event.status.processedBytes; | |
| 375 item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( | |
| 376 this.fileOperationManager_, | |
| 377 event.taskId); | |
| 378 progressCenter.updateItem(item); | |
| 379 break; | |
| 380 | |
| 381 case 'PROGRESS': | |
| 382 item = progressCenter.getItemById(event.taskId); | |
| 383 if (!item) { | |
| 384 console.error('Cannot find copying item.'); | |
| 385 return; | |
| 386 } | |
| 387 item.message = ProgressCenterHandler.getMessage_(event); | |
| 388 item.progressValue = event.status.processedBytes; | |
| 389 progressCenter.updateItem(item); | |
| 390 break; | |
| 391 | |
| 392 case 'SUCCESS': | |
| 393 case 'ERROR': | |
| 394 item = progressCenter.getItemById(event.taskId); | |
| 395 if (!item) { | |
| 396 // ERROR events can be dispatched before BEGIN events. | |
| 397 item = new ProgressCenterItem(); | |
| 398 item.id = event.taskId; | |
| 399 item.progressMax = 1; | |
| 400 } | |
| 401 if (event.reason === 'SUCCESS') { | |
| 402 // TODO(hirono): Add a message for complete. | |
| 403 item.state = ProgressItemState.COMPLETE; | |
| 404 item.progressValue = item.progressMax; | |
| 405 } else if (event.error.data.code === FileError.ABORT_ERR) { | |
| 406 item.message = strf('COPY_CANCELLED'); | |
| 407 item.state = ProgressItemState.CANCELED; | |
| 408 } else { | |
| 409 item.message = ProgressCenterHandler.getMessage_(event); | |
| 410 item.state = ProgressItemState.ERROR; | |
| 411 } | |
| 412 progressCenter.updateItem(item); | |
| 413 break; | |
| 414 } | |
| 415 }; | |
| 416 | |
| 417 /** | |
| 418 * Handles the delete event. | |
| 419 * @param {Event} event The delete event. | |
| 420 * @private | |
| 421 */ | |
| 422 ProgressCenterHandler.prototype.onDeleteProgress_ = function(event) { | |
| 423 var progressCenter = this.progressCenter_; | |
| 424 var item; | |
| 425 switch (event.reason) { | |
| 426 case 'BEGIN': | |
| 427 this.totalDeleted_ = 0; | |
| 428 item = new ProgressCenterItem(); | |
| 429 item.id = event.taskId; | |
| 430 // TODO(hirono): Specifying the correct message. | |
| 431 item.message = | |
| 432 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
| 433 item.progressMax = 100; | |
| 434 progressCenter.updateItem(item); | |
| 435 break; | |
| 436 | |
| 437 case 'PROGRESS': | |
| 438 item = progressCenter.getItemById(event.taskId); | |
| 439 if (!item) { | |
| 440 console.error('Cannot find deleting item.'); | |
| 441 return; | |
| 442 } | |
| 443 this.totalDeleted_ += event.urls.length; | |
| 444 item.message = | |
| 445 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
| 446 progressCenter.updateItem(item); | |
| 447 break; | |
| 448 | |
| 449 case 'SUCCESS': | |
| 450 case 'ERROR': | |
| 451 item = progressCenter.getItemById(event.taskId); | |
| 452 if (!item) { | |
| 453 console.error('Cannot find deleting item.'); | |
| 454 return; | |
| 455 } | |
| 456 if (event.reason === 'SUCCESS') { | |
| 457 this.totalDeleted_ += event.urls.length; | |
| 458 item.message = | |
| 459 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
| 460 item.state = ProgressItemState.COMPLETE; | |
| 461 item.progressValue = item.progressMax; | |
| 462 } else { | |
| 463 item.message = str('DELETE_ERROR'); | |
| 464 item.state = ProgressItemState.ERROR; | |
| 465 } | |
| 466 progressCenter.updateItem(item); | |
| 467 break; | |
| 468 } | |
| 469 }; | |
| OLD | NEW |