Home | History | Annotate | Download | only in metadata
      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
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 // All of these scripts could be imported with a single call to importScripts,
      8 // but then load and compile time errors would all be reported from the same
      9 // line.
     10 importScripts('metadata_parser.js');
     11 importScripts('byte_reader.js');
     12 importScripts('../util.js');
     13 
     14 /**
     15  * Dispatches metadata requests to the correct parser.
     16  *
     17  * @param {Object} port Worker port.
     18  * @constructor
     19  */
     20 function MetadataDispatcher(port) {
     21   this.port_ = port;
     22   this.port_.onmessage = this.onMessage.bind(this);
     23 
     24   // Make sure to update component_extension_resources.grd
     25   // when adding new parsers.
     26   importScripts('exif_parser.js');
     27   importScripts('image_parsers.js');
     28   importScripts('mpeg_parser.js');
     29   importScripts('id3_parser.js');
     30 
     31   var patterns = [];
     32 
     33   this.parserInstances_ = [];
     34   for (var i = 0; i < MetadataDispatcher.parserClasses_.length; i++) {
     35     var parserClass = MetadataDispatcher.parserClasses_[i];
     36     var parser = new parserClass(this);
     37     this.parserInstances_.push(parser);
     38     patterns.push(parser.urlFilter.source);
     39   }
     40 
     41   this.parserRegexp_ = new RegExp('(' + patterns.join('|') + ')', 'i');
     42 
     43   this.messageHandlers_ = {
     44     init: this.init_.bind(this),
     45     request: this.request_.bind(this)
     46   };
     47 }
     48 
     49 /**
     50  * List of registered parser classes.
     51  * @private
     52  */
     53 MetadataDispatcher.parserClasses_ = [];
     54 
     55 /**
     56  * @param {function} parserClass Parser constructor function.
     57  */
     58 MetadataDispatcher.registerParserClass = function(parserClass) {
     59   MetadataDispatcher.parserClasses_.push(parserClass);
     60 };
     61 
     62 /**
     63  * Verbose logging for the dispatcher.
     64  *
     65  * Individual parsers also take this as their default verbosity setting.
     66  */
     67 MetadataDispatcher.prototype.verbose = false;
     68 
     69 /**
     70  * |init| message handler.
     71  * @private
     72  */
     73 MetadataDispatcher.prototype.init_ = function() {
     74   // Inform our owner that we're done initializing.
     75   // If we need to pass more data back, we can add it to the param array.
     76   this.postMessage('initialized', [this.parserRegexp_]);
     77   this.log('initialized with URL filter ' + this.parserRegexp_);
     78 };
     79 
     80 /**
     81  * |request| message handler.
     82  * @param {string} fileURL File URL.
     83  * @private
     84  */
     85 MetadataDispatcher.prototype.request_ = function(fileURL) {
     86   try {
     87     this.processOneFile(fileURL, function callback(metadata) {
     88         this.postMessage('result', [fileURL, metadata]);
     89     }.bind(this));
     90   } catch (ex) {
     91     this.error(fileURL, ex);
     92   }
     93 };
     94 
     95 /**
     96  * Indicate to the caller that an operation has failed.
     97  *
     98  * No other messages relating to the failed operation should be sent.
     99  * @param {...Object} var_args Arguments.
    100  */
    101 MetadataDispatcher.prototype.error = function(var_args) {
    102   var ary = Array.apply(null, arguments);
    103   this.postMessage('error', ary);
    104 };
    105 
    106 /**
    107  * Send a log message to the caller.
    108  *
    109  * Callers must not parse log messages for control flow.
    110  * @param {...Object} var_args Arguments.
    111  */
    112 MetadataDispatcher.prototype.log = function(var_args) {
    113   var ary = Array.apply(null, arguments);
    114   this.postMessage('log', ary);
    115 };
    116 
    117 /**
    118  * Send a log message to the caller only if this.verbose is true.
    119  * @param {...Object} var_args Arguments.
    120  */
    121 MetadataDispatcher.prototype.vlog = function(var_args) {
    122   if (this.verbose)
    123     this.log.apply(this, arguments);
    124 };
    125 
    126 /**
    127  * Post a properly formatted message to the caller.
    128  * @param {string} verb Message type descriptor.
    129  * @param {Array.<Object>} args Arguments array.
    130  */
    131 MetadataDispatcher.prototype.postMessage = function(verb, args) {
    132   this.port_.postMessage({verb: verb, arguments: args});
    133 };
    134 
    135 /**
    136  * Message handler.
    137  * @param {Event} event Event object.
    138  */
    139 MetadataDispatcher.prototype.onMessage = function(event) {
    140   var data = event.data;
    141 
    142   if (this.messageHandlers_.hasOwnProperty(data.verb)) {
    143     this.messageHandlers_[data.verb].apply(this, data.arguments);
    144   } else {
    145     this.log('Unknown message from client: ' + data.verb, data);
    146   }
    147 };
    148 
    149 /**
    150  * @param {string} fileURL File URL.
    151  * @param {function(Object)} callback Completion callback.
    152  */
    153 MetadataDispatcher.prototype.processOneFile = function(fileURL, callback) {
    154   var self = this;
    155   var currentStep = -1;
    156 
    157   function nextStep(var_args) {
    158     self.vlog('nextStep: ' + steps[currentStep + 1].name);
    159     steps[++currentStep].apply(self, arguments);
    160   }
    161 
    162   var metadata;
    163 
    164   function onError(err, stepName) {
    165     self.error(fileURL, stepName || steps[currentStep].name, err.toString(),
    166         metadata);
    167   }
    168 
    169   var steps =
    170   [ // Step one, find the parser matching the url.
    171     function detectFormat() {
    172       for (var i = 0; i != self.parserInstances_.length; i++) {
    173         var parser = self.parserInstances_[i];
    174         if (fileURL.match(parser.urlFilter)) {
    175           // Create the metadata object as early as possible so that we can
    176           // pass it with the error message.
    177           metadata = parser.createDefaultMetadata();
    178           nextStep(parser);
    179           return;
    180         }
    181       }
    182       onError('unsupported format');
    183     },
    184 
    185     // Step two, turn the url into an entry.
    186     function getEntry(parser) {
    187       webkitResolveLocalFileSystemURL(
    188           fileURL,
    189           function(entry) { nextStep(entry, parser) },
    190           onError);
    191     },
    192 
    193     // Step three, turn the entry into a file.
    194     function getFile(entry, parser) {
    195       entry.file(function(file) { nextStep(file, parser) }, onError);
    196     },
    197 
    198     // Step four, parse the file content.
    199     function parseContent(file, parser) {
    200       metadata.fileSize = file.size;
    201       try {
    202         parser.parse(file, metadata, callback, onError);
    203       } catch (e) {
    204         onError(e.stack);
    205       }
    206     }
    207   ];
    208 
    209   nextStep();
    210 };
    211 
    212 // Webworker spec says that the worker global object is called self.  That's
    213 // a terrible name since we use it all over the chrome codebase to capture
    214 // the 'this' keyword in lambdas.
    215 var global = self;
    216 
    217 if (global.constructor.name == 'SharedWorkerGlobalScope') {
    218   global.addEventListener('connect', function(e) {
    219     var port = e.ports[0];
    220     new MetadataDispatcher(port);
    221     port.start();
    222   });
    223 } else {
    224   // Non-shared worker.
    225   new MetadataDispatcher(global);
    226 }
    227