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