OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 // Namespace | 5 // Namespace |
6 var importer = importer || {}; | 6 var importer = importer || {}; |
7 | 7 |
8 /** | 8 /** |
9 * Interface providing access to information about active import processes. | 9 * Interface providing access to information about active import processes. |
10 * | 10 * |
(...skipping 14 matching lines...) Expand all Loading... |
25 | 25 |
26 /** | 26 /** |
27 * Handler for importing media from removable devices into the user's Drive. | 27 * Handler for importing media from removable devices into the user's Drive. |
28 * | 28 * |
29 * @constructor | 29 * @constructor |
30 * @implements {importer.ImportRunner} | 30 * @implements {importer.ImportRunner} |
31 * @struct | 31 * @struct |
32 * | 32 * |
33 * @param {!ProgressCenter} progressCenter | 33 * @param {!ProgressCenter} progressCenter |
34 * @param {!importer.HistoryLoader} historyLoader | 34 * @param {!importer.HistoryLoader} historyLoader |
35 * @param {!importer.DuplicateFinder.Factory} duplicateFinderFactory | |
36 * @param {!analytics.Tracker} tracker | 35 * @param {!analytics.Tracker} tracker |
37 */ | 36 */ |
38 importer.MediaImportHandler = | 37 importer.MediaImportHandler = function(progressCenter, historyLoader, tracker) { |
39 function(progressCenter, historyLoader, duplicateFinderFactory, tracker) { | |
40 /** @private {!ProgressCenter} */ | 38 /** @private {!ProgressCenter} */ |
41 this.progressCenter_ = progressCenter; | 39 this.progressCenter_ = progressCenter; |
42 | 40 |
43 /** @private {!importer.HistoryLoader} */ | 41 /** @private {!importer.HistoryLoader} */ |
44 this.historyLoader_ = historyLoader; | 42 this.historyLoader_ = historyLoader; |
45 | 43 |
46 /** @private {!importer.TaskQueue} */ | 44 /** @private {!importer.TaskQueue} */ |
47 this.queue_ = new importer.TaskQueue(); | 45 this.queue_ = new importer.TaskQueue(); |
48 | 46 |
49 // Prevent the system from sleeping while imports are active. | 47 // Prevent the system from sleeping while imports are active. |
50 this.queue_.setActiveCallback(function() { | 48 this.queue_.setActiveCallback(function() { |
51 chrome.power.requestKeepAwake('system'); | 49 chrome.power.requestKeepAwake('system'); |
52 }); | 50 }); |
53 this.queue_.setIdleCallback(function() { | 51 this.queue_.setIdleCallback(function() { |
54 chrome.power.releaseKeepAwake(); | 52 chrome.power.releaseKeepAwake(); |
55 }); | 53 }); |
56 | 54 |
57 /** @private {!importer.DuplicateFinder.Factory} */ | |
58 this.duplicateFinderFactory_ = duplicateFinderFactory; | |
59 | |
60 /** @private {!analytics.Tracker} */ | 55 /** @private {!analytics.Tracker} */ |
61 this.tracker_ = tracker; | 56 this.tracker_ = tracker; |
62 | 57 |
63 /** @private {number} */ | 58 /** @private {number} */ |
64 this.nextTaskId_ = 0; | 59 this.nextTaskId_ = 0; |
65 }; | 60 }; |
66 | 61 |
67 /** @override */ | 62 /** @override */ |
68 importer.MediaImportHandler.prototype.importFromScanResult = | 63 importer.MediaImportHandler.prototype.importFromScanResult = |
69 function(scanResult, destination, directoryPromise) { | 64 function(scanResult, destination, directoryPromise) { |
70 | 65 |
71 var task = new importer.MediaImportHandler.ImportTask( | 66 var task = new importer.MediaImportHandler.ImportTask( |
72 this.generateTaskId_(), | 67 this.generateTaskId_(), |
73 this.historyLoader_, | 68 this.historyLoader_, |
74 scanResult, | 69 scanResult, |
75 directoryPromise, | 70 directoryPromise, |
76 this.duplicateFinderFactory_.create(), | |
77 destination, | 71 destination, |
78 this.tracker_); | 72 this.tracker_); |
79 | 73 |
80 task.addObserver(this.onTaskProgress_.bind(this, task)); | 74 task.addObserver(this.onTaskProgress_.bind(this, task)); |
81 | 75 |
82 this.queue_.queueTask(task); | 76 this.queue_.queueTask(task); |
83 | 77 |
84 return task; | 78 return task; |
85 }; | 79 }; |
86 | 80 |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 * FileOperationManager.CopyTask) but this is a temporary state of affairs. | 142 * FileOperationManager.CopyTask) but this is a temporary state of affairs. |
149 * | 143 * |
150 * @constructor | 144 * @constructor |
151 * @extends {importer.TaskQueue.BaseTask} | 145 * @extends {importer.TaskQueue.BaseTask} |
152 * @struct | 146 * @struct |
153 * | 147 * |
154 * @param {string} taskId | 148 * @param {string} taskId |
155 * @param {!importer.HistoryLoader} historyLoader | 149 * @param {!importer.HistoryLoader} historyLoader |
156 * @param {!importer.ScanResult} scanResult | 150 * @param {!importer.ScanResult} scanResult |
157 * @param {!Promise<!DirectoryEntry>} directoryPromise | 151 * @param {!Promise<!DirectoryEntry>} directoryPromise |
158 * @param {!importer.DuplicateFinder} duplicateFinder A duplicate-finder linked | |
159 * to the import destination, that will be used to deduplicate imports. | |
160 * @param {!importer.Destination} destination The logical destination. | 152 * @param {!importer.Destination} destination The logical destination. |
161 * @param {!analytics.Tracker} tracker | 153 * @param {!analytics.Tracker} tracker |
162 */ | 154 */ |
163 importer.MediaImportHandler.ImportTask = function( | 155 importer.MediaImportHandler.ImportTask = function( |
164 taskId, | 156 taskId, |
165 historyLoader, | 157 historyLoader, |
166 scanResult, | 158 scanResult, |
167 directoryPromise, | 159 directoryPromise, |
168 duplicateFinder, | |
169 destination, | 160 destination, |
170 tracker) { | 161 tracker) { |
171 | 162 |
172 importer.TaskQueue.BaseTask.call(this, taskId); | 163 importer.TaskQueue.BaseTask.call(this, taskId); |
173 /** @private {string} */ | 164 /** @private {string} */ |
174 this.taskId_ = taskId; | 165 this.taskId_ = taskId; |
175 | 166 |
176 /** @private {!importer.Destination} */ | 167 /** @private {!importer.Destination} */ |
177 this.destination_ = destination; | 168 this.destination_ = destination; |
178 | 169 |
179 /** @private {!Promise<!DirectoryEntry>} */ | 170 /** @private {!Promise<!DirectoryEntry>} */ |
180 this.directoryPromise_ = directoryPromise; | 171 this.directoryPromise_ = directoryPromise; |
181 | 172 |
182 /** @private {!importer.DuplicateFinder} */ | |
183 this.deduplicator_ = duplicateFinder; | |
184 | |
185 /** @private {!importer.ScanResult} */ | 173 /** @private {!importer.ScanResult} */ |
186 this.scanResult_ = scanResult; | 174 this.scanResult_ = scanResult; |
187 | 175 |
188 /** @private {!importer.HistoryLoader} */ | 176 /** @private {!importer.HistoryLoader} */ |
189 this.historyLoader_ = historyLoader; | 177 this.historyLoader_ = historyLoader; |
190 | 178 |
191 /** @private {!analytics.Tracker} */ | 179 /** @private {!analytics.Tracker} */ |
192 this.tracker_ = tracker; | 180 this.tracker_ = tracker; |
193 | 181 |
194 /** @private {number} */ | 182 /** @private {number} */ |
195 this.totalBytes_ = 0; | 183 this.totalBytes_ = 0; |
196 | 184 |
197 /** @private {number} */ | 185 /** @private {number} */ |
198 this.processedBytes_ = 0; | 186 this.processedBytes_ = 0; |
199 | 187 |
200 /** @private {number} */ | 188 /** @private {number} */ |
201 this.remainingFilesCount_ = 0; | 189 this.remainingFilesCount_ = 0; |
202 | 190 |
203 /** @private {?function()} */ | 191 /** @private {?function()} */ |
204 this.cancelCallback_ = null; | 192 this.cancelCallback_ = null; |
205 | 193 |
206 /** @private {boolean} Indicates whether this task was canceled. */ | 194 /** @private {boolean} Indicates whether this task was canceled. */ |
207 this.canceled_ = false; | 195 this.canceled_ = false; |
208 | 196 |
209 /** @private {number} Number of files deduped by content dedupe. */ | |
210 this.dedupeCount_ = 0; | |
211 | |
212 /** @private {number} */ | 197 /** @private {number} */ |
213 this.errorCount_ = 0; | 198 this.errorCount_ = 0; |
214 }; | 199 }; |
215 | 200 |
216 /** @struct */ | 201 /** @struct */ |
217 importer.MediaImportHandler.ImportTask.prototype = { | 202 importer.MediaImportHandler.ImportTask.prototype = { |
218 /** @return {number} Number of imported bytes */ | 203 /** @return {number} Number of imported bytes */ |
219 get processedBytes() { return this.processedBytes_; }, | 204 get processedBytes() { return this.processedBytes_; }, |
220 | 205 |
221 /** @return {number} Total number of bytes to import */ | 206 /** @return {number} Total number of bytes to import */ |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
270 // (including calling #requestCancel again). | 255 // (including calling #requestCancel again). |
271 var cancelCallback = this.cancelCallback_; | 256 var cancelCallback = this.cancelCallback_; |
272 this.cancelCallback_ = null; | 257 this.cancelCallback_ = null; |
273 cancelCallback(); | 258 cancelCallback(); |
274 } | 259 } |
275 }; | 260 }; |
276 | 261 |
277 /** @private */ | 262 /** @private */ |
278 importer.MediaImportHandler.ImportTask.prototype.initialize_ = function() { | 263 importer.MediaImportHandler.ImportTask.prototype.initialize_ = function() { |
279 var stats = this.scanResult_.getStatistics(); | 264 var stats = this.scanResult_.getStatistics(); |
280 | |
281 this.remainingFilesCount_ = stats.newFileCount; | 265 this.remainingFilesCount_ = stats.newFileCount; |
282 this.totalBytes_ = stats.sizeBytes; | 266 this.totalBytes_ = stats.sizeBytes; |
283 this.notify(importer.TaskQueue.UpdateType.PROGRESS); | 267 this.notify(importer.TaskQueue.UpdateType.PROGRESS); |
284 | 268 |
285 this.tracker_.send(metrics.ImportEvents.STARTED); | 269 this.tracker_.send(metrics.ImportEvents.STARTED); |
286 this.tracker_.send(metrics.ImportEvents.HISTORY_DEDUPE_COUNT | |
287 .value(stats.duplicateFileCount)); | |
288 }; | 270 }; |
289 | 271 |
290 /** | 272 /** |
291 * Initiates an import to the given location. This should only be called once | 273 * Initiates an import to the given location. This should only be called once |
292 * the scan result indicates that it is ready. | 274 * the scan result indicates that it is ready. |
293 * | 275 * |
294 * @private | 276 * @private |
295 */ | 277 */ |
296 importer.MediaImportHandler.ImportTask.prototype.importScanEntries_ = | 278 importer.MediaImportHandler.ImportTask.prototype.importScanEntries_ = |
297 function() { | 279 function() { |
(...skipping 16 matching lines...) Expand all Loading... |
314 */ | 296 */ |
315 importer.MediaImportHandler.ImportTask.prototype.importOne_ = | 297 importer.MediaImportHandler.ImportTask.prototype.importOne_ = |
316 function(destinationDirectory, completionCallback, entry) { | 298 function(destinationDirectory, completionCallback, entry) { |
317 if (this.canceled_) { | 299 if (this.canceled_) { |
318 this.notify(importer.TaskQueue.UpdateType.CANCELED); | 300 this.notify(importer.TaskQueue.UpdateType.CANCELED); |
319 this.tracker_.send(metrics.ImportEvents.CANCELLED); | 301 this.tracker_.send(metrics.ImportEvents.CANCELLED); |
320 this.sendImportStats_(); | 302 this.sendImportStats_(); |
321 return; | 303 return; |
322 } | 304 } |
323 | 305 |
324 this.deduplicator_.checkDuplicate(entry) | 306 this.copy_(entry, destinationDirectory) |
325 .then( | |
326 /** @param {boolean} isDuplicate */ | |
327 function(isDuplicate) { | |
328 if (isDuplicate) { | |
329 // If the given file is a duplicate, don't import it again. Just | |
330 // update the progress indicator. | |
331 this.dedupeCount_++; | |
332 this.markAsImported_(entry); | |
333 this.processedBytes_ += entry.size; | |
334 this.notify(importer.TaskQueue.UpdateType.PROGRESS); | |
335 return Promise.resolve(); | |
336 } else { | |
337 return this.copy_(entry, destinationDirectory); | |
338 } | |
339 }.bind(this)) | |
340 // Regardless of the result of this copy, push on to the next file. | 307 // Regardless of the result of this copy, push on to the next file. |
341 .then(completionCallback) | 308 .then(completionCallback) |
342 .catch( | 309 .catch( |
343 /** @param {*} error */ | 310 /** @param {*} error */ |
344 function(error) { | 311 function(error) { |
345 importer.getLogger().catcher('import-task-import-one')(error); | 312 importer.getLogger().catcher('import-task-import-one')(error); |
346 completionCallback(); | 313 completionCallback(); |
347 }); | 314 }); |
348 }; | 315 }; |
349 | 316 |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
472 /** @param {!importer.ImportHistory} history */ | 439 /** @param {!importer.ImportHistory} history */ |
473 function(history) { | 440 function(history) { |
474 history.markImported(entry, this.destination_); | 441 history.markImported(entry, this.destination_); |
475 }.bind(this)) | 442 }.bind(this)) |
476 .catch(importer.getLogger().catcher('import-task-mark-as-imported')); | 443 .catch(importer.getLogger().catcher('import-task-mark-as-imported')); |
477 }; | 444 }; |
478 | 445 |
479 /** @private */ | 446 /** @private */ |
480 importer.MediaImportHandler.ImportTask.prototype.onSuccess_ = function() { | 447 importer.MediaImportHandler.ImportTask.prototype.onSuccess_ = function() { |
481 this.notify(importer.TaskQueue.UpdateType.COMPLETE); | 448 this.notify(importer.TaskQueue.UpdateType.COMPLETE); |
482 this.tracker_.send(metrics.ImportEvents.ENDED); | |
483 this.sendImportStats_(); | 449 this.sendImportStats_(); |
484 }; | 450 }; |
485 | 451 |
486 /** | 452 /** |
487 * Sends import statistics to analytics. | 453 * Sends import statistics to analytics. |
488 */ | 454 */ |
489 importer.MediaImportHandler.ImportTask.prototype.sendImportStats_ = function() { | 455 importer.MediaImportHandler.ImportTask.prototype.sendImportStats_ = |
| 456 function() { |
| 457 |
| 458 var scanStats = this.scanResult_.getStatistics(); |
| 459 |
490 this.tracker_.send( | 460 this.tracker_.send( |
491 metrics.ImportEvents.CONTENT_DEDUPE_COUNT | 461 metrics.ImportEvents.MEGABYTES_IMPORTED.value( |
492 .value(this.dedupeCount_)); | 462 // Make megabytes of our bytes. |
493 // TODO(kenobi): Send correct import byte counts. | 463 Math.floor(this.processedBytes_ / (1024 * 1024)))); |
494 var importFileCount = this.scanResult_.getStatistics().newFileCount - | 464 |
495 (this.dedupeCount_ + this.remainingFilesCount_); | |
496 this.tracker_.send( | 465 this.tracker_.send( |
497 metrics.ImportEvents.FILE_COUNT | 466 metrics.ImportEvents.FILES_IMPORTED.value( |
498 .value(importFileCount)); | 467 // Substract the remaining files, in case the task was cancelled. |
| 468 scanStats.newFileCount - this.remainingFilesCount_)); |
499 | 469 |
500 this.tracker_.send(metrics.ImportEvents.ERROR.value(this.errorCount_)); | 470 if (this.errorCount_ > 0) { |
| 471 this.tracker_.send(metrics.ImportEvents.ERRORS.value(this.errorCount_)); |
| 472 } |
501 | 473 |
502 // Send aggregate deduplication timings, to avoid flooding analytics with one | 474 // Finally we want to report on the number of duplicates |
503 // timing per file. | 475 // that were identified during scanning. |
504 var deduplicatorStats = this.deduplicator_.getStatistics(); | 476 var totalDeduped = 0; |
505 this.tracker_.sendTiming( | 477 Object.keys(scanStats.duplicates).forEach( |
506 metrics.Categories.ACQUISITION, | 478 /** |
507 metrics.timing.Variables.COMPUTE_HASH, | 479 * @param {!importer.Disposition} disposition |
508 deduplicatorStats.computeHashTime, | 480 * @this {importer.MediaImportHandler.ImportTask} |
509 'In Place'); | 481 */ |
510 this.tracker_.sendTiming( | 482 function(disposition) { |
511 metrics.Categories.ACQUISITION, | 483 var count = scanStats.duplicates[disposition]; |
512 metrics.timing.Variables.SEARCH_BY_HASH, | 484 totalDeduped += count; |
513 deduplicatorStats.searchHashTime); | 485 this.tracker_.send( |
| 486 metrics.ImportEvents.FILES_DEDUPLICATED |
| 487 .label(disposition) |
| 488 .value(count)); |
| 489 }.bind(this)); |
514 | 490 |
| 491 this.tracker_.send( |
| 492 metrics.ImportEvents.FILES_DEDUPLICATED |
| 493 .label('all-duplicates') |
| 494 .value(totalDeduped)); |
515 }; | 495 }; |
OLD | NEW |