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 /** | 5 /** |
6 * @constructor | 6 * @constructor |
7 * @param {DirectoryEntry} root Root directory entry. | 7 * @param {DirectoryEntry} root Root directory entry. |
8 */ | 8 */ |
9 function FileCopyManager(root) { | 9 function FileCopyManager(root) { |
10 this.copyTasks_ = []; | 10 this.copyTasks_ = []; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 this.pendingDirectories = []; | 54 this.pendingDirectories = []; |
55 this.pendingFiles = []; | 55 this.pendingFiles = []; |
56 this.pendingBytes = 0; | 56 this.pendingBytes = 0; |
57 | 57 |
58 this.completedDirectories = []; | 58 this.completedDirectories = []; |
59 this.completedFiles = []; | 59 this.completedFiles = []; |
60 this.completedBytes = 0; | 60 this.completedBytes = 0; |
61 | 61 |
62 this.deleteAfterCopy = false; | 62 this.deleteAfterCopy = false; |
63 this.move = false; | 63 this.move = false; |
| 64 this.zip = false; |
64 this.sourceOnGData = false; | 65 this.sourceOnGData = false; |
65 this.targetOnGData = false; | 66 this.targetOnGData = false; |
66 | 67 |
67 // If directory already exists, we try to make a copy named 'dir (X)', | 68 // If directory already exists, we try to make a copy named 'dir (X)', |
68 // where X is a number. When we do this, all subsequent copies from | 69 // where X is a number. When we do this, all subsequent copies from |
69 // inside the subtree should be mapped to the new directory name. | 70 // inside the subtree should be mapped to the new directory name. |
70 // For example, if 'dir' was copied as 'dir (1)', then 'dir\file.txt' should | 71 // For example, if 'dir' was copied as 'dir (1)', then 'dir\file.txt' should |
71 // become 'dir (1)\file.txt'. | 72 // become 'dir (1)\file.txt'. |
72 this.renamedDirectories_ = []; | 73 this.renamedDirectories_ = []; |
73 }; | 74 }; |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
223 pendingBytes: 0, | 224 pendingBytes: 0, |
224 | 225 |
225 completedItems: 0, // Files + Directories | 226 completedItems: 0, // Files + Directories |
226 completedFiles: 0, | 227 completedFiles: 0, |
227 completedDirectories: 0, | 228 completedDirectories: 0, |
228 completedBytes: 0, | 229 completedBytes: 0, |
229 | 230 |
230 percentage: NaN, | 231 percentage: NaN, |
231 pendingCopies: 0, | 232 pendingCopies: 0, |
232 pendingMoves: 0, | 233 pendingMoves: 0, |
| 234 pendingZips: 0, |
233 filename: '' // In case pendingItems == 1 | 235 filename: '' // In case pendingItems == 1 |
234 }; | 236 }; |
235 | 237 |
236 var pendingFile = null; | 238 var pendingFile = null; |
237 | 239 |
238 for (var i = 0; i < this.copyTasks_.length; i++) { | 240 for (var i = 0; i < this.copyTasks_.length; i++) { |
239 var task = this.copyTasks_[i]; | 241 var task = this.copyTasks_[i]; |
240 var pendingFiles = task.pendingFiles.length; | 242 var pendingFiles = task.pendingFiles.length; |
241 var pendingDirectories = task.pendingDirectories.length; | 243 var pendingDirectories = task.pendingDirectories.length; |
242 rv.pendingFiles += pendingFiles; | 244 rv.pendingFiles += pendingFiles; |
243 rv.pendingDirectories += pendingDirectories; | 245 rv.pendingDirectories += pendingDirectories; |
244 rv.pendingBytes += task.pendingBytes; | 246 rv.pendingBytes += task.pendingBytes; |
245 | 247 |
246 rv.completedFiles += task.completedFiles.length; | 248 rv.completedFiles += task.completedFiles.length; |
247 rv.completedDirectories += task.completedDirectories.length; | 249 rv.completedDirectories += task.completedDirectories.length; |
248 rv.completedBytes += task.completedBytes; | 250 rv.completedBytes += task.completedBytes; |
249 | 251 |
250 if (task.move || task.deleteAfterCopy) { | 252 if (task.zip) { |
| 253 rv.pendingZips += pendingFiles + pendingDirectories; |
| 254 } else if (task.move || task.deleteAfterCopy) { |
251 rv.pendingMoves += pendingFiles + pendingDirectories; | 255 rv.pendingMoves += pendingFiles + pendingDirectories; |
252 } else { | 256 } else { |
253 rv.pendingCopies += pendingFiles + pendingDirectories; | 257 rv.pendingCopies += pendingFiles + pendingDirectories; |
254 } | 258 } |
255 | 259 |
256 if (task.pendingFiles.length === 1) | 260 if (task.pendingFiles.length === 1) |
257 pendingFile = task.pendingFiles[0]; | 261 pendingFile = task.pendingFiles[0]; |
258 | 262 |
259 if (task.pendingDirectories.length === 1) | 263 if (task.pendingDirectories.length === 1) |
260 pendingFile = task.pendingDirectories[0]; | 264 pendingFile = task.pendingDirectories[0]; |
(...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 | 643 |
640 self.sendProgressEvent_('PROGRESS'); | 644 self.sendProgressEvent_('PROGRESS'); |
641 | 645 |
642 // We yield a few ms between copies to give the browser a chance to service | 646 // We yield a few ms between copies to give the browser a chance to service |
643 // events (like perhaps the user clicking to cancel the copy, for example). | 647 // events (like perhaps the user clicking to cancel the copy, for example). |
644 setTimeout(function() { | 648 setTimeout(function() { |
645 self.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); | 649 self.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); |
646 }, 10); | 650 }, 10); |
647 } | 651 } |
648 | 652 |
649 this.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); | 653 if (!task.zip) |
| 654 this.serviceNextTaskEntry_(task, onEntryServiced, errorCallback); |
| 655 else |
| 656 this.serviceZipTask_(task, onTaskComplete, errorCallback); |
650 }; | 657 }; |
651 | 658 |
652 /** | 659 /** |
653 * Service the next entry in a given task. | 660 * Service the next entry in a given task. |
654 * TODO(olege): Refactor this method into a separate class. | 661 * TODO(olege): Refactor this method into a separate class. |
655 * | 662 * |
656 * @private | 663 * @private |
657 * @param {FileManager.Task} task A task. | 664 * @param {FileManager.Task} task A task. |
658 * @param {Function} successCallback On success. | 665 * @param {Function} successCallback On success. |
659 * @param {Function} errorCallback On error. | 666 * @param {Function} errorCallback On error. |
(...skipping 309 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
969 // Check to see if the target exists. This kicks off the rest of the copy | 976 // Check to see if the target exists. This kicks off the rest of the copy |
970 // if the target is not found, or raises an error if it does. | 977 // if the target is not found, or raises an error if it does. |
971 util.resolvePath(targetDirEntry, targetRelativePath, onTargetExists, | 978 util.resolvePath(targetDirEntry, targetRelativePath, onTargetExists, |
972 onTargetNotResolved); | 979 onTargetNotResolved); |
973 } | 980 } |
974 | 981 |
975 tryNextCopy(); | 982 tryNextCopy(); |
976 }; | 983 }; |
977 | 984 |
978 /** | 985 /** |
| 986 * Service a zip file creation task. |
| 987 * |
| 988 * @private |
| 989 * @param {FileManager.Task} task A task. |
| 990 * @param {Function} completeCallback On complete. |
| 991 * @param {Function} errorCallback On error. |
| 992 */ |
| 993 FileCopyManager.prototype.serviceZipTask_ = function(task, completeCallback, |
| 994 errorCallback) { |
| 995 var self = this; |
| 996 var dirURL = task.sourceDirEntry.toURL(); |
| 997 var selectionURLs = []; |
| 998 for (var i = 0; i < task.pendingDirectories.length; i++) |
| 999 selectionURLs.push(task.pendingDirectories[i].toURL()); |
| 1000 for (var i = 0; i < task.pendingFiles.length; i++) |
| 1001 selectionURLs.push(task.pendingFiles[i].toURL()); |
| 1002 |
| 1003 var destName = 'Archive'; |
| 1004 if (task.originalEntries.length == 1) { |
| 1005 var entryPath = task.originalEntries[0].fullPath; |
| 1006 var i = entryPath.lastIndexOf('/'); |
| 1007 var basename = (i < 0) ? entryPath : entryPath.substr(i + 1); |
| 1008 i = basename.lastIndexOf('.'); |
| 1009 destName = ((i < 0) ? basename : basename.substr(0, i)); |
| 1010 } |
| 1011 |
| 1012 var copyNumber = 0; |
| 1013 var firstExistingEntry = null; |
| 1014 var destPath = destName + '.zip'; |
| 1015 |
| 1016 function onError(reason, data) { |
| 1017 self.log_('serviceZipTask error: ' + reason + ':', data); |
| 1018 errorCallback(new FileCopyManager.Error(reason, data)); |
| 1019 } |
| 1020 |
| 1021 function onTargetExists(existingEntry) { |
| 1022 if (copyNumber < 10) { |
| 1023 if (!firstExistingEntry) |
| 1024 firstExistingEntry = existingEntry; |
| 1025 copyNumber++; |
| 1026 tryZipSelection(); |
| 1027 } else { |
| 1028 onError('TARGET_EXISTS', firstExistingEntry); |
| 1029 } |
| 1030 } |
| 1031 |
| 1032 function onTargetNotResolved() { |
| 1033 function onZipSelectionComplete(success) { |
| 1034 if (success) { |
| 1035 self.sendProgressEvent_('SUCCESS'); |
| 1036 } else { |
| 1037 self.sendProgressEvent_('ERROR', |
| 1038 new FileCopyManager.Error('FILESYSTEM_ERROR', '')); |
| 1039 } |
| 1040 completeCallback(task); |
| 1041 } |
| 1042 |
| 1043 self.sendProgressEvent_('PROGRESS'); |
| 1044 chrome.fileBrowserPrivate.zipSelection(dirURL, selectionURLs, destPath, |
| 1045 onZipSelectionComplete); |
| 1046 } |
| 1047 |
| 1048 function tryZipSelection() { |
| 1049 if (copyNumber > 0) |
| 1050 destPath = destName + ' (' + copyNumber + ').zip'; |
| 1051 |
| 1052 // Check if the target exists. This kicks off the rest of the zip file |
| 1053 // creation if the target is not found, or raises an error if it does. |
| 1054 util.resolvePath(task.targetDirEntry, destPath, onTargetExists, |
| 1055 onTargetNotResolved); |
| 1056 } |
| 1057 |
| 1058 tryZipSelection(); |
| 1059 }; |
| 1060 |
| 1061 /** |
979 * Copy the contents of sourceEntry into targetEntry. | 1062 * Copy the contents of sourceEntry into targetEntry. |
980 * | 1063 * |
981 * @private | 1064 * @private |
982 * @param {Entry} sourceEntry entry that will be copied. | 1065 * @param {Entry} sourceEntry entry that will be copied. |
983 * @param {Entry} targetEntry entry to which sourceEntry will be copied. | 1066 * @param {Entry} targetEntry entry to which sourceEntry will be copied. |
984 * @param {function(Entry, number)} progressCallback function that will be | 1067 * @param {function(Entry, number)} progressCallback function that will be |
985 * called when a part of the source entry is copied. It takes |targetEntry| | 1068 * called when a part of the source entry is copied. It takes |targetEntry| |
986 * and size of the last copied chunk as parameters. | 1069 * and size of the last copied chunk as parameters. |
987 * @param {function(Entry, number)} successCallback function that will be called | 1070 * @param {function(Entry, number)} successCallback function that will be called |
988 * the copy operation finishes. It takes |targetEntry| and size of the last | 1071 * the copy operation finishes. It takes |targetEntry| and size of the last |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1056 timeout: setTimeout(this.forceDeleteTask.bind(this, id), | 1139 timeout: setTimeout(this.forceDeleteTask.bind(this, id), |
1057 FileCopyManager.DELETE_TIMEOUT) | 1140 FileCopyManager.DELETE_TIMEOUT) |
1058 }; | 1141 }; |
1059 this.deleteTasks_.push(task); | 1142 this.deleteTasks_.push(task); |
1060 this.maybeScheduleCloseBackgroundPage_(); | 1143 this.maybeScheduleCloseBackgroundPage_(); |
1061 callback(id); | 1144 callback(id); |
1062 this.sendDeleteEvent_(task, 'SCHEDULED'); | 1145 this.sendDeleteEvent_(task, 'SCHEDULED'); |
1063 }; | 1146 }; |
1064 | 1147 |
1065 /** | 1148 /** |
| 1149 * Creates a zip file for the selection of files. |
| 1150 * @param {Entry} dirEntry the directory containing the selection. |
| 1151 * @param {boolean} isOnGData If directory is on GDrive. |
| 1152 * @param {Array.<Entry>} selectionEntries the selected entries. |
| 1153 */ |
| 1154 FileCopyManager.prototype.zipSelection = function(dirEntry, isOnGData, |
| 1155 selectionEntries) { |
| 1156 var self = this; |
| 1157 var zipTask = new FileCopyManager.Task(dirEntry, dirEntry); |
| 1158 zipTask.zip = true; |
| 1159 zipTask.sourceOnGData = isOnGData; |
| 1160 zipTask.targetOnGData = isOnGData; |
| 1161 zipTask.setEntries(selectionEntries, function() { |
| 1162 // TODO: per-entry zip progress update with accurate byte count. |
| 1163 // For now just set pendingBytes to zero so that the progress bar is full. |
| 1164 zipTask.pendingBytes = 0; |
| 1165 self.copyTasks_.push(zipTask); |
| 1166 if (self.copyTasks_.length == 1) { |
| 1167 // Assume self.cancelRequested_ == false. |
| 1168 // This moved us from 0 to 1 active tasks, let the servicing begin! |
| 1169 self.serviceAllTasks_(); |
| 1170 } else { |
| 1171 // Force to update the progress of butter bar when there are new tasks |
| 1172 // coming while servicing current task. |
| 1173 self.sendProgressEvent_('PROGRESS'); |
| 1174 } |
| 1175 }); |
| 1176 }; |
| 1177 |
| 1178 /** |
1066 * Force deletion before timeout runs out. | 1179 * Force deletion before timeout runs out. |
1067 * @param {number} id The delete task id (as returned by deleteEntries). | 1180 * @param {number} id The delete task id (as returned by deleteEntries). |
1068 */ | 1181 */ |
1069 FileCopyManager.prototype.forceDeleteTask = function(id) { | 1182 FileCopyManager.prototype.forceDeleteTask = function(id) { |
1070 var task = this.findDeleteTaskAndCancelTimeout_(id); | 1183 var task = this.findDeleteTaskAndCancelTimeout_(id); |
1071 if (task) this.serviceDeleteTask_(task); | 1184 if (task) this.serviceDeleteTask_(task); |
1072 }; | 1185 }; |
1073 | 1186 |
1074 /** | 1187 /** |
1075 * Cancels the scheduled deletion. | 1188 * Cancels the scheduled deletion. |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1133 */ | 1246 */ |
1134 FileCopyManager.prototype.sendDeleteEvent_ = function(task, reason) { | 1247 FileCopyManager.prototype.sendDeleteEvent_ = function(task, reason) { |
1135 this.sendEvent_('delete', { | 1248 this.sendEvent_('delete', { |
1136 reason: reason, | 1249 reason: reason, |
1137 id: task.id, | 1250 id: task.id, |
1138 urls: task.entries.map(function(e) { | 1251 urls: task.entries.map(function(e) { |
1139 return util.makeFilesystemUrl(e.fullPath); | 1252 return util.makeFilesystemUrl(e.fullPath); |
1140 }) | 1253 }) |
1141 }); | 1254 }); |
1142 }; | 1255 }; |
OLD | NEW |