594 lines
13 KiB
C++
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[] = {
|
|
{'"' , """},
|
|
{'&' , "&"},
|
|
{'\'', "'"},
|
|
{'<' , "<"},
|
|
{'>' , ">"},
|
|
{'\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
|
|
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;
|
|
}
|
|
|