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