UNPKG

16.2 kBJavaScriptView Raw
1/*jslint node: true, esversion: 6, maxlen: 180 */
2"use strict";
3
4const Path = require('path');
5const EventEmitter = require('events');
6
7const assert = require('assert');
8const debug = require('debug')('upnpserver:contentHandlers:MetasJson');
9const logger = require('../logger');
10
11const Abstract_Metas = require('./abstract_metas');
12const NodeWeakHashmap = require('../util/nodeWeakHashmap');
13const NamedSemaphore = require('../util/namedSemaphore');
14
15const METADATAS_DIRECTORY = ".upnpserver";
16const DIRECTORY_JSON = "";
17
18const JSON_NOT_FOUND_OR_INVALID = "***INVALID***";
19
20class MetasJson extends Abstract_Metas {
21 constructor(configuration) {
22 super(configuration);
23
24 this._basePath = this._configuration.basePath;
25
26 this._jsonSemaphores = new NamedSemaphore("json:" + this.name);
27
28 if (!MetasJson._jsonCache) {
29 MetasJson._jsonCache = new NodeWeakHashmap("json", 1000 * 60, false);
30 }
31 }
32
33
34 initialize(contentDirectoryService, callback) {
35
36 if (this._basePath) {
37 this._baseURL = contentDirectoryService.newURL(this._basePath);
38 }
39
40 super.initialize(contentDirectoryService, callback);
41 }
42
43 static get METADATAS_DIRECTORY() {
44 return METADATAS_DIRECTORY;
45 }
46
47 _searchDirectoryJSONFile(contentURL, metasContext, callback) {
48 assert(typeof (callback) === "function", "Invalid callback parameter");
49
50 this._jsonSemaphores.take(contentURL.path, (semaphore) => {
51 this._searchDirectoryJSONFile0(contentURL, metasContext, (error, jsonContext, directoryInfos) => {
52 semaphore.leave();
53
54 debug("_searchDirectoryJSONFile", "return error=", error);
55
56 if (error) {
57 callback();
58 return;
59 }
60
61 callback(null, jsonContext, directoryInfos);
62 });
63 });
64 }
65
66 _searchForKey(name, type, callback) {
67 assert(typeof (callback) === "function", "Invalid callback parameter");
68 callback(null);
69 }
70
71 _searchDirectoryJSONFile0(contentURL, metasContext, callback) {
72 assert(typeof (callback) === "function", "Invalid callback parameter");
73
74 var jsonURL = contentURL.join(METADATAS_DIRECTORY, DIRECTORY_JSON + "." + this.name + ".json");
75
76 debug("_searchDirectoryJSONFile", "Directory: test", contentURL, "jsonURL=", jsonURL);
77
78 var directoryInfos = {
79 type: 'directory',
80 base: false
81 };
82
83 // Search tmdb json file in the directory
84
85 this._loadJSON(jsonURL, false, (error, jsonContext) => {
86 var content;
87
88 debug("_searchDirectoryJSONFile", "loadJSON url=", jsonURL, "returns", jsonContext);
89
90 if (jsonContext) {
91 content = jsonContext.content;
92
93 if (content.key && content.type === 'tvShow' && content.tvShowInfo) {
94 return callback(null, jsonContext, directoryInfos);
95 }
96 }
97
98 if (!this._baseURL) {
99 // no tmdb repository
100
101 debug("_searchDirectoryJSONFile", "no tmdb baseURL, exit !");
102 return callback(error);
103 }
104
105 // Compute the json file name associated to the directory
106 var loadBaseJSON = (k, jsonContext) => {
107
108 debug("_searchDirectoryJSONFile", "loadBaseJSON=", k);
109
110 this._computeJSONPathInBase(k, directoryInfos, (error, jsonURL, directoryInfos) => {
111
112 debug("_searchDirectoryJSONFile", "computeJSONPath error=", error, "url=", jsonURL, "directoryInfos=", directoryInfos);
113 if (error) {
114 return callback(error);
115 }
116
117 if (!jsonURL) {
118 return callback(null);
119 }
120
121 directoryInfos.base = true;
122
123 // Load the json
124
125 this._loadJSON(jsonURL, true, (error, jsonContext) => {
126 if (error) {
127 return callback(error);
128 }
129
130 if (!jsonContext) {
131 debug("_searchDirectoryJSONFile", "No json", jsonURL);
132
133 this._updateInfos(k.key, content.type, {}, (error2, content) => {
134 debug("_searchDirectoryJSONFile", "UpdateInfos key=", k.key, "type=", content.type,
135 "returns error=", error2, "content=", content);
136
137 if (error2) {
138 return callback(error2);
139 }
140
141 if (!content) {
142 return callback(error);
143 }
144
145 var ret = {
146 key: k.key,
147 type: k.type
148 };
149 ret[k.type] = content;
150
151 this.saveJSON(jsonURL, ret, true, (error, jsonContext) => {
152 if (error) {
153 logger.error(error);
154 }
155
156 callback(null, jsonContext, directoryInfos);
157 });
158 });
159 return;
160 }
161
162 callback(null, jsonContext, directoryInfos);
163 });
164 });
165 };
166
167
168 // A tmdb repository ! search the key in the folder name
169
170 var reg;
171 if (!reg && content && content.key && content.type) {
172 reg = {
173 key: content.key,
174 type: content.type
175 };
176 }
177
178 if (!reg) {
179 reg = this._getKeyFromDirectoryName(contentURL.basename);
180 }
181
182 if (!reg && content && content.type) {
183 debug("_searchDirectoryJSONFile", "Search for key=", contentURL.basename);
184
185 this._searchForKey(contentURL.basename, content.type, (error, key) => {
186 if (error) {
187 return callback(error);
188 }
189
190 if (!key) {
191 return callback();
192 }
193
194 content.key = key;
195 reg = {
196 key: key,
197 type: content.type
198 };
199
200 this.saveJSON(jsonContext.url, jsonContext.content, jsonContext.isBaseURL, (error, jsonContext) => {
201 if (error) {
202 logger.error(error);
203 }
204 loadBaseJSON(reg, jsonContext);
205 });
206 });
207 return;
208 }
209
210 if (!reg) {
211 // No key in the foldername nor in the metaContext (mkv metadatas), abort the search
212 return callback(error);
213 }
214
215 loadBaseJSON(reg, jsonContext);
216 });
217 }
218
219 _searchJSONFile(contentURL, metasContext, callback) {
220 assert(typeof (callback) === "function", "Invalid callback parameter");
221 this._jsonSemaphores.take(contentURL.path, (semaphore) => {
222 this._searchJSONFile0(contentURL, metasContext, (error, jsonContext, directoryInfos) => {
223 semaphore.leave();
224
225 if (error) {
226 callback();
227 return;
228 }
229 callback(null, jsonContext, directoryInfos);
230 });
231 });
232 }
233
234 _searchJSONFile0(contentURL, metasContext, callback) {
235 assert(typeof (callback) === "function", "Invalid callback parameter");
236
237 var basename = contentURL.basename;
238
239 var jsonURL = contentURL.join('..', METADATAS_DIRECTORY, basename + "." + this.name + ".json");
240
241 debug("_searchJSONFile", "File: jsonURL=", jsonURL, "contentURL=", contentURL, "basename=", basename);
242
243 var fileInfos = { type: 'file', base: false };
244
245 var regBasename = basename.replace(/_/g, ' ');
246 var season;
247 var episode;
248 var reg = /\bS(\d{1,})[-\s]*E(\d{1,})\b/i.exec(regBasename);
249 if (reg) {
250 fileInfos.season = parseInt(reg[1], 10);
251 fileInfos.episode = parseInt(reg[2], 10);
252 fileInfos.type = "tvShow";
253
254 } else {
255 reg = /\bE(\d{1,})\b/i.exec(regBasename);
256 if (reg) {
257 fileInfos.season = 0;
258 fileInfos.episode = parseInt(reg[1], 10);
259 fileInfos.type = "tvShow";
260 }
261 }
262
263 debug("_searchJSONFile", "File no JSON season=", season, "episode=", episode);
264
265 var searchInDirectory = () => {
266
267 if (fileInfos.season === undefined || fileInfos.episode === undefined) {
268 // No season, no episode number, no reason to search metadatas in directory
269 return callback();
270 }
271
272 var directoryURL = contentURL.join('..');
273
274 debug("_searchJSONFile", "tvShow: directoryURL=", directoryURL, "contentURL=", contentURL);
275
276 this._searchDirectoryJSONFile(directoryURL, metasContext, (error, jsonContext, directoryInfos) => {
277 if (error) {
278 return callback(error);
279 }
280 if (!jsonContext) {
281 debug("_searchJSONFile", "tvShow => no JSON for url=", directoryURL);
282 return callback();
283 }
284
285 directoryInfos.season = fileInfos.season;
286 directoryInfos.episode = fileInfos.episode;
287 directoryInfos.type = fileInfos.type;
288
289 callback(null, jsonContext, directoryInfos);
290 });
291 };
292
293 this._loadJSON(jsonURL, false, (error, jsonContext) => {
294 if (error) {
295 logger.error(error);
296 }
297
298 if (jsonContext) {
299 return callback(error, jsonContext, fileInfos);
300 }
301
302 // JSON is not found search in tmdb repository if any !
303
304 var resourceKey;
305 if (metasContext.keys) {
306 resourceKey = metasContext.keys[this.domainKey];
307 }
308
309 if (!resourceKey) {
310 // Search tv key in filename
311 resourceKey = this._getKeyFromFileName(basename);
312 }
313
314 if (!resourceKey) {
315 // No key
316 searchInDirectory();
317 return;
318 }
319
320 this._computeJSONPathInBase(resourceKey, fileInfos, (error, jsonURL, fileInfos) => {
321 if (error || !jsonURL) {
322 searchInDirectory();
323 return callback(error);
324 }
325
326 this._loadJSON(jsonURL, true, (error, jsonContext) => {
327 if (error) {
328 return callback(error);
329 }
330
331 if (!jsonContext) {
332 // No data, search metas in directory
333 searchInDirectory();
334 return;
335 }
336
337 callback(null, jsonContext, fileInfos);
338 });
339 });
340 });
341 }
342
343 /**
344 *
345 */
346 prepareMetas(contentInfos, metasContext, callback) {
347 var contentURL = contentInfos.contentURL;
348 assert(contentURL, "Invalid contentURL");
349
350 if (this.enabled === false) {
351 return callback();
352 }
353
354 if (contentInfos.stats.isDirectory()) {
355 this._searchDirectoryJSONFile(contentURL, metasContext, (error, jsonContext, directoryInfos) => {
356 // Test key in
357 if (error || !jsonContext) {
358 return callback(error);
359 }
360
361 this._processFolder(contentInfos, metasContext, jsonContext, directoryInfos, (error, metas) => {
362 if (error) {
363 return callback(error);
364 }
365
366 metas = metas || {};
367 var mt = metas[this.name];
368 if (!mt) {
369 mt = {};
370 metas[this.name] = mt;
371 }
372
373 metas.resourceType = directoryInfos.type;
374 if (directoryInfos.key) {
375 mt.key = directoryInfos.key;
376 }
377 if (directoryInfos.base) {
378 mt.base = directoryInfos.base;
379 }
380
381 callback(null, metas);
382 });
383 });
384 return;
385 }
386
387 this._searchJSONFile(contentURL, metasContext, (error, jsonContext, fileInfos) => {
388 // Test key in
389 if (error || !jsonContext) {
390 return callback(error);
391 }
392
393 this._processFile(contentInfos, metasContext, jsonContext, fileInfos, (error, metas) => {
394 if (error) {
395 return callback(error);
396 }
397
398 metas = metas || {};
399 var mt = metas[this.name];
400 if (!mt) {
401 mt = {};
402 metas[this.name] = mt;
403 }
404
405 metas.resourceType = fileInfos.type;
406 if (fileInfos.key) {
407 mt.key = fileInfos.key;
408 }
409 if (fileInfos.base) {
410 mt.base = fileInfos.base;
411 }
412
413 callback(null, metas);
414 });
415 });
416 }
417
418 _getKeyFromFileName(contentURL) {
419 return null;
420 }
421
422 _getKeyFromDirectoryName(contentURL) {
423 return null;
424 }
425
426 _processFile(contentInfos, metasContext, jsonContext, fileInfos, callback) {
427 callback();
428 }
429
430 _processFolder(contentInfos, metasContext, jsonContext, directoryInfos, callback) {
431 callback();
432 }
433
434 _getBaseDirectoryFromNode(node, callback) {
435 var attributes = node.attributes;
436 var contentURL = node.contentURL;
437 var resourceType = attributes.resourceType;
438
439 if (resourceType === "directory") {
440 return contentURL.join(METADATAS_DIRECTORY, this.name);
441 }
442
443 return contentURL.join('..', METADATAS_DIRECTORY, this.name);
444 }
445
446 saveJSON(jsonURL, content, isBaseURL, callback) {
447 assert(typeof (callback) === "function", "Invalid callback parameter");
448
449 debug("saveJSON", "try to save jsonURL=", jsonURL, "content=", content);
450
451 // var oldJsonContext = MetasJson._jsonCache.get(jsonURL.path);
452 var newJsonContext = {
453 url: jsonURL,
454 content: content,
455 isBaseURL: isBaseURL
456 };
457
458 MetasJson._jsonCache.put({ id: jsonURL.path }, newJsonContext);
459
460 var buffer = JSON.stringify(content, null, 2);
461
462 jsonURL.writeContent(buffer, "utf8", (error) => {
463 if (error) {
464 return callback(error);
465 }
466
467 callback(null, newJsonContext);
468 });
469 }
470
471 _loadJSON(jsonURL, isBaseURL, callback) {
472 assert(typeof (callback) === "function", "Invalid callback parameter");
473 assert(typeof (isBaseURL) === "boolean", "Invalid isBaseURL parameter");
474
475 debug("_loadJSON", "try to load jsonURL=", jsonURL, "isBaseURL=", isBaseURL);
476
477 var jsonContext = MetasJson._jsonCache.get(jsonURL.path);
478 if (jsonContext) {
479 if (jsonContext instanceof EventEmitter) {
480 var eventEmitter = jsonContext;
481 debug("_loadJSON", "Wait for async result path=", jsonURL);
482
483 eventEmitter.on('result', (jsonContext) => callback(null, jsonContext));
484 eventEmitter.on('problem', (error) => callback( /* no error */));
485 return;
486 }
487
488 if (jsonContext === JSON_NOT_FOUND_OR_INVALID) {
489 debug("_loadJSON", "Return NOT FOUND");
490 return callback(null, null);
491 }
492
493 debug("_loadJSON", "Return json from url=", jsonURL, "typeof json=", typeof (jsonContext));
494 return callback(null, jsonContext);
495 }
496
497 var ev = new EventEmitter();
498 MetasJson._jsonCache.put({ id: jsonURL.path }, ev);
499
500 jsonURL.stat((error, stats) => {
501 if (error) {
502 debug("_loadJSON", "Stat returns error", error);
503
504 var ex = new Error("Can not stat '" + jsonURL.path + "'");
505 ex.reason = error;
506 ex.url = jsonURL;
507
508 MetasJson._jsonCache.put({ id: jsonURL.path }, JSON_NOT_FOUND_OR_INVALID);
509
510 ev.emit('problem', ex);
511
512 // logger.error(ex);
513 return callback(); // Report no problem !
514 }
515
516 jsonURL.readContent("utf8", (error, content) => {
517 // debug("_loadJSON", "path=",path, "JSON=",content);
518
519 if (error) {
520 debug("_loadJSON", "ReadContent returns error", error);
521
522 MetasJson._jsonCache.put({ id: jsonURL.path }, JSON_NOT_FOUND_OR_INVALID);
523
524 ev.emit('problem', error);
525 logger.error(error);
526
527 return callback();
528 }
529
530 var json;
531 try {
532 json = JSON.parse(content);
533
534 } catch (x) {
535 debug("_loadJSON", "JSON parsing throws exception", x);
536
537 var ex = new Error("Can not parse JSON ");
538 ex.reason = x;
539 logger.error(ex);
540
541 MetasJson._jsonCache.put({ id: jsonURL.path }, JSON_NOT_FOUND_OR_INVALID);
542
543 ev.emit('problem', ex);
544 logger.error(ex);
545 return callback();
546 }
547
548 var jsonContext = {
549 content: json,
550 url: jsonURL,
551 isBaseURL: isBaseURL
552 };
553
554 debug("_loadJSON", "jsonContext=", jsonContext);
555
556 MetasJson._jsonCache.put({ id: jsonURL.path }, jsonContext);
557
558 ev.emit('result', jsonContext);
559
560 callback(null, jsonContext);
561 });
562 });
563 }
564}
565
566module.exports = MetasJson;