Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(458)

Unified Diff: ui/file_manager/file_manager/background/js/media_scanner.js

Issue 795833002: MediaScanner improvements (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: pull 'n merge. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/file_manager/file_manager/background/js/media_scanner.js
diff --git a/ui/file_manager/file_manager/background/js/media_scanner.js b/ui/file_manager/file_manager/background/js/media_scanner.js
index a3acfa6890483f7cd1a805fe69f3e7c34c4e4f56..9c7e40fd596d852a83a6e5ce0aee6ec3b7e5ae1c 100644
--- a/ui/file_manager/file_manager/background/js/media_scanner.js
+++ b/ui/file_manager/file_manager/background/js/media_scanner.js
@@ -3,56 +3,234 @@
// found in the LICENSE file.
/**
+ * Class representing the results of a scan operation.
+ *
+ * @interface
+ */
+importer.MediaScanner = function() {};
+
+/**
+ * Initiates scanning.
+ *
+ * @param {!Array.<!Entry>} entries Must be non-empty.
+ * @return {!importer.ScanResult} ScanResult object representing the scan
+ * job both while in-progress and when completed.
+ */
+importer.MediaScanner.prototype.scan;
+
+/**
+ * Class representing the results of a scan operation.
+ *
+ * @interface
+ */
+importer.ScanResult = function() {};
+
+/**
+ * Returns all files entries discovered so far. The list will be
+ * complete only after scanning has completed and {@code isFinished}
+ * returns {@code true}.
+ *
+ * @return {!Array.<!FileEntry>}
+ */
+importer.ScanResult.prototype.getFileEntries;
+
+/**
+ * Returns the aggregate size, in bytes, of all FileEntries discovered
+ * during scanning.
+ *
+ * @return {number}
+ */
+importer.ScanResult.prototype.getTotalBytes;
+
+/**
+ * Returns the scan duration in milliseconds.
+ *
+ * @return {number}
+ */
+importer.ScanResult.prototype.getScanDurationMs;
+
+/**
+ * Returns a promise that fires when scanning is complete.
+ *
+ * @return {!Promise.<!importer.ScanResult>}
+ */
+importer.ScanResult.prototype.whenFinished;
+
+/**
* Recursively scans through a list of given files and directories, and creates
* a list of media files.
*
* @constructor
+ * @struct
+ * @implements {importer.MediaScanner}
+ */
+importer.DefaultMediaScanner = function() {};
+
+/** @override */
+importer.DefaultMediaScanner.prototype.scan = function(entries) {
+ if (entries.length == 0) {
+ throw new Error('Cannot scan empty list of entries.');
+ }
+
+ var scanResult = new importer.DefaultScanResult();
+ var scanPromises = entries.map(this.scanEntry_.bind(this, scanResult));
+ Promise.all(scanPromises)
+ .then(scanResult.resolveScan.bind(scanResult))
+ .catch(scanResult.rejectScan.bind(scanResult));
+
+ return scanResult;
+};
+
+/**
+ * Resolves the entry to a list of {@code FileEntry}.
+ *
+ * @param {!importer.DefaultScanResult} result
+ * @param {!Entry} entry
+ * @return {!Promise}
+ * @private
+ */
+importer.DefaultMediaScanner.prototype.scanEntry_ =
+ function(result, entry) {
+ return entry.isFile ?
+ result.addFileEntry(/** @type {!FileEntry} */ (entry)) :
+ this.scanDirectory_(result, /** @type {!DirectoryEntry} */ (entry));
+};
+
+/**
+ * Finds all files beneath directory.
+ *
+ * @param {!importer.DefaultScanResult} result
+ * @param {!DirectoryEntry} entry
+ * @return {!Promise}
+ * @private
*/
-function MediaScanner() {}
+importer.DefaultMediaScanner.prototype.scanDirectory_ =
+ function(result, entry) {
+ return new Promise(
+ function(resolve, reject) {
+ // Collect promises for all files being added to results.
+ // The directory scan promise can't resolve until all
+ // file entries are completely promised.
+ var promises = [];
+ fileOperationUtil.findFilesRecursively(
+ entry,
+ /** @param {!FileEntry} fileEntry */
+ function(fileEntry) {
+ promises.push(result.addFileEntry(fileEntry));
+ })
+ .then(
+ /** @this {importer.DefaultScanResult} */
+ function() {
+ Promise.all(promises).then(resolve).catch(reject);
+ });
+ });
+};
/**
- * Scans a list of directory and file entries, returning image and video files.
- * @param {!Array<!Entry>} entries A list of file and directory entries. File
- * entries are added directly to the media list; directory entries are
- * recursively traversed to find files, which are added to the media list.
- * @return {!Promise<!Array<!FileEntry>>}
+ * Results of a scan operation. The object is "live" in that data can and
+ * will change as the scan operation discovers files.
+ *
+ * <p>The scan is complete, and the object will become static once the
+ * {@code whenFinished} promise resolves.
+ *
+ * @constructor
+ * @struct
+ * @implements {importer.ScanResult}
*/
-MediaScanner.prototype.scan = function(entries) {
+importer.DefaultScanResult = function() {
/**
- * Returns files and directories found under the given Entry.
- * @param {!Entry} entry
- * @return {!Promise<!Array<!Entry>>}
+ * List of file entries found while scanning.
+ * @private {!Array.<!FileEntry>}
*/
- var scanRecurse = function(entry) {
- if (entry.isFile) {
- return Promise.resolve([entry]);
- } else {
- return fileOperationUtil.gatherEntriesRecursively(
- /** @type {!DirectoryEntry} */ (entry));
- }
- };
+ this.fileEntries_ = [];
+
+ /** @private {number} */
+ this.totalBytes_ = 0;
/**
- * Flattens a nested list of Entries.
- * @param {!Array<!Array<!Entry>>} array
- * @return {!Array<!Entry>}
+ * The point in time when the scan was started.
+ * @type {Date}
*/
- var flatten = function(array) {
- return array.reduce(function(prev, curr) {
- return prev.concat(curr);
- }, []);
- };
+ this.scanStarted_ = new Date();
/**
- * Filters non-image and non-video files out of the given list.
- * @param {!Array<!Entry>} array
- * @return {!Array<!FileEntry>}
+ * The point in time when the last scan activity occured.
+ * @type {Date}
*/
- var filter = function(array) {
- return array.filter(FileType.isImageOrVideo);
- };
+ this.lastScanActivity_ = this.scanStarted_;
+
+ /** @type {function()} */
+ this.resolveScan;
+
+ /** @type {function(*)} */
+ this.rejectScan;
+
+ /** @private {!Promise.<!importer.ScanResult>} */
+ this.finishedPromise_ = new Promise(
+ function(resolve, reject) {
+ this.resolveScan = function() {
+ resolve(this);
+ };
+ this.rejectScan = reject;
+ }.bind(this));
+};
+
+/** @override */
+importer.DefaultScanResult.prototype.getFileEntries = function() {
+ return this.fileEntries_;
+};
+
+/** @override */
+importer.DefaultScanResult.prototype.getTotalBytes = function() {
+ return this.totalBytes_;
+};
+
+/** @override */
+importer.DefaultScanResult.prototype.getScanDurationMs = function() {
+ return this.lastScanActivity_.getTime() - this.scanStarted_.getTime();
+};
+
+/** @override */
+importer.DefaultScanResult.prototype.whenFinished = function() {
+ return this.finishedPromise_;
+};
+
+/**
+ * Handles files discovered during scanning.
+ *
+ * @param {!FileEntry} entry
+ * @return {!Promise} Resolves once file entry has been processed
+ * and is represented in results.
+ * @private
+ */
+importer.DefaultScanResult.prototype.addFileEntry = function(entry) {
+ this.lastScanActivity_ = new Date();
- return Promise.all(entries.map(scanRecurse))
- .then(flatten)
- .then(filter);
+ if (!FileType.isImageOrVideo(entry)) {
+ return Promise.resolve();
+ }
+ return new Promise(
+ function(resolve, reject) {
+ // TODO(smckay): Update to use MetadataCache.
+ entry.getMetadata(
+ /**
+ * @param {!Metadata} metadata
+ * @this {importer.DefaultScanResult}
+ */
+ function(metadata) {
+ this.lastScanActivity_ = new Date();
+ if ('size' in metadata) {
+ this.totalBytes_ += metadata['size'];
+ this.fileEntries_.push(entry);
+ // Closure compiler currently requires an arg to resolve
+ // and reject. If this is 2015, you can probably remove it.
+ resolve(undefined);
+ } else {
+ // Closure compiler currently requires an arg to resolve
+ // and reject. If this is 2015, you can probably remove it.
+ reject(undefined);
+ }
+ }.bind(this),
+ reject);
+ }.bind(this));
};

Powered by Google App Engine
This is Rietveld 408576698