| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| /** |
| * An event handler of the background page for file operaitons. |
| * @param {Background} background Background page. |
| * @constructor |
| */ |
| var FileOperationHandler = function(background) { |
| /** |
| * Background page. |
| * @type {Background} |
| * @private |
| */ |
| this.background_ = background; |
| |
| /** |
| * File operation manager. |
| * @type {FileOperationManager} |
| * @private |
| */ |
| this.fileOperationManager_ = background.fileOperationManager; |
| |
| /** |
| * Progress center. |
| * @type {progressCenter} |
| * @private |
| */ |
| this.progressCenter_ = background.progressCenter; |
| |
| /** |
| * Pending items of delete operation. |
| * |
| * Delete operations are usually complete quickly. |
| * So we would not like to show the progress bar at first. |
| * If the operation takes more than FileOperationHandler.PENDING_TIME_MS_, |
| * we adds the item to the progress center. |
| * |
| * @type {Object.<string, ProgressCenterItem>}} |
| * @private |
| */ |
| this.pendingItems_ = {}; |
| |
| // Register event. |
| this.fileOperationManager_.addEventListener( |
| 'copy-progress', |
| this.onCopyProgress_.bind(this)); |
| this.fileOperationManager_.addEventListener( |
| 'delete', |
| this.onDeleteProgress_.bind(this)); |
| |
| // Seal the object. |
| Object.seal(this); |
| }; |
| |
| /** |
| * Pending time before a delete item is added to the progress center. |
| * |
| * @type {number} |
| * @const |
| * @private |
| */ |
| FileOperationHandler.PENDING_TIME_MS_ = 500; |
| |
| /** |
| * Generate a progress message from the event. |
| * @param {Event} event Progress event. |
| * @return {string} message. |
| * @private |
| */ |
| FileOperationHandler.getMessage_ = function(event) { |
| if (event.reason === 'ERROR') { |
| switch (event.error.code) { |
| case util.FileOperationErrorType.TARGET_EXISTS: |
| var name = event.error.data.name; |
| if (event.error.data.isDirectory) |
| name += '/'; |
| switch (event.status.operationType) { |
| case 'COPY': return strf('COPY_TARGET_EXISTS_ERROR', name); |
| case 'MOVE': return strf('MOVE_TARGET_EXISTS_ERROR', name); |
| case 'ZIP': return strf('ZIP_TARGET_EXISTS_ERROR', name); |
| default: return strf('TRANSFER_TARGET_EXISTS_ERROR', name); |
| } |
| |
| case util.FileOperationErrorType.FILESYSTEM_ERROR: |
| var detail = util.getFileErrorString(event.error.data.code); |
| switch (event.status.operationType) { |
| case 'COPY': return strf('COPY_FILESYSTEM_ERROR', detail); |
| case 'MOVE': return strf('MOVE_FILESYSTEM_ERROR', detail); |
| case 'ZIP': return strf('ZIP_FILESYSTEM_ERROR', detail); |
| default: return strf('TRANSFER_FILESYSTEM_ERROR', detail); |
| } |
| |
| default: |
| switch (event.status.operationType) { |
| case 'COPY': return strf('COPY_UNEXPECTED_ERROR', event.error.code); |
| case 'MOVE': return strf('MOVE_UNEXPECTED_ERROR', event.error.code); |
| case 'ZIP': return strf('ZIP_UNEXPECTED_ERROR', event.error.code); |
| default: return strf('TRANSFER_UNEXPECTED_ERROR', event.error.code); |
| } |
| } |
| } else if (event.status.numRemainingItems === 1) { |
| var name = event.status.processingEntry.name; |
| switch (event.status.operationType) { |
| case 'COPY': return strf('COPY_FILE_NAME', name); |
| case 'MOVE': return strf('MOVE_FILE_NAME', name); |
| case 'ZIP': return strf('ZIP_FILE_NAME', name); |
| default: return strf('TRANSFER_FILE_NAME', name); |
| } |
| } else { |
| var remainNumber = event.status.numRemainingItems; |
| switch (event.status.operationType) { |
| case 'COPY': return strf('COPY_ITEMS_REMAINING', remainNumber); |
| case 'MOVE': return strf('MOVE_ITEMS_REMAINING', remainNumber); |
| case 'ZIP': return strf('ZIP_ITEMS_REMAINING', remainNumber); |
| default: return strf('TRANSFER_ITEMS_REMAINING', remainNumber); |
| } |
| } |
| }; |
| |
| /** |
| * Generates a delete message from the event. |
| * @param {Event} event Progress event. |
| * @return {string} message. |
| * @private |
| */ |
| FileOperationHandler.getDeleteMessage_ = function(event) { |
| if (event.reason === 'ERROR') { |
| return str('DELETE_ERROR'); |
| } else if (event.entries.length == 1) { |
| var fileName = event.entries[0].name; |
| return strf('DELETE_FILE_NAME', fileName); |
| } else if (event.entries.length > 1) { |
| return strf('DELETE_ITEMS_REMAINING', event.entries.length); |
| } else { |
| return ''; |
| } |
| }; |
| |
| /** |
| * Obtains ProgressItemType from OperationType of FileTransferManager. |
| * @param {string} operationType OperationType of FileTransferManager. |
| * @return {ProgressItemType} ProgreeType corresponding to the specified |
| * operation type. |
| * @private |
| */ |
| FileOperationHandler.getType_ = function(operationType) { |
| switch (operationType) { |
| case 'COPY': return ProgressItemType.COPY; |
| case 'MOVE': return ProgressItemType.MOVE; |
| case 'ZIP': return ProgressItemType.ZIP; |
| default: |
| console.error('Unknown operation type.'); |
| return ProgressItemType.TRANSFER; |
| } |
| }; |
| |
| /** |
| * Handles the copy-progress event. |
| * @param {Event} event The copy-progress event. |
| * @private |
| */ |
| FileOperationHandler.prototype.onCopyProgress_ = function(event) { |
| // If the copy is finished, may be we can close the background page. |
| if (event.reason !== 'BEGIN' && event.reason !== 'PROGRESS') |
| this.background_.tryClose(); |
| |
| // Update progress center. |
| var progressCenter = this.progressCenter_; |
| var item; |
| switch (event.reason) { |
| case 'BEGIN': |
| item = new ProgressCenterItem(); |
| item.id = event.taskId; |
| item.type = FileOperationHandler.getType_(event.status.operationType); |
| item.message = FileOperationHandler.getMessage_(event); |
| item.progressMax = event.status.totalBytes; |
| item.progressValue = event.status.processedBytes; |
| item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( |
| this.fileOperationManager_, |
| event.taskId); |
| progressCenter.updateItem(item); |
| break; |
| |
| case 'PROGRESS': |
| item = progressCenter.getItemById(event.taskId); |
| if (!item) { |
| console.error('Cannot find copying item.'); |
| return; |
| } |
| item.message = FileOperationHandler.getMessage_(event); |
| item.progressValue = event.status.processedBytes; |
| progressCenter.updateItem(item); |
| break; |
| |
| case 'SUCCESS': |
| case 'CANCELED': |
| case 'ERROR': |
| item = progressCenter.getItemById(event.taskId); |
| if (!item) { |
| // ERROR events can be dispatched before BEGIN events. |
| item = new ProgressCenterItem(); |
| item.type = FileOperationHandler.getType_(event.status.operationType); |
| item.id = event.taskId; |
| item.progressMax = 1; |
| } |
| if (event.reason === 'SUCCESS') { |
| item.message = ''; |
| item.state = ProgressItemState.COMPLETED; |
| item.progressValue = item.progressMax; |
| } else if (event.reason === 'CANCELED') { |
| item.message = ''; |
| item.state = ProgressItemState.CANCELED; |
| } else { |
| item.message = FileOperationHandler.getMessage_(event); |
| item.state = ProgressItemState.ERROR; |
| } |
| progressCenter.updateItem(item); |
| break; |
| } |
| }; |
| |
| /** |
| * Handles the delete event. |
| * @param {Event} event The delete event. |
| * @private |
| */ |
| FileOperationHandler.prototype.onDeleteProgress_ = function(event) { |
| // If the copy is finished, may be we can close the background page. |
| if (event.reason !== 'BEGIN' && event.reason !== 'PROGRESS') |
| this.background_.tryClose(); |
| |
| // Update progress center. |
| var progressCenter = this.progressCenter_; |
| var item; |
| var pending; |
| switch (event.reason) { |
| case 'BEGIN': |
| item = new ProgressCenterItem(); |
| item.id = event.taskId; |
| item.type = ProgressItemType.DELETE; |
| item.message = FileOperationHandler.getDeleteMessage_(event); |
| item.progressMax = event.totalBytes; |
| item.progressValue = event.processedBytes; |
| item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( |
| this.fileOperationManager_, |
| event.taskId); |
| this.pendingItems_[item.id] = item; |
| setTimeout(this.showPendingItem_.bind(this, item), |
| FileOperationHandler.PENDING_TIME_MS_); |
| break; |
| |
| case 'PROGRESS': |
| pending = event.taskId in this.pendingItems_; |
| item = this.pendingItems_[event.taskId] || |
| progressCenter.getItemById(event.taskId); |
| if (!item) { |
| console.error('Cannot find deleting item.'); |
| return; |
| } |
| item.message = FileOperationHandler.getDeleteMessage_(event); |
| item.progressMax = event.totalBytes; |
| item.progressValue = event.processedBytes; |
| if (!pending) |
| progressCenter.updateItem(item); |
| break; |
| |
| case 'SUCCESS': |
| case 'CANCELED': |
| case 'ERROR': |
| // Obtain working variable. |
| pending = event.taskId in this.pendingItems_; |
| item = this.pendingItems_[event.taskId] || |
| progressCenter.getItemById(event.taskId); |
| if (!item) { |
| console.error('Cannot find deleting item.'); |
| return; |
| } |
| |
| // Update the item. |
| item.message = FileOperationHandler.getDeleteMessage_(event); |
| if (event.reason === 'SUCCESS') { |
| item.state = ProgressItemState.COMPLETED; |
| item.progressValue = item.progressMax; |
| } else if (event.reason === 'CANCELED') { |
| item.state = ProgressItemState.CANCELED; |
| } else { |
| item.state = ProgressItemState.ERROR; |
| } |
| |
| // Apply the change. |
| if (!pending || event.reason === 'ERROR') |
| progressCenter.updateItem(item); |
| if (pending) |
| delete this.pendingItems_[event.taskId]; |
| break; |
| } |
| }; |
| |
| /** |
| * Shows the pending item. |
| * |
| * @param {ProgressCenterItem} item Pending item. |
| * @private |
| */ |
| FileOperationHandler.prototype.showPendingItem_ = function(item) { |
| // The item is already gone. |
| if (!this.pendingItems_[item.id]) |
| return; |
| delete this.pendingItems_[item.id]; |
| this.progressCenter_.updateItem(item); |
| }; |