| 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 |