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