Mercurial > pt1.oyama
annotate src/metadata.c @ 141:519a035533f6
Fix DLNA streaming issue.
author | Naoya OYAMA <naoya.oyama@gmail.com> |
---|---|
date | Sun, 29 Jul 2012 18:43:46 +0900 |
parents | 2a9ac5ce2c7e |
children | 066f33b2213a |
rev | line source |
---|---|
125 | 1 /* -*- tab-width: 4; indent-tabs-mode: nil -*- */ |
2 /* vim: set ts=4 sts=4 sw=4 expandtab number : */ | |
3 /* | |
4 * metadata.c : GeeXboX uShare CDS Metadata DB. | |
5 * Originally developped for the GeeXboX project. | |
6 * Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org> | |
7 * | |
8 * This program is free software; you can redistribute it and/or modify | |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU Library General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License along | |
19 * with this program; if not, write to the Free Software Foundation, | |
20 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
21 */ | |
22 | |
23 #include <stdlib.h> | |
24 #include <string.h> | |
25 #include <stdio.h> | |
26 #include <dirent.h> | |
27 #include <sys/types.h> | |
28 #include <sys/stat.h> | |
29 #include <unistd.h> | |
30 #include <stdbool.h> | |
31 | |
136
2a9ac5ce2c7e
Remove internal libdlna and libupnp.(using OS package by default)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
126
diff
changeset
|
32 #include <upnp.h> |
2a9ac5ce2c7e
Remove internal libdlna and libupnp.(using OS package by default)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
126
diff
changeset
|
33 #include <upnptools.h> |
125 | 34 |
35 #include "mime.h" | |
36 #include "metadata.h" | |
37 #include "util_iconv.h" | |
38 #include "content.h" | |
39 #include "gettext.h" | |
40 #include "trace.h" | |
41 | |
42 #define TITLE_UNKNOWN "unknown" | |
43 | |
44 #define MAX_URL_SIZE 32 | |
45 | |
46 struct upnp_entry_lookup_t { | |
47 int id; | |
48 struct upnp_entry_t *entry_ptr; | |
49 }; | |
50 | |
51 static char * | |
52 getExtension (const char *filename) | |
53 { | |
54 char *str = NULL; | |
55 | |
56 str = strrchr (filename, '.'); | |
57 if (str) | |
58 str++; | |
59 | |
60 return str; | |
61 } | |
62 | |
63 static struct mime_type_t * | |
64 getMimeType (const char *extension) | |
65 { | |
66 extern struct mime_type_t MIME_Type_List[]; | |
67 struct mime_type_t *list; | |
68 | |
69 if (!extension) | |
70 return NULL; | |
71 | |
72 list = MIME_Type_List; | |
73 while (list->extension) | |
74 { | |
75 if (!strcasecmp (list->extension, extension)) | |
76 return list; | |
77 list++; | |
78 } | |
79 | |
80 return NULL; | |
81 } | |
82 | |
83 static bool | |
84 is_valid_extension (const char *extension) | |
85 { | |
86 if (!extension) | |
87 return false; | |
88 | |
89 if (getMimeType (extension)) | |
90 return true; | |
91 | |
92 return false; | |
93 } | |
94 | |
95 static int | |
96 get_list_length (void *list) | |
97 { | |
98 void **l = list; | |
99 int n = 0; | |
100 | |
101 while (*(l++)) | |
102 n++; | |
103 | |
104 return n; | |
105 } | |
106 | |
107 static xml_convert_t xml_convert[] = { | |
108 {'"' , """}, | |
109 {'&' , "&"}, | |
110 {'\'', "'"}, | |
111 {'<' , "<"}, | |
112 {'>' , ">"}, | |
113 {'\n', "
"}, | |
114 {'\r', "
"}, | |
115 {'\t', "	"}, | |
116 {0, NULL}, | |
117 }; | |
118 | |
119 static char * | |
120 get_xmlconvert (int c) | |
121 { | |
122 int j; | |
123 for (j = 0; xml_convert[j].xml; j++) | |
124 { | |
125 if (c == xml_convert[j].charac) | |
126 return xml_convert[j].xml; | |
127 } | |
128 return NULL; | |
129 } | |
130 | |
131 static char * | |
132 convert_xml (const char *title) | |
133 { | |
134 char *newtitle, *s, *t, *xml; | |
135 int nbconvert = 0; | |
136 | |
137 /* calculate extra size needed */ | |
138 for (t = (char*) title; *t; t++) | |
139 { | |
140 xml = get_xmlconvert (*t); | |
141 if (xml) | |
142 nbconvert += strlen (xml) - 1; | |
143 } | |
144 if (!nbconvert) | |
145 return NULL; | |
146 | |
147 newtitle = s = (char*) malloc (strlen (title) + nbconvert + 1); | |
148 | |
149 for (t = (char*) title; *t; t++) | |
150 { | |
151 xml = get_xmlconvert (*t); | |
152 if (xml) | |
153 { | |
154 strcpy (s, xml); | |
155 s += strlen (xml); | |
156 } | |
157 else | |
158 *s++ = *t; | |
159 } | |
160 *s = '\0'; | |
161 | |
162 return newtitle; | |
163 } | |
164 | |
165 static struct mime_type_t Container_MIME_Type = | |
166 { NULL, "object.container.storageFolder", NULL}; | |
167 | |
168 static struct upnp_entry_t * | |
169 upnp_entry_new (struct ushare_t *ut, const char *name, const char *fullpath, | |
170 struct upnp_entry_t *parent, off_t size, int dir) | |
171 { | |
172 struct upnp_entry_t *entry = NULL; | |
173 char *title = NULL, *x = NULL; | |
174 char url_tmp[MAX_URL_SIZE] = { '\0' }; | |
175 char *title_or_name = NULL; | |
176 | |
177 if (!name) | |
178 return NULL; | |
179 | |
180 entry = (struct upnp_entry_t *) malloc (sizeof (struct upnp_entry_t)); | |
181 | |
182 #ifdef HAVE_DLNA | |
183 // dlna_profile_t $B$rYTB$(B | |
184 entry->dlna_profile = NULL; | |
185 entry->url = NULL; | |
186 if (ut->dlna_enabled && fullpath && !dir) | |
187 { | |
188 dlna_profile_t *p = malloc(sizeof(dlna_profile_t)); | |
189 p->id = "MPEG_TS_HD_60_L2_ISO;DLNA.ORG_OP=01;"; | |
190 p->mime = "video/mpeg"; | |
191 p->label = "label"; | |
192 p->class = DLNA_CLASS_AV; | |
193 #if 0 | |
194 dlna_profile_t *p = dlna_guess_media_profile (ut->dlna, fullpath); | |
195 if (!p) | |
196 { | |
197 free (entry); | |
198 return NULL; | |
199 } | |
200 #endif | |
201 entry->dlna_profile = p; | |
202 } | |
203 #endif /* HAVE_DLNA */ | |
204 | |
205 if (ut->xbox360) | |
206 { | |
207 if (ut->root_entry) | |
208 entry->id = ut->starting_id + ut->nr_entries++; | |
209 else | |
210 entry->id = 0; /* Creating the root node so don't use the usual IDs */ | |
211 } | |
212 else | |
213 entry->id = ut->starting_id + ut->nr_entries++; | |
214 log_verbose ("upnp_entry_new(), entry->id[%d]\n", entry->id); | |
215 | |
216 entry->fullpath = fullpath ? strdup (fullpath) : NULL; | |
217 entry->parent = parent; | |
218 entry->child_count = dir ? 0 : -1; | |
219 entry->title = NULL; | |
220 | |
221 entry->childs = (struct upnp_entry_t **) | |
222 malloc (sizeof (struct upnp_entry_t *)); | |
223 *(entry->childs) = NULL; | |
224 | |
225 if (!dir) /* item */ | |
226 { | |
227 #if 0 | |
228 #ifdef HAVE_DLNA | |
229 if (ut->dlna_enabled) | |
230 entry->mime_type = NULL; | |
231 else | |
232 { | |
233 #endif /* HAVE_DLNA */ | |
234 #endif | |
235 struct mime_type_t *mime = getMimeType (getExtension (name)); | |
236 if (!mime) | |
237 { | |
238 --ut->nr_entries; | |
239 upnp_entry_free (ut, entry); | |
240 log_error ("Invalid Mime type for %s, entry ignored", name); | |
241 return NULL; | |
242 } | |
243 entry->mime_type = mime; | |
244 #if 0 | |
245 #ifdef HAVE_DLNA | |
246 } | |
247 #endif /* HAVE_DLNA */ | |
248 #endif | |
249 | |
250 if (snprintf (url_tmp, MAX_URL_SIZE, "%d.%s", | |
251 entry->id, getExtension (name)) >= MAX_URL_SIZE) | |
252 log_error ("URL string too long for id %d, truncated!!", entry->id); | |
253 | |
254 /* Only malloc() what we really need */ | |
255 entry->url = strdup (url_tmp); | |
256 } | |
257 else /* container */ | |
258 { | |
259 entry->mime_type = &Container_MIME_Type; | |
260 entry->url = NULL; | |
261 } | |
262 | |
263 /* Try Iconv'ing the name but if it fails the end device | |
264 may still be able to handle it */ | |
265 title = iconv_convert (name); | |
266 if (title) | |
267 title_or_name = title; | |
268 else | |
269 { | |
270 if (ut->override_iconv_err) | |
271 { | |
272 title_or_name = strdup (name); | |
273 log_error ("Entry invalid name id=%d [%s]\n", entry->id, name); | |
274 } | |
275 else | |
276 { | |
277 upnp_entry_free (ut, entry); | |
278 log_error ("Freeing entry invalid name id=%d [%s]\n", entry->id, name); | |
279 return NULL; | |
280 } | |
281 } | |
282 | |
283 if (!dir) | |
284 { | |
285 x = strrchr (title_or_name, '.'); | |
286 if (x) /* avoid displaying file extension */ | |
287 *x = '\0'; | |
288 } | |
289 x = convert_xml (title_or_name); | |
290 if (x) | |
291 { | |
292 free (title_or_name); | |
293 title_or_name = x; | |
294 } | |
295 entry->title = title_or_name; | |
296 | |
297 if (!strcmp (title_or_name, "")) /* DIDL dc:title can't be empty */ | |
298 { | |
299 free (title_or_name); | |
300 entry->title = strdup (TITLE_UNKNOWN); | |
301 } | |
302 | |
303 entry->size = size; | |
304 entry->fd = -1; | |
305 | |
306 if (entry->id && entry->url) | |
307 log_verbose ("Entry->URL (%d): %s\n", entry->id, entry->url); | |
308 | |
309 return entry; | |
310 } | |
311 | |
312 /* Seperate recursive free() function in order to avoid freeing off | |
313 * the parents child list within the freeing of the first child, as | |
314 * the only entry which is not part of a childs list is the root entry | |
315 */ | |
316 static void | |
317 _upnp_entry_free (struct upnp_entry_t *entry) | |
318 { | |
319 struct upnp_entry_t **childs; | |
320 | |
321 if (!entry) | |
322 return; | |
323 | |
324 if (entry->fullpath) | |
325 free (entry->fullpath); | |
326 if (entry->title) | |
327 free (entry->title); | |
328 if (entry->url) | |
329 free (entry->url); | |
330 #ifdef HAVE_DLNA | |
331 if (entry->dlna_profile) | |
332 entry->dlna_profile = NULL; | |
333 #endif /* HAVE_DLNA */ | |
334 | |
335 for (childs = entry->childs; *childs; childs++) | |
336 _upnp_entry_free (*childs); | |
337 free (entry->childs); | |
338 } | |
339 | |
340 void | |
341 upnp_entry_free (struct ushare_t *ut, struct upnp_entry_t *entry) | |
342 { | |
343 if (!ut || !entry) | |
344 return; | |
345 | |
346 /* Free all entries (i.e. children) */ | |
347 if (entry == ut->root_entry) | |
348 { | |
349 struct upnp_entry_t *entry_found = NULL; | |
350 struct upnp_entry_lookup_t *lk = NULL; | |
351 RBLIST *rblist; | |
352 int i = 0; | |
353 | |
354 rblist = rbopenlist (ut->rb); | |
355 lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); | |
356 | |
357 while (lk) | |
358 { | |
359 entry_found = lk->entry_ptr; | |
360 if (entry_found) | |
361 { | |
362 if (entry_found->fullpath) | |
363 free (entry_found->fullpath); | |
364 if (entry_found->title) | |
365 free (entry_found->title); | |
366 if (entry_found->url) | |
367 free (entry_found->url); | |
368 | |
369 free (entry_found); | |
370 i++; | |
371 } | |
372 | |
373 free (lk); /* delete the lookup */ | |
374 lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); | |
375 } | |
376 | |
377 rbcloselist (rblist); | |
378 rbdestroy (ut->rb); | |
379 ut->rb = NULL; | |
380 | |
381 log_verbose ("Freed [%d] entries\n", i); | |
382 } | |
383 else | |
384 _upnp_entry_free (entry); | |
385 | |
386 free (entry); | |
387 } | |
388 | |
389 static void | |
390 upnp_entry_add_child (struct ushare_t *ut, | |
391 struct upnp_entry_t *entry, struct upnp_entry_t *child) | |
392 { | |
393 struct upnp_entry_lookup_t *entry_lookup_ptr = NULL; | |
394 struct upnp_entry_t **childs; | |
395 int n; | |
396 | |
397 if (!entry || !child) | |
398 return; | |
399 | |
400 for (childs = entry->childs; *childs; childs++) | |
401 if (*childs == child) | |
402 return; | |
403 | |
404 n = get_list_length ((void *) entry->childs) + 1; | |
405 entry->childs = (struct upnp_entry_t **) | |
406 realloc (entry->childs, (n + 1) * sizeof (*(entry->childs))); | |
407 entry->childs[n] = NULL; | |
408 entry->childs[n - 1] = child; | |
409 entry->child_count++; | |
410 | |
411 entry_lookup_ptr = (struct upnp_entry_lookup_t *) | |
412 malloc (sizeof (struct upnp_entry_lookup_t)); | |
413 entry_lookup_ptr->id = child->id; | |
414 entry_lookup_ptr->entry_ptr = child; | |
415 | |
416 if (rbsearch ((void *) entry_lookup_ptr, ut->rb) == NULL) | |
417 log_info (_("Failed to add the RB lookup tree\n")); | |
418 } | |
419 | |
420 struct upnp_entry_t * | |
421 upnp_get_entry (struct ushare_t *ut, int id) | |
422 { | |
423 struct upnp_entry_lookup_t *res, entry_lookup; | |
424 | |
425 log_verbose ("Looking for entry id %d\n", id); | |
426 if (id == 0) /* We do not store the root (id 0) as it is not a child */ | |
427 return ut->root_entry; | |
428 | |
429 entry_lookup.id = id; | |
430 res = (struct upnp_entry_lookup_t *) | |
431 rbfind ((void *) &entry_lookup, ut->rb); | |
432 | |
433 if (res) | |
434 { | |
435 log_verbose ("Found at %p\n", | |
436 ((struct upnp_entry_lookup_t *) res)->entry_ptr); | |
437 return ((struct upnp_entry_lookup_t *) res)->entry_ptr; | |
438 } | |
439 | |
440 log_verbose ("Not Found\n"); | |
441 | |
442 return NULL; | |
443 } | |
444 | |
445 static void | |
446 metadata_add_file (struct ushare_t *ut, struct upnp_entry_t *entry, | |
447 const char *file, const char *name, struct stat *st_ptr) | |
448 { | |
449 if (!entry || !file || !name) | |
450 return; | |
451 | |
452 #ifdef HAVE_DLNA | |
453 if (ut->dlna_enabled || is_valid_extension (getExtension (file))) | |
454 #else | |
455 if (is_valid_extension (getExtension (file))) | |
456 #endif | |
457 { | |
458 struct upnp_entry_t *child = NULL; | |
459 | |
460 child = upnp_entry_new (ut, name, file, entry, st_ptr->st_size, false); | |
461 if (child) | |
462 upnp_entry_add_child (ut, entry, child); | |
463 } | |
464 } | |
465 | |
466 static void | |
467 metadata_add_container (struct ushare_t *ut, | |
468 struct upnp_entry_t *entry, const char *container) | |
469 { | |
470 struct dirent **namelist; | |
471 int n,i; | |
472 | |
473 if (!entry || !container) | |
474 return; | |
475 | |
476 n = scandir (container, &namelist, 0, alphasort); | |
477 if (n < 0) | |
478 { | |
479 perror ("scandir"); | |
480 return; | |
481 } | |
482 | |
483 for (i = 0; i < n; i++) | |
484 { | |
485 struct stat st; | |
486 char *fullpath = NULL; | |
487 | |
488 if (namelist[i]->d_name[0] == '.') | |
489 { | |
490 free (namelist[i]); | |
491 continue; | |
492 } | |
493 | |
494 fullpath = (char *) | |
495 malloc (strlen (container) + strlen (namelist[i]->d_name) + 2); | |
496 sprintf (fullpath, "%s/%s", container, namelist[i]->d_name); | |
497 | |
498 log_verbose ("%s\n", fullpath); | |
499 | |
500 if (stat (fullpath, &st) < 0) | |
501 { | |
502 free (namelist[i]); | |
503 free (fullpath); | |
504 continue; | |
505 } | |
506 | |
507 if (S_ISDIR (st.st_mode)) | |
508 { | |
509 struct upnp_entry_t *child = NULL; | |
510 | |
511 child = upnp_entry_new (ut, namelist[i]->d_name, | |
512 fullpath, entry, 0, true); | |
513 if (child) | |
514 { | |
515 metadata_add_container (ut, child, fullpath); | |
516 upnp_entry_add_child (ut, entry, child); | |
517 } | |
518 } | |
519 else | |
520 metadata_add_file (ut, entry, fullpath, namelist[i]->d_name, &st); | |
521 | |
522 free (namelist[i]); | |
523 free (fullpath); | |
524 } | |
525 free (namelist); | |
526 } | |
527 | |
528 void | |
529 free_metadata_list (struct ushare_t *ut) | |
530 { | |
531 ut->init = 0; | |
532 if (ut->root_entry) | |
533 upnp_entry_free (ut, ut->root_entry); | |
534 ut->root_entry = NULL; | |
535 ut->nr_entries = 0; | |
536 | |
537 if (ut->rb) | |
538 { | |
539 rbdestroy (ut->rb); | |
540 ut->rb = NULL; | |
541 } | |
542 | |
543 ut->rb = rbinit (rb_compare, NULL); | |
544 if (!ut->rb) | |
545 log_error (_("Cannot create RB tree for lookups\n")); | |
546 } | |
547 | |
548 void | |
549 build_metadata_list (struct ushare_t *ut) | |
550 { | |
551 int i; | |
552 struct stat st; | |
553 log_info (_("Building Metadata List ...\n")); | |
554 | |
555 /* build root entry */ | |
556 if (!ut->root_entry) | |
557 ut->root_entry = upnp_entry_new (ut, "root", NULL, NULL, -1, true); | |
558 | |
559 #if 0 | |
560 entry = upnp_entry_new (ut, "stream.ts", "/web/stream.ts", | |
561 ut->root_entry, -1, false); | |
562 upnp_entry_add_child (ut, ut->root_entry, entry); | |
563 metadata_add_container (ut, entry, "/web/"); | |
564 #endif | |
565 struct upnp_entry_t *entry = ut->root_entry; | |
566 st.st_size = 100*1024*1024; | |
567 metadata_add_file (ut, ut->root_entry, STREAM_LOCATION, STREAM_FILE_NAME, &st); | |
126
5dcaf3785ebe
fix process terminate problem.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
125
diff
changeset
|
568 ut->contentlist = NULL; |
125 | 569 //metadata_add_container (ut, ut->root_entry, "/web/"); |
570 | |
571 #if 0 | |
572 /* add files from content directory */ | |
573 for (i=0 ; i < ut->contentlist->count ; i++) | |
574 { | |
575 struct upnp_entry_t *entry = NULL; | |
576 char *title = NULL; | |
577 int size = 0; | |
578 | |
579 log_info (_("Looking for files in content directory : %s\n"), | |
580 ut->contentlist->content[i]); | |
581 | |
582 size = strlen (ut->contentlist->content[i]); | |
583 if (ut->contentlist->content[i][size - 1] == '/') | |
584 ut->contentlist->content[i][size - 1] = '\0'; | |
585 title = strrchr (ut->contentlist->content[i], '/'); | |
586 if (title) | |
587 title++; | |
588 else | |
589 { | |
590 /* directly use content directory name if no '/' before basename */ | |
591 title = ut->contentlist->content[i]; | |
592 } | |
593 | |
594 entry = upnp_entry_new (ut, title, ut->contentlist->content[i], | |
595 ut->root_entry, -1, true); | |
596 | |
597 if (!entry) | |
598 continue; | |
599 upnp_entry_add_child (ut, ut->root_entry, entry); | |
600 metadata_add_container (ut, entry, ut->contentlist->content[i]); | |
601 } | |
602 #endif | |
603 | |
604 log_info (_("Found %d files and subdirectories.\n"), ut->nr_entries); | |
605 ut->init = 1; | |
606 } | |
607 | |
608 int | |
609 rb_compare (const void *pa, const void *pb, | |
610 const void *config __attribute__ ((unused))) | |
611 { | |
612 struct upnp_entry_lookup_t *a, *b; | |
613 | |
614 a = (struct upnp_entry_lookup_t *) pa; | |
615 b = (struct upnp_entry_lookup_t *) pb; | |
616 | |
617 if (a->id < b->id) | |
618 return -1; | |
619 | |
620 if (a->id > b->id) | |
621 return 1; | |
622 | |
623 return 0; | |
624 } | |
625 |