Index: ui/file_manager/file_manager/background/js/duplicate_finder.js |
diff --git a/ui/file_manager/file_manager/background/js/duplicate_finder.js b/ui/file_manager/file_manager/background/js/duplicate_finder.js |
index ce797e677d92e339a6db3c1902567c4335feb48f..7a0e54eb840438bd099ed7d7893933ae7af692b5 100644 |
--- a/ui/file_manager/file_manager/background/js/duplicate_finder.js |
+++ b/ui/file_manager/file_manager/background/js/duplicate_finder.js |
@@ -6,63 +6,27 @@ |
var importer = importer || {}; |
/** |
- * Interface for import deduplicators. A duplicate finder is linked to an |
- * import destination, and will check whether files already exist in that import |
- * destination. |
- * @interface |
- */ |
-importer.DuplicateFinder = function() {}; |
- |
-/** |
- * Checks whether the given file already exists in the import destination. |
- * @param {!FileEntry} entry The file entry to check. |
- * @return {!Promise<boolean>} |
- */ |
-importer.DuplicateFinder.prototype.checkDuplicate; |
- |
-/** |
- * A factory for producing duplicate finders. |
- * @interface |
- */ |
-importer.DuplicateFinder.Factory = function() {}; |
- |
-/** @return {!importer.DuplicateFinder} */ |
-importer.DuplicateFinder.Factory.prototype.create; |
- |
-/** |
* A duplicate finder for Google Drive. |
* |
* @constructor |
- * @implements {importer.DuplicateFinder} |
* @struct |
+ * |
+ * @param {!analytics.Tracker} tracker |
*/ |
-importer.DriveDuplicateFinder = function() { |
+importer.DriveDuplicateFinder = function(tracker) { |
+ |
+ /** @private {!analytics.Tracker} */ |
+ this.tracker_ = tracker; |
+ |
/** @private {Promise<string>} */ |
this.driveIdPromise_ = null; |
- |
- /** |
- * Aggregate time spent computing content hashes (in ms). |
- * @private {number} |
- */ |
- this.computeHashTime_ = 0; |
- |
- /** |
- * Aggregate time spent performing content hash searches (in ms). |
- * @private {number} |
- */ |
- this.searchHashTime_ = 0; |
}; |
/** |
- * @typedef {{ |
- * computeHashTime: number, |
- * searchHashTime: number |
- * }} |
+ * @param {!FileEntry} entry |
+ * @return {!Promise<boolean>} |
*/ |
-importer.DriveDuplicateFinder.Statistics; |
- |
-/** @override */ |
-importer.DriveDuplicateFinder.prototype.checkDuplicate = function(entry) { |
+importer.DriveDuplicateFinder.prototype.isDuplicate = function(entry) { |
return this.computeHash_(entry) |
.then(this.findByHash_.bind(this)) |
.then( |
@@ -75,6 +39,12 @@ importer.DriveDuplicateFinder.prototype.checkDuplicate = function(entry) { |
}); |
}; |
+/** @private @const {number} */ |
+importer.DriveDuplicateFinder.HASH_EVENT_THRESHOLD_ = 5000; |
+ |
+/** @private @const {number} */ |
+importer.DriveDuplicateFinder.SEARCH_EVENT_THRESHOLD_ = 1000; |
+ |
/** |
* Computes the content hash for the given file entry. |
* @param {!FileEntry} entry |
@@ -87,16 +57,27 @@ importer.DriveDuplicateFinder.prototype.computeHash_ = function(entry) { |
var startTime = new Date().getTime(); |
chrome.fileManagerPrivate.computeChecksum( |
entry.toURL(), |
- /** @param {string} result The content hash. */ |
+ /** |
+ * @param {string} result The content hash. |
+ * @this {importer.DriveDuplicateFinder} |
+ */ |
function(result) { |
- var endTime = new Date().getTime(); |
- this.searchHashTime_ += endTime - startTime; |
+ var elapsedTime = new Date().getTime() - startTime; |
+ // Send the timing to GA only if it is sorta exceptionally long. |
+ // A one second, CPU intensive operation, is pretty long. |
+ if (elapsedTime >= |
+ importer.DriveDuplicateFinder.HASH_EVENT_THRESHOLD_) { |
+ this.tracker_.sendTiming( |
+ metrics.Categories.ACQUISITION, |
+ metrics.timing.Variables.COMPUTE_HASH, |
+ elapsedTime); |
+ } |
if (chrome.runtime.lastError) { |
reject(chrome.runtime.lastError); |
} else { |
resolve(result); |
} |
- }); |
+ }.bind(this)); |
}.bind(this)); |
}; |
@@ -152,8 +133,15 @@ importer.DriveDuplicateFinder.prototype.searchFilesByHash_ = |
* @this {importer.DriveDuplicateFinder} |
*/ |
function(urls) { |
- var endTime = new Date().getTime(); |
- this.searchHashTime_ += endTime - startTime; |
+ var elapsedTime = new Date().getTime() - startTime; |
+ // Send the timing to GA only if it is sorta exceptionally long. |
+ if (elapsedTime >= |
+ importer.DriveDuplicateFinder.SEARCH_EVENT_THRESHOLD_) { |
+ this.tracker_.sendTiming( |
+ metrics.Categories.ACQUISITION, |
+ metrics.timing.Variables.SEARCH_BY_HASH, |
+ elapsedTime); |
+ } |
if (chrome.runtime.lastError) { |
reject(chrome.runtime.lastError); |
} else { |
@@ -163,21 +151,110 @@ importer.DriveDuplicateFinder.prototype.searchFilesByHash_ = |
}.bind(this)); |
}; |
-/** @return {!importer.DriveDuplicateFinder.Statistics} */ |
-importer.DriveDuplicateFinder.prototype.getStatistics = function() { |
- return { |
- computeHashTime: this.computeHashTime_, |
- searchHashTime: this.searchHashTime_ |
- }; |
+/** |
+ * A class that aggregates history/content-dupe checking |
+ * into a single "Disposition" value. Should now be the |
+ * primary source for duplicate checking (with the exception |
+ * of in-scan deduplication, where duplicate results that |
+ * are within the scan are ignored). |
+ * |
+ * @constructor |
+ * |
+ * @param {!importer.HistoryLoader} historyLoader |
+ * @param {!importer.DriveDuplicateFinder} contentMatcher |
+ */ |
+importer.DispositionChecker = function(historyLoader, contentMatcher) { |
+ /** @private {!importer.HistoryLoader} */ |
+ this.historyLoader_ = historyLoader; |
+ |
+ /** @private {!importer.DriveDuplicateFinder} */ |
+ this.contentMatcher_ = contentMatcher; |
}; |
/** |
- * @constructor |
- * @implements {importer.DuplicateFinder.Factory} |
+ * @param {!FileEntry} entry |
+ * @param {!importer.Destination} destination |
+ * @return {!Promise<!importer.Disposition>} |
*/ |
-importer.DriveDuplicateFinder.Factory = function() {}; |
+importer.DispositionChecker.prototype.getDisposition = |
+ function(entry, destination) { |
+ if (destination !== importer.Destination.GOOGLE_DRIVE) { |
+ return Promise.reject('Unsupported destination: ' + destination); |
+ } |
+ |
+ return new Promise( |
+ /** @this {importer.DispositionChecker} */ |
+ function(resolve, reject) { |
+ this.hasHistoryDuplicate_(entry, destination) |
+ .then( |
+ /** |
+ * @param {boolean} duplicate |
+ * @this {importer.DispositionChecker} |
+ */ |
+ function(duplicate) { |
+ if (duplicate) { |
+ resolve(importer.Disposition.HISTORY_DUPLICATE); |
+ } else { |
+ this.contentMatcher_.isDuplicate(entry) |
+ .then( |
+ /** @param {boolean} duplicate */ |
+ function(duplicate) { |
+ if (duplicate) { |
+ resolve( |
+ importer.Disposition.CONTENT_DUPLICATE); |
+ } else { |
+ resolve(importer.Disposition.ORIGINAL); |
+ } |
+ }); |
+ } |
+ }.bind(this)); |
+ }.bind(this)); |
+}; |
-/** @override */ |
-importer.DriveDuplicateFinder.Factory.prototype.create = function() { |
- return new importer.DriveDuplicateFinder(); |
+/** |
+ * @param {!FileEntry} entry |
+ * @param {!importer.Destination} destination |
+ * @return {!Promise.<boolean>} True if there is a history-entry-duplicate |
+ * for the file. |
+ * @private |
+ */ |
+importer.DispositionChecker.prototype.hasHistoryDuplicate_ = |
+ function(entry, destination) { |
+ return this.historyLoader_.getHistory() |
+ .then( |
+ /** |
+ * @param {!importer.ImportHistory} history |
+ * @return {!Promise} |
+ * @this {importer.DefaultMediaScanner} |
+ */ |
+ function(history) { |
+ return Promise.all([ |
+ history.wasCopied(entry, destination), |
+ history.wasImported(entry, destination) |
+ ]).then( |
+ /** |
+ * @param {!Array<boolean>} results |
+ * @return {boolean} |
+ */ |
+ function(results) { |
+ return results[0] || results[1]; |
+ }); |
+ }.bind(this)); |
+}; |
+ |
+/** |
+ * Factory for a function that returns an entry's disposition. |
+ * |
+ * @param {!importer.HistoryLoader} historyLoader |
+ * @param {!analytics.Tracker} tracker |
+ * |
+ * @return {function(!FileEntry, !importer.Destination): |
+ * !Promise<!importer.Disposition>} |
+ */ |
+importer.DispositionChecker.createChecker = |
+ function(historyLoader, tracker) { |
+ var checker = new importer.DispositionChecker( |
+ historyLoader, |
+ new importer.DriveDuplicateFinder(tracker)); |
+ return checker.getDisposition.bind(checker); |
}; |