blob: 07ee39cf84520001fb127dd679f6b142ff16cd17 [file] [log] [blame]
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Protocol + host parts of extension URL.
* @type {string}
* @const
*/
var FILE_MANAGER_HOST = 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj';
// All of these scripts could be imported with a single call to importScripts,
// but then load and compile time errors would all be reported from the same
// line.
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/metadata_parser.js');
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/byte_reader.js');
importScripts(FILE_MANAGER_HOST + '/common/js/util.js');
/**
* Dispatches metadata requests to the correct parser.
*
* @param {Object} port Worker port.
* @constructor
*/
function MetadataDispatcher(port) {
this.port_ = port;
this.port_.onmessage = this.onMessage.bind(this);
// Make sure to update component_extension_resources.grd
// when adding new parsers.
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/exif_parser.js');
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/image_parsers.js');
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/mpeg_parser.js');
importScripts(FILE_MANAGER_HOST + '/foreground/js/metadata/id3_parser.js');
var patterns = [];
this.parserInstances_ = [];
for (var i = 0; i < MetadataDispatcher.parserClasses_.length; i++) {
var parserClass = MetadataDispatcher.parserClasses_[i];
var parser = new parserClass(this);
this.parserInstances_.push(parser);
patterns.push(parser.urlFilter.source);
}
this.parserRegexp_ = new RegExp('(' + patterns.join('|') + ')', 'i');
this.messageHandlers_ = {
init: this.init_.bind(this),
request: this.request_.bind(this)
};
}
/**
* List of registered parser classes.
* @private
*/
MetadataDispatcher.parserClasses_ = [];
/**
* @param {function} parserClass Parser constructor function.
*/
MetadataDispatcher.registerParserClass = function(parserClass) {
MetadataDispatcher.parserClasses_.push(parserClass);
};
/**
* Verbose logging for the dispatcher.
*
* Individual parsers also take this as their default verbosity setting.
*/
MetadataDispatcher.prototype.verbose = false;
/**
* |init| message handler.
* @private
*/
MetadataDispatcher.prototype.init_ = function() {
// Inform our owner that we're done initializing.
// If we need to pass more data back, we can add it to the param array.
this.postMessage('initialized', [this.parserRegexp_]);
this.log('initialized with URL filter ' + this.parserRegexp_);
};
/**
* |request| message handler.
* @param {string} fileURL File URL.
* @private
*/
MetadataDispatcher.prototype.request_ = function(fileURL) {
try {
this.processOneFile(fileURL, function callback(metadata) {
this.postMessage('result', [fileURL, metadata]);
}.bind(this));
} catch (ex) {
this.error(fileURL, ex);
}
};
/**
* Indicate to the caller that an operation has failed.
*
* No other messages relating to the failed operation should be sent.
* @param {...Object} var_args Arguments.
*/
MetadataDispatcher.prototype.error = function(var_args) {
var ary = Array.apply(null, arguments);
this.postMessage('error', ary);
};
/**
* Send a log message to the caller.
*
* Callers must not parse log messages for control flow.
* @param {...Object} var_args Arguments.
*/
MetadataDispatcher.prototype.log = function(var_args) {
var ary = Array.apply(null, arguments);
this.postMessage('log', ary);
};
/**
* Send a log message to the caller only if this.verbose is true.
* @param {...Object} var_args Arguments.
*/
MetadataDispatcher.prototype.vlog = function(var_args) {
if (this.verbose)
this.log.apply(this, arguments);
};
/**
* Post a properly formatted message to the caller.
* @param {string} verb Message type descriptor.
* @param {Array.<Object>} args Arguments array.
*/
MetadataDispatcher.prototype.postMessage = function(verb, args) {
this.port_.postMessage({verb: verb, arguments: args});
};
/**
* Message handler.
* @param {Event} event Event object.
*/
MetadataDispatcher.prototype.onMessage = function(event) {
var data = event.data;
if (this.messageHandlers_.hasOwnProperty(data.verb)) {
this.messageHandlers_[data.verb].apply(this, data.arguments);
} else {
this.log('Unknown message from client: ' + data.verb, data);
}
};
/**
* @param {string} fileURL File URL.
* @param {function(Object)} callback Completion callback.
*/
MetadataDispatcher.prototype.processOneFile = function(fileURL, callback) {
var self = this;
var currentStep = -1;
function nextStep(var_args) {
self.vlog('nextStep: ' + steps[currentStep + 1].name);
steps[++currentStep].apply(self, arguments);
}
var metadata;
function onError(err, stepName) {
self.error(fileURL, stepName || steps[currentStep].name, err.toString(),
metadata);
}
var steps = [
// Step one, find the parser matching the url.
function detectFormat() {
for (var i = 0; i != self.parserInstances_.length; i++) {
var parser = self.parserInstances_[i];
if (fileURL.match(parser.urlFilter)) {
// Create the metadata object as early as possible so that we can
// pass it with the error message.
metadata = parser.createDefaultMetadata();
nextStep(parser);
return;
}
}
onError('unsupported format');
},
// Step two, turn the url into an entry.
function getEntry(parser) {
webkitResolveLocalFileSystemURL(
fileURL,
function(entry) { nextStep(entry, parser) },
onError);
},
// Step three, turn the entry into a file.
function getFile(entry, parser) {
entry.file(function(file) { nextStep(file, parser) }, onError);
},
// Step four, parse the file content.
function parseContent(file, parser) {
metadata.fileSize = file.size;
try {
parser.parse(file, metadata, callback, onError);
} catch (e) {
onError(e.stack);
}
}
];
nextStep();
};
// Webworker spec says that the worker global object is called self. That's
// a terrible name since we use it all over the chrome codebase to capture
// the 'this' keyword in lambdas.
var global = self;
if (global.constructor.name == 'SharedWorkerGlobalScope') {
global.addEventListener('connect', function(e) {
var port = e.ports[0];
new MetadataDispatcher(port);
port.start();
});
} else {
// Non-shared worker.
new MetadataDispatcher(global);
}