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[] = {
+  {'"' , "&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
+  // 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;
+}
+