UNPKG

5.38 kBJavaScriptView Raw
1/*jslint node: true, esversion: 6 */
2"use strict";
3
4const assert = require('assert');
5const fs = require('fs');
6
7const debug = require('debug')('upnpserver:contentHandler');
8const logger = require('../logger');
9
10class ContentHandler {
11
12 constructor(configuration) {
13 this._configuration = configuration || {};
14 }
15
16 get configuration() {
17 return this._configuration;
18 }
19
20 /**
21 *
22 */
23 get service() {
24 return this._contentDirectoryService;
25 }
26
27 /**
28 *
29 */
30 initialize(contentDirectoryService, callback) {
31 this._contentDirectoryService = contentDirectoryService;
32
33 var mimeTypes = this.mimeTypes;
34 if (!mimeTypes) {
35 return callback();
36 }
37
38 var prepareNode = (contentInfos, attributes, callback) => {
39 debug("[", this.name, "] PrepareNode event of content", contentInfos);
40
41 this.prepareMetasFromContentURL(contentInfos, attributes, (error) => {
42 if (error) {
43 logger.error("Prepare node " + contentInfos + " of contentHandler=" + this.name + " error=", error);
44 return callback(error);
45 }
46
47 debug("[", this.name, "] PrepareNode event END of content", contentInfos);
48 callback();
49 });
50 };
51
52
53 var toJXML = (node, attributes, request, xml, callback) => {
54
55 debug("[", this.name, "] toJXML event #", node.id);
56
57 this.toJXML(node, attributes, request, xml, callback);
58 };
59
60
61 // Don't use => because we use arguments !
62 var browse = (node, callback) => {
63 debug("[", this.name, "] browse event #", node.id);
64
65 this.browse(node, callback);
66 };
67
68 var priority = this.priority;
69
70 mimeTypes.forEach((mimeType) => {
71
72 if (this.prepareMetas) {
73 debug("[", this.name, "] Register 'prepare' for mimeType", mimeType, "priority=", priority);
74
75 contentDirectoryService.asyncOn("prepare:" + mimeType, prepareNode, priority);
76 }
77
78 if (this.toJXML) {
79 debug("[", this.name, "] Register 'toJXML' for mimeType", mimeType, "priority=", priority);
80
81 contentDirectoryService.asyncOn("toJXML:" + mimeType, toJXML, priority);
82 }
83
84 if (this.browse) {
85 debug("[", this.name, "] Register 'browse' for mimeType", mimeType, "priority=", priority);
86
87 contentDirectoryService.asyncOn("browse:" + mimeType, browse, priority);
88 }
89 });
90
91 callback();
92 }
93
94 /*
95 * prepareNode(node, callback) { callback(); }
96 */
97
98 searchUpnpClass(fileInfos, callback) {
99 callback();
100 }
101
102 /**
103 *
104 */
105 getResourceByParameter(node, parameter) {
106 if (parameter instanceof Array) {
107 parameter = parameter[0];
108 }
109
110 var res = node.attributes.res || [];
111
112 debug("Find resource by parameter res=", res, "parameter=", parameter);
113
114 return res.find((r) => r.key === parameter);
115 }
116
117 /**
118 *
119 */
120 sendResource(contentURL, attributes, request, response, callback) {
121 debug("[", this.name, "] Send resource contentURL=", contentURL, "attributes=", attributes);
122
123 var opts = {};
124 if (attributes._start) {
125 opts.start = attributes._start;
126 opts.end = opts.start + attributes.size - 1;
127 }
128
129 contentURL.createReadStream(null, opts, (error, stream) => {
130 if (error) {
131 logger.error('No stream for contentURL=', contentURL);
132
133 if (!response.headersSent) {
134 response.writeHead(404, 'Stream not found for linked content');
135 }
136 response.end();
137 return callback(null, true);
138 }
139
140 if (attributes.mtime) {
141 var m = attributes.mtime;
142 if (typeof(m) === "number") {
143 m = new Date(m);
144 }
145 response.setHeader('Last-Modified', m.toUTCString());
146 }
147 if (attributes.contentHash) {
148 response.setHeader('ETag', attributes.hash);
149 }
150 response.setHeader('Content-Length', attributes.size);
151 if (attributes.mimeType !== undefined) {
152 response.setHeader('Content-Type', "image/jpeg"); //attributes.mimeType);
153 }
154
155 stream.pipe(response);
156
157 stream.on('end', () => callback(null, true));
158 });
159
160 }
161
162 /**
163 *
164 */
165 _mergeMetas(attributes, metas) {
166
167 debug("Merge metas=", metas, "to attributes=", attributes);
168 if (!metas) {
169 return attributes;
170 }
171
172 var copyRes = (index, datas) => {
173 attributes.res = attributes.res || [];
174
175 var r = attributes.res[index];
176 if (!r) {
177 r = {};
178 attributes.res[index] = r;
179 }
180
181 for (var n in datas) {
182 r[n] = datas[n];
183 }
184 };
185
186 for (var n in metas) {
187 var m = metas[n];
188 if (n === 'res') {
189 for (var i = 0; i < m.length; i++) {
190 copyRes(i, m[i]);
191 }
192 continue;
193 }
194
195 var c = attributes[n];
196 /*if (false) {
197 // Merge artists, albums ??? (a good or bad idea ?)
198 if (Array.isArray(c) && Array.isArray(m)) {
199 m.forEach((tok) => {
200 if (c.indexOf(tok)>=0) {
201 return;
202 }
203 c.push(tok);
204 });
205 }
206 }*/
207
208 if (c) {
209 return;
210 }
211
212 attributes[n] = m;
213 }
214
215 return attributes;
216 }
217
218 /**
219 *
220 */
221 prepareMetasFromContentURL(contentInfos, attributes, callback) {
222 if (!this.prepareMetas) {
223 return callback(null, attributes);
224 }
225
226 this.prepareMetas(contentInfos, attributes, (error, metas) => {
227 if (error) {
228 logger.error("loadMetas error", contentInfos, error);
229 // return callback(error); // Continue processing ...
230 }
231
232 attributes = this._mergeMetas(attributes, metas);
233
234 callback(null, attributes);
235 });
236 }
237}
238
239module.exports = ContentHandler;