OLD | NEW |
| (Empty) |
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 } | |
OLD | NEW |