blob: f374a3d304e3432c8ab5642431744416e5161b0f [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// Copyright (c) 2012 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00005'use strict';
6
Torne (Richard Coles)58218062012-11-14 11:43:16 +00007/**
Ben Murdoch32409262013-08-07 11:04:47 +01008 * Utilities for FileCopyManager.
9 */
10var fileOperationUtil = {};
11
12/**
13 * Simple wrapper for util.deduplicatePath. On error, this method translates
14 * the FileError to FileCopyManager.Error object.
15 *
16 * @param {DirectoryEntry} dirEntry The target directory entry.
17 * @param {string} relativePath The path to be deduplicated.
18 * @param {function(string)} successCallback Callback run with the deduplicated
19 * path on success.
20 * @param {function(FileCopyManager.Error)} errorCallback Callback run on error.
21 */
22fileOperationUtil.deduplicatePath = function(
23 dirEntry, relativePath, successCallback, errorCallback) {
24 util.deduplicatePath(
25 dirEntry, relativePath, successCallback,
26 function(err) {
27 var onFileSystemError = function(error) {
28 errorCallback(new FileCopyManager.Error(
29 util.FileOperationErrorType.FILESYSTEM_ERROR, error));
30 };
31
32 if (err.code == FileError.PATH_EXISTS_ERR) {
33 // Failed to uniquify the file path. There should be an existing
34 // entry, so return the error with it.
35 util.resolvePath(
36 dirEntry, relativePath,
37 function(entry) {
38 errorCallback(new FileCopyManager.Error(
39 util.FileOperationErrorType.TARGET_EXISTS, entry));
40 },
41 onFileSystemError);
42 return;
43 }
44 onFileSystemError(err);
45 });
46};
47
48/**
49 * Sets last modified date to the entry.
50 * @param {Entry} entry The entry to which the last modified is set.
51 * @param {Date} modificationTime The last modified time.
52 */
53fileOperationUtil.setLastModified = function(entry, modificationTime) {
54 chrome.fileBrowserPrivate.setLastModified(
55 entry.toURL(), '' + Math.round(modificationTime.getTime() / 1000));
56};
57
58/**
Ben Murdochbb1529c2013-08-08 10:24:53 +010059 * Copies a file from source to the parent directory with newName.
60 * See also copyFileByStream_ and copyFileOnDrive_ for the implementation
61 * details.
62 *
63 * @param {FileEntry} source The file entry to be copied.
64 * @param {DirectoryEntry} parent The entry of the destination directory.
65 * @param {string} newName The name of copied file.
66 * @param {function(FileEntry, number)} progressCallback Callback invoked
67 * periodically during the file writing. It takes source and the number of
68 * copied bytes since the last invocation. This is also called just before
69 * starting the operation (with '0' bytes) and just after the finishing the
70 * operation (with the total copied size).
71 * @param {function(FileEntry)} successCallback Callback invoked when the copy
72 * is successfully done with the entry of the created file.
73 * @param {function(FileError)} errorCallback Callback invoked when an error
74 * is found.
75 * @return {function()} Callback to cancle the current file copy operation.
76 * When the cancel is done, errorCallback will be called. The returned
77 * callback must not be called more than once.
78 */
79fileOperationUtil.copyFile = function(
80 source, parent, newName, progressCallback, successCallback, errorCallback) {
81 if (!PathUtil.isDriveBasedPath(source.fullPath) &&
82 !PathUtil.isDriveBasedPath(parent.fullPath)) {
83 // Copying a file between non-Drive file systems.
84 return fileOperationUtil.copyFileByStream_(
85 source, parent, newName, progressCallback, successCallback,
86 errorCallback);
87 } else {
88 // Copying related to the Drive file system.
89 return fileOperationUtil.copyFileOnDrive_(
90 source, parent, newName, progressCallback, successCallback,
91 errorCallback);
92 }
93};
94
95/**
96 * Copies a file by using File and FileWriter objects.
97 *
98 * This is a js-implementation of FileEntry.copyTo(). Unfortunately, copyTo
99 * doesn't support periodical progress updating nor cancelling. To support
100 * these operations, this method implements copyTo by streaming way in
101 * JavaScript.
102 *
103 * Note that this is designed for file copying on local file system. We have
104 * some special cases about copying on Drive file system. See also
105 * copyFileOnDrive_() for more details.
106 *
107 * @param {FileEntry} source The file entry to be copied.
108 * @param {DirectoryEntry} parent The entry of the destination directory.
109 * @param {string} newName The name of copied file.
110 * @param {function(FileEntry, number)} progressCallback Callback invoked
111 * periodically during the file writing. It takes source and the number of
112 * copied bytes since the last invocation. This is also called just before
113 * starting the operation (with '0' bytes) and just after the finishing the
114 * operation (with the total copied size).
115 * @param {function(FileEntry)} successCallback Callback invoked when the copy
116 * is successfully done with the entry of the created file.
117 * @param {function(FileError)} errorCallback Callback invoked when an error
118 * is found.
119 * @return {function()} Callback to cancel the current file copy operation.
120 * When the cancel is done, errorCallback will be called. The returned
121 * callback must not be called more than once.
122 * @private
123 */
124fileOperationUtil.copyFileByStream_ = function(
125 source, parent, newName, progressCallback, successCallback, errorCallback) {
126 // Set to true when cancel is requested.
127 var cancelRequested = false;
128
129 source.file(function(file) {
130 if (cancelRequested) {
131 errorCallback(util.createFileError(FileError.ABORT_ERR));
132 return;
133 }
134
135 parent.getFile(newName, {create: true, exclusive: true}, function(target) {
136 if (cancelRequested) {
137 errorCallback(util.createFileError(FileError.ABORT_ERR));
138 return;
139 }
140
141 target.createWriter(function(writer) {
142 if (cancelRequested) {
143 errorCallback(util.createFileError(FileError.ABORT_ERR));
144 return;
145 }
146
147 writer.onerror = writer.onabort = function(progress) {
148 errorCallback(cancelRequested ?
149 util.createFileError(FileError.ABORT_ERR) :
150 writer.error);
151 };
152
153 var reportedProgress = 0;
154 writer.onprogress = function(progress) {
155 if (cancelRequested) {
156 // If the copy was cancelled, we should abort the operation.
157 // The errorCallback will be called by writer.onabort after the
158 // termination.
159 writer.abort();
160 return;
161 }
162
163 // |progress.loaded| will contain total amount of data copied by now.
164 // |progressCallback| expects data amount delta from the last progress
165 // update.
166 progressCallback(target, progress.loaded - reportedProgress);
167 reportedProgress = progress.loaded;
168 };
169
170 writer.onwrite = function() {
171 if (cancelRequested) {
172 errorCallback(util.createFileError(FileError.ABORT_ERR));
173 return;
174 }
175
176 source.getMetadata(function(metadata) {
177 if (cancelRequested) {
178 errorCallback(util.createFileError(FileError.ABORT_ERR));
179 return;
180 }
181
182 fileOperationUtil.setLastModified(
183 target, metadata.modificationTime);
184 successCallback(target);
185 }, errorCallback);
186 };
187
188 writer.write(file);
189 }, errorCallback);
190 }, errorCallback);
191 }, errorCallback);
192
193 return function() { cancelRequested = true; };
194};
195
196/**
Ben Murdoch32409262013-08-07 11:04:47 +0100197 * Copies a file a) from Drive to local, b) from local to Drive, or c) from
198 * Drive to Drive.
199 * Currently, we need to take care about following two things for Drive:
200 *
201 * 1) Copying hosted document.
202 * In theory, it is impossible to actual copy a hosted document to other
203 * file system. Thus, instead, Drive file system backend creates a JSON file
204 * referring to the hosted document. Also, when it is uploaded by copyTo,
205 * the hosted document is copied on the server. Note that, this doesn't work
206 * when a user creates a file by FileWriter (as copyFileEntry_ does).
207 *
208 * 2) File transfer between local and Drive server.
209 * There are two directions of file transfer; from local to Drive and from
210 * Drive to local.
211 * The file transfer from local to Drive is done as a part of file system
212 * background sync (kicked after the copy operation is done). So we don't need
213 * to take care about it here. To copy the file from Drive to local (or Drive
214 * to Drive with GData WAPI), we need to download the file content (if it is
215 * not locally cached). During the downloading, we can listen the periodical
216 * updating and cancel the downloding via private API.
217 *
218 * This function supports progress updating and cancelling partially.
219 * Unfortunately, FileEntry.copyTo doesn't support progress updating nor
220 * cancelling, so we support them only during file downloading.
221 *
222 * Note: we're planning to move copyTo logic into c++ side. crbug.com/261492
223 *
224 * @param {FileEntry} source The entry of the file to be copied.
225 * @param {DirectoryEntry} parent The entry of the destination directory.
226 * @param {string} newName The name of the copied file.
227 * @param {function(FileEntry, number)} progressCallback Callback periodically
228 * invoked during file transfer with the source and the number of
229 * transferred bytes from the last call.
230 * @param {function(FileEntry)} successCallback Callback invoked when the
231 * file copy is successfully done with the entry of the copied file.
232 * @param {function(FileError)} errorCallback Callback invoked when an error
233 * is found.
234 * @return {function()} Callback to cancel the current file copy operation.
235 * When the cancel is done, errorCallback will be called. The returned
236 * callback must not be called more than once.
Ben Murdochbb1529c2013-08-08 10:24:53 +0100237 * @private
Ben Murdoch32409262013-08-07 11:04:47 +0100238 */
Ben Murdochbb1529c2013-08-08 10:24:53 +0100239fileOperationUtil.copyFileOnDrive_ = function(
Ben Murdoch32409262013-08-07 11:04:47 +0100240 source, parent, newName, progressCallback, successCallback, errorCallback) {
241 // Set to true when cancel is requested.
242 var cancelRequested = false;
243 var cancelCallback = null;
244
245 var onCopyToCompleted = null;
246
247 // Progress callback.
248 // Because the uploading the file from local cache to Drive server will be
249 // done as a part of background Drive file system sync, so for this copy
250 // operation, what we need to take care about is only file downloading.
251 var numTransferredBytes = 0;
252 if (PathUtil.isDriveBasedPath(source.fullPath)) {
253 var sourceUrl = source.toURL();
254 var sourcePath = util.extractFilePath(sourceUrl);
255 var onFileTransfersUpdated = function(statusList) {
256 for (var i = 0; i < statusList.length; i++) {
257 var status = statusList[i];
258
259 // Comparing urls is unreliable, since they may use different
260 // url encoding schemes (eg. rfc2396 vs. rfc3986).
261 var filePath = util.extractFilePath(status.fileUrl);
262 if (filePath == sourcePath) {
263 var processed = status.processed;
264 if (processed > numTransferredBytes) {
265 progressCallback(source, processed - numTransferredBytes);
266 numTransferredBytes = processed;
267 }
268 return;
269 }
270 }
271 };
272
273 // Subscribe to listen file transfer updating notifications.
274 chrome.fileBrowserPrivate.onFileTransfersUpdated.addListener(
275 onFileTransfersUpdated);
276
277 // Currently, we do NOT upload the file during the copy operation.
278 // It will be done as a part of file system sync after copy operation.
279 // So, we can cancel only file downloading.
280 cancelCallback = function() {
281 chrome.fileBrowserPrivate.cancelFileTransfers(
282 [sourceUrl], function() {});
283 };
284
285 // We need to clean up on copyTo completion regardless if it is
286 // successfully done or not.
287 onCopyToCompleted = function() {
288 cancelCallback = null;
289 chrome.fileBrowserPrivate.onFileTransfersUpdated.removeListener(
290 onFileTransfersUpdated);
291 };
292 }
293
294 source.copyTo(
295 parent, newName,
296 function(entry) {
297 if (onCopyToCompleted)
298 onCopyToCompleted();
299
300 if (cancelRequested) {
301 errorCallback(util.createFileError(FileError.ABORT_ERR));
302 return;
303 }
304
305 entry.getMetadata(function(metadata) {
306 if (metadata.size > numTransferredBytes)
307 progressCallback(source, metadata.size - numTransferredBytes);
308 successCallback(entry);
309 }, errorCallback);
310 },
311 function(error) {
312 if (onCopyToCompleted)
313 onCopyToCompleted();
314
315 errorCallback(error);
316 });
317
318 return function() {
319 cancelRequested = true;
320 if (cancelCallback) {
321 cancelCallback();
322 cancelCallback = null;
323 }
324 };
325};
326
327/**
328 * Thin wrapper of chrome.fileBrowserPrivate.zipSelection to adapt its
329 * interface similar to copyTo().
330 *
331 * @param {Array.<Entry>} sources The array of entries to be archived.
332 * @param {DirectoryEntry} parent The entry of the destination directory.
333 * @param {string} newName The name of the archive to be created.
334 * @param {function(FileEntry)} successCallback Callback invoked when the
335 * operation is successfully done with the entry of the created archive.
336 * @param {function(FileError)} errorCallback Callback invoked when an error
337 * is found.
338 */
339fileOperationUtil.zipSelection = function(
340 sources, parent, newName, successCallback, errorCallback) {
341 chrome.fileBrowserPrivate.zipSelection(
342 parent.toURL(),
343 sources.map(function(e) { return e.toURL(); }),
344 newName, function(success) {
345 if (!success) {
346 // Failed to create a zip archive.
347 errorCallback(
348 util.createFileError(FileError.INVALID_MODIFICATION_ERR));
349 return;
350 }
351
352 // Returns the created entry via callback.
353 parent.getFile(
354 newName, {create: false}, successCallback, errorCallback);
355 });
356};
357
358/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000359 * @constructor
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000360 */
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100361function FileCopyManager() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000362 this.copyTasks_ = [];
363 this.deleteTasks_ = [];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000364 this.cancelObservers_ = [];
365 this.cancelRequested_ = false;
366 this.cancelCallback_ = null;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000367 this.unloadTimeout_ = null;
Ben Murdochd3868032013-07-31 10:55:33 +0100368
369 this.eventRouter_ = new FileCopyManager.EventRouter();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000370}
371
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000372/**
373 * Get FileCopyManager instance. In case is hasn't been initialized, a new
374 * instance is created.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000375 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000376 * @return {FileCopyManager} A FileCopyManager instance.
377 */
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100378FileCopyManager.getInstance = function() {
379 if (!FileCopyManager.instance_)
380 FileCopyManager.instance_ = new FileCopyManager();
381
382 return FileCopyManager.instance_;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000383};
384
385/**
Ben Murdochd3868032013-07-31 10:55:33 +0100386 * Manages cr.Event dispatching.
387 * Currently this can send three types of events: "copy-progress",
388 * "copy-operation-completed" and "delete".
389 *
390 * TODO(hidehiko): Reorganize the event dispatching mechanism.
391 * @constructor
392 * @extends {cr.EventTarget}
393 */
394FileCopyManager.EventRouter = function() {
395};
396
397/**
398 * Extends cr.EventTarget.
399 */
400FileCopyManager.EventRouter.prototype.__proto__ = cr.EventTarget.prototype;
401
402/**
403 * Dispatches a simple "copy-progress" event with reason and current
404 * FileCopyManager status. If it is an ERROR event, error should be set.
405 *
406 * @param {string} reason Event type. One of "BEGIN", "PROGRESS", "SUCCESS",
407 * "ERROR" or "CANCELLED". TODO(hidehiko): Use enum.
408 * @param {Object} status Current FileCopyManager's status. See also
409 * FileCopyManager.getStatus().
410 * @param {FileCopyManager.Error=} opt_error The info for the error. This
411 * should be set iff the reason is "ERROR".
412 */
413FileCopyManager.EventRouter.prototype.sendProgressEvent = function(
414 reason, status, opt_error) {
415 var event = new cr.Event('copy-progress');
416 event.reason = reason;
417 event.status = status;
418 if (opt_error)
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100419 event.error = opt_error;
Ben Murdochd3868032013-07-31 10:55:33 +0100420 this.dispatchEvent(event);
421};
422
423/**
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100424 * Dispatches an event to notify that an entry is changed (created or deleted).
425 * @param {util.EntryChangedType} type The enum to represent if the entry
426 * is created or deleted.
427 * @param {Entry} entry The changed entry.
Ben Murdochd3868032013-07-31 10:55:33 +0100428 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100429FileCopyManager.EventRouter.prototype.sendEntryChangedEvent = function(
430 type, entry) {
431 var event = new cr.Event('entry-changed');
432 event.type = type;
433 event.entry = entry;
Ben Murdochd3868032013-07-31 10:55:33 +0100434 this.dispatchEvent(event);
435};
436
437/**
438 * Dispatches an event to notify entries are changed for delete task.
439 *
440 * @param {string} reason Event type. One of "BEGIN", "PROGRESS", "SUCCESS",
441 * or "ERROR". TODO(hidehiko): Use enum.
442 * @param {Array.<string>} urls An array of URLs which are affected by delete
443 * operation.
444 */
445FileCopyManager.EventRouter.prototype.sendDeleteEvent = function(
446 reason, urls) {
447 var event = new cr.Event('delete');
448 event.reason = reason;
449 event.urls = urls;
450 this.dispatchEvent(event);
451};
452
453/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000454 * A record of a queued copy operation.
455 *
456 * Multiple copy operations may be queued at any given time. Additional
457 * Tasks may be added while the queue is being serviced. Though a
458 * cancel operation cancels everything in the queue.
459 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000460 * @param {DirectoryEntry} targetDirEntry Target directory.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100461 * @param {DirectoryEntry=} opt_zipBaseDirEntry Base directory dealt as a root
462 * in ZIP archive.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000463 * @constructor
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000464 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100465FileCopyManager.Task = function(targetDirEntry, opt_zipBaseDirEntry) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000466 this.targetDirEntry = targetDirEntry;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100467 this.zipBaseDirEntry = opt_zipBaseDirEntry;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000468 this.originalEntries = null;
469
Ben Murdochbb1529c2013-08-08 10:24:53 +0100470 // TODO(hidehiko): When we support recursive copy, we should be able to
471 // rely on originalEntries. Then remove this.
472 this.entries = [];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000473
Ben Murdochbb1529c2013-08-08 10:24:53 +0100474 /**
475 * The index of entries being processed. The entries should be processed
476 * from 0, so this is also the number of completed entries.
477 * @type {number}
478 */
479 this.entryIndex = 0;
480 this.totalBytes = 0;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000481 this.completedBytes = 0;
482
483 this.deleteAfterCopy = false;
484 this.move = false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000485 this.zip = false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000486
Ben Murdochbb1529c2013-08-08 10:24:53 +0100487 // TODO(hidehiko): After we support recursive copy, we don't need this.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000488 // If directory already exists, we try to make a copy named 'dir (X)',
489 // where X is a number. When we do this, all subsequent copies from
490 // inside the subtree should be mapped to the new directory name.
491 // For example, if 'dir' was copied as 'dir (1)', then 'dir\file.txt' should
492 // become 'dir (1)\file.txt'.
493 this.renamedDirectories_ = [];
494};
495
Ben Murdoch558790d2013-07-30 15:19:42 +0100496/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000497 * @param {Array.<Entry>} entries Entries.
Ben Murdoch558790d2013-07-30 15:19:42 +0100498 * @param {function()} callback When entries resolved.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000499 */
500FileCopyManager.Task.prototype.setEntries = function(entries, callback) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000501 this.originalEntries = entries;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100502
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000503 // When moving directories, FileEntry.moveTo() is used if both source
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000504 // and target are on Drive. There is no need to recurse into directories.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100505 util.recurseAndResolveEntries(entries, !this.move, function(result) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100506 if (this.move) {
507 // This may be moving from search results, where it fails if we move
508 // parent entries earlier than child entries. We should process the
509 // deepest entry first. Since move of each entry is done by a single
510 // moveTo() call, we don't need to care about the recursive traversal
511 // order.
512 this.entries = result.dirEntries.concat(result.fileEntries).sort(
513 function(entry1, entry2) {
514 return entry2.fullPath.length - entry1.fullPath.length;
515 });
516 } else {
517 // Copying tasks are recursively processed. So, directories must be
518 // processed earlier than their child files. Since
519 // util.recurseAndResolveEntries is already listing entries in the
520 // recursive traversal order, we just keep the ordering.
521 this.entries = result.dirEntries.concat(result.fileEntries);
522 }
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100523
Ben Murdochbb1529c2013-08-08 10:24:53 +0100524 this.totalBytes = result.fileBytes;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100525 callback();
Ben Murdochbb1529c2013-08-08 10:24:53 +0100526 }.bind(this));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000527};
528
529/**
530 * Updates copy progress status for the entry.
531 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000532 * @param {number} size Number of bytes that has been copied since last update.
533 */
Ben Murdochbb1529c2013-08-08 10:24:53 +0100534FileCopyManager.Task.prototype.updateFileCopyProgress = function(size) {
535 this.completedBytes += size;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000536};
537
538/**
539 * @param {string} fromName Old name.
540 * @param {string} toName New name.
541 */
542FileCopyManager.Task.prototype.registerRename = function(fromName, toName) {
543 this.renamedDirectories_.push({from: fromName + '/', to: toName + '/'});
544};
545
546/**
547 * @param {string} path A path.
548 * @return {string} Path after renames.
549 */
550FileCopyManager.Task.prototype.applyRenames = function(path) {
551 // Directories are processed in pre-order, so we will store only the first
552 // renaming point:
553 // x -> x (1) -- new directory created.
554 // x\y -> x (1)\y -- no more renames inside the new directory, so
555 // this one will not be stored.
556 // x\y\a.txt -- only one rename will be applied.
557 for (var index = 0; index < this.renamedDirectories_.length; ++index) {
558 var rename = this.renamedDirectories_[index];
559 if (path.indexOf(rename.from) == 0) {
560 path = rename.to + path.substr(rename.from.length);
561 }
562 }
563 return path;
564};
565
566/**
567 * Error class used to report problems with a copy operation.
Ben Murdoch558790d2013-07-30 15:19:42 +0100568 * If the code is UNEXPECTED_SOURCE_FILE, data should be a path of the file.
569 * If the code is TARGET_EXISTS, data should be the existing Entry.
570 * If the code is FILESYSTEM_ERROR, data should be the FileError.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000571 *
Ben Murdoch558790d2013-07-30 15:19:42 +0100572 * @param {util.FileOperationErrorType} code Error type.
573 * @param {string|Entry|FileError} data Additional data.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000574 * @constructor
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000575 */
Ben Murdoch558790d2013-07-30 15:19:42 +0100576FileCopyManager.Error = function(code, data) {
577 this.code = code;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000578 this.data = data;
579};
580
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000581// FileCopyManager methods.
582
583/**
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100584 * Initializes the filesystem if it is not done yet.
585 * @param {function()} callback Completion callback.
586 */
587FileCopyManager.prototype.initialize = function(callback) {
588 // Already initialized.
589 if (this.root_) {
590 callback();
591 return;
592 }
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100593 chrome.fileBrowserPrivate.requestFileSystem(function(filesystem) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100594 this.root_ = filesystem.root;
595 callback();
596 }.bind(this));
597};
598
599/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000600 * Called before a new method is run in the manager. Prepares the manager's
601 * state for running a new method.
602 */
603FileCopyManager.prototype.willRunNewMethod = function() {
604 // Cancel any pending close actions so the file copy manager doesn't go away.
605 if (this.unloadTimeout_)
606 clearTimeout(this.unloadTimeout_);
607 this.unloadTimeout_ = null;
608};
609
610/**
611 * @return {Object} Status object.
612 */
613FileCopyManager.prototype.getStatus = function() {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100614 // TODO(hidehiko): Reorganize the structure when delete queue is merged
615 // into copy task queue.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000616 var rv = {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100617 totalItems: 0,
618 completedItems: 0,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000619
Ben Murdochbb1529c2013-08-08 10:24:53 +0100620 totalBytes: 0,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000621 completedBytes: 0,
622
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000623 pendingCopies: 0,
624 pendingMoves: 0,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000625 pendingZips: 0,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100626
627 // In case the number of the incompleted entry is exactly one.
628 filename: '',
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000629 };
630
Ben Murdochbb1529c2013-08-08 10:24:53 +0100631 var pendingEntry = null;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000632 for (var i = 0; i < this.copyTasks_.length; i++) {
633 var task = this.copyTasks_[i];
Ben Murdochbb1529c2013-08-08 10:24:53 +0100634 rv.totalItems += task.entries.length;
635 rv.completedItems += task.entryIndex;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000636
Ben Murdochbb1529c2013-08-08 10:24:53 +0100637 rv.totalBytes += task.totalBytes;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000638 rv.completedBytes += task.completedBytes;
639
Ben Murdochbb1529c2013-08-08 10:24:53 +0100640 var numPendingEntries = task.entries.length - task.entryIndex;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000641 if (task.zip) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100642 rv.pendingZips += numPendingEntries;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000643 } else if (task.move || task.deleteAfterCopy) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100644 rv.pendingMoves += numPendingEntries;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000645 } else {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100646 rv.pendingCopies += numPendingEntries;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000647 }
648
Ben Murdochbb1529c2013-08-08 10:24:53 +0100649 if (numPendingEntries == 1)
650 pendingEntry = task.entries[task.entries.length - 1];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000651 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000652
Ben Murdochbb1529c2013-08-08 10:24:53 +0100653 if (rv.totalItems - rv.completedItems == 1 && pendingEntry)
654 rv.filename = pendingEntry.name;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000655
656 return rv;
657};
658
659/**
Ben Murdochd3868032013-07-31 10:55:33 +0100660 * Adds an event listener for the tasks.
661 * @param {string} type The name of the event.
662 * @param {function(cr.Event)} handler The handler for the event.
663 * This is called when the event is dispatched.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000664 */
Ben Murdochd3868032013-07-31 10:55:33 +0100665FileCopyManager.prototype.addEventListener = function(type, handler) {
666 this.eventRouter_.addEventListener(type, handler);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000667};
668
669/**
Ben Murdochd3868032013-07-31 10:55:33 +0100670 * Removes an event listener for the tasks.
671 * @param {string} type The name of the event.
672 * @param {function(cr.Event)} handler The handler to be removed.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100673 */
Ben Murdochd3868032013-07-31 10:55:33 +0100674FileCopyManager.prototype.removeEventListener = function(type, handler) {
675 this.eventRouter_.removeEventListener(type, handler);
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100676};
677
678/**
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100679 * Says if there are any tasks in the queue.
680 * @return {boolean} True, if there are any tasks.
681 */
682FileCopyManager.prototype.hasQueuedTasks = function() {
683 return this.copyTasks_.length > 0 || this.deleteTasks_.length > 0;
684};
685
686/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000687 * Unloads the host page in 5 secs of idleing. Need to be called
688 * each time this.copyTasks_.length or this.deleteTasks_.length
689 * changed.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000690 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000691 * @private
692 */
693FileCopyManager.prototype.maybeScheduleCloseBackgroundPage_ = function() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100694 if (!this.hasQueuedTasks()) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100695 if (this.unloadTimeout_ === null)
696 this.unloadTimeout_ = setTimeout(maybeCloseBackgroundPage, 5000);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000697 } else if (this.unloadTimeout_) {
698 clearTimeout(this.unloadTimeout_);
699 this.unloadTimeout_ = null;
700 }
701};
702
703/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000704 * Completely clear out the copy queue, either because we encountered an error
705 * or completed successfully.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000706 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000707 * @private
708 */
709FileCopyManager.prototype.resetQueue_ = function() {
710 for (var i = 0; i < this.cancelObservers_.length; i++)
711 this.cancelObservers_[i]();
712
713 this.copyTasks_ = [];
714 this.cancelObservers_ = [];
715 this.maybeScheduleCloseBackgroundPage_();
716};
717
718/**
719 * Request that the current copy queue be abandoned.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000720 *
Ben Murdoch558790d2013-07-30 15:19:42 +0100721 * @param {function()=} opt_callback On cancel.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000722 */
723FileCopyManager.prototype.requestCancel = function(opt_callback) {
724 this.cancelRequested_ = true;
Ben Murdoch2385ea32013-08-06 11:01:04 +0100725 if (this.cancelCallback_) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000726 this.cancelCallback_();
Ben Murdoch2385ea32013-08-06 11:01:04 +0100727 this.cancelCallback_ = null;
728 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000729 if (opt_callback)
730 this.cancelObservers_.push(opt_callback);
731
732 // If there is any active task it will eventually call maybeCancel_.
733 // Otherwise call it right now.
734 if (this.copyTasks_.length == 0)
735 this.doCancel_();
736};
737
738/**
739 * Perform the bookkeeping required to cancel.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000740 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000741 * @private
742 */
743FileCopyManager.prototype.doCancel_ = function() {
744 this.resetQueue_();
745 this.cancelRequested_ = false;
Ben Murdochd3868032013-07-31 10:55:33 +0100746 this.eventRouter_.sendProgressEvent('CANCELLED', this.getStatus());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000747};
748
749/**
750 * Used internally to check if a cancel has been requested, and handle
751 * it if so.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000752 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000753 * @return {boolean} If canceled.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000754 * @private
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000755 */
756FileCopyManager.prototype.maybeCancel_ = function() {
757 if (!this.cancelRequested_)
758 return false;
759
760 this.doCancel_();
761 return true;
762};
763
764/**
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100765 * Kick off pasting.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000766 *
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100767 * @param {Array.<string>} files Pathes of source files.
768 * @param {Array.<string>} directories Pathes of source directories.
769 * @param {boolean} isCut If the source items are removed from original
770 * location.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000771 * @param {string} targetPath Target path.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000772 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100773FileCopyManager.prototype.paste = function(
774 files, directories, isCut, targetPath) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000775 var self = this;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100776 var entries = [];
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100777 var added = 0;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100778 var total;
779
780 var steps = {
781 start: function() {
782 // Filter entries.
783 var entryFilterFunc = function(entry) {
784 if (entry == '')
785 return false;
786 if (isCut && entry.replace(/\/[^\/]+$/, '') == targetPath)
787 // Moving to the same directory is a redundant operation.
788 return false;
789 return true;
790 };
791 directories = directories ? directories.filter(entryFilterFunc) : [];
792 files = files ? files.filter(entryFilterFunc) : [];
793
794 // Check the number of filtered entries.
795 total = directories.length + files.length;
796 if (total == 0)
797 return;
798
799 // Retrieve entries.
800 util.getDirectories(self.root_, {create: false}, directories,
801 steps.onEntryFound, steps.onPathError);
802 util.getFiles(self.root_, {create: false}, files,
803 steps.onEntryFound, steps.onPathError);
804 },
805
806 onEntryFound: function(entry) {
807 // When getDirectories/getFiles finish, they call addEntry with null.
808 // We don't want to add null to our entries.
809 if (entry == null)
810 return;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100811 entries.push(entry);
812 added++;
813 if (added == total)
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100814 steps.onSourceEntriesFound();
815 },
816
817 onSourceEntriesFound: function() {
818 self.root_.getDirectory(targetPath, {},
819 steps.onTargetEntryFound, steps.onPathError);
820 },
821
822 onTargetEntryFound: function(targetEntry) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100823 self.queueCopy_(targetEntry, entries, isCut);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100824 },
825
826 onPathError: function(err) {
Ben Murdochd3868032013-07-31 10:55:33 +0100827 self.eventRouter_.sendProgressEvent(
Ben Murdoch558790d2013-07-30 15:19:42 +0100828 'ERROR',
Ben Murdochd3868032013-07-31 10:55:33 +0100829 self.getStatus(),
Ben Murdoch558790d2013-07-30 15:19:42 +0100830 new FileCopyManager.Error(
831 util.FileOperationErrorType.FILESYSTEM_ERROR, err));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100832 }
833 };
834
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100835 steps.start();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000836};
837
838/**
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100839 * Checks if the move operation is avaiable between the given two locations.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000840 *
841 * @param {DirectoryEntry} sourceEntry An entry from the source.
842 * @param {DirectoryEntry} targetDirEntry Directory entry for the target.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100843 * @return {boolean} Whether we can move from the source to the target.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000844 */
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100845FileCopyManager.prototype.isMovable = function(sourceEntry,
846 targetDirEntry) {
847 return (PathUtil.isDriveBasedPath(sourceEntry.fullPath) &&
848 PathUtil.isDriveBasedPath(targetDirEntry.fullPath)) ||
849 (PathUtil.getRootPath(sourceEntry.fullPath) ==
850 PathUtil.getRootPath(targetDirEntry.fullPath));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000851};
852
853/**
854 * Initiate a file copy.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000855 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000856 * @param {DirectoryEntry} targetDirEntry Target directory.
857 * @param {Array.<Entry>} entries Entries to copy.
858 * @param {boolean} deleteAfterCopy In case of move.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000859 * @return {FileCopyManager.Task} Copy task.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100860 * @private
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000861 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100862FileCopyManager.prototype.queueCopy_ = function(
863 targetDirEntry, entries, deleteAfterCopy) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000864 var self = this;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100865 // When copying files, null can be specified as source directory.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100866 var copyTask = new FileCopyManager.Task(targetDirEntry);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000867 if (deleteAfterCopy) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100868 if (this.isMovable(entries[0], targetDirEntry)) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000869 copyTask.move = true;
870 } else {
871 copyTask.deleteAfterCopy = true;
872 }
873 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000874 copyTask.setEntries(entries, function() {
875 self.copyTasks_.push(copyTask);
876 self.maybeScheduleCloseBackgroundPage_();
877 if (self.copyTasks_.length == 1) {
878 // Assume self.cancelRequested_ == false.
879 // This moved us from 0 to 1 active tasks, let the servicing begin!
880 self.serviceAllTasks_();
881 } else {
882 // Force to update the progress of butter bar when there are new tasks
883 // coming while servicing current task.
Ben Murdochd3868032013-07-31 10:55:33 +0100884 self.eventRouter_.sendProgressEvent('PROGRESS', self.getStatus());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000885 }
886 });
887
888 return copyTask;
889};
890
891/**
892 * Service all pending tasks, as well as any that might appear during the
893 * copy.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000894 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000895 * @private
896 */
897FileCopyManager.prototype.serviceAllTasks_ = function() {
898 var self = this;
899
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100900 var onTaskProgress = function() {
901 self.eventRouter_.sendProgressEvent('PROGRESS', self.getStatus());
902 };
903
904 var onEntryChanged = function(type, entry) {
905 self.eventRouter_.sendEntryChangedEvent(type, entry);
906 };
907
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000908 var onTaskError = function(err) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000909 if (self.maybeCancel_())
910 return;
Ben Murdochd3868032013-07-31 10:55:33 +0100911 self.eventRouter_.sendProgressEvent('ERROR', self.getStatus(), err);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000912 self.resetQueue_();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000913 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000914
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100915 var onTaskSuccess = function() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000916 if (self.maybeCancel_())
917 return;
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100918
919 // The task at the front of the queue is completed. Pop it from the queue.
920 self.copyTasks_.shift();
921 self.maybeScheduleCloseBackgroundPage_();
922
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000923 if (!self.copyTasks_.length) {
924 // All tasks have been serviced, clean up and exit.
Ben Murdochd3868032013-07-31 10:55:33 +0100925 self.eventRouter_.sendProgressEvent('SUCCESS', self.getStatus());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000926 self.resetQueue_();
927 return;
928 }
929
930 // We want to dispatch a PROGRESS event when there are more tasks to serve
931 // right after one task finished in the queue. We treat all tasks as one
932 // big task logically, so there is only one BEGIN/SUCCESS event pair for
933 // these continuous tasks.
Ben Murdochd3868032013-07-31 10:55:33 +0100934 self.eventRouter_.sendProgressEvent('PROGRESS', self.getStatus());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000935
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100936 self.serviceTask_(self.copyTasks_[0], onEntryChanged, onTaskProgress,
937 onTaskSuccess, onTaskError);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000938 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000939
940 // If the queue size is 1 after pushing our task, it was empty before,
941 // so we need to kick off queue processing and dispatch BEGIN event.
942
Ben Murdochd3868032013-07-31 10:55:33 +0100943 this.eventRouter_.sendProgressEvent('BEGIN', this.getStatus());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100944 this.serviceTask_(this.copyTasks_[0], onEntryChanged, onTaskProgress,
945 onTaskSuccess, onTaskError);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000946};
947
948/**
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100949 * Runs a given task.
950 * Note that the responsibility of this method is just dispatching to the
951 * appropriate serviceXxxTask_() method.
952 * TODO(hidehiko): Remove this method by introducing FileCopyManager.Task.run()
953 * (crbug.com/246976).
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000954 *
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100955 * @param {FileCopyManager.Task} task A task to be run.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100956 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
957 * invoked when an entry is changed.
958 * @param {function()} progressCallback Callback invoked periodically during
959 * the operation.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100960 * @param {function()} successCallback Callback run on success.
961 * @param {function(FileCopyManager.Error)} errorCallback Callback run on error.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000962 * @private
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000963 */
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100964FileCopyManager.prototype.serviceTask_ = function(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100965 task, entryChangedCallback, progressCallback,
966 successCallback, errorCallback) {
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100967 if (task.zip)
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100968 this.serviceZipTask_(task, entryChangedCallback, progressCallback,
969 successCallback, errorCallback);
Ben Murdoch58e6fbe2013-07-26 10:20:38 +0100970 else if (task.move)
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100971 this.serviceMoveTask_(task, entryChangedCallback, progressCallback,
972 successCallback, errorCallback);
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100973 else
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100974 this.serviceCopyTask_(task, entryChangedCallback, progressCallback,
975 successCallback, errorCallback);
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100976};
977
978/**
979 * Service all entries in the copy (and move) task.
980 * Note: this method contains also the operation of "Move" due to historical
981 * reason.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100982 *
983 * @param {FileCopyManager.Task} task A copy task to be run.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100984 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
985 * invoked when an entry is changed.
986 * @param {function()} progressCallback Callback invoked periodically during
987 * the copying.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100988 * @param {function()} successCallback On success.
989 * @param {function(FileCopyManager.Error)} errorCallback On error.
990 * @private
991 */
992FileCopyManager.prototype.serviceCopyTask_ = function(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100993 task, entryChangedCallback, progressCallback, successCallback,
994 errorCallback) {
995 // TODO(hidehiko): We should be able to share the code to iterate on entries
996 // with serviceMoveTask_().
Ben Murdochbb1529c2013-08-08 10:24:53 +0100997 if (task.entries.length == 0) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100998 successCallback();
999 return;
1000 }
1001
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001002 var self = this;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001003
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001004 var deleteOriginals = function() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001005 var count = task.originalEntries.length;
1006
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001007 var onEntryDeleted = function(entry) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001008 entryChangedCallback(util.EntryChangedType.DELETED, entry);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001009 count--;
1010 if (!count)
Ben Murdocha3f7b4e2013-07-24 10:36:34 +01001011 successCallback();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001012 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001013
Ben Murdoch558790d2013-07-30 15:19:42 +01001014 var onFilesystemError = function(err) {
1015 errorCallback(new FileCopyManager.Error(
1016 util.FileOperationErrorType.FILESYSTEM_ERROR, err));
1017 };
1018
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001019 for (var i = 0; i < task.originalEntries.length; i++) {
1020 var entry = task.originalEntries[i];
1021 util.removeFileOrDirectory(
1022 entry, onEntryDeleted.bind(self, entry), onFilesystemError);
1023 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001024 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001025
Ben Murdocha3f7b4e2013-07-24 10:36:34 +01001026 var onEntryServiced = function() {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001027 task.entryIndex++;
1028
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001029 // We should not dispatch a PROGRESS event when there is no pending items
1030 // in the task.
Ben Murdochbb1529c2013-08-08 10:24:53 +01001031 if (task.entryIndex >= task.entries.length) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001032 if (task.deleteAfterCopy) {
1033 deleteOriginals();
1034 } else {
Ben Murdocha3f7b4e2013-07-24 10:36:34 +01001035 successCallback();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001036 }
1037 return;
1038 }
1039
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001040 progressCallback();
1041 self.processCopyEntry_(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001042 task, task.entries[task.entryIndex], entryChangedCallback,
1043 progressCallback, onEntryServiced, errorCallback);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001044 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001045
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001046 this.processCopyEntry_(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001047 task, task.entries[task.entryIndex], entryChangedCallback,
1048 progressCallback, onEntryServiced, errorCallback);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001049};
1050
1051/**
Ben Murdoch58e6fbe2013-07-26 10:20:38 +01001052 * Copies the next entry in a given task.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001053 * TODO(olege): Refactor this method into a separate class.
1054 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001055 * @param {FileManager.Task} task A task.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001056 * @param {Entry} sourceEntry An entry to be copied.
1057 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
1058 * invoked when an entry is changed.
1059 * @param {function()} progressCallback Callback invoked periodically during
1060 * the copying.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +01001061 * @param {function()} successCallback On success.
1062 * @param {function(FileCopyManager.Error)} errorCallback On error.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001063 * @private
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001064 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001065FileCopyManager.prototype.processCopyEntry_ = function(
1066 task, sourceEntry, entryChangedCallback, progressCallback, successCallback,
1067 errorCallback) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001068 if (this.maybeCancel_())
1069 return;
1070
1071 var self = this;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001072
1073 // |sourceEntry.originalSourcePath| is set in util.recurseAndResolveEntries.
1074 var sourcePath = sourceEntry.originalSourcePath;
1075 if (sourceEntry.fullPath.substr(0, sourcePath.length) != sourcePath) {
1076 // We found an entry in the list that is not relative to the base source
1077 // path, something is wrong.
Ben Murdoch558790d2013-07-30 15:19:42 +01001078 errorCallback(new FileCopyManager.Error(
1079 util.FileOperationErrorType.UNEXPECTED_SOURCE_FILE,
1080 sourceEntry.fullPath));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001081 return;
1082 }
1083
1084 var targetDirEntry = task.targetDirEntry;
1085 var originalPath = sourceEntry.fullPath.substr(sourcePath.length + 1);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001086 originalPath = task.applyRenames(originalPath);
1087
Ben Murdochca12bfa2013-07-23 11:17:05 +01001088 var onDeduplicated = function(targetRelativePath) {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001089 var onCopyComplete = function(entry) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001090 entryChangedCallback(util.EntryChangedType.CREATED, entry);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001091 successCallback();
1092 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001093
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001094 var onFilesystemError = function(err) {
1095 errorCallback(new FileCopyManager.Error(
1096 util.FileOperationErrorType.FILESYSTEM_ERROR, err));
1097 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001098
1099 if (sourceEntry.isDirectory) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001100 // Copying the directory means just creating a new directory.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001101 targetDirEntry.getDirectory(
1102 targetRelativePath,
1103 {create: true, exclusive: true},
1104 function(targetEntry) {
1105 if (targetRelativePath != originalPath) {
1106 task.registerRename(originalPath, targetRelativePath);
1107 }
Ben Murdochbb1529c2013-08-08 10:24:53 +01001108 onCopyComplete(targetEntry);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001109 },
1110 util.flog('Error getting dir: ' + targetRelativePath,
1111 onFilesystemError));
Ben Murdoch32409262013-08-07 11:04:47 +01001112 } else {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001113 // Copy a file.
Ben Murdoch32409262013-08-07 11:04:47 +01001114 targetDirEntry.getDirectory(
1115 PathUtil.dirname(targetRelativePath), {create: false},
1116 function(dirEntry) {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001117 self.cancelCallback_ = fileOperationUtil.copyFile(
Ben Murdoch32409262013-08-07 11:04:47 +01001118 sourceEntry, dirEntry, PathUtil.basename(targetRelativePath),
Ben Murdochbb1529c2013-08-08 10:24:53 +01001119 function(entry, size) {
1120 task.updateFileCopyProgress(size);
1121 progressCallback();
1122 },
Ben Murdoch32409262013-08-07 11:04:47 +01001123 function(entry) {
1124 self.cancelCallback_ = null;
Ben Murdochbb1529c2013-08-08 10:24:53 +01001125 onCopyComplete(entry);
Ben Murdoch32409262013-08-07 11:04:47 +01001126 },
1127 function(error) {
1128 self.cancelCallback_ = null;
1129 onFilesystemError(error);
Ben Murdoch2385ea32013-08-06 11:01:04 +01001130 });
Ben Murdoch32409262013-08-07 11:04:47 +01001131 },
1132 onFilesystemError);
1133 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001134 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001135
Ben Murdoch32409262013-08-07 11:04:47 +01001136 fileOperationUtil.deduplicatePath(
Ben Murdoch558790d2013-07-30 15:19:42 +01001137 targetDirEntry, originalPath, onDeduplicated, errorCallback);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001138};
1139
1140/**
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001141 * Moves all entries in the task.
1142 *
1143 * @param {FileCopyManager.Task} task A move task to be run.
1144 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
1145 * invoked when an entry is changed.
1146 * @param {function()} progressCallback Callback invoked periodically during
1147 * the moving.
1148 * @param {function()} successCallback On success.
1149 * @param {function(FileCopyManager.Error)} errorCallback On error.
1150 * @private
1151 */
1152FileCopyManager.prototype.serviceMoveTask_ = function(
1153 task, entryChangedCallback, progressCallback, successCallback,
1154 errorCallback) {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001155 if (task.entries.length == 0) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001156 successCallback();
1157 return;
1158 }
1159
1160 this.processMoveEntry_(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001161 task, task.entries[task.entryIndex], entryChangedCallback,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001162 (function onCompleted() {
Ben Murdochbb1529c2013-08-08 10:24:53 +01001163 task.entryIndex++;
1164
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001165 // We should not dispatch a PROGRESS event when there is no pending
1166 // items in the task.
Ben Murdochbb1529c2013-08-08 10:24:53 +01001167 if (task.entryIndex >= task.entries.length) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001168 successCallback();
1169 return;
1170 }
1171
1172 // Move the next entry.
1173 progressCallback();
1174 this.processMoveEntry_(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001175 task, task.entries[task.entryIndex], entryChangedCallback,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001176 onCompleted.bind(this), errorCallback);
1177 }).bind(this),
1178 errorCallback);
1179};
1180
1181/**
1182 * Moves the next entry in a given task.
1183 *
1184 * Implementation note: This method can be simplified more. For example, in
1185 * Task.setEntries(), the flag to recurse is set to false for move task,
1186 * so that all the entries' originalSourcePath should be
1187 * dirname(sourceEntry.fullPath).
1188 * Thus, targetRelativePath should contain exact one component. Also we can
1189 * skip applyRenames, because the destination directory always should be
1190 * task.targetDirEntry.
1191 * The unnecessary complexity is due to historical reason.
1192 * TODO(hidehiko): Refactor this method.
1193 *
1194 * @param {FileManager.Task} task A move task.
1195 * @param {Entry} sourceEntry An entry to be moved.
1196 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
1197 * invoked when an entry is changed.
1198 * @param {function()} successCallback On success.
1199 * @param {function(FileCopyManager.Error)} errorCallback On error.
1200 * @private
1201 */
1202FileCopyManager.prototype.processMoveEntry_ = function(
1203 task, sourceEntry, entryChangedCallback, successCallback, errorCallback) {
1204 if (this.maybeCancel_())
1205 return;
1206
1207 // |sourceEntry.originalSourcePath| is set in util.recurseAndResolveEntries.
1208 var sourcePath = sourceEntry.originalSourcePath;
1209 if (sourceEntry.fullPath.substr(0, sourcePath.length) != sourcePath) {
1210 // We found an entry in the list that is not relative to the base source
1211 // path, something is wrong.
1212 errorCallback(new FileCopyManager.Error(
1213 util.FileOperationErrorType.UNEXPECTED_SOURCE_FILE,
1214 sourceEntry.fullPath));
1215 return;
1216 }
1217
Ben Murdoch32409262013-08-07 11:04:47 +01001218 fileOperationUtil.deduplicatePath(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001219 task.targetDirEntry,
1220 task.applyRenames(sourceEntry.fullPath.substr(sourcePath.length + 1)),
1221 function(targetRelativePath) {
1222 var onFilesystemError = function(err) {
1223 errorCallback(new FileCopyManager.Error(
1224 util.FileOperationErrorType.FILESYSTEM_ERROR,
1225 err));
1226 };
1227
1228 task.targetDirEntry.getDirectory(
1229 PathUtil.dirname(targetRelativePath), {create: false},
1230 function(dirEntry) {
1231 sourceEntry.moveTo(
1232 dirEntry, PathUtil.basename(targetRelativePath),
1233 function(targetEntry) {
1234 entryChangedCallback(
1235 util.EntryChangedType.CREATED, targetEntry);
1236 entryChangedCallback(
1237 util.EntryChangedType.DELETED, sourceEntry);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001238 successCallback();
1239 },
1240 onFilesystemError);
1241 },
1242 onFilesystemError);
1243 },
1244 errorCallback);
1245};
1246
1247/**
1248 * Service a zip file creation task.
1249 *
1250 * @param {FileCopyManager.Task} task A zip task to be run.
1251 * @param {function(util.EntryChangedType, Entry)} entryChangedCallback Callback
1252 * invoked when an entry is changed.
1253 * @param {function()} progressCallback Callback invoked periodically during
1254 * the moving.
1255 * @param {function()} successCallback On complete.
1256 * @param {function(FileCopyManager.Error)} errorCallback On error.
1257 * @private
1258 */
1259FileCopyManager.prototype.serviceZipTask_ = function(
1260 task, entryChangedCallback, progressCallback, successCallback,
1261 errorCallback) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001262 // TODO(hidehiko): we should localize the name.
1263 var destName = 'Archive';
1264 if (task.originalEntries.length == 1) {
1265 var entryPath = task.originalEntries[0].fullPath;
1266 var i = entryPath.lastIndexOf('/');
1267 var basename = (i < 0) ? entryPath : entryPath.substr(i + 1);
1268 i = basename.lastIndexOf('.');
1269 destName = ((i < 0) ? basename : basename.substr(0, i));
1270 }
1271
Ben Murdoch32409262013-08-07 11:04:47 +01001272 fileOperationUtil.deduplicatePath(
1273 task.targetDirEntry, destName + '.zip',
1274 function(destPath) {
1275 progressCallback();
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001276
Ben Murdoch32409262013-08-07 11:04:47 +01001277 fileOperationUtil.zipSelection(
Ben Murdochbb1529c2013-08-08 10:24:53 +01001278 task.entries,
Ben Murdoch32409262013-08-07 11:04:47 +01001279 task.zipBaseDirEntry,
1280 destPath,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001281 function(entry) {
1282 entryChangedCallback(util.EntryChangedType.CREATE, entry);
1283 successCallback();
1284 },
Ben Murdoch32409262013-08-07 11:04:47 +01001285 function(error) {
1286 errorCallback(new FileCopyManager.Error(
1287 util.FileOperationErrorType.FILESYSTEM_ERROR, error));
1288 });
1289 },
1290 errorCallback);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001291};
1292
1293/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001294 * Timeout before files are really deleted (to allow undo).
1295 */
1296FileCopyManager.DELETE_TIMEOUT = 30 * 1000;
1297
1298/**
1299 * Schedules the files deletion.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001300 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001301 * @param {Array.<Entry>} entries The entries.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001302 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001303FileCopyManager.prototype.deleteEntries = function(entries) {
1304 var task = { entries: entries };
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001305 this.deleteTasks_.push(task);
1306 this.maybeScheduleCloseBackgroundPage_();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001307 if (this.deleteTasks_.length == 1)
1308 this.serviceAllDeleteTasks_();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001309};
1310
1311/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001312 * Service all pending delete tasks, as well as any that might appear during the
1313 * deletion.
1314 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001315 * @private
1316 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001317FileCopyManager.prototype.serviceAllDeleteTasks_ = function() {
1318 var self = this;
1319
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001320 var onTaskSuccess = function() {
1321 var task = self.deleteTasks_[0];
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001322 self.deleteTasks_.shift();
1323 if (!self.deleteTasks_.length) {
1324 // All tasks have been serviced, clean up and exit.
Ben Murdochd3868032013-07-31 10:55:33 +01001325 self.eventRouter_.sendDeleteEvent(
1326 'SUCCESS',
1327 task.entries.map(function(e) {
1328 return util.makeFilesystemUrl(e.fullPath);
1329 }));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001330 self.maybeScheduleCloseBackgroundPage_();
1331 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001332 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001333
1334 // We want to dispatch a PROGRESS event when there are more tasks to serve
1335 // right after one task finished in the queue. We treat all tasks as one
1336 // big task logically, so there is only one BEGIN/SUCCESS event pair for
1337 // these continuous tasks.
Ben Murdochd3868032013-07-31 10:55:33 +01001338 self.eventRouter_.sendDeleteEvent(
1339 'PROGRESS',
1340 task.entries.map(function(e) {
1341 return util.makeFilesystemUrl(e.fullPath);
1342 }));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001343 self.serviceDeleteTask_(self.deleteTasks_[0], onTaskSuccess, onTaskFailure);
1344 };
1345
1346 var onTaskFailure = function(task) {
1347 self.deleteTasks_ = [];
Ben Murdochd3868032013-07-31 10:55:33 +01001348 self.eventRouter_.sendDeleteEvent(
1349 'ERROR',
1350 task.entries.map(function(e) {
1351 return util.makeFilesystemUrl(e.fullPath);
1352 }));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001353 self.maybeScheduleCloseBackgroundPage_();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001354 };
1355
1356 // If the queue size is 1 after pushing our task, it was empty before,
1357 // so we need to kick off queue processing and dispatch BEGIN event.
Ben Murdochd3868032013-07-31 10:55:33 +01001358 this.eventRouter_.sendDeleteEvent(
1359 'BEGIN',
1360 this.deleteTasks_[0].entries.map(function(e) {
1361 return util.makeFilesystemUrl(e.fullPath);
1362 }));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001363 this.serviceDeleteTask_(this.deleteTasks_[0], onTaskSuccess, onTaskFailure);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001364};
1365
1366/**
1367 * Performs the deletion.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001368 *
1369 * @param {Object} task The delete task (see deleteEntries function).
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001370 * @param {function()} successCallback Callback run on success.
1371 * @param {function(FileCopyManager.Error)} errorCallback Callback run on error.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001372 * @private
1373 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001374FileCopyManager.prototype.serviceDeleteTask_ = function(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001375 task, successCallback, errorCallback) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001376 var downcount = task.entries.length;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001377 if (downcount == 0) {
1378 successCallback();
1379 return;
1380 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001381
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001382 var filesystemError = null;
1383 var onComplete = function() {
1384 if (--downcount > 0)
1385 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001386
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001387 // All remove operations are processed. Run callback.
1388 if (filesystemError) {
1389 errorCallback(new FileCopyManager.Error(
1390 util.FileOperationErrorType.FILESYSTEM_ERROR, filesystemError));
1391 } else {
1392 successCallback();
1393 }
1394 };
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001395
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001396 for (var i = 0; i < task.entries.length; i++) {
1397 var entry = task.entries[i];
1398 util.removeFileOrDirectory(
1399 entry,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001400 function(currentEntry) {
1401 this.eventRouter_.sendEntryChangedEvent(
1402 util.EntryChangedType.DELETED, currentEntry);
1403 onComplete();
1404 }.bind(this, entry),
1405 function(error) {
1406 if (!filesystemError)
1407 filesystemError = error;
1408 onComplete();
1409 });
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001410 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001411};
1412
1413/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001414 * Creates a zip file for the selection of files.
1415 *
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001416 * @param {Entry} dirEntry The directory containing the selection.
1417 * @param {Array.<Entry>} selectionEntries The selected entries.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001418 */
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001419FileCopyManager.prototype.zipSelection = function(dirEntry, selectionEntries) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001420 var self = this;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001421 var zipTask = new FileCopyManager.Task(dirEntry, dirEntry);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001422 zipTask.zip = true;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001423 zipTask.setEntries(selectionEntries, function() {
1424 // TODO: per-entry zip progress update with accurate byte count.
Ben Murdoch2385ea32013-08-06 11:01:04 +01001425 // For now just set completedBytes to same value as totalBytes so that the
1426 // progress bar is full.
Ben Murdoch32409262013-08-07 11:04:47 +01001427 zipTask.completedBytes = zipTask.totalBytes;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001428 self.copyTasks_.push(zipTask);
1429 if (self.copyTasks_.length == 1) {
1430 // Assume self.cancelRequested_ == false.
1431 // This moved us from 0 to 1 active tasks, let the servicing begin!
1432 self.serviceAllTasks_();
1433 } else {
1434 // Force to update the progress of butter bar when there are new tasks
1435 // coming while servicing current task.
Ben Murdochd3868032013-07-31 10:55:33 +01001436 self.eventRouter_.sendProgressEvent('PROGRESS', self.getStatus());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001437 }
1438 });
1439};