- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / metadata / metadata_dispatcher.js
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('../../../common/js/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 }