ushare-/appl/metadata.cpp

594 lines
13 KiB
C++

/*
* 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 <string.h>
#include <stdio.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 = (void **)list;
int n = 0;
while (*(l++))
n++;
return n;
}
static xml_convert_t xml_convert[] = {
{'"' , "&quot;"},
{'&' , "&amp;"},
{'\'', "&apos;"},
{'<' , "&lt;"},
{'>' , "&gt;"},
{'\n', "&#xA;"},
{'\r', "&#xD;"},
{'\t', "&#x9;"},
{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
entry->dlna_profile = NULL;
entry->url = NULL;
if (ut->dlna_enabled && fullpath && !dir)
{
dlna_profile_t *p = dlna_guess_media_profile (ut->dlna, fullpath);
if (!p)
{
free (entry);
return NULL;
}
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++;
}
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 */ {
#ifdef HAVE_DLNA
if (ut->dlna_enabled) {
entry->mime_type = NULL;
} else {
#endif /* HAVE_DLNA */
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;
#ifdef HAVE_DLNA
}
#endif /* HAVE_DLNA */
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;
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);
/* 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]);
}
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;
}