OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 }; |
OLD | NEW |