Mercurial > pt1.oyama
annotate src/metadata.c @ 170:4f3640bf350d
Change display name of DLNA.
- old_display name: (recpt1:devN)
- new_display name: (hostname: /dev/ptXdevY)
- change UUID format.
author | Naoya OYAMA <naoya.oyama@gmail.com> |
---|---|
date | Wed, 17 Oct 2012 23:47:16 +0900 |
parents | 5d010d0ff6a1 |
children |
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" | |
152
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
41 #include "recpt1.h" |
125 | 42 |
43 #define TITLE_UNKNOWN "unknown" | |
44 | |
45 #define MAX_URL_SIZE 32 | |
46 | |
47 struct upnp_entry_lookup_t { | |
48 int id; | |
49 struct upnp_entry_t *entry_ptr; | |
50 }; | |
51 | |
52 static char * | |
53 getExtension (const char *filename) | |
54 { | |
55 char *str = NULL; | |
56 | |
57 str = strrchr (filename, '.'); | |
58 if (str) | |
59 str++; | |
60 | |
61 return str; | |
62 } | |
63 | |
64 static struct mime_type_t * | |
65 getMimeType (const char *extension) | |
66 { | |
67 extern struct mime_type_t MIME_Type_List[]; | |
68 struct mime_type_t *list; | |
69 | |
70 if (!extension) | |
71 return NULL; | |
72 | |
73 list = MIME_Type_List; | |
74 while (list->extension) | |
75 { | |
76 if (!strcasecmp (list->extension, extension)) | |
77 return list; | |
78 list++; | |
79 } | |
80 | |
81 return NULL; | |
82 } | |
83 | |
84 static bool | |
85 is_valid_extension (const char *extension) | |
86 { | |
87 if (!extension) | |
88 return false; | |
89 | |
90 if (getMimeType (extension)) | |
91 return true; | |
92 | |
93 return false; | |
94 } | |
95 | |
96 static int | |
97 get_list_length (void *list) | |
98 { | |
99 void **l = list; | |
100 int n = 0; | |
101 | |
102 while (*(l++)) | |
103 n++; | |
104 | |
105 return n; | |
106 } | |
107 | |
108 static xml_convert_t xml_convert[] = { | |
109 {'"' , """}, | |
110 {'&' , "&"}, | |
111 {'\'', "'"}, | |
112 {'<' , "<"}, | |
113 {'>' , ">"}, | |
114 {'\n', "
"}, | |
115 {'\r', "
"}, | |
116 {'\t', "	"}, | |
117 {0, NULL}, | |
118 }; | |
119 | |
120 static char * | |
121 get_xmlconvert (int c) | |
122 { | |
123 int j; | |
124 for (j = 0; xml_convert[j].xml; j++) | |
125 { | |
126 if (c == xml_convert[j].charac) | |
127 return xml_convert[j].xml; | |
128 } | |
129 return NULL; | |
130 } | |
131 | |
132 static char * | |
133 convert_xml (const char *title) | |
134 { | |
135 char *newtitle, *s, *t, *xml; | |
136 int nbconvert = 0; | |
137 | |
138 /* calculate extra size needed */ | |
139 for (t = (char*) title; *t; t++) | |
140 { | |
141 xml = get_xmlconvert (*t); | |
142 if (xml) | |
143 nbconvert += strlen (xml) - 1; | |
144 } | |
145 if (!nbconvert) | |
146 return NULL; | |
147 | |
148 newtitle = s = (char*) malloc (strlen (title) + nbconvert + 1); | |
149 | |
150 for (t = (char*) title; *t; t++) | |
151 { | |
152 xml = get_xmlconvert (*t); | |
153 if (xml) | |
154 { | |
155 strcpy (s, xml); | |
156 s += strlen (xml); | |
157 } | |
158 else | |
159 *s++ = *t; | |
160 } | |
161 *s = '\0'; | |
162 | |
163 return newtitle; | |
164 } | |
165 | |
166 static struct mime_type_t Container_MIME_Type = | |
167 { NULL, "object.container.storageFolder", NULL}; | |
168 | |
169 static struct upnp_entry_t * | |
170 upnp_entry_new (struct ushare_t *ut, const char *name, const char *fullpath, | |
171 struct upnp_entry_t *parent, off_t size, int dir) | |
172 { | |
173 struct upnp_entry_t *entry = NULL; | |
174 char *title = NULL, *x = NULL; | |
175 char url_tmp[MAX_URL_SIZE] = { '\0' }; | |
176 char *title_or_name = NULL; | |
177 | |
178 if (!name) | |
179 return NULL; | |
180 | |
181 entry = (struct upnp_entry_t *) malloc (sizeof (struct upnp_entry_t)); | |
182 | |
183 #ifdef HAVE_DLNA | |
184 // dlna_profile_t $B$rYTB$(B | |
185 entry->dlna_profile = NULL; | |
186 entry->url = NULL; | |
187 if (ut->dlna_enabled && fullpath && !dir) | |
188 { | |
189 dlna_profile_t *p = malloc(sizeof(dlna_profile_t)); | |
190 p->id = "MPEG_TS_HD_60_L2_ISO;DLNA.ORG_OP=01;"; | |
191 p->mime = "video/mpeg"; | |
192 p->label = "label"; | |
193 p->class = DLNA_CLASS_AV; | |
194 #if 0 | |
195 dlna_profile_t *p = dlna_guess_media_profile (ut->dlna, fullpath); | |
196 if (!p) | |
197 { | |
198 free (entry); | |
199 return NULL; | |
200 } | |
201 #endif | |
202 entry->dlna_profile = p; | |
203 } | |
204 #endif /* HAVE_DLNA */ | |
205 | |
206 if (ut->xbox360) | |
207 { | |
208 if (ut->root_entry) | |
209 entry->id = ut->starting_id + ut->nr_entries++; | |
210 else | |
211 entry->id = 0; /* Creating the root node so don't use the usual IDs */ | |
212 } | |
213 else | |
214 entry->id = ut->starting_id + ut->nr_entries++; | |
215 log_verbose ("upnp_entry_new(), entry->id[%d]\n", entry->id); | |
216 | |
217 entry->fullpath = fullpath ? strdup (fullpath) : NULL; | |
218 entry->parent = parent; | |
219 entry->child_count = dir ? 0 : -1; | |
220 entry->title = NULL; | |
221 | |
222 entry->childs = (struct upnp_entry_t **) | |
223 malloc (sizeof (struct upnp_entry_t *)); | |
224 *(entry->childs) = NULL; | |
225 | |
226 if (!dir) /* item */ | |
227 { | |
228 #if 0 | |
229 #ifdef HAVE_DLNA | |
230 if (ut->dlna_enabled) | |
231 entry->mime_type = NULL; | |
232 else | |
233 { | |
234 #endif /* HAVE_DLNA */ | |
235 #endif | |
236 struct mime_type_t *mime = getMimeType (getExtension (name)); | |
237 if (!mime) | |
238 { | |
239 --ut->nr_entries; | |
240 upnp_entry_free (ut, entry); | |
241 log_error ("Invalid Mime type for %s, entry ignored", name); | |
242 return NULL; | |
243 } | |
244 entry->mime_type = mime; | |
245 #if 0 | |
246 #ifdef HAVE_DLNA | |
247 } | |
248 #endif /* HAVE_DLNA */ | |
249 #endif | |
250 | |
251 if (snprintf (url_tmp, MAX_URL_SIZE, "%d.%s", | |
252 entry->id, getExtension (name)) >= MAX_URL_SIZE) | |
253 log_error ("URL string too long for id %d, truncated!!", entry->id); | |
254 | |
255 /* Only malloc() what we really need */ | |
256 entry->url = strdup (url_tmp); | |
257 } | |
258 else /* container */ | |
259 { | |
260 entry->mime_type = &Container_MIME_Type; | |
261 entry->url = NULL; | |
262 } | |
263 | |
264 /* Try Iconv'ing the name but if it fails the end device | |
265 may still be able to handle it */ | |
266 title = iconv_convert (name); | |
267 if (title) | |
268 title_or_name = title; | |
269 else | |
270 { | |
271 if (ut->override_iconv_err) | |
272 { | |
273 title_or_name = strdup (name); | |
274 log_error ("Entry invalid name id=%d [%s]\n", entry->id, name); | |
275 } | |
276 else | |
277 { | |
278 upnp_entry_free (ut, entry); | |
279 log_error ("Freeing entry invalid name id=%d [%s]\n", entry->id, name); | |
280 return NULL; | |
281 } | |
282 } | |
283 | |
284 if (!dir) | |
285 { | |
286 x = strrchr (title_or_name, '.'); | |
287 if (x) /* avoid displaying file extension */ | |
288 *x = '\0'; | |
289 } | |
290 x = convert_xml (title_or_name); | |
291 if (x) | |
292 { | |
293 free (title_or_name); | |
294 title_or_name = x; | |
295 } | |
296 entry->title = title_or_name; | |
297 | |
298 if (!strcmp (title_or_name, "")) /* DIDL dc:title can't be empty */ | |
299 { | |
300 free (title_or_name); | |
301 entry->title = strdup (TITLE_UNKNOWN); | |
302 } | |
303 | |
304 entry->size = size; | |
305 entry->fd = -1; | |
306 | |
307 if (entry->id && entry->url) | |
308 log_verbose ("Entry->URL (%d): %s\n", entry->id, entry->url); | |
309 | |
310 return entry; | |
311 } | |
312 | |
313 /* Seperate recursive free() function in order to avoid freeing off | |
314 * the parents child list within the freeing of the first child, as | |
315 * the only entry which is not part of a childs list is the root entry | |
316 */ | |
317 static void | |
318 _upnp_entry_free (struct upnp_entry_t *entry) | |
319 { | |
320 struct upnp_entry_t **childs; | |
321 | |
322 if (!entry) | |
323 return; | |
324 | |
325 if (entry->fullpath) | |
326 free (entry->fullpath); | |
327 if (entry->title) | |
328 free (entry->title); | |
329 if (entry->url) | |
330 free (entry->url); | |
331 #ifdef HAVE_DLNA | |
332 if (entry->dlna_profile) | |
333 entry->dlna_profile = NULL; | |
334 #endif /* HAVE_DLNA */ | |
335 | |
336 for (childs = entry->childs; *childs; childs++) | |
337 _upnp_entry_free (*childs); | |
338 free (entry->childs); | |
339 } | |
340 | |
341 void | |
342 upnp_entry_free (struct ushare_t *ut, struct upnp_entry_t *entry) | |
343 { | |
344 if (!ut || !entry) | |
345 return; | |
346 | |
347 /* Free all entries (i.e. children) */ | |
348 if (entry == ut->root_entry) | |
349 { | |
350 struct upnp_entry_t *entry_found = NULL; | |
351 struct upnp_entry_lookup_t *lk = NULL; | |
352 RBLIST *rblist; | |
353 int i = 0; | |
354 | |
355 rblist = rbopenlist (ut->rb); | |
356 lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); | |
357 | |
358 while (lk) | |
359 { | |
360 entry_found = lk->entry_ptr; | |
361 if (entry_found) | |
362 { | |
363 if (entry_found->fullpath) | |
364 free (entry_found->fullpath); | |
365 if (entry_found->title) | |
366 free (entry_found->title); | |
367 if (entry_found->url) | |
368 free (entry_found->url); | |
369 | |
370 free (entry_found); | |
371 i++; | |
372 } | |
373 | |
374 free (lk); /* delete the lookup */ | |
375 lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); | |
376 } | |
377 | |
378 rbcloselist (rblist); | |
379 rbdestroy (ut->rb); | |
380 ut->rb = NULL; | |
381 | |
382 log_verbose ("Freed [%d] entries\n", i); | |
383 } | |
384 else | |
385 _upnp_entry_free (entry); | |
386 | |
387 free (entry); | |
388 } | |
389 | |
390 static void | |
391 upnp_entry_add_child (struct ushare_t *ut, | |
392 struct upnp_entry_t *entry, struct upnp_entry_t *child) | |
393 { | |
394 struct upnp_entry_lookup_t *entry_lookup_ptr = NULL; | |
395 struct upnp_entry_t **childs; | |
396 int n; | |
397 | |
398 if (!entry || !child) | |
399 return; | |
400 | |
401 for (childs = entry->childs; *childs; childs++) | |
402 if (*childs == child) | |
403 return; | |
404 | |
405 n = get_list_length ((void *) entry->childs) + 1; | |
406 entry->childs = (struct upnp_entry_t **) | |
407 realloc (entry->childs, (n + 1) * sizeof (*(entry->childs))); | |
408 entry->childs[n] = NULL; | |
409 entry->childs[n - 1] = child; | |
410 entry->child_count++; | |
411 | |
412 entry_lookup_ptr = (struct upnp_entry_lookup_t *) | |
413 malloc (sizeof (struct upnp_entry_lookup_t)); | |
414 entry_lookup_ptr->id = child->id; | |
415 entry_lookup_ptr->entry_ptr = child; | |
416 | |
417 if (rbsearch ((void *) entry_lookup_ptr, ut->rb) == NULL) | |
418 log_info (_("Failed to add the RB lookup tree\n")); | |
419 } | |
420 | |
421 struct upnp_entry_t * | |
422 upnp_get_entry (struct ushare_t *ut, int id) | |
423 { | |
424 struct upnp_entry_lookup_t *res, entry_lookup; | |
425 | |
426 log_verbose ("Looking for entry id %d\n", id); | |
427 if (id == 0) /* We do not store the root (id 0) as it is not a child */ | |
428 return ut->root_entry; | |
429 | |
430 entry_lookup.id = id; | |
431 res = (struct upnp_entry_lookup_t *) | |
432 rbfind ((void *) &entry_lookup, ut->rb); | |
433 | |
434 if (res) | |
435 { | |
436 log_verbose ("Found at %p\n", | |
437 ((struct upnp_entry_lookup_t *) res)->entry_ptr); | |
438 return ((struct upnp_entry_lookup_t *) res)->entry_ptr; | |
439 } | |
440 | |
441 log_verbose ("Not Found\n"); | |
442 | |
443 return NULL; | |
444 } | |
445 | |
446 static void | |
447 metadata_add_file (struct ushare_t *ut, struct upnp_entry_t *entry, | |
448 const char *file, const char *name, struct stat *st_ptr) | |
449 { | |
450 if (!entry || !file || !name) | |
451 return; | |
452 | |
453 #ifdef HAVE_DLNA | |
454 if (ut->dlna_enabled || is_valid_extension (getExtension (file))) | |
455 #else | |
456 if (is_valid_extension (getExtension (file))) | |
457 #endif | |
458 { | |
459 struct upnp_entry_t *child = NULL; | |
460 | |
461 child = upnp_entry_new (ut, name, file, entry, st_ptr->st_size, false); | |
462 if (child) | |
463 upnp_entry_add_child (ut, entry, child); | |
464 } | |
465 } | |
466 | |
467 static void | |
468 metadata_add_container (struct ushare_t *ut, | |
469 struct upnp_entry_t *entry, const char *container) | |
470 { | |
471 struct dirent **namelist; | |
472 int n,i; | |
473 | |
474 if (!entry || !container) | |
475 return; | |
476 | |
477 n = scandir (container, &namelist, 0, alphasort); | |
478 if (n < 0) | |
479 { | |
480 perror ("scandir"); | |
481 return; | |
482 } | |
483 | |
484 for (i = 0; i < n; i++) | |
485 { | |
486 struct stat st; | |
487 char *fullpath = NULL; | |
488 | |
489 if (namelist[i]->d_name[0] == '.') | |
490 { | |
491 free (namelist[i]); | |
492 continue; | |
493 } | |
494 | |
495 fullpath = (char *) | |
496 malloc (strlen (container) + strlen (namelist[i]->d_name) + 2); | |
497 sprintf (fullpath, "%s/%s", container, namelist[i]->d_name); | |
498 | |
499 log_verbose ("%s\n", fullpath); | |
500 | |
501 if (stat (fullpath, &st) < 0) | |
502 { | |
503 free (namelist[i]); | |
504 free (fullpath); | |
505 continue; | |
506 } | |
507 | |
508 if (S_ISDIR (st.st_mode)) | |
509 { | |
510 struct upnp_entry_t *child = NULL; | |
511 | |
512 child = upnp_entry_new (ut, namelist[i]->d_name, | |
513 fullpath, entry, 0, true); | |
514 if (child) | |
515 { | |
516 metadata_add_container (ut, child, fullpath); | |
517 upnp_entry_add_child (ut, entry, child); | |
518 } | |
519 } | |
520 else | |
521 metadata_add_file (ut, entry, fullpath, namelist[i]->d_name, &st); | |
522 | |
523 free (namelist[i]); | |
524 free (fullpath); | |
525 } | |
526 free (namelist); | |
527 } | |
528 | |
529 void | |
530 free_metadata_list (struct ushare_t *ut) | |
531 { | |
532 ut->init = 0; | |
533 if (ut->root_entry) | |
534 upnp_entry_free (ut, ut->root_entry); | |
535 ut->root_entry = NULL; | |
536 ut->nr_entries = 0; | |
537 | |
538 if (ut->rb) | |
539 { | |
540 rbdestroy (ut->rb); | |
541 ut->rb = NULL; | |
542 } | |
543 | |
544 ut->rb = rbinit (rb_compare, NULL); | |
545 if (!ut->rb) | |
546 log_error (_("Cannot create RB tree for lookups\n")); | |
547 } | |
548 | |
549 void | |
550 build_metadata_list (struct ushare_t *ut) | |
551 { | |
552 int i; | |
553 struct stat st; | |
554 log_info (_("Building Metadata List ...\n")); | |
555 | |
556 /* build root entry */ | |
557 if (!ut->root_entry) | |
558 ut->root_entry = upnp_entry_new (ut, "root", NULL, NULL, -1, true); | |
559 | |
560 struct upnp_entry_t *entry = ut->root_entry; | |
561 st.st_size = 100*1024*1024; | |
152
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
562 for(i=0; i < ut->channel_list->nr_channel; i++) { |
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
563 char name[1024]; |
155
5d010d0ff6a1
Change line number of the CSV file to be used for ID management.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
152
diff
changeset
|
564 char id[16]; |
152
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
565 name[0] = '\0'; |
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
566 strncpy(name, ut->channel_list->channel_info[i]->name, sizeof(name)); |
155
5d010d0ff6a1
Change line number of the CSV file to be used for ID management.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
152
diff
changeset
|
567 snprintf(id, sizeof(id), "%d", ut->channel_list->channel_info[i]->id); |
152
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
568 strncat(name, ".ts", sizeof(name)); |
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
569 metadata_add_file (ut, |
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
570 ut->root_entry, |
155
5d010d0ff6a1
Change line number of the CSV file to be used for ID management.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
152
diff
changeset
|
571 id, |
152
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
572 name, |
30e91361506a
EXPERIMENTAL: Enable change phisical channel by DLNA.(ISDB-T only)
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
146
diff
changeset
|
573 &st); |
146
066f33b2213a
EXPERIMENTAL: Select a particular program from multi-channel.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
136
diff
changeset
|
574 } |
126
5dcaf3785ebe
fix process terminate problem.
Naoya OYAMA <naoya.oyama@gmail.com>
parents:
125
diff
changeset
|
575 ut->contentlist = NULL; |
125 | 576 log_info (_("Found %d files and subdirectories.\n"), ut->nr_entries); |
577 ut->init = 1; | |
578 } | |
579 | |
580 int | |
581 rb_compare (const void *pa, const void *pb, | |
582 const void *config __attribute__ ((unused))) | |
583 { | |
584 struct upnp_entry_lookup_t *a, *b; | |
585 | |
586 a = (struct upnp_entry_lookup_t *) pa; | |
587 b = (struct upnp_entry_lookup_t *) pb; | |
588 | |
589 if (a->id < b->id) | |
590 return -1; | |
591 | |
592 if (a->id > b->id) | |
593 return 1; | |
594 | |
595 return 0; | |
596 } | |
597 |