Mercurial > pt1.oyama
diff src/metadata.c @ 125:e413158cae13
Add ushare project files.
author | naoyan@johnstown.minaminoshima.org |
---|---|
date | Sun, 03 Oct 2010 11:35:19 +0900 |
parents | |
children | 5dcaf3785ebe |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/metadata.c Sun Oct 03 11:35:19 2010 +0900 @@ -0,0 +1,624 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil -*- */ +/* vim: set ts=4 sts=4 sw=4 expandtab number : */ +/* + * metadata.c : GeeXboX uShare CDS Metadata DB. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdbool.h> + +#include <upnp/upnp.h> +#include <upnp/upnptools.h> + +#include "mime.h" +#include "metadata.h" +#include "util_iconv.h" +#include "content.h" +#include "gettext.h" +#include "trace.h" + +#define TITLE_UNKNOWN "unknown" + +#define MAX_URL_SIZE 32 + +struct upnp_entry_lookup_t { + int id; + struct upnp_entry_t *entry_ptr; +}; + +static char * +getExtension (const char *filename) +{ + char *str = NULL; + + str = strrchr (filename, '.'); + if (str) + str++; + + return str; +} + +static struct mime_type_t * +getMimeType (const char *extension) +{ + extern struct mime_type_t MIME_Type_List[]; + struct mime_type_t *list; + + if (!extension) + return NULL; + + list = MIME_Type_List; + while (list->extension) + { + if (!strcasecmp (list->extension, extension)) + return list; + list++; + } + + return NULL; +} + +static bool +is_valid_extension (const char *extension) +{ + if (!extension) + return false; + + if (getMimeType (extension)) + return true; + + return false; +} + +static int +get_list_length (void *list) +{ + void **l = list; + int n = 0; + + while (*(l++)) + n++; + + return n; +} + +static xml_convert_t xml_convert[] = { + {'"' , """}, + {'&' , "&"}, + {'\'', "'"}, + {'<' , "<"}, + {'>' , ">"}, + {'\n', "
"}, + {'\r', "
"}, + {'\t', "	"}, + {0, NULL}, +}; + +static char * +get_xmlconvert (int c) +{ + int j; + for (j = 0; xml_convert[j].xml; j++) + { + if (c == xml_convert[j].charac) + return xml_convert[j].xml; + } + return NULL; +} + +static char * +convert_xml (const char *title) +{ + char *newtitle, *s, *t, *xml; + int nbconvert = 0; + + /* calculate extra size needed */ + for (t = (char*) title; *t; t++) + { + xml = get_xmlconvert (*t); + if (xml) + nbconvert += strlen (xml) - 1; + } + if (!nbconvert) + return NULL; + + newtitle = s = (char*) malloc (strlen (title) + nbconvert + 1); + + for (t = (char*) title; *t; t++) + { + xml = get_xmlconvert (*t); + if (xml) + { + strcpy (s, xml); + s += strlen (xml); + } + else + *s++ = *t; + } + *s = '\0'; + + return newtitle; +} + +static struct mime_type_t Container_MIME_Type = + { NULL, "object.container.storageFolder", NULL}; + +static struct upnp_entry_t * +upnp_entry_new (struct ushare_t *ut, const char *name, const char *fullpath, + struct upnp_entry_t *parent, off_t size, int dir) +{ + struct upnp_entry_t *entry = NULL; + char *title = NULL, *x = NULL; + char url_tmp[MAX_URL_SIZE] = { '\0' }; + char *title_or_name = NULL; + + if (!name) + return NULL; + + entry = (struct upnp_entry_t *) malloc (sizeof (struct upnp_entry_t)); + +#ifdef HAVE_DLNA + // dlna_profile_t を捏造 + entry->dlna_profile = NULL; + entry->url = NULL; + if (ut->dlna_enabled && fullpath && !dir) + { + dlna_profile_t *p = malloc(sizeof(dlna_profile_t)); + p->id = "MPEG_TS_HD_60_L2_ISO;DLNA.ORG_OP=01;"; + p->mime = "video/mpeg"; + p->label = "label"; + p->class = DLNA_CLASS_AV; +#if 0 + dlna_profile_t *p = dlna_guess_media_profile (ut->dlna, fullpath); + if (!p) + { + free (entry); + return NULL; + } +#endif + entry->dlna_profile = p; + } +#endif /* HAVE_DLNA */ + + if (ut->xbox360) + { + if (ut->root_entry) + entry->id = ut->starting_id + ut->nr_entries++; + else + entry->id = 0; /* Creating the root node so don't use the usual IDs */ + } + else + entry->id = ut->starting_id + ut->nr_entries++; + log_verbose ("upnp_entry_new(), entry->id[%d]\n", entry->id); + + entry->fullpath = fullpath ? strdup (fullpath) : NULL; + entry->parent = parent; + entry->child_count = dir ? 0 : -1; + entry->title = NULL; + + entry->childs = (struct upnp_entry_t **) + malloc (sizeof (struct upnp_entry_t *)); + *(entry->childs) = NULL; + + if (!dir) /* item */ + { +#if 0 +#ifdef HAVE_DLNA + if (ut->dlna_enabled) + entry->mime_type = NULL; + else + { +#endif /* HAVE_DLNA */ +#endif + struct mime_type_t *mime = getMimeType (getExtension (name)); + if (!mime) + { + --ut->nr_entries; + upnp_entry_free (ut, entry); + log_error ("Invalid Mime type for %s, entry ignored", name); + return NULL; + } + entry->mime_type = mime; +#if 0 +#ifdef HAVE_DLNA + } +#endif /* HAVE_DLNA */ +#endif + + if (snprintf (url_tmp, MAX_URL_SIZE, "%d.%s", + entry->id, getExtension (name)) >= MAX_URL_SIZE) + log_error ("URL string too long for id %d, truncated!!", entry->id); + + /* Only malloc() what we really need */ + entry->url = strdup (url_tmp); + } + else /* container */ + { + entry->mime_type = &Container_MIME_Type; + entry->url = NULL; + } + + /* Try Iconv'ing the name but if it fails the end device + may still be able to handle it */ + title = iconv_convert (name); + if (title) + title_or_name = title; + else + { + if (ut->override_iconv_err) + { + title_or_name = strdup (name); + log_error ("Entry invalid name id=%d [%s]\n", entry->id, name); + } + else + { + upnp_entry_free (ut, entry); + log_error ("Freeing entry invalid name id=%d [%s]\n", entry->id, name); + return NULL; + } + } + + if (!dir) + { + x = strrchr (title_or_name, '.'); + if (x) /* avoid displaying file extension */ + *x = '\0'; + } + x = convert_xml (title_or_name); + if (x) + { + free (title_or_name); + title_or_name = x; + } + entry->title = title_or_name; + + if (!strcmp (title_or_name, "")) /* DIDL dc:title can't be empty */ + { + free (title_or_name); + entry->title = strdup (TITLE_UNKNOWN); + } + + entry->size = size; + entry->fd = -1; + + if (entry->id && entry->url) + log_verbose ("Entry->URL (%d): %s\n", entry->id, entry->url); + + return entry; +} + +/* Seperate recursive free() function in order to avoid freeing off + * the parents child list within the freeing of the first child, as + * the only entry which is not part of a childs list is the root entry + */ +static void +_upnp_entry_free (struct upnp_entry_t *entry) +{ + struct upnp_entry_t **childs; + + if (!entry) + return; + + if (entry->fullpath) + free (entry->fullpath); + if (entry->title) + free (entry->title); + if (entry->url) + free (entry->url); +#ifdef HAVE_DLNA + if (entry->dlna_profile) + entry->dlna_profile = NULL; +#endif /* HAVE_DLNA */ + + for (childs = entry->childs; *childs; childs++) + _upnp_entry_free (*childs); + free (entry->childs); +} + +void +upnp_entry_free (struct ushare_t *ut, struct upnp_entry_t *entry) +{ + if (!ut || !entry) + return; + + /* Free all entries (i.e. children) */ + if (entry == ut->root_entry) + { + struct upnp_entry_t *entry_found = NULL; + struct upnp_entry_lookup_t *lk = NULL; + RBLIST *rblist; + int i = 0; + + rblist = rbopenlist (ut->rb); + lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); + + while (lk) + { + entry_found = lk->entry_ptr; + if (entry_found) + { + if (entry_found->fullpath) + free (entry_found->fullpath); + if (entry_found->title) + free (entry_found->title); + if (entry_found->url) + free (entry_found->url); + + free (entry_found); + i++; + } + + free (lk); /* delete the lookup */ + lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); + } + + rbcloselist (rblist); + rbdestroy (ut->rb); + ut->rb = NULL; + + log_verbose ("Freed [%d] entries\n", i); + } + else + _upnp_entry_free (entry); + + free (entry); +} + +static void +upnp_entry_add_child (struct ushare_t *ut, + struct upnp_entry_t *entry, struct upnp_entry_t *child) +{ + struct upnp_entry_lookup_t *entry_lookup_ptr = NULL; + struct upnp_entry_t **childs; + int n; + + if (!entry || !child) + return; + + for (childs = entry->childs; *childs; childs++) + if (*childs == child) + return; + + n = get_list_length ((void *) entry->childs) + 1; + entry->childs = (struct upnp_entry_t **) + realloc (entry->childs, (n + 1) * sizeof (*(entry->childs))); + entry->childs[n] = NULL; + entry->childs[n - 1] = child; + entry->child_count++; + + entry_lookup_ptr = (struct upnp_entry_lookup_t *) + malloc (sizeof (struct upnp_entry_lookup_t)); + entry_lookup_ptr->id = child->id; + entry_lookup_ptr->entry_ptr = child; + + if (rbsearch ((void *) entry_lookup_ptr, ut->rb) == NULL) + log_info (_("Failed to add the RB lookup tree\n")); +} + +struct upnp_entry_t * +upnp_get_entry (struct ushare_t *ut, int id) +{ + struct upnp_entry_lookup_t *res, entry_lookup; + + log_verbose ("Looking for entry id %d\n", id); + if (id == 0) /* We do not store the root (id 0) as it is not a child */ + return ut->root_entry; + + entry_lookup.id = id; + res = (struct upnp_entry_lookup_t *) + rbfind ((void *) &entry_lookup, ut->rb); + + if (res) + { + log_verbose ("Found at %p\n", + ((struct upnp_entry_lookup_t *) res)->entry_ptr); + return ((struct upnp_entry_lookup_t *) res)->entry_ptr; + } + + log_verbose ("Not Found\n"); + + return NULL; +} + +static void +metadata_add_file (struct ushare_t *ut, struct upnp_entry_t *entry, + const char *file, const char *name, struct stat *st_ptr) +{ + if (!entry || !file || !name) + return; + +#ifdef HAVE_DLNA + if (ut->dlna_enabled || is_valid_extension (getExtension (file))) +#else + if (is_valid_extension (getExtension (file))) +#endif + { + struct upnp_entry_t *child = NULL; + + child = upnp_entry_new (ut, name, file, entry, st_ptr->st_size, false); + if (child) + upnp_entry_add_child (ut, entry, child); + } +} + +static void +metadata_add_container (struct ushare_t *ut, + struct upnp_entry_t *entry, const char *container) +{ + struct dirent **namelist; + int n,i; + + if (!entry || !container) + return; + + n = scandir (container, &namelist, 0, alphasort); + if (n < 0) + { + perror ("scandir"); + return; + } + + for (i = 0; i < n; i++) + { + struct stat st; + char *fullpath = NULL; + + if (namelist[i]->d_name[0] == '.') + { + free (namelist[i]); + continue; + } + + fullpath = (char *) + malloc (strlen (container) + strlen (namelist[i]->d_name) + 2); + sprintf (fullpath, "%s/%s", container, namelist[i]->d_name); + + log_verbose ("%s\n", fullpath); + + if (stat (fullpath, &st) < 0) + { + free (namelist[i]); + free (fullpath); + continue; + } + + if (S_ISDIR (st.st_mode)) + { + struct upnp_entry_t *child = NULL; + + child = upnp_entry_new (ut, namelist[i]->d_name, + fullpath, entry, 0, true); + if (child) + { + metadata_add_container (ut, child, fullpath); + upnp_entry_add_child (ut, entry, child); + } + } + else + metadata_add_file (ut, entry, fullpath, namelist[i]->d_name, &st); + + free (namelist[i]); + free (fullpath); + } + free (namelist); +} + +void +free_metadata_list (struct ushare_t *ut) +{ + ut->init = 0; + if (ut->root_entry) + upnp_entry_free (ut, ut->root_entry); + ut->root_entry = NULL; + ut->nr_entries = 0; + + if (ut->rb) + { + rbdestroy (ut->rb); + ut->rb = NULL; + } + + ut->rb = rbinit (rb_compare, NULL); + if (!ut->rb) + log_error (_("Cannot create RB tree for lookups\n")); +} + +void +build_metadata_list (struct ushare_t *ut) +{ + int i; + struct stat st; + log_info (_("Building Metadata List ...\n")); + + /* build root entry */ + if (!ut->root_entry) + ut->root_entry = upnp_entry_new (ut, "root", NULL, NULL, -1, true); + +#if 0 + entry = upnp_entry_new (ut, "stream.ts", "/web/stream.ts", + ut->root_entry, -1, false); + upnp_entry_add_child (ut, ut->root_entry, entry); + metadata_add_container (ut, entry, "/web/"); +#endif + struct upnp_entry_t *entry = ut->root_entry; + st.st_size = 100*1024*1024; + metadata_add_file (ut, ut->root_entry, STREAM_LOCATION, STREAM_FILE_NAME, &st); + //metadata_add_container (ut, ut->root_entry, "/web/"); + +#if 0 + /* add files from content directory */ + for (i=0 ; i < ut->contentlist->count ; i++) + { + struct upnp_entry_t *entry = NULL; + char *title = NULL; + int size = 0; + + log_info (_("Looking for files in content directory : %s\n"), + ut->contentlist->content[i]); + + size = strlen (ut->contentlist->content[i]); + if (ut->contentlist->content[i][size - 1] == '/') + ut->contentlist->content[i][size - 1] = '\0'; + title = strrchr (ut->contentlist->content[i], '/'); + if (title) + title++; + else + { + /* directly use content directory name if no '/' before basename */ + title = ut->contentlist->content[i]; + } + + entry = upnp_entry_new (ut, title, ut->contentlist->content[i], + ut->root_entry, -1, true); + + if (!entry) + continue; + upnp_entry_add_child (ut, ut->root_entry, entry); + metadata_add_container (ut, entry, ut->contentlist->content[i]); + } +#endif + + log_info (_("Found %d files and subdirectories.\n"), ut->nr_entries); + ut->init = 1; +} + +int +rb_compare (const void *pa, const void *pb, + const void *config __attribute__ ((unused))) +{ + struct upnp_entry_lookup_t *a, *b; + + a = (struct upnp_entry_lookup_t *) pa; + b = (struct upnp_entry_lookup_t *) pb; + + if (a->id < b->id) + return -1; + + if (a->id > b->id) + return 1; + + return 0; +} +