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

Side by Side Diff: chrome/browser/resources/file_manager/js/file_copy_manager.js

Issue 9652024: Only show progress bar after 500ms (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Merge to trunk. Created 8 years, 9 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | chrome/browser/resources/file_manager/js/file_manager.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 function FileCopyManager() { 5 function FileCopyManager() {
6 this.copyTasks_ = []; 6 this.copyTasks_ = [];
7 this.cancelObservers_ = []; 7 this.cancelObservers_ = [];
8 this.cancelRequested_ = false; 8 this.cancelRequested_ = false;
9 } 9 }
10 10
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
53 callback(); 53 callback();
54 } 54 }
55 55
56 this.originalEntries = entries; 56 this.originalEntries = entries;
57 // When moving directories, FileEntry.moveTo() is used if both source 57 // When moving directories, FileEntry.moveTo() is used if both source
58 // and target are on GData. There is no need to recurse into directories. 58 // and target are on GData. There is no need to recurse into directories.
59 var recurse = !(this.deleteAfterCopy && this.sourceAndTargetOnGData); 59 var recurse = !(this.deleteAfterCopy && this.sourceAndTargetOnGData);
60 util.recurseAndResolveEntries(entries, recurse, onEntriesRecursed); 60 util.recurseAndResolveEntries(entries, recurse, onEntriesRecursed);
61 } 61 }
62 62
63 FileCopyManager.Task.prototype.takeNextEntry = function() { 63 FileCopyManager.Task.prototype.getNextEntry = function() {
64 if (this.pendingDirectories.length) 64 // We should keep the file in pending list and remove it after complete.
65 return this.pendingDirectories.shift(); 65 // Otherwise, if we try to get status in the middle of copying. The returned
66 // status is wrong (miss count the pasting item in totalItems).
67 if (this.pendingDirectories.length) {
68 this.pendingDirectories[0].inProgress = true;
69 return this.pendingDirectories[0];
70 }
66 71
67 if (this.pendingFiles.length) 72 if (this.pendingFiles.length) {
68 return this.pendingFiles.shift(); 73 this.pendingFiles[0].inProgress = true;
74 return this.pendingFiles[0];
75 }
69 76
70 return null; 77 return null;
71 }; 78 };
72 79
73 FileCopyManager.Task.prototype.markEntryComplete = function(entry, size) { 80 FileCopyManager.Task.prototype.markEntryComplete = function(entry, size) {
74 if (entry.isDirectory) { 81 // It is probably not safe to directly remove the first entry in pending list.
82 // We need to check if the removed entry (srcEntry) corresponding to the added
83 // entry (target entry).
84 if (entry.isDirectory && this.pendingDirectories &&
85 this.pendingDirectories[0].inProgress) {
75 this.completedDirectories.push(entry); 86 this.completedDirectories.push(entry);
76 } else { 87 this.pendingDirectories.shift();
88 } else if (this.pendingFiles && this.pendingFiles[0].inProgress) {
77 this.completedFiles.push(entry); 89 this.completedFiles.push(entry);
78 this.completedBytes += size; 90 this.completedBytes += size;
91 this.pendingFiles.shift();
92 } else {
93 throw new Error('Try to remove a source entry which is not correspond to' +
94 ' the finished target entry');
79 } 95 }
80 }; 96 };
81 97
82 FileCopyManager.Task.prototype.registerRename = function(fromName, toName) { 98 FileCopyManager.Task.prototype.registerRename = function(fromName, toName) {
83 this.renamedDirectories_.push({from: fromName + '/', to: toName + '/'}); 99 this.renamedDirectories_.push({from: fromName + '/', to: toName + '/'});
84 }; 100 };
85 101
86 FileCopyManager.Task.prototype.applyRenames = function(path) { 102 FileCopyManager.Task.prototype.applyRenames = function(path) {
87 // Directories are processed in pre-order, so we will store only the first 103 // Directories are processed in pre-order, so we will store only the first
88 // renaming point: 104 // renaming point:
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
126 completedFiles: 0, 142 completedFiles: 0,
127 completedDirectories: 0, 143 completedDirectories: 0,
128 completedBytes: 0 144 completedBytes: 0
129 }; 145 };
130 146
131 for (var i = 0; i < this.copyTasks_.length; i++) { 147 for (var i = 0; i < this.copyTasks_.length; i++) {
132 var task = this.copyTasks_[i]; 148 var task = this.copyTasks_[i];
133 rv.pendingFiles += task.pendingFiles.length; 149 rv.pendingFiles += task.pendingFiles.length;
134 rv.pendingDirectories += task.pendingDirectories.length; 150 rv.pendingDirectories += task.pendingDirectories.length;
135 rv.pendingBytes += task.pendingBytes; 151 rv.pendingBytes += task.pendingBytes;
136 rv.pendingItems += rv.pendingFiles + rv.pendingDirectories;
137 152
138 rv.completedFiles += task.completedFiles.length; 153 rv.completedFiles += task.completedFiles.length;
139 rv.completedDirectories += task.completedDirectories.length; 154 rv.completedDirectories += task.completedDirectories.length;
140 rv.completedBytes += task.completedBytes; 155 rv.completedBytes += task.completedBytes;
141 rv.completedItems += rv.completedFiles + rv.completedDirectories;
142
143 } 156 }
157 rv.pendingItems = rv.pendingFiles + rv.pendingDirectories;
158 rv.completedItems = rv.completedFiles + rv.completedDirectories;
144 159
145 rv.totalFiles = rv.pendingFiles + rv.completedFiles; 160 rv.totalFiles = rv.pendingFiles + rv.completedFiles;
146 rv.totalDirectories = rv.pendingDirectories + rv.completedDirectories; 161 rv.totalDirectories = rv.pendingDirectories + rv.completedDirectories;
147 rv.totalItems = rv.pendingItems + rv.completedItems; 162 rv.totalItems = rv.pendingItems + rv.completedItems;
148 rv.totalBytes = rv.pendingBytes + rv.completedBytes; 163 rv.totalBytes = rv.pendingBytes + rv.completedBytes;
149 164
150 return rv; 165 return rv;
151 }; 166 };
152 167
153 /** 168 /**
169 * Get the overall progress data of all queued copy tasks.
170 * @return {Object} An object containing the following parameters:
171 * percentage - The percentage (0-1) of finished items.
172 * pendingItems - The number of pending/unfinished items.
173 */
174 FileCopyManager.prototype.getProgress = function() {
175 var status = this.getStatus();
176 return {
177 // TODO(bshe): Need to figure out a way to get completed bytes in real
178 // time. We currently use completedItems and totalItems to estimate the
179 // progress. There are completeBytes and totalBytes ready to use.
180 // However, the completedBytes is not in real time. It only updates
181 // itself after each item finished. So if there is a large item to
182 // copy, the progress bar will stop moving until it finishes and jump
183 // a large portion of the bar.
184 // There is case that when user copy a large file, we want to show an
185 // 100% animated progress bar. So we use completedItems + 1 here.
186 percentage: (status.completedItems + 1) / status.totalItems,
187 pendingItems: status.pendingItems
188 };
189 };
190
191 /**
192 * Dispatch a simple copy-progress event with reason and optional err data.
193 */
194 FileCopyManager.prototype.sendProgressEvent_ = function(reason, opt_err) {
195 var event = new cr.Event('copy-progress');
196 event.reason = reason;
197 if (opt_err)
198 event.error = opt_err;
199 this.dispatchEvent(event);
200 };
201
202 /**
154 * Completely clear out the copy queue, either because we encountered an error 203 * Completely clear out the copy queue, either because we encountered an error
155 * or completed successfully. 204 * or completed successfully.
156 */ 205 */
157 FileCopyManager.prototype.resetQueue_ = function() { 206 FileCopyManager.prototype.resetQueue_ = function() {
158 for (var i = 0; i < this.cancelObservers_.length; i++) 207 for (var i = 0; i < this.cancelObservers_.length; i++)
159 this.cancelObservers_[i](); 208 this.cancelObservers_[i]();
160 209
161 this.copyTasks_ = []; 210 this.copyTasks_ = [];
162 this.cancelObservers_ = []; 211 this.cancelObservers_ = [];
163 this.cancelRequested_ = false; 212 this.cancelRequested_ = false;
164 }; 213 };
165 214
166 /** 215 /**
167 * Request that the current copy queue be abandoned. 216 * Request that the current copy queue be abandoned.
168 */ 217 */
169 FileCopyManager.prototype.requestCancel = function(opt_callback) { 218 FileCopyManager.prototype.requestCancel = function(opt_callback) {
170 this.cancelRequested_ = true; 219 this.cancelRequested_ = true;
171 if (opt_callback) 220 if (opt_callback)
172 this.cancelObservers_.push(opt_callback); 221 this.cancelObservers_.push(opt_callback);
173 }; 222 };
174 223
175 /** 224 /**
176 * Perform the bookeeping required to cancel. 225 * Perform the bookeeping required to cancel.
177 */ 226 */
178 FileCopyManager.prototype.doCancel_ = function() { 227 FileCopyManager.prototype.doCancel_ = function() {
179 var event = new cr.Event('copy-progress'); 228 this.sendProgressEvent_('CANCELLED');
180 event.reason = 'CANCELLED';
181 this.dispatchEvent(event);
182 this.resetQueue_(); 229 this.resetQueue_();
183 }; 230 };
184 231
185 /** 232 /**
186 * Used internally to check if a cancel has been requested, and handle 233 * Used internally to check if a cancel has been requested, and handle
187 * it if so. 234 * it if so.
188 */ 235 */
189 FileCopyManager.prototype.maybeCancel_ = function() { 236 FileCopyManager.prototype.maybeCancel_ = function() {
190 if (!this.cancelRequested_) 237 if (!this.cancelRequested_)
191 return false; 238 return false;
192 239
193 this.doCancel_(); 240 this.doCancel_();
194 return true; 241 return true;
195 } 242 }
196 243
197 /** 244 /**
198 * Convert string in clipboard to entries and kick off pasting. 245 * Convert string in clipboard to entries and kick off pasting.
199 */ 246 */
200 FileCopyManager.prototype.paste = function(clipboard, targetEntry, 247 FileCopyManager.prototype.paste = function(clipboard, targetEntry,
201 sourceAndTargetOnGData, root) { 248 sourceAndTargetOnGData, root) {
202 var self = this; 249 var self = this;
203 var results = { 250 var results = {
204 sourceDirEntry: null, 251 sourceDirEntry: null,
205 entries: [], 252 entries: [],
206 isCut: false 253 isCut: false
207 }; 254 };
208 255
209 function onPathError(err) { 256 function onPathError(err) {
210 var event = new cr.Event('copy-progress'); 257 self.sendProgressEvent_('ERROR',
211 event.reason = 'ERROR'; 258 new FileCopyManager.Error('FILESYSTEM_ERROR', err));
212 event.error = new FileCopyManager.Error('FILESYSTEM_ERROR', err);
213 self.dispatchEvent(event);
214 } 259 }
215 260
216 function onSourceEntryFound(dirEntry) { 261 function onSourceEntryFound(dirEntry) {
217 function onComplete() { 262 function onComplete() {
218 self.queueCopy(results.sourceDirEntry, 263 self.queueCopy(results.sourceDirEntry,
219 targetEntry, 264 targetEntry,
220 results.entries, 265 results.entries,
221 results.isCut, 266 results.isCut,
222 sourceAndTargetOnGData); 267 sourceAndTargetOnGData);
223 } 268 }
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
272 sourceAndTargetOnGData) { 317 sourceAndTargetOnGData) {
273 var self = this; 318 var self = this;
274 var copyTask = new FileCopyManager.Task(sourceDirEntry, targetDirEntry); 319 var copyTask = new FileCopyManager.Task(sourceDirEntry, targetDirEntry);
275 copyTask.deleteAfterCopy = deleteAfterCopy; 320 copyTask.deleteAfterCopy = deleteAfterCopy;
276 copyTask.sourceAndTargetOnGData = sourceAndTargetOnGData; 321 copyTask.sourceAndTargetOnGData = sourceAndTargetOnGData;
277 copyTask.setEntries(entries, function() { 322 copyTask.setEntries(entries, function() {
278 self.copyTasks_.push(copyTask); 323 self.copyTasks_.push(copyTask);
279 if (self.copyTasks_.length == 1) { 324 if (self.copyTasks_.length == 1) {
280 // This moved us from 0 to 1 active tasks, let the servicing begin! 325 // This moved us from 0 to 1 active tasks, let the servicing begin!
281 self.serviceAllTasks_(); 326 self.serviceAllTasks_();
327 } else {
328 // Force to update the progress of butter bar when there are new tasks
329 // coming while servicing current task.
330 self.sendProgressEvent_('PROGRESS');
282 } 331 }
283 }); 332 });
284 333
285 return copyTask; 334 return copyTask;
286 }; 335 };
287 336
288 /** 337 /**
289 * Service all pending tasks, as well as any that might appear during the 338 * Service all pending tasks, as well as any that might appear during the
290 * copy. 339 * copy.
291 */ 340 */
292 FileCopyManager.prototype.serviceAllTasks_ = function() { 341 FileCopyManager.prototype.serviceAllTasks_ = function() {
293 var self = this; 342 var self = this;
294 343
295 function onTaskError(err) { 344 function onTaskError(err) {
296 var event = new cr.Event('copy-progress'); 345 self.sendProgressEvent_('ERROR', err);
297 event.reason = 'ERROR';
298 event.error = err;
299 self.dispatchEvent(event);
300 self.resetQueue_(); 346 self.resetQueue_();
301 } 347 }
302 348
303 function onTaskSuccess(task) { 349 function onTaskSuccess(task) {
304 if (task == null) { 350 if (!self.copyTasks_.length) {
305 // All tasks have been serviced, clean up and exit. 351 // All tasks have been serviced, clean up and exit.
306 var event = new cr.Event('copy-progress'); 352 self.sendProgressEvent_('SUCCESS');
307 event.reason = 'SUCCESS';
308 self.dispatchEvent(event);
309 self.resetQueue_(); 353 self.resetQueue_();
310 return; 354 return;
311 } 355 }
312 356
357 // We want to dispatch a PROGRESS event when there are more tasks to serve
358 // right after one task finished in the queue. We treat all tasks as one
359 // big task logically, so there is only one BEGIN/SUCCESS event pair for
360 // these continuous tasks.
361 self.sendProgressEvent_('PROGRESS');
362
313 self.serviceNextTask_(onTaskSuccess, onTaskError); 363 self.serviceNextTask_(onTaskSuccess, onTaskError);
314 } 364 }
315 365
316 // If the queue size is 1 after pushing our task, it was empty before, 366 // If the queue size is 1 after pushing our task, it was empty before,
317 // so we need to kick off queue processing. 367 // so we need to kick off queue processing and dispatch BEGIN event.
368
369 this.sendProgressEvent_('BEGIN');
318 this.serviceNextTask_(onTaskSuccess, onTaskError); 370 this.serviceNextTask_(onTaskSuccess, onTaskError);
319 }; 371 };
320 372
321 /** 373 /**
322 * Service all entries in the next copy task. 374 * Service all entries in the next copy task.
323 */ 375 */
324 FileCopyManager.prototype.serviceNextTask_ = function( 376 FileCopyManager.prototype.serviceNextTask_ = function(
325 successCallback, errorCallback) { 377 successCallback, errorCallback) {
326 if (this.maybeCancel_()) 378 if (this.maybeCancel_())
327 return; 379 return;
328 380
329 if (!this.copyTasks_.length) {
330 successCallback(null);
331 return;
332 }
333
334 var self = this; 381 var self = this;
335 var task = this.copyTasks_[0]; 382 var task = this.copyTasks_[0];
336 383
337 function onFilesystemError(err) { 384 function onFilesystemError(err) {
338 errorCallback(new FileCopyManager.Error('FILESYSTEM_ERROR', err)); 385 errorCallback(new FileCopyManager.Error('FILESYSTEM_ERROR', err));
339 } 386 }
340 387
341 function onTaskComplete() { 388 function onTaskComplete() {
342 self.copyTasks_.shift(); 389 self.copyTasks_.shift();
343 successCallback(task); 390 successCallback(task);
344 } 391 }
345 392
346 function deleteOriginals() { 393 function deleteOriginals() {
347 var count = task.originalEntries.length; 394 var count = task.originalEntries.length;
348 395
349 function onEntryDeleted() { 396 function onEntryDeleted() {
350 count--; 397 count--;
351 if (!count) 398 if (!count)
352 onTaskComplete(); 399 onTaskComplete();
353 } 400 }
354 401
355 for (var i = 0; i < task.originalEntries.length; i++) { 402 for (var i = 0; i < task.originalEntries.length; i++) {
356 util.removeFileOrDirectory( 403 util.removeFileOrDirectory(
357 task.originalEntries[i], onEntryDeleted, onFilesystemError); 404 task.originalEntries[i], onEntryDeleted, onFilesystemError);
358 } 405 }
359 } 406 }
360 407
361 function onEntryServiced(targetEntry, size) { 408 function onEntryServiced(targetEntry, size) {
362 if (!targetEntry) { 409 // We should not dispatch a PROGRESS event when there is no pending items
410 // in the task.
411 if (task.pendingDirectories.length + task.pendingFiles.length == 0) {
363 // All done with the entries in this task. 412 // All done with the entries in this task.
364 // If files are moved within GData, FileEntry.moveTo() is used and 413 // If files are moved within GData, FileEntry.moveTo() is used and
365 // there is no need to delete the original files. 414 // there is no need to delete the original files.
366 if (task.deleteAfterCopy && !task.sourceAndTargetOnGData) { 415 if (task.deleteAfterCopy && !task.sourceAndTargetOnGData) {
367 deleteOriginals(); 416 deleteOriginals();
368 } else { 417 } else {
369 onTaskComplete(); 418 onTaskComplete();
370 } 419 }
371 return; 420 return;
372 } 421 }
373 422
374 var event = new cr.Event('copy-progress'); 423 self.sendProgressEvent_('PROGRESS');
375 event.reason = 'PROGRESS';
376 self.dispatchEvent(event);
377 424
378 // We yield a few ms between copies to give the browser a chance to service 425 // We yield a few ms between copies to give the browser a chance to service
379 // events (like perhaps the user clicking to cancel the copy, for example). 426 // events (like perhaps the user clicking to cancel the copy, for example).
380 setTimeout(function() { 427 setTimeout(function() {
381 self.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); 428 self.serviceNextTaskEntry_(task, onEntryServiced, errorCallback);
382 }, 10); 429 }, 10);
383 } 430 }
384 431
385 var event = new cr.Event('copy-progress');
386 event.reason = 'BEGIN';
387 this.dispatchEvent(event);
388 this.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); 432 this.serviceNextTaskEntry_(task, onEntryServiced, errorCallback);
389 } 433 }
390 434
391 /** 435 /**
392 * Service the next entry in a given task. 436 * Service the next entry in a given task.
393 */ 437 */
394 FileCopyManager.prototype.serviceNextTaskEntry_ = function( 438 FileCopyManager.prototype.serviceNextTaskEntry_ = function(
395 task, successCallback, errorCallback) { 439 task, successCallback, errorCallback) {
396 if (this.maybeCancel_()) 440 if (this.maybeCancel_())
397 return; 441 return;
398 442
399 var self = this; 443 var self = this;
400 var sourceEntry = task.takeNextEntry(); 444 var sourceEntry = task.getNextEntry();
401 445
402 if (!sourceEntry) { 446 if (!sourceEntry) {
403 // All entries in this task have been copied. 447 // All entries in this task have been copied.
404 successCallback(null); 448 successCallback(null);
405 return; 449 return;
406 } 450 }
407 451
408 var sourcePath = task.sourceDirEntry.fullPath; 452 var sourcePath = task.sourceDirEntry.fullPath;
409 if (sourceEntry.fullPath.substr(0, sourcePath.length) != sourcePath) { 453 if (sourceEntry.fullPath.substr(0, sourcePath.length) != sourcePath) {
410 // We found an entry in the list that is not relative to the base source 454 // We found an entry in the list that is not relative to the base source
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after
546 successCallback(targetEntry, file.size) 590 successCallback(targetEntry, file.size)
547 }; 591 };
548 writer.write(file); 592 writer.write(file);
549 } 593 }
550 594
551 targetEntry.createWriter(onWriterCreated, errorCallback); 595 targetEntry.createWriter(onWriterCreated, errorCallback);
552 } 596 }
553 597
554 sourceEntry.file(onSourceFileFound, errorCallback); 598 sourceEntry.file(onSourceFileFound, errorCallback);
555 }; 599 };
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/file_manager/js/file_manager.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698