Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2249)

Unified Diff: chrome/browser/resources/file_manager/js/file_operation_manager.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/file_manager/js/file_operation_manager.js
diff --git a/chrome/browser/resources/file_manager/js/file_operation_manager.js b/chrome/browser/resources/file_manager/js/file_operation_manager.js
deleted file mode 100644
index 434fd1e29ac03db06369e9d1448066db04b2e533..0000000000000000000000000000000000000000
--- a/chrome/browser/resources/file_manager/js/file_operation_manager.js
+++ /dev/null
@@ -1,1502 +0,0 @@
-// 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';
-
-/**
- * Utilities for FileOperationManager.
- */
-var fileOperationUtil = {};
-
-/**
- * Simple wrapper for util.deduplicatePath. On error, this method translates
- * the FileError to FileOperationManager.Error object.
- *
- * @param {DirectoryEntry} dirEntry The target directory entry.
- * @param {string} relativePath The path to be deduplicated.
- * @param {function(string)} successCallback Callback run with the deduplicated
- * path on success.
- * @param {function(FileOperationManager.Error)} errorCallback Callback run on
- * error.
- */
-fileOperationUtil.deduplicatePath = function(
- dirEntry, relativePath, successCallback, errorCallback) {
- util.deduplicatePath(
- dirEntry, relativePath, successCallback,
- function(err) {
- var onFileSystemError = function(error) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, error));
- };
-
- if (err.code == FileError.PATH_EXISTS_ERR) {
- // Failed to uniquify the file path. There should be an existing
- // entry, so return the error with it.
- util.resolvePath(
- dirEntry, relativePath,
- function(entry) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.TARGET_EXISTS, entry));
- },
- onFileSystemError);
- return;
- }
- onFileSystemError(err);
- });
-};
-
-/**
- * Traverses files/subdirectories of the given entry, and returns them.
- * In addition, this method annotate the size of each entry. The result will
- * include the entry itself.
- *
- * @param {Entry} entry The root Entry for traversing.
- * @param {function(Array.<Entry>)} successCallback Called when the traverse
- * is successfully done with the array of the entries.
- * @param {function(FileError)} errorCallback Called on error with the first
- * occurred error (i.e. following errors will just be discarded).
- */
-fileOperationUtil.resolveRecursively = function(
- entry, successCallback, errorCallback) {
- var result = [];
- var error = null;
- var numRunningTasks = 0;
-
- var maybeInvokeCallback = function() {
- // If there still remain some running tasks, wait their finishing.
- if (numRunningTasks > 0)
- return;
-
- if (error)
- errorCallback(error);
- else
- successCallback(result);
- };
-
- // The error handling can be shared.
- var onError = function(fileError) {
- // If this is the first error, remember it.
- if (!error)
- error = fileError;
- --numRunningTasks;
- maybeInvokeCallback();
- };
-
- var process = function(entry) {
- numRunningTasks++;
- result.push(entry);
- if (entry.isDirectory) {
- // The size of a directory is 1 bytes here, so that the progress bar
- // will work smoother.
- // TODO(hidehiko): Remove this hack.
- entry.size = 1;
-
- // Recursively traverse children.
- var reader = entry.createReader();
- reader.readEntries(
- function processSubEntries(subEntries) {
- if (error || subEntries.length == 0) {
- // If an error is found already, or this is the completion
- // callback, then finish the process.
- --numRunningTasks;
- maybeInvokeCallback();
- return;
- }
-
- for (var i = 0; i < subEntries.length; i++)
- process(subEntries[i]);
-
- // Continue to read remaining children.
- reader.readEntries(processSubEntries, onError);
- },
- onError);
- } else {
- // For a file, annotate the file size.
- entry.getMetadata(function(metadata) {
- entry.size = metadata.size;
- --numRunningTasks;
- maybeInvokeCallback();
- }, onError);
- }
- };
-
- process(entry);
-};
-
-/**
- * Copies source to parent with the name newName recursively.
- * This should work very similar to FileSystem API's copyTo. The difference is;
- * - The progress callback is supported.
- * - The cancellation is supported.
- *
- * @param {Entry} source The entry to be copied.
- * @param {DirectoryEntry} parent The entry of the destination directory.
- * @param {string} newName The name of copied file.
- * @param {function(string, string)} entryChangedCallback
- * Callback invoked when an entry is created with the source url and
- * the destination url.
- * @param {function(string, number)} progressCallback Callback invoked
- * periodically during the copying. It takes the source url and the
- * processed bytes of it.
- * @param {function(string)} successCallback Callback invoked when the copy
- * is successfully done with the url of the created entry.
- * @param {function(FileError)} errorCallback Callback invoked when an error
- * is found.
- * @return {function()} Callback to cancel the current file copy operation.
- * When the cancel is done, errorCallback will be called. The returned
- * callback must not be called more than once.
- */
-fileOperationUtil.copyTo = function(
- source, parent, newName, entryChangedCallback, progressCallback,
- successCallback, errorCallback) {
- var copyId = null;
- var pendingCallbacks = [];
-
- var onCopyProgress = function(progressCopyId, status) {
- if (copyId == null) {
- // If the copyId is not yet available, wait for it.
- pendingCallbacks.push(
- onCopyProgress.bind(null, progressCopyId, status));
- return;
- }
-
- // This is not what we're interested in.
- if (progressCopyId != copyId)
- return;
-
- switch (status.type) {
- case 'begin_copy_entry':
- break;
-
- case 'end_copy_entry':
- entryChangedCallback(status.sourceUrl, status.destinationUrl);
- break;
-
- case 'progress':
- progressCallback(status.sourceUrl, status.size);
- break;
-
- case 'success':
- chrome.fileBrowserPrivate.onCopyProgress.removeListener(onCopyProgress);
- successCallback(status.destinationUrl);
- break;
-
- case 'error':
- chrome.fileBrowserPrivate.onCopyProgress.removeListener(onCopyProgress);
- errorCallback(util.createFileError(status.error));
- break;
-
- default:
- // Found unknown state. Cancel the task, and return an error.
- console.error('Unknown progress type: ' + status.type);
- chrome.fileBrowserPrivate.onCopyProgress.removeListener(onCopyProgress);
- chrome.fileBrowserPrivate.cancelCopy(copyId);
- errorCallback(util.createFileError(FileError.INVALID_STATE_ERR));
- }
- };
-
- // Register the listener before calling startCopy. Otherwise some events
- // would be lost.
- chrome.fileBrowserPrivate.onCopyProgress.addListener(onCopyProgress);
-
- // Then starts the copy.
- chrome.fileBrowserPrivate.startCopy(
- source.toURL(), parent.toURL(), newName, function(startCopyId) {
- // last error contains the FileError code on error.
- if (chrome.runtime.lastError) {
- // Unsubscribe the progress listener.
- chrome.fileBrowserPrivate.onCopyProgress.removeListener(
- onCopyProgress);
- errorCallback(util.createFileError(
- Integer.parseInt(chrome.runtime.lastError, 10)));
- return;
- }
-
- copyId = startCopyId;
- for (var i = 0; i < pendingCallbacks.length; i++) {
- pendingCallbacks[i]();
- }
- });
-
- return function() {
- // If copyId is not yet available, wait for it.
- if (copyId == null) {
- pendingCallbacks.push(function() {
- chrome.fileBrowserPrivate.cancelCopy(copyId);
- });
- return;
- }
-
- chrome.fileBrowserPrivate.cancelCopy(copyId);
- };
-};
-
-/**
- * Thin wrapper of chrome.fileBrowserPrivate.zipSelection to adapt its
- * interface similar to copyTo().
- *
- * @param {Array.<Entry>} sources The array of entries to be archived.
- * @param {DirectoryEntry} parent The entry of the destination directory.
- * @param {string} newName The name of the archive to be created.
- * @param {function(FileEntry)} successCallback Callback invoked when the
- * operation is successfully done with the entry of the created archive.
- * @param {function(FileError)} errorCallback Callback invoked when an error
- * is found.
- */
-fileOperationUtil.zipSelection = function(
- sources, parent, newName, successCallback, errorCallback) {
- chrome.fileBrowserPrivate.zipSelection(
- parent.toURL(),
- sources.map(function(e) { return e.toURL(); }),
- newName, function(success) {
- if (!success) {
- // Failed to create a zip archive.
- errorCallback(
- util.createFileError(FileError.INVALID_MODIFICATION_ERR));
- return;
- }
-
- // Returns the created entry via callback.
- parent.getFile(
- newName, {create: false}, successCallback, errorCallback);
- });
-};
-
-/**
- * @constructor
- */
-function FileOperationManager() {
- this.copyTasks_ = [];
- this.deleteTasks_ = [];
- this.cancelObservers_ = [];
- this.cancelRequested_ = false;
- this.cancelCallback_ = null;
- this.unloadTimeout_ = null;
- this.taskIdCounter_ = 0;
-
- this.eventRouter_ = new FileOperationManager.EventRouter();
-
- Object.seal(this);
-}
-
-/**
- * Get FileOperationManager instance. In case is hasn't been initialized, a new
- * instance is created.
- *
- * @return {FileOperationManager} A FileOperationManager instance.
- */
-FileOperationManager.getInstance = function() {
- if (!FileOperationManager.instance_)
- FileOperationManager.instance_ = new FileOperationManager();
-
- return FileOperationManager.instance_;
-};
-
-/**
- * Manages Event dispatching.
- * Currently this can send three types of events: "copy-progress",
- * "copy-operation-completed" and "delete".
- *
- * TODO(hidehiko): Reorganize the event dispatching mechanism.
- * @constructor
- * @extends {cr.EventTarget}
- */
-FileOperationManager.EventRouter = function() {
-};
-
-/**
- * Extends cr.EventTarget.
- */
-FileOperationManager.EventRouter.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
- * Dispatches a simple "copy-progress" event with reason and current
- * FileOperationManager status. If it is an ERROR event, error should be set.
- *
- * @param {string} reason Event type. One of "BEGIN", "PROGRESS", "SUCCESS",
- * "ERROR" or "CANCELLED". TODO(hidehiko): Use enum.
- * @param {Object} status Current FileOperationManager's status. See also
- * FileOperationManager.getStatus().
- * @param {string} taskId ID of task related with the event.
- * @param {FileOperationManager.Error=} opt_error The info for the error. This
- * should be set iff the reason is "ERROR".
- */
-FileOperationManager.EventRouter.prototype.sendProgressEvent = function(
- reason, status, taskId, opt_error) {
- var event = new Event('copy-progress');
- event.reason = reason;
- event.status = status;
- event.taskId = taskId;
- if (opt_error)
- event.error = opt_error;
- this.dispatchEvent(event);
-};
-
-/**
- * Dispatches an event to notify that an entry is changed (created or deleted).
- * @param {util.EntryChangedKind} kind The enum to represent if the entry is
- * created or deleted.
- * @param {Entry} entry The changed entry.
- */
-FileOperationManager.EventRouter.prototype.sendEntryChangedEvent = function(
- kind, entry) {
- var event = new Event('entry-changed');
- event.kind = kind;
- event.entry = entry;
- this.dispatchEvent(event);
-};
-
-/**
- * Dispatches an event to notify entries are changed for delete task.
- *
- * @param {string} reason Event type. One of "BEGIN", "PROGRESS", "SUCCESS",
- * or "ERROR". TODO(hidehiko): Use enum.
- * @param {Array.<string>} urls An array of URLs which are affected by delete
- * operation.
- * @param {string} taskId ID of task related with the event.
- */
-FileOperationManager.EventRouter.prototype.sendDeleteEvent = function(
- reason, urls, taskId) {
- var event = new Event('delete');
- event.reason = reason;
- event.urls = urls;
- this.dispatchEvent(event);
-};
-
-/**
- * A record of a queued copy operation.
- *
- * Multiple copy operations may be queued at any given time. Additional
- * Tasks may be added while the queue is being serviced. Though a
- * cancel operation cancels everything in the queue.
- *
- * @param {util.FileOperationType} operationType The type of this operation.
- * @param {Array.<Entry>} sourceEntries Array of source entries.
- * @param {DirectoryEntry} targetDirEntry Target directory.
- * @constructor
- */
-FileOperationManager.Task = function(
- operationType, sourceEntries, targetDirEntry) {
- this.operationType = operationType;
- this.sourceEntries = sourceEntries;
- this.targetDirEntry = targetDirEntry;
-
- /**
- * An array of map from url to Entry being processed.
- * @type {Array.<Object<string, Entry>>}
- */
- this.processingEntries = null;
-
- /**
- * Total number of bytes to be processed. Filled in initialize().
- * @type {number}
- */
- this.totalBytes = 0;
-
- /**
- * Total number of already processed bytes. Updated periodically.
- * @type {number}
- */
- this.processedBytes = 0;
-
- this.deleteAfterCopy = false;
-
- /**
- * Set to true when cancel is requested.
- * @private {boolean}
- */
- this.cancelRequested_ = false;
-
- /**
- * Callback to cancel the running process.
- * @private {function()}
- */
- this.cancelCallback_ = null;
-
- // TODO(hidehiko): After we support recursive copy, we don't need this.
- // If directory already exists, we try to make a copy named 'dir (X)',
- // where X is a number. When we do this, all subsequent copies from
- // inside the subtree should be mapped to the new directory name.
- // For example, if 'dir' was copied as 'dir (1)', then 'dir\file.txt' should
- // become 'dir (1)\file.txt'.
- this.renamedDirectories_ = [];
-};
-
-/**
- * @param {function()} callback When entries resolved.
- */
-FileOperationManager.Task.prototype.initialize = function(callback) {
-};
-
-/**
- * Updates copy progress status for the entry.
- *
- * @param {number} size Number of bytes that has been copied since last update.
- */
-FileOperationManager.Task.prototype.updateFileCopyProgress = function(size) {
- this.completedBytes += size;
-};
-
-/**
- * Requests cancellation of this task.
- * When the cancellation is done, it is notified via callbacks of run().
- */
-FileOperationManager.Task.prototype.requestCancel = function() {
- this.cancelRequested_ = true;
- if (this.cancelCallback_) {
- this.cancelCallback_();
- this.cancelCallback_ = null;
- }
-};
-
-/**
- * Runs the task. Sub classes must implement this method.
- *
- * @param {function(util.EntryChangedKind, Entry)} entryChangedCallback
- * Callback invoked when an entry is changed.
- * @param {function()} progressCallback Callback invoked periodically during
- * the operation.
- * @param {function()} successCallback Callback run on success.
- * @param {function(FileOperationManager.Error)} errorCallback Callback run on
- * error.
- */
-FileOperationManager.Task.prototype.run = function(
- entryChangedCallback, progressCallback, successCallback, errorCallback) {
-};
-
-/**
- * Get states of the task.
- * TOOD(hirono): Removes this method and sets a task to progress events.
- * @return {object} Status object.
- */
-FileOperationManager.Task.prototype.getStatus = function() {
- var numRemainingItems = this.countRemainingItems();
- return {
- operationType: this.operationType,
- numRemainingItems: numRemainingItems,
- totalBytes: this.totalBytes,
- processedBytes: this.processedBytes,
- processingEntry: this.getSingleEntry()
- };
-};
-
-/**
- * Counts the number of remaining items.
- * @return {number} Number of remaining items.
- */
-FileOperationManager.Task.prototype.countRemainingItems = function() {
- var count = 0;
- for (var i = 0; i < this.processingEntries.length; i++) {
- for (var url in this.processingEntries[i]) {
- count++;
- }
- }
- return count;
-};
-
-/**
- * Obtains the single processing entry. If there are multiple processing
- * entries, it returns null.
- * @return {Entry} First entry.
- */
-FileOperationManager.Task.prototype.getSingleEntry = function() {
- if (this.countRemainingItems() !== 1)
- return null;
- for (var i = 0; i < this.processingEntries.length; i++) {
- var entryMap = this.processingEntries[i];
- for (var name in entryMap)
- return entryMap[name];
- }
- return null;
-};
-
-/**
- * Task to copy entries.
- *
- * @param {Array.<Entry>} sourceEntries Array of source entries.
- * @param {DirectoryEntry} targetDirEntry Target directory.
- * @constructor
- * @extends {FileOperationManager.Task}
- */
-FileOperationManager.CopyTask = function(sourceEntries, targetDirEntry) {
- FileOperationManager.Task.call(
- this, util.FileOperationType.COPY, sourceEntries, targetDirEntry);
-};
-
-/**
- * Extends FileOperationManager.Task.
- */
-FileOperationManager.CopyTask.prototype.__proto__ =
- FileOperationManager.Task.prototype;
-
-/**
- * Initializes the CopyTask.
- * @param {function()} callback Called when the initialize is completed.
- */
-FileOperationManager.CopyTask.prototype.initialize = function(callback) {
- var group = new AsyncUtil.Group();
- // Correct all entries to be copied for status update.
- this.processingEntries = [];
- for (var i = 0; i < this.sourceEntries.length; i++) {
- group.add(function(index, callback) {
- fileOperationUtil.resolveRecursively(
- this.sourceEntries[index],
- function(resolvedEntries) {
- var resolvedEntryMap = {};
- for (var j = 0; j < resolvedEntries.length; ++j) {
- var entry = resolvedEntries[j];
- entry.processedBytes = 0;
- resolvedEntryMap[entry.toURL()] = entry;
- }
- this.processingEntries[index] = resolvedEntryMap;
- callback();
- }.bind(this),
- function(error) {
- console.error(
- 'Failed to resolve for copy: %s',
- util.getFileErrorMnemonic(error.code));
- });
- }.bind(this, i));
- }
-
- group.run(function() {
- // Fill totalBytes.
- this.totalBytes = 0;
- for (var i = 0; i < this.processingEntries.length; i++) {
- for (var url in this.processingEntries[i])
- this.totalBytes += this.processingEntries[i][url].size;
- }
-
- callback();
- }.bind(this));
-};
-
-/**
- * Copies all entries to the target directory.
- * Note: this method contains also the operation of "Move" due to historical
- * reason.
- *
- * @param {function(util.EntryChangedKind, Entry)} entryChangedCallback
- * Callback invoked when an entry is changed.
- * @param {function()} progressCallback Callback invoked periodically during
- * the copying.
- * @param {function()} successCallback On success.
- * @param {function(FileOperationManager.Error)} errorCallback On error.
- * @override
- */
-FileOperationManager.CopyTask.prototype.run = function(
- entryChangedCallback, progressCallback, successCallback, errorCallback) {
- // TODO(hidehiko): We should be able to share the code to iterate on entries
- // with serviceMoveTask_().
- if (this.sourceEntries.length == 0) {
- successCallback();
- return;
- }
-
- // TODO(hidehiko): Delete after copy is the implementation of Move.
- // Migrate the part into MoveTask.run().
- var deleteOriginals = function() {
- var count = this.sourceEntries.length;
-
- var onEntryDeleted = function(entry) {
- entryChangedCallback(util.EntryChangedKind.DELETED, entry);
- count--;
- if (!count)
- successCallback();
- };
-
- var onFilesystemError = function(err) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, err));
- };
-
- for (var i = 0; i < this.sourceEntries.length; i++) {
- var entry = this.sourceEntries[i];
- util.removeFileOrDirectory(
- entry, onEntryDeleted.bind(null, entry), onFilesystemError);
- }
- }.bind(this);
-
- AsyncUtil.forEach(
- this.sourceEntries,
- function(callback, entry, index) {
- if (this.cancelRequested_) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR,
- util.createFileError(FileError.ABORT_ERR)));
- return;
- }
- progressCallback();
- this.cancelCallback_ = FileOperationManager.CopyTask.processEntry_(
- entry, this.targetDirEntry,
- function(sourceUrl, destinationUrl) {
- // Finalize the entry's progress state.
- var entry = this.processingEntries[index][sourceUrl];
- if (entry) {
- this.processedBytes += entry.size - entry.processedBytes;
- progressCallback();
- delete this.processingEntries[index][sourceUrl];
- }
-
- webkitResolveLocalFileSystemURL(
- destinationUrl, function(destinationEntry) {
- entryChangedCallback(
- util.EntryChangedKind.CREATED, destinationEntry);
- });
- }.bind(this),
- function(source_url, size) {
- var entry = this.processingEntries[index][source_url];
- if (entry) {
- this.processedBytes += size - entry.processedBytes;
- entry.processedBytes = size;
- progressCallback();
- }
- }.bind(this),
- function() {
- this.cancelCallback_ = null;
- callback();
- }.bind(this),
- function(error) {
- this.cancelCallback_ = null;
- errorCallback(error);
- }.bind(this));
- },
- function() {
- if (this.deleteAfterCopy) {
- deleteOriginals();
- } else {
- successCallback();
- }
- }.bind(this),
- this);
-};
-
-/**
- * Copies the source entry to the target directory.
- *
- * @param {Entry} sourceEntry An entry to be copied.
- * @param {DirectoryEntry} destinationEntry The entry which will contain the
- * copied entry.
- * @param {function(string, string)} entryChangedCallback
- * Callback invoked when an entry is created with the source url and
- * the destination url.
- * @param {function(string, number)} progressCallback Callback invoked
- * periodically during the copying.
- * @param {function()} successCallback On success.
- * @param {function(FileOperationManager.Error)} errorCallback On error.
- * @return {function()} Callback to cancel the current file copy operation.
- * When the cancel is done, errorCallback will be called. The returned
- * callback must not be called more than once.
- * @private
- */
-FileOperationManager.CopyTask.processEntry_ = function(
- sourceEntry, destinationEntry, entryChangedCallback, progressCallback,
- successCallback, errorCallback) {
- var cancelRequested = false;
- var cancelCallback = null;
- fileOperationUtil.deduplicatePath(
- destinationEntry, sourceEntry.name,
- function(destinationName) {
- if (cancelRequested) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR,
- util.createFileError(FileError.ABORT_ERR)));
- return;
- }
-
- cancelCallback = fileOperationUtil.copyTo(
- sourceEntry, destinationEntry, destinationName,
- entryChangedCallback, progressCallback,
- function(entry) {
- cancelCallback = null;
- successCallback();
- },
- function(error) {
- cancelCallback = null;
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, error));
- });
- },
- errorCallback);
-
- return function() {
- cancelRequested = true;
- if (cancelCallback) {
- cancelCallback();
- cancelCallback = null;
- }
- };
-};
-
-/**
- * Task to move entries.
- *
- * @param {Array.<Entry>} sourceEntries Array of source entries.
- * @param {DirectoryEntry} targetDirEntry Target directory.
- * @constructor
- * @extends {FileOperationManager.Task}
- */
-FileOperationManager.MoveTask = function(sourceEntries, targetDirEntry) {
- FileOperationManager.Task.call(
- this, util.FileOperationType.MOVE, sourceEntries, targetDirEntry);
-};
-
-/**
- * Extends FileOperationManager.Task.
- */
-FileOperationManager.MoveTask.prototype.__proto__ =
- FileOperationManager.Task.prototype;
-
-/**
- * Initializes the MoveTask.
- * @param {function()} callback Called when the initialize is completed.
- */
-FileOperationManager.MoveTask.prototype.initialize = function(callback) {
- // This may be moving from search results, where it fails if we
- // move parent entries earlier than child entries. We should
- // process the deepest entry first. Since move of each entry is
- // done by a single moveTo() call, we don't need to care about the
- // recursive traversal order.
- this.sourceEntries.sort(function(entry1, entry2) {
- return entry2.fullPath.length - entry1.fullPath.length;
- });
-
- this.processingEntries = [];
- for (var i = 0; i < this.sourceEntries.length; i++) {
- var processingEntryMap = {};
- var entry = this.sourceEntries[i];
-
- // The move should be done with updating the metadata. So here we assume
- // all the file size is 1 byte. (Avoiding 0, so that progress bar can
- // move smoothly).
- // TODO(hidehiko): Remove this hack.
- entry.size = 1;
- processingEntryMap[entry.toURL()] = entry;
- this.processingEntries[i] = processingEntryMap;
- }
-
- callback();
-};
-
-/**
- * Moves all entries in the task.
- *
- * @param {function(util.EntryChangedKind, Entry)} entryChangedCallback
- * Callback invoked when an entry is changed.
- * @param {function()} progressCallback Callback invoked periodically during
- * the moving.
- * @param {function()} successCallback On success.
- * @param {function(FileOperationManager.Error)} errorCallback On error.
- * @override
- */
-FileOperationManager.MoveTask.prototype.run = function(
- entryChangedCallback, progressCallback, successCallback, errorCallback) {
- if (this.sourceEntries.length == 0) {
- successCallback();
- return;
- }
-
- AsyncUtil.forEach(
- this.sourceEntries,
- function(callback, entry, index) {
- if (this.cancelRequested_) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR,
- util.createFileError(FileError.ABORT_ERR)));
- return;
- }
- progressCallback();
- FileOperationManager.MoveTask.processEntry_(
- entry, this.targetDirEntry, entryChangedCallback,
- function() {
- // Erase the processing entry.
- this.processingEntries[index] = {};
- this.processedBytes++;
- callback();
- }.bind(this),
- errorCallback);
- },
- function() {
- successCallback();
- }.bind(this),
- this);
-};
-
-/**
- * Moves the sourceEntry to the targetDirEntry in this task.
- *
- * @param {Entry} sourceEntry An entry to be moved.
- * @param {DirectoryEntry} destinationEntry The entry of the destination
- * directory.
- * @param {function(util.EntryChangedKind, Entry)} entryChangedCallback
- * Callback invoked when an entry is changed.
- * @param {function()} successCallback On success.
- * @param {function(FileOperationManager.Error)} errorCallback On error.
- * @private
- */
-FileOperationManager.MoveTask.processEntry_ = function(
- sourceEntry, destinationEntry, entryChangedCallback, successCallback,
- errorCallback) {
- fileOperationUtil.deduplicatePath(
- destinationEntry,
- sourceEntry.name,
- function(destinationName) {
- sourceEntry.moveTo(
- destinationEntry, destinationName,
- function(movedEntry) {
- entryChangedCallback(util.EntryChangedKind.CREATED, movedEntry);
- entryChangedCallback(util.EntryChangedKind.DELETED, sourceEntry);
- successCallback();
- },
- function(error) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, error));
- });
- },
- errorCallback);
-};
-
-/**
- * Task to create a zip archive.
- *
- * @param {Array.<Entry>} sourceEntries Array of source entries.
- * @param {DirectoryEntry} targetDirEntry Target directory.
- * @param {DirectoryEntry} zipBaseDirEntry Base directory dealt as a root
- * in ZIP archive.
- * @constructor
- * @extends {FileOperationManager.Task}
- */
-FileOperationManager.ZipTask = function(
- sourceEntries, targetDirEntry, zipBaseDirEntry) {
- FileOperationManager.Task.call(
- this, util.FileOperationType.ZIP, sourceEntries, targetDirEntry);
- this.zipBaseDirEntry = zipBaseDirEntry;
-};
-
-/**
- * Extends FileOperationManager.Task.
- */
-FileOperationManager.ZipTask.prototype.__proto__ =
- FileOperationManager.Task.prototype;
-
-
-/**
- * Initializes the ZipTask.
- * @param {function()} callback Called when the initialize is completed.
- */
-FileOperationManager.ZipTask.prototype.initialize = function(callback) {
- var resolvedEntryMap = {};
- var group = new AsyncUtil.Group();
- for (var i = 0; i < this.sourceEntries.length; i++) {
- group.add(function(index, callback) {
- fileOperationUtil.resolveRecursively(
- this.sourceEntries[index],
- function(entries) {
- for (var j = 0; j < entries.length; j++)
- resolvedEntryMap[entries[j].toURL()] = entries[j];
- callback();
- },
- function(error) {});
- }.bind(this, i));
- }
-
- group.run(function() {
- // For zip archiving, all the entries are processed at once.
- this.processingEntries = [resolvedEntryMap];
-
- this.totalBytes = 0;
- for (var url in resolvedEntryMap)
- this.totalBytes += resolvedEntryMap[url].size;
-
- callback();
- }.bind(this));
-};
-
-/**
- * Runs a zip file creation task.
- *
- * @param {function(util.EntryChangedKind, Entry)} entryChangedCallback
- * Callback invoked when an entry is changed.
- * @param {function()} progressCallback Callback invoked periodically during
- * the moving.
- * @param {function()} successCallback On complete.
- * @param {function(FileOperationManager.Error)} errorCallback On error.
- * @override
- */
-FileOperationManager.ZipTask.prototype.run = function(
- entryChangedCallback, progressCallback, successCallback, errorCallback) {
- // TODO(hidehiko): we should localize the name.
- var destName = 'Archive';
- if (this.sourceEntries.length == 1) {
- var entryPath = this.sourceEntries[0].fullPath;
- var i = entryPath.lastIndexOf('/');
- var basename = (i < 0) ? entryPath : entryPath.substr(i + 1);
- i = basename.lastIndexOf('.');
- destName = ((i < 0) ? basename : basename.substr(0, i));
- }
-
- fileOperationUtil.deduplicatePath(
- this.targetDirEntry, destName + '.zip',
- function(destPath) {
- // TODO: per-entry zip progress update with accurate byte count.
- // For now just set completedBytes to same value as totalBytes so
- // that the progress bar is full.
- this.processedBytes = this.totalBytes;
- progressCallback();
-
- // The number of elements in processingEntries is 1. See also
- // initialize().
- var entries = [];
- for (var url in this.processingEntries[0])
- entries.push(this.processingEntries[0][url]);
-
- fileOperationUtil.zipSelection(
- entries,
- this.zipBaseDirEntry,
- destPath,
- function(entry) {
- entryChangedCallback(util.EntryChangedKind.CREATE, entry);
- successCallback();
- },
- function(error) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, error));
- });
- }.bind(this),
- errorCallback);
-};
-
-/**
- * Error class used to report problems with a copy operation.
- * If the code is UNEXPECTED_SOURCE_FILE, data should be a path of the file.
- * If the code is TARGET_EXISTS, data should be the existing Entry.
- * If the code is FILESYSTEM_ERROR, data should be the FileError.
- *
- * @param {util.FileOperationErrorType} code Error type.
- * @param {string|Entry|FileError} data Additional data.
- * @constructor
- */
-FileOperationManager.Error = function(code, data) {
- this.code = code;
- this.data = data;
-};
-
-// FileOperationManager methods.
-
-/**
- * Called before a new method is run in the manager. Prepares the manager's
- * state for running a new method.
- */
-FileOperationManager.prototype.willRunNewMethod = function() {
- // Cancel any pending close actions so the file copy manager doesn't go away.
- if (this.unloadTimeout_)
- clearTimeout(this.unloadTimeout_);
- this.unloadTimeout_ = null;
-};
-
-/**
- * @return {Object} Status object.
- */
-FileOperationManager.prototype.getStatus = function() {
- // TODO(hidehiko): Reorganize the structure when delete queue is merged
- // into copy task queue.
- var result = {
- // Set to util.FileOperationType if all the running/pending tasks is
- // the same kind of task.
- operationType: null,
-
- // The number of entries to be processed.
- numRemainingItems: 0,
-
- // The total number of bytes to be processed.
- totalBytes: 0,
-
- // The number of bytes.
- processedBytes: 0,
-
- // Available if numRemainingItems == 1. Pointing to an Entry which is
- // begin processed.
- processingEntry: task.getSingleEntry()
- };
-
- var operationType =
- this.copyTasks_.length > 0 ? this.copyTasks_[0].operationType : null;
- var task = null;
- for (var i = 0; i < this.copyTasks_.length; i++) {
- task = this.copyTasks_[i];
- if (task.operationType != operationType)
- operationType = null;
-
- // Assuming the number of entries is small enough, count every time.
- result.numRemainingItems += task.countRemainingItems();
- result.totalBytes += task.totalBytes;
- result.processedBytes += task.processedBytes;
- }
-
- result.operationType = operationType;
- return result;
-};
-
-/**
- * Adds an event listener for the tasks.
- * @param {string} type The name of the event.
- * @param {function(Event)} handler The handler for the event.
- * This is called when the event is dispatched.
- */
-FileOperationManager.prototype.addEventListener = function(type, handler) {
- this.eventRouter_.addEventListener(type, handler);
-};
-
-/**
- * Removes an event listener for the tasks.
- * @param {string} type The name of the event.
- * @param {function(Event)} handler The handler to be removed.
- */
-FileOperationManager.prototype.removeEventListener = function(type, handler) {
- this.eventRouter_.removeEventListener(type, handler);
-};
-
-/**
- * Says if there are any tasks in the queue.
- * @return {boolean} True, if there are any tasks.
- */
-FileOperationManager.prototype.hasQueuedTasks = function() {
- return this.copyTasks_.length > 0 || this.deleteTasks_.length > 0;
-};
-
-/**
- * Unloads the host page in 5 secs of idling. Need to be called
- * each time this.copyTasks_.length or this.deleteTasks_.length
- * changed.
- *
- * @private
- */
-FileOperationManager.prototype.maybeScheduleCloseBackgroundPage_ = function() {
- if (!this.hasQueuedTasks()) {
- if (this.unloadTimeout_ === null)
- this.unloadTimeout_ = setTimeout(maybeCloseBackgroundPage, 5000);
- } else if (this.unloadTimeout_) {
- clearTimeout(this.unloadTimeout_);
- this.unloadTimeout_ = null;
- }
-};
-
-/**
- * Completely clear out the copy queue, either because we encountered an error
- * or completed successfully.
- *
- * @private
- */
-FileOperationManager.prototype.resetQueue_ = function() {
- for (var i = 0; i < this.cancelObservers_.length; i++)
- this.cancelObservers_[i]();
-
- this.copyTasks_ = [];
- this.cancelObservers_ = [];
- this.maybeScheduleCloseBackgroundPage_();
-};
-
-/**
- * Request that the current copy queue be abandoned.
- *
- * @param {function()=} opt_callback On cancel.
- */
-FileOperationManager.prototype.requestCancel = function(opt_callback) {
- this.cancelRequested_ = true;
- if (this.cancelCallback_) {
- this.cancelCallback_();
- this.cancelCallback_ = null;
- }
- if (opt_callback)
- this.cancelObservers_.push(opt_callback);
-
- // If there is any active task it will eventually call maybeCancel_.
- // Otherwise call it right now.
- if (this.copyTasks_.length == 0)
- this.doCancel_();
- else
- this.copyTasks_[0].requestCancel();
-};
-
-/**
- * Requests the specified task to be canceled.
- * @param {string} taskId ID of task to be canceled.
- */
-FileOperationManager.prototype.requestTaskCancel = function(taskId) {
- var task = null;
- for (var i = 0; i < this.copyTasks_.length; i++) {
- if (this.copyTasks_[i].taskId === taskId) {
- this.copyTasks_[i].requestCancel();
- return;
- }
- }
- for (var i = 0; i < this.deleteTasks_.length; i++) {
- if (this.deleteTasks_[i].taskId === taskId) {
- this.deleteTasks_[i].requestCancel();
- return;
- }
- }
-};
-
-/**
- * Perform the bookkeeping required to cancel.
- *
- * @private
- */
-FileOperationManager.prototype.doCancel_ = function() {
- var taskId = this.copyTasks_[0].taskId;
- this.resetQueue_();
- this.cancelRequested_ = false;
- this.eventRouter_.sendProgressEvent('CANCELLED', this.getStatus(), taskId);
-};
-
-/**
- * Used internally to check if a cancel has been requested, and handle
- * it if so.
- *
- * @return {boolean} If canceled.
- * @private
- */
-FileOperationManager.prototype.maybeCancel_ = function() {
- if (!this.cancelRequested_)
- return false;
-
- this.doCancel_();
- return true;
-};
-
-/**
- * Kick off pasting.
- *
- * @param {Array.<string>} sourcePaths Path of the source files.
- * @param {string} targetPath The destination path of the target directory.
- * @param {boolean} isMove True if the operation is "move", otherwise (i.e.
- * if the operation is "copy") false.
- */
-FileOperationManager.prototype.paste = function(
- sourcePaths, targetPath, isMove) {
- // Do nothing if sourcePaths is empty.
- if (sourcePaths.length == 0)
- return;
-
- var errorCallback = function(error) {
- this.eventRouter_.sendProgressEvent(
- 'ERROR',
- this.getStatus(),
- this.generateTaskId_(null),
- new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, error));
- }.bind(this);
-
- var targetEntry = null;
- var entries = [];
-
- // Resolve paths to entries.
- var resolveGroup = new AsyncUtil.Group();
- resolveGroup.add(function(callback) {
- webkitResolveLocalFileSystemURL(
- util.makeFilesystemUrl(targetPath),
- function(entry) {
- if (!entry.isDirectory) {
- // Found a non directory entry.
- errorCallback(util.createFileError(FileError.TYPE_MISMATCH_ERR));
- return;
- }
-
- targetEntry = entry;
- callback();
- },
- errorCallback);
- });
-
- for (var i = 0; i < sourcePaths.length; i++) {
- resolveGroup.add(function(sourcePath, callback) {
- webkitResolveLocalFileSystemURL(
- util.makeFilesystemUrl(sourcePath),
- function(entry) {
- entries.push(entry);
- callback();
- },
- errorCallback);
- }.bind(this, sourcePaths[i]));
- }
-
- resolveGroup.run(function() {
- if (isMove) {
- // Moving to the same directory is a redundant operation.
- entries = entries.filter(function(entry) {
- return targetEntry.fullPath + '/' + entry.name != entry.fullPath;
- });
-
- // Do nothing, if we have no entries to be moved.
- if (entries.length == 0)
- return;
- }
-
- this.queueCopy_(targetEntry, entries, isMove);
- }.bind(this));
-};
-
-/**
- * Checks if the move operation is available between the given two locations.
- *
- * @param {DirectoryEntry} sourceEntry An entry from the source.
- * @param {DirectoryEntry} targetDirEntry Directory entry for the target.
- * @return {boolean} Whether we can move from the source to the target.
- */
-FileOperationManager.prototype.isMovable = function(sourceEntry,
- targetDirEntry) {
- return (PathUtil.isDriveBasedPath(sourceEntry.fullPath) &&
- PathUtil.isDriveBasedPath(targetDirEntry.fullPath)) ||
- (PathUtil.getRootPath(sourceEntry.fullPath) ==
- PathUtil.getRootPath(targetDirEntry.fullPath));
-};
-
-/**
- * Initiate a file copy.
- *
- * @param {DirectoryEntry} targetDirEntry Target directory.
- * @param {Array.<Entry>} entries Entries to copy.
- * @param {boolean} isMove In case of move.
- * @return {FileOperationManager.Task} Copy task.
- * @private
- */
-FileOperationManager.prototype.queueCopy_ = function(
- targetDirEntry, entries, isMove) {
- // When copying files, null can be specified as source directory.
- var task;
- if (isMove) {
- if (this.isMovable(entries[0], targetDirEntry)) {
- task = new FileOperationManager.MoveTask(entries, targetDirEntry);
- } else {
- task = new FileOperationManager.CopyTask(entries, targetDirEntry);
- task.deleteAfterCopy = true;
- }
- } else {
- task = new FileOperationManager.CopyTask(entries, targetDirEntry);
- }
-
- task.taskId = this.generateTaskId_();
- task.initialize(function() {
- this.copyTasks_.push(task);
- this.maybeScheduleCloseBackgroundPage_();
- this.eventRouter_.sendProgressEvent('BEGIN', task.getStatus(), task.taskId);
- if (this.copyTasks_.length == 1) {
- // Assume this.cancelRequested_ == false.
- // This moved us from 0 to 1 active tasks, let the servicing begin!
- this.serviceAllTasks_();
- }
- }.bind(this));
-
- return task;
-};
-
-/**
- * Service all pending tasks, as well as any that might appear during the
- * copy.
- *
- * @private
- */
-FileOperationManager.prototype.serviceAllTasks_ = function() {
- if (!this.copyTasks_.length) {
- // All tasks have been serviced, clean up and exit.
- this.resetQueue_();
- return;
- }
-
- var onTaskProgress = function() {
- this.eventRouter_.sendProgressEvent('PROGRESS',
- this.copyTasks_[0].getStatus(),
- this.copyTasks_[0].taskId);
- }.bind(this);
-
- var onEntryChanged = function(kind, entry) {
- this.eventRouter_.sendEntryChangedEvent(kind, entry);
- }.bind(this);
-
- var onTaskError = function(err) {
- var task = this.copyTasks_.shift();
- if (this.maybeCancel_())
- return;
- this.eventRouter_.sendProgressEvent('ERROR',
- task.getStatus(),
- task.taskId,
- err);
- this.serviceAllTasks_();
- }.bind(this);
-
- var onTaskSuccess = function() {
- if (this.maybeCancel_())
- return;
-
- // The task at the front of the queue is completed. Pop it from the queue.
- var task = this.copyTasks_.shift();
- this.maybeScheduleCloseBackgroundPage_();
- this.eventRouter_.sendProgressEvent('SUCCESS',
- task.getStatus(),
- task.taskId);
- this.serviceAllTasks_();
- }.bind(this);
-
- var nextTask = this.copyTasks_[0];
- this.eventRouter_.sendProgressEvent('PROGRESS',
- nextTask.getStatus(),
- nextTask.taskId);
- nextTask.run(onEntryChanged, onTaskProgress, onTaskSuccess, onTaskError);
-};
-
-/**
- * Timeout before files are really deleted (to allow undo).
- */
-FileOperationManager.DELETE_TIMEOUT = 30 * 1000;
-
-/**
- * Schedules the files deletion.
- *
- * @param {Array.<Entry>} entries The entries.
- */
-FileOperationManager.prototype.deleteEntries = function(entries) {
- var task = {
- entries: entries,
- taskId: this.generateTaskId_()
- };
- this.deleteTasks_.push(task);
- this.eventRouter_.sendDeleteEvent('BEGIN', entries.map(function(entry) {
- return util.makeFilesystemUrl(entry.fullPath);
- }), task.taskId);
- this.maybeScheduleCloseBackgroundPage_();
- if (this.deleteTasks_.length == 1)
- this.serviceAllDeleteTasks_();
-};
-
-/**
- * Service all pending delete tasks, as well as any that might appear during the
- * deletion.
- *
- * Must not be called if there is an in-flight delete task.
- *
- * @private
- */
-FileOperationManager.prototype.serviceAllDeleteTasks_ = function() {
- // Returns the urls of the given task's entries.
- var getTaskUrls = function(task) {
- return task.entries.map(function(entry) {
- return util.makeFilesystemUrl(entry.fullPath);
- });
- };
-
- var onTaskSuccess = function() {
- var urls = getTaskUrls(this.deleteTasks_[0]);
- var taskId = this.deleteTasks_[0].taskId;
- this.deleteTasks_.shift();
- this.eventRouter_.sendDeleteEvent('SUCCESS', urls, taskId);
-
- if (!this.deleteTasks_.length) {
- // All tasks have been serviced, clean up and exit.
- this.maybeScheduleCloseBackgroundPage_();
- return;
- }
-
- var nextTask = this.deleteTasks_[0];
- this.eventRouter_.sendDeleteEvent('PROGRESS',
- urls,
- nextTask.taskId);
- this.serviceDeleteTask_(nextTask, onTaskSuccess, onTaskFailure);
- }.bind(this);
-
- var onTaskFailure = function(error) {
- var urls = getTaskUrls(this.deleteTasks_[0]);
- var taskId = this.deleteTasks_[0].taskId;
- this.deleteTasks_ = [];
- this.eventRouter_.sendDeleteEvent('ERROR',
- urls,
- taskId);
- this.maybeScheduleCloseBackgroundPage_();
- }.bind(this);
-
- this.serviceDeleteTask_(this.deleteTasks_[0], onTaskSuccess, onTaskFailure);
-};
-
-/**
- * Performs the deletion.
- *
- * @param {Object} task The delete task (see deleteEntries function).
- * @param {function()} successCallback Callback run on success.
- * @param {function(FileOperationManager.Error)} errorCallback Callback run on
- * error.
- * @private
- */
-FileOperationManager.prototype.serviceDeleteTask_ = function(
- task, successCallback, errorCallback) {
- var downcount = task.entries.length;
- if (downcount == 0) {
- successCallback();
- return;
- }
-
- var filesystemError = null;
- var onComplete = function() {
- if (--downcount > 0)
- return;
-
- // All remove operations are processed. Run callback.
- if (filesystemError) {
- errorCallback(new FileOperationManager.Error(
- util.FileOperationErrorType.FILESYSTEM_ERROR, filesystemError));
- } else {
- successCallback();
- }
- };
-
- for (var i = 0; i < task.entries.length; i++) {
- var entry = task.entries[i];
- util.removeFileOrDirectory(
- entry,
- function(currentEntry) {
- this.eventRouter_.sendEntryChangedEvent(
- util.EntryChangedKind.DELETED, currentEntry);
- onComplete();
- }.bind(this, entry),
- function(error) {
- if (!filesystemError)
- filesystemError = error;
- onComplete();
- });
- }
-};
-
-/**
- * Creates a zip file for the selection of files.
- *
- * @param {Entry} dirEntry The directory containing the selection.
- * @param {Array.<Entry>} selectionEntries The selected entries.
- */
-FileOperationManager.prototype.zipSelection = function(
- dirEntry, selectionEntries) {
- var zipTask = new FileOperationManager.ZipTask(
- selectionEntries, dirEntry, dirEntry);
- zipTask.taskId = this.generateTaskId_(this.copyTasks_);
- zipTask.zip = true;
- zipTask.initialize(function() {
- this.copyTasks_.push(zipTask);
- this.eventRouter_.sendProgressEvent('BEGIN',
- zipTask.getStatus(),
- zipTask.taskId);
- if (this.copyTasks_.length == 1) {
- // Assume this.cancelRequested_ == false.
- // This moved us from 0 to 1 active tasks, let the servicing begin!
- this.serviceAllTasks_();
- }
- }.bind(this));
-};
-
-/**
- * Generates new task ID.
- *
- * @return {string} New task ID.
- * @private
- */
-FileOperationManager.prototype.generateTaskId_ = function() {
- return 'file-operation-' + this.taskIdCounter_++;
-};

Powered by Google App Engine
This is Rietveld 408576698