blob: 43c8b5c58b93b5c3b3a876581c598d229f75adb6 [file] [log] [blame]
// 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';
/**
* Progress center at the background page.
* @constructor
*/
var ProgressCenter = function() {
cr.EventTarget.call(this);
/**
* Default container.
* @type {ProgressItemContainer}
* @private
*/
this.targetContainer_ = ProgressItemContainer.CLIENT;
/**
* Current items managed by the progress center.
* @type {Array.<ProgressItem>}
* @private
*/
this.items_ = [];
/**
* Timeout callback to remove items.
* @type {TimeoutManager}
* @private
*/
this.resetTimeout_ = new ProgressCenter.TimeoutManager(
this.reset_.bind(this));
};
/**
* The default amount of milliseconds time, before a progress item will reset
* after the last complete.
* @type {number}
* @private
* @const
*/
ProgressCenter.RESET_DELAY_TIME_MS_ = 5000;
/**
* Utility for timeout callback.
*
* @param {function(*):*} callback Callbakc function.
* @constructor
*/
ProgressCenter.TimeoutManager = function(callback) {
this.callback_ = callback;
this.id_ = null;
Object.seal(this);
};
/**
* Requests timeout. Previous request is canceled.
* @param {number} milliseconds Time to invoke the callback function.
*/
ProgressCenter.TimeoutManager.prototype.request = function(milliseconds) {
if (this.id_)
clearTimeout(this.id_);
this.id_ = setTimeout(function() {
this.id_ = null;
this.callback_();
}.bind(this), milliseconds);
};
ProgressCenter.prototype = {
__proto__: cr.EventTarget.prototype,
/**
* Obtains the items to be displayed in the application window.
* @private
*/
get applicationItems() {
return this.items_.filter(function(item) {
return item.container == ProgressItemContainer.CLIENT;
});
}
};
/**
* Updates the item in the progress center.
* If the item has a new ID, the item is added to the item list.
*
* @param {ProgressCenterItem} item Updated item.
*/
ProgressCenter.prototype.updateItem = function(item) {
var index = this.getItemIndex_(item.id);
if (index === -1) {
item.container = this.targetContainer_;
this.items_.push(item);
} else {
this.items_[index] = item;
}
if (item.status !== ProgressItemState.PROGRESSING)
this.resetTimeout_.request(ProgressCenter.RESET_DELAY_TIME_MS_);
var event = new Event(ProgressCenterEvent.ITEM_UPDATED);
event.item = item;
this.dispatchEvent(event);
};
/**
* Requests to cancel the progress item.
* @param {string} id Progress ID to be requested to cancel.
*/
ProgressCenter.prototype.requestCancel = function(id) {
var item = this.getItemById(id);
if (item && item.cancelCallback)
item.cancelCallback();
};
/**
* Switches the default container.
* @param {ProgressItemContainer} newContainer New value of the default
* container.
*/
ProgressCenter.prototype.switchContainer = function(newContainer) {
if (this.targetContainer_ === newContainer)
return;
// Current items to be moved to the notification center.
if (newContainer == ProgressItemContainer.NOTIFICATION) {
var items = this.applicationItems;
for (var i = 0; i < items.length; i++) {
items[i].container = ProgressItemContainer.NOTIFICATION;
this.postItemToNotification_(items);
}
}
// The items in the notification center does not come back to the Files.app
// clients.
// Assign the new value.
this.targetContainer_ = newContainer;
};
/**
* Obtains the summarized item to be displayed in the closed progress center
* panel.
* @return {ProgressCenterItem} Summarized item. Returns null if there is no
* item.
*/
ProgressCenter.prototype.getSummarizedItem = function() {
var applicationItems = this.applicationItems;
if (applicationItems.length == 0)
return null;
if (applicationItems.length == 1)
return applicationItems[0];
var summarizedItem = new ProgressCenterItem();
summarizedItem.summarized = true;
var completeCount = 0;
var progressingCount = 0;
var canceledCount = 0;
var errorCount = 0;
for (var i = 0; i < applicationItems.length; i++) {
switch (applicationItems[i].state) {
case ProgressItemState.COMPLETE:
completeCount++;
break;
case ProgressItemState.PROGRESSING:
progressingCount++;
break;
case ProgressItemState.ERROR:
errorCount++;
continue;
case ProgressItemState.CANCELED:
canceledCount++;
continue;
}
summarizedItem.progressMax += applicationItems[i].progressMax;
summarizedItem.progressValue += applicationItems[i].progressValue;
}
var messages = [];
if (completeCount)
messages.push(completeCount + ' complete');
if (progressingCount)
messages.push(progressingCount + ' active');
if (canceledCount)
messages.push(canceledCount + ' canceled');
if (errorCount)
messages.push(errorCount + ' error');
summarizedItem.message = messages.join(', ') + '.';
summarizedItem.state =
completeCount + progressingCount == 0 ? ProgressItemState.CANCELED :
progressingCount > 0 ? ProgressItemState.PROGRESSING :
ProgressItemState.COMPLETE;
return summarizedItem;
};
/**
* Obtains item by ID.
* @param {string} id ID of progress item.
* @return {ProgressCenterItem} Progress center item having the specified
* ID. Null if the item is not found.
*/
ProgressCenter.prototype.getItemById = function(id) {
return this.items_[this.getItemIndex_(id)];
};
/**
* Obtains item index that have the specifying ID.
* @param {string} id Item ID.
* @return {number} Item index. Returns -1 If the item is not found.
* @private
*/
ProgressCenter.prototype.getItemIndex_ = function(id) {
for (var i = 0; i < this.items_.length; i++) {
if (this.items_[i].id === id)
return i;
}
return -1;
};
/**
* Passes the item to the ChromeOS's message center.
*
* TODO(hirono): Implement the method.
*
* @private
*/
ProgressCenter.prototype.passItemsToNotification_ = function() {
};
/**
* Hides the progress center if there is no progressing items.
* @private
*/
ProgressCenter.prototype.reset_ = function() {
// If we have a progressing item, stop reset.
for (var i = 0; i < this.items_.length; i++) {
if (this.items_[i].state == ProgressItemState.PROGRESSING)
return;
}
// Reset items.
this.items_.splice(0, this.items_.length);
// Dispatch a event.
this.dispatchEvent(new Event(ProgressCenterEvent.RESET));
};
/**
* An event handler for progress center.
* @param {FileOperationManager} fileOperationManager File operation manager.
* @param {ProgressCenter} progressCenter Progress center.
* @constructor
*/
var ProgressCenterHandler = function(fileOperationManager, progressCenter) {
/**
* Number of deleted files.
* @type {number}
* @private
*/
this.totalDeleted_ = 0;
/**
* File operation manager.
* @type {FileOperationManager}
* @private
*/
this.fileOperationManager_ = fileOperationManager;
/**
* Progress center.
* @type {progressCenter}
* @private
*/
this.progressCenter_ = progressCenter;
// Seal the object.
Object.seal(this);
// Register event.
fileOperationManager.addEventListener('copy-progress',
this.onCopyProgress_.bind(this));
fileOperationManager.addEventListener('delete',
this.onDeleteProgress_.bind(this));
};
/**
* Generate a progress message from the event.
* @param {Event} event Progress event.
* @return {string} message.
* @private
*/
ProgressCenterHandler.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);
case 'MOVE': return strf('MOVE_UNEXPECTED_ERROR', event.error);
case 'ZIP': return strf('ZIP_UNEXPECTED_ERROR', event.error);
default: return strf('TRANSFER_UNEXPECTED_ERROR', event.error);
}
}
} 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);
}
}
};
/**
* Generate a delete message from the event.
* @param {Event} event Progress event.
* @param {number} totalDeleted Total number of deleted files.
* @return {string} message.
* @private
*/
ProgressCenterHandler.getDeleteMessage_ = function(event, totalDeleted) {
if (totalDeleted === 1) {
var fullPath = util.extractFilePath(event.urls[0]);
var fileName = PathUtil.split(fullPath).pop();
return strf('DELETED_MESSAGE', fileName);
} else {
return strf('DELETED_MESSAGE_PLURAL', totalDeleted);
}
};
/**
* Handles the copy-progress event.
* @param {Event} event The copy-progress event.
* @private
*/
ProgressCenterHandler.prototype.onCopyProgress_ = function(event) {
var progressCenter = this.progressCenter_;
var item;
switch (event.reason) {
case 'BEGIN':
item = new ProgressCenterItem();
item.id = event.taskId;
item.message = ProgressCenterHandler.getMessage_(event);
item.progressMax = event.status.totalBytes;
item.progressValue = event.status.processedBytes;
item.cancelCallback = function(inItem) {
this.fileOperationManager_.requestCancel(function() {
inItem.message = strf('COPY_CANCELLED');
inItem.state = ProgressItemState.CANCELED;
progressCenter.updateItem(inItem);
}.bind(this));
}.bind(this, item);
progressCenter.updateItem(item);
break;
case 'PROGRESS':
item = progressCenter.getItemById(event.taskId);
if (!item) {
console.error('Cannot find copying item.');
return;
}
item.message = ProgressCenterHandler.getMessage_(event);
item.progressValue = event.status.processedBytes;
progressCenter.updateItem(item);
break;
case 'SUCCESS':
case 'ERROR':
item = progressCenter.getItemById(event.taskId);
if (!item) {
// ERROR events can be dispatched before BEGIN events.
item = new ProgressCenterItem();
item.id = event.taskId;
item.progressMax = 1;
}
if (event.reason === 'SUCCESS') {
// TODO(hirono): Add a message for complete.
item.state = ProgressItemState.COMPLETE;
item.progressValue = item.progressMax;
} else {
item.message = ProgressCenterHandler.getMessage_(event);
item.state = ProgressItemState.ERROR;
}
progressCenter.updateItem(item);
break;
}
};
/**
* Handles the delete event.
* @param {Event} event The delete event.
* @private
*/
ProgressCenterHandler.prototype.onDeleteProgress_ = function(event) {
var progressCenter = this.progressCenter_;
var item;
switch (event.reason) {
case 'BEGIN':
this.totalDeleted_ = 0;
item = new ProgressCenterItem();
item.id = event.taskId;
// TODO(hirono): Specifying the correct message.
item.message =
ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_);
item.progressMax = 100;
progressCenter.updateItem(item);
break;
case 'PROGRESS':
item = progressCenter.getItemById(event.taskId);
if (!item) {
console.error('Cannot find deleting item.');
return;
}
this.totalDeleted_ += event.urls.length;
item.message =
ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_);
progressCenter.updateItem(item);
break;
case 'SUCCESS':
case 'ERROR':
item = progressCenter.getItemById(event.taskId);
if (!item) {
console.error('Cannot find deleting item.');
return;
}
if (event.reason === 'SUCCESS') {
this.totalDeleted_ += event.urls.length;
item.message =
ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_);
item.state = ProgressItemState.COMPLETE;
item.progressValue = item.progressMax;
} else {
item.message = str('DELETE_ERROR');
item.state = ProgressItemState.ERROR;
}
progressCenter.updateItem(item);
break;
}
};