• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
10importScripts('metadata_parser.js');
11importScripts('byte_reader.js');
12importScripts('../../../common/js/util.js');
13
14/**
15 * Dispatches metadata requests to the correct parser.
16 *
17 * @param {Object} port Worker port.
18 * @constructor
19 */
20function 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 */
53MetadataDispatcher.parserClasses_ = [];
54
55/**
56 * @param {function} parserClass Parser constructor function.
57 */
58MetadataDispatcher.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 */
67MetadataDispatcher.prototype.verbose = false;
68
69/**
70 * |init| message handler.
71 * @private
72 */
73MetadataDispatcher.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 */
85MetadataDispatcher.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 */
101MetadataDispatcher.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 */
112MetadataDispatcher.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 */
121MetadataDispatcher.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 */
131MetadataDispatcher.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 */
139MetadataDispatcher.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 */
153MetadataDispatcher.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.
215var global = self;
216
217if (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