[grilo-plugins] thetvdb: Include The TVDB source
- From: Victor Toso de Carvalho <victortoso src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [grilo-plugins] thetvdb: Include The TVDB source
- Date: Mon, 23 Jun 2014 05:06:51 +0000 (UTC)
commit b2b937413a26a0fe2ad18891a27a30a35b12b26b
Author: Victor Toso <me victortoso com>
Date: Sat May 24 20:08:48 2014 -0300
thetvdb: Include The TVDB source
https://bugzilla.gnome.org/show_bug.cgi?id=672933
Makefile.am | 1 +
configure.ac | 51 +
src/Makefile.am | 6 +-
src/thetvdb/Makefile.am | 48 +
src/thetvdb/grl-thetvdb.c | 1594 ++++++++++++++++++++++++++++++
src/thetvdb/grl-thetvdb.h | 66 ++
src/thetvdb/grl-thetvdb.xml | 10 +
src/thetvdb/thetvdb-resources-episodes.c | 349 +++++++
src/thetvdb/thetvdb-resources-series.c | 338 +++++++
src/thetvdb/thetvdb-resources.h | 155 +++
10 files changed, 2617 insertions(+), 1 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 813aeac..1a36173 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,6 +53,7 @@ DISTCHECK_CONFIGURE_FLAGS = --enable-apple-trailers \
--enable-optical-media \
--enable-pocket \
--enable-podcasts \
+ --enable-thetvdb \
--enable-tmdb \
--enable-tracker \
--enable-upnp \
diff --git a/configure.ac b/configure.ac
index b5ef16b..c84cdcf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1273,6 +1273,55 @@ DEPS_DMAP_LIBS="$DEPS_LIBS $DMAP_LIBS $XML_LIBS"
AC_SUBST(DEPS_DMAP_LIBS)
# ----------------------------------------------------------
+# BUILD THETVDB PLUGIN
+# ----------------------------------------------------------
+
+AC_ARG_ENABLE(thetvdb,
+ AC_HELP_STRING([--enable-thetvdb],
+ [enable thetvdb plugin (default: auto)]),
+ [
+ case "$enableval" in
+ yes)
+ if test "x$HAVE_GRLNET" = "xno"; then
+ AC_MSG_ERROR([grilo-net-0.2 >= 0.2.2 not found, install it or use
--disable-thetvdb])
+ fi
+ if test "x$HAVE_XML" = "xno"; then
+ AC_MSG_ERROR([libxml-2.0 not found, install it or use --disable-thetvdb])
+ fi
+ if test "x$HAVE_ARCHIVE" = "xno"; then
+ AC_MSG_ERROR([libarchive not found, install it or use --disable-thetvdb])
+ fi
+ if test "x$HAVE_GOM" = "xno"; then
+ AC_MSG_ERROR([gom-1.0 not found, install it or use --disable-thetvdb])
+ fi
+ ;;
+ esac
+ ],
+ [
+ if test "x$HAVE_GRLNET" = "xyes" -a "x$HAVE_XML" = "xyes" -a "x$HAVE_ARCHIVE" = "xyes" -a
"x$HAVE_GOM" = "xyes"; then
+ enable_thetvdb=yes
+ else
+ enable_thetvdb=no
+ fi
+ ])
+
+AM_CONDITIONAL([THETVDB_PLUGIN], [test "x$enable_thetvdb" = "xyes"])
+GRL_PLUGINS_ALL="$GRL_PLUGINS_ALL thetvdb"
+if test "x$enable_thetvdb" = "xyes"
+then
+ GRL_PLUGINS_ENABLED="$GRL_PLUGINS_ENABLED thetvdb"
+fi
+
+THETVDB_PLUGIN_ID="grl-thetvdb"
+AC_SUBST(THETVDB_PLUGIN_ID)
+AC_DEFINE_UNQUOTED([THETVDB_PLUGIN_ID], ["$THETVDB_PLUGIN_ID"], [thetvdb plugin ID])
+
+DEPS_THETVDB_CFLAGS="$DEPS_CFLAGS $ARCHIVE_CFLAGS $GRLNET_CFLAGS $XML_CFLAGS $GOM_CFLAGS"
+AC_SUBST(DEPS_THETVDB_CFLAGS)
+DEPS_THETVDB_LIBS="$DEPS_LIBS $ARCHIVE_LIBS $GRLNET_LIBS $XML_LIBS $GOM_LIBS"
+AC_SUBST(DEPS_THETVDB_LIBS)
+
+# ----------------------------------------------------------
# BUILD TMDB PLUGIN
# ----------------------------------------------------------
@@ -1399,6 +1448,7 @@ AC_CONFIG_FILES([
src/lua-factory/Makefile
src/lua-factory/sources/Makefile
src/magnatune/Makefile
+ src/thetvdb/Makefile
src/metadata-store/Makefile
src/optical-media/Makefile
src/pocket/Makefile
@@ -1416,6 +1466,7 @@ AC_CONFIG_FILES([
tests/dleyna/Makefile
tests/dleyna/dbusmock/dleyna-server-mock.service
tests/local-metadata/Makefile
+ tests/thetvdb/Makefile
tests/tmdb/Makefile
tests/vimeo/Makefile
])
diff --git a/src/Makefile.am b/src/Makefile.am
index 7b4f309..746ccd8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -86,6 +86,10 @@ if SHOUTCAST_PLUGIN
SUBDIRS += shoutcast
endif
+if THETVDB_PLUGIN
+SUBDIRS += thetvdb
+endif
+
if TMDB_PLUGIN
SUBDIRS += tmdb
endif
@@ -113,7 +117,7 @@ endif
DIST_SUBDIRS = \
apple-trailers bliptv bookmarks dleyna dmap filesystem flickr freebox gravatar jamendo \
lastfm-albumart local-metadata lua-factory magnatune metadata-store optical-media \
- pocket podcasts raitv shoutcast tmdb tracker upnp vimeo youtube
+ pocket podcasts raitv shoutcast thetvdb tmdb tracker upnp vimeo youtube
MAINTAINERCLEANFILES = \
*.in \
diff --git a/src/thetvdb/Makefile.am b/src/thetvdb/Makefile.am
new file mode 100644
index 0000000..05b0c94
--- /dev/null
+++ b/src/thetvdb/Makefile.am
@@ -0,0 +1,48 @@
+#
+# Makefile.am
+#
+# Author: Victor Toso <me victortoso com>
+#
+# Copyright (C) 2014 Victor Toso. All rights reserved.
+
+include $(top_srcdir)/gtester.mk
+
+ext_LTLIBRARIES = libgrlthetvdb.la
+
+libgrlthetvdb_la_CFLAGS = \
+ $(DEPS_THETVDB_CFLAGS) \
+ -DG_LOG_DOMAIN=\"GrlTheTVDB\" \
+ -DLOCALEDIR=\"$(localedir)\"
+
+libgrlthetvdb_la_LIBADD = \
+ $(DEPS_THETVDB_LIBS)
+
+libgrlthetvdb_la_LDFLAGS = \
+ -no-undefined \
+ -module \
+ -avoid-version
+
+libgrlthetvdb_la_SOURCES = \
+ grl-thetvdb.c \
+ grl-thetvdb.h \
+ thetvdb-resources-series.c \
+ thetvdb-resources-episodes.c \
+ thetvdb-resources.h
+
+extdir = $(GRL_PLUGINS_DIR)
+thetvdbxmldir = $(GRL_PLUGINS_DIR)
+thetvdbxml_DATA = $(THETVDB_PLUGIN_ID).xml
+
+# Rules to enable tests
+copy-xml-to-libs-dir: libgrlthetvdb.la
+ cp -f $(srcdir)/$(thetvdbxml_DATA) $(builddir)/.libs/
+
+all-local: copy-xml-to-libs-dir
+
+EXTRA_DIST = $(thetvdbxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/thetvdb/grl-thetvdb.c b/src/thetvdb/grl-thetvdb.c
new file mode 100644
index 0000000..f7fff2f
--- /dev/null
+++ b/src/thetvdb/grl-thetvdb.c
@@ -0,0 +1,1594 @@
+/*
+ * Copyright (C) 2014 Victor Toso.
+ *
+ * Contact: Victor Toso <me victortoso com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <net/grl-net.h>
+#include <libxml/xmlreader.h>
+#include <archive.h>
+#include <archive_entry.h>
+
+#include "grl-thetvdb.h"
+#include "thetvdb-resources.h"
+
+#define GRL_THETVDB_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_THETVDB_SOURCE_TYPE, \
+ GrlTheTVDBPrivate))
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT thetvdb_log_domain
+GRL_LOG_DOMAIN_STATIC (thetvdb_log_domain);
+
+/* --- URLs --- */
+
+#define THETVDB_BASE "http://thetvdb.com/"
+#define THETVDB_BASE_API THETVDB_BASE "api/"
+#define THETVDB_BASE_IMG THETVDB_BASE "banners/%s"
+
+#define THETVDB_GET_SERIES THETVDB_BASE_API "GetSeries.php?seriesname=%s"
+#define THETVDB_GET_EPISODES THETVDB_BASE_API "%s/series/%s/all/%s.zip"
+
+/* --- Files and Path to Images --- */
+#define THETVDB_ALL_DATA_XML "%s.xml"
+#define THETVDB_URL_TO_SCREEN THETVDB_BASE "banners/%s"
+
+/* --- Helpers --- */
+#define THETVDB_STR_DELIMITER "|"
+#define THETVDB_DEFAULT_LANG "en"
+#define GRL_SQL_DB "grl-thetvdb.db"
+
+/* --- XML Fields --- */
+#define THETVDB_ID BAD_CAST "id"
+#define THETVDB_SERIES_ID BAD_CAST "SeriesID"
+#define THETVDB_GENRE BAD_CAST "Genre"
+#define THETVDB_ACTORS BAD_CAST "Actors"
+#define THETVDB_LANGUAGE BAD_CAST "Language"
+#define THETVDB_STATUS BAD_CAST "Status"
+#define THETVDB_SERIES_NAME BAD_CAST "SeriesName"
+#define THETVDB_OVERVIEW BAD_CAST "Overview"
+#define THETVDB_RATING BAD_CAST "Rating"
+#define THETVDB_FIRST_AIRED BAD_CAST "FirstAired"
+#define THETVDB_IMDB_ID BAD_CAST "IMDB_ID"
+#define THETVDB_ZAP2IT_ID BAD_CAST "zap2it_id"
+#define THETVDB_BANNER BAD_CAST "banner"
+#define THETVDB_POSTER BAD_CAST "poster"
+#define THETVDB_FANART BAD_CAST "fanart"
+
+#define THETVDB_SEASON_NUMBER BAD_CAST "SeasonNumber"
+#define THETVDB_EPISODE_NUMBER BAD_CAST "EpisodeNumber"
+#define THETVDB_ABSOLUTE_NUMBER BAD_CAST "absolute_number"
+#define THETVDB_EPISODE_NAME BAD_CAST "EpisodeName"
+#define THETVDB_DIRECTOR BAD_CAST "Director"
+#define THETVDB_GUEST_STARS BAD_CAST "GuestStars"
+#define THETVDB_FILENAME BAD_CAST "filename"
+#define THETVDB_SEASON_ID BAD_CAST "seasonid"
+#define THETVDB_SERIE_ID BAD_CAST "seriesid"
+
+/* --- Plugin information --- */
+
+#define SOURCE_ID "grl-thetvdb"
+#define SOURCE_NAME "TheTVDB"
+#define SOURCE_DESC _("A source for fetching metadata of television shows")
+
+/* --- TheTVDB keys --- */
+static GrlKeyID GRL_THETVDB_METADATA_KEY_THETVDB_ID = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_IMDB_ID = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_ZAP2IT_ID = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_GUEST_STARS = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_FANART = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_BANNER = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_POSTER = GRL_METADATA_KEY_INVALID;
+static GrlKeyID GRL_THETVDB_METADATA_KEY_EPISODE_SS = GRL_METADATA_KEY_INVALID;
+
+struct _GrlTheTVDBPrivate {
+ gchar *api_key;
+ GList *supported_keys;
+ GomAdapter *adapter;
+ GomRepository *repository;
+
+ /* Hash table with a string as key (the show name) and a list as its value
+ * (a list of OperationSpec *) */
+ GHashTable *ht_wait_list;
+};
+
+typedef struct _OperationSpec {
+ GrlSource *source;
+ guint operation_id;
+ GList *keys;
+ GrlMedia *media;
+ gpointer user_data;
+ guint error_code;
+ gchar *lang;
+ gboolean fetched_web;
+ SeriesResource *serie_resource;
+ GrlSourceResolveCb callback;
+} OperationSpec;
+
+/* All supported languages from
+ * thetvdb.com/wiki/index.php?title=Multi_Language#Available_Languages */
+static struct {
+ gint lid;
+ const gchar *name;
+} supported_languages[] = {
+ { 7, "en" },
+ { 8, "sv" },
+ { 9, "no" },
+ { 10, "da" },
+ { 11, "fi" },
+ { 13, "nl" },
+ { 14, "de" },
+ { 15, "it" },
+ { 16, "es" },
+ { 17, "fr" },
+ { 18, "pl" },
+ { 19, "hu" },
+ { 20, "el" },
+ { 21, "tr" },
+ { 22, "ru" },
+ { 24, "he" },
+ { 25, "ja" },
+ { 26, "pt" },
+ { 27, "zh" },
+ { 28, "cs" },
+ { 30, "sl" },
+ { 31, "hr" },
+ { 32, "ko" },
+};
+
+static GrlTheTVDBSource *grl_thetvdb_source_new (const gchar *api_key);
+
+static void grl_thetvdb_source_finalize (GObject *object);
+
+static void grl_thetvdb_source_resolve (GrlSource *source,
+ GrlSourceResolveSpec *rs);
+
+static gboolean grl_thetvdb_source_may_resolve (GrlSource *source,
+ GrlMedia *media,
+ GrlKeyID key_id,
+ GList **missing_keys);
+
+static const GList *grl_thetvdb_source_supported_keys (GrlSource *source);
+
+static void thetvdb_migrate_db_done (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+static void cache_find_episode (OperationSpec *os);
+
+/* ================== TheTVDB Plugin ================= */
+
+static gboolean
+grl_thetvdb_plugin_init (GrlRegistry *registry,
+ GrlPlugin *plugin,
+ GList *configs)
+{
+ GrlTheTVDBSource *source;
+ GParamSpec *spec;
+ GrlConfig *config;
+ char *api_key = NULL;
+
+ GRL_LOG_DOMAIN_INIT (thetvdb_log_domain, "thetvdb");
+
+ GRL_DEBUG ("thetvdb_plugin_init");
+
+ if (configs) {
+ config = GRL_CONFIG (configs->data);
+ api_key = grl_config_get_api_key (config);
+ }
+
+ if (api_key == NULL) {
+ GRL_INFO ("Cannot load plugin: missing API Key");
+ return FALSE;
+ }
+
+ spec = g_param_spec_string ("thetvdb-id",
+ "thetvdb-id",
+ "TV Show or episode id for The TVDB source.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_THETVDB_ID =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-imdb-id",
+ "thetvdb-imdb-id",
+ "TV Show or episode id for IMDB source.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_IMDB_ID =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-zap2it-id",
+ "thetvdb-zap2it-id",
+ "TV Show or episode id for Zap2it source.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_ZAP2IT_ID =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-guest-stars",
+ "thetvdb-guest-stars",
+ "Guest stars perfoming in the episode.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_GUEST_STARS =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-fanart",
+ "thetvdb-fanart",
+ "The mosted voted fanart of the TV Show.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_FANART =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-banner",
+ "thetvdb-banner",
+ "The most voted banner of the TV Show.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_BANNER =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-poster",
+ "thetvdb-poster",
+ "The most voted poster of the TV Show.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_POSTER =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ spec = g_param_spec_string ("thetvdb-episode-screenshot",
+ "thetvdb-episode-screenshot",
+ "One screenshot of the episode.",
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE),
+ GRL_THETVDB_METADATA_KEY_EPISODE_SS =
+ grl_registry_register_metadata_key (registry, spec, NULL);
+ g_param_spec_unref (spec);
+
+ source = grl_thetvdb_source_new (api_key);
+ grl_registry_register_source (registry,
+ plugin,
+ GRL_SOURCE (source),
+ NULL);
+ g_free (api_key);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_thetvdb_plugin_init, NULL, SOURCE_ID);
+
+/* ================== TheTVDB GObject ================= */
+
+static GrlTheTVDBSource *
+grl_thetvdb_source_new (const gchar *api_key)
+{
+ GObject *object;
+ GrlTheTVDBSource *source;
+
+ GRL_DEBUG ("thetvdb_source_new");
+
+ object = g_object_new (GRL_THETVDB_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ "supported-media", GRL_MEDIA_TYPE_VIDEO,
+ NULL);
+
+ source = GRL_THETVDB_SOURCE (object);
+ source->priv->api_key = g_strdup (api_key);
+ return source;
+}
+
+static void
+grl_thetvdb_source_class_init (GrlTheTVDBSourceClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+ source_class->supported_keys = grl_thetvdb_source_supported_keys;
+ source_class->may_resolve = grl_thetvdb_source_may_resolve;
+ source_class->resolve = grl_thetvdb_source_resolve;
+ gobject_class->finalize = grl_thetvdb_source_finalize;
+
+ g_type_class_add_private (klass, sizeof (GrlTheTVDBPrivate));
+}
+
+static void
+grl_thetvdb_source_init (GrlTheTVDBSource *source)
+{
+ gchar *path;
+ gchar *db_path;
+ GList *tables;
+ GError *error = NULL;
+
+ GRL_DEBUG ("thetvdb_source_init");
+
+ source->priv = GRL_THETVDB_GET_PRIVATE (source);
+
+ /* All supported keys in a GList */
+ source->priv->supported_keys =
+ grl_metadata_key_list_new (GRL_METADATA_KEY_SEASON,
+ GRL_METADATA_KEY_EPISODE,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_PERFORMER,
+ GRL_METADATA_KEY_DIRECTOR,
+ GRL_METADATA_KEY_PUBLICATION_DATE,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_THETVDB_METADATA_KEY_THETVDB_ID,
+ GRL_THETVDB_METADATA_KEY_IMDB_ID,
+ GRL_THETVDB_METADATA_KEY_ZAP2IT_ID,
+ GRL_THETVDB_METADATA_KEY_GUEST_STARS,
+ GRL_THETVDB_METADATA_KEY_FANART,
+ GRL_THETVDB_METADATA_KEY_BANNER,
+ GRL_THETVDB_METADATA_KEY_POSTER,
+ GRL_THETVDB_METADATA_KEY_EPISODE_SS,
+ GRL_METADATA_KEY_INVALID);
+
+ /* Get database connection */
+ path = g_build_filename (g_get_user_data_dir (), "grilo-plugins", NULL);
+ if (!g_file_test (path, G_FILE_TEST_IS_DIR))
+ g_mkdir_with_parents (path, 0775);
+
+ GRL_DEBUG ("Opening database connection...");
+ db_path = g_build_filename (path, GRL_SQL_DB, NULL);
+ g_free (path);
+
+ source->priv->adapter = gom_adapter_new ();
+ if (!gom_adapter_open_sync (source->priv->adapter, db_path, &error)) {
+ GrlRegistry *registry;
+ GRL_WARNING ("Could not open database '%s': %s", db_path, error->message);
+ g_error_free (error);
+ g_free (db_path);
+
+ /* Removes itself from list of sources */
+ registry = grl_registry_get_default ();
+ grl_registry_unregister_source (registry,
+ GRL_SOURCE (source),
+ NULL);
+ return;
+ }
+ g_free (db_path);
+
+ /* Creates our pending queue */
+ source->priv->ht_wait_list = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ source->priv->repository = gom_repository_new (source->priv->adapter);
+ tables = g_list_prepend (NULL, GINT_TO_POINTER (SERIES_TYPE_RESOURCE));
+ tables = g_list_prepend (tables, GINT_TO_POINTER (EPISODE_TYPE_RESOURCE));
+ gom_repository_automatic_migrate_async (source->priv->repository, 2, tables,
+ thetvdb_migrate_db_done, source);
+}
+
+G_DEFINE_TYPE (GrlTheTVDBSource, grl_thetvdb_source, GRL_TYPE_SOURCE);
+
+static void
+grl_thetvdb_source_finalize (GObject *object)
+{
+ GrlTheTVDBSource *source;
+
+ GRL_DEBUG ("grl_thetvdb_source_finalize");
+
+ source = GRL_THETVDB_SOURCE (object);
+
+ g_list_free (source->priv->supported_keys);
+ g_hash_table_destroy (source->priv->ht_wait_list);
+ g_clear_object (&source->priv->repository);
+ g_clear_pointer (&source->priv->api_key, g_free);
+
+ if (source->priv->adapter) {
+ gom_adapter_close_sync (source->priv->adapter, NULL);
+ g_clear_object (&source->priv->adapter);
+ }
+
+ G_OBJECT_CLASS (grl_thetvdb_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+static void
+thetvdb_migrate_db_done (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GrlRegistry *registry;
+
+ ret = gom_repository_migrate_finish (GOM_REPOSITORY (object), result, &error);
+ if (ret == FALSE) {
+ GRL_WARNING ("Failed to migrate database: %s", error->message);
+ g_error_free (error);
+
+ /* Removes itself from list of sources */
+ registry = grl_registry_get_default ();
+ grl_registry_unregister_source (registry,
+ GRL_SOURCE (user_data),
+ NULL);
+ }
+}
+
+static void
+free_operation_spec (gpointer os_pointer)
+{
+ OperationSpec *os = (OperationSpec *) os_pointer;
+ g_free (os->lang);
+ g_list_free (os->keys);
+ g_clear_object (&os->serie_resource);
+ g_slice_free (OperationSpec, os);
+}
+
+static gchar *
+get_pref_language (GrlTheTVDBSource *tvdb_source)
+{
+ const gchar * const *strv;
+ gint strv_len, langv_len;
+ gint i, j;
+
+ strv = g_get_language_names ();
+ strv_len = g_strv_length ((gchar **) strv);
+ langv_len = G_N_ELEMENTS (supported_languages);
+ for (i = 0; i < strv_len; i++) {
+ /* We are only interested in two letter language */
+ if (strlen (strv[i]) != 2)
+ continue;
+
+ for (j = 0; j < langv_len; j++) {
+ if (g_strcmp0 (supported_languages[j].name, strv[i]) == 0)
+ return g_strdup (strv[i]);
+ }
+ }
+
+ return g_strdup (THETVDB_DEFAULT_LANG);
+}
+
+static EpisodeResource *
+xml_parse_and_save_episodes (GomRepository *repository,
+ xmlDocPtr doc,
+ const gchar *title,
+ gint season_number,
+ gint episode_number)
+{
+ xmlNodePtr node, child;
+ xmlChar *node_data = NULL;
+ EpisodeResource *episode_resource= NULL;
+
+ /* Get ptr to <data> */
+ node = xmlDocGetRootElement (doc);
+ /* Get ptr to <Serie> */
+ node = node->xmlChildrenNode;
+ /* Iterate in the <Episode> nodes */
+ for (node = node->next; node != NULL; node = node->next) {
+ GError *error = NULL;
+ gint tmp_season_number = -1;
+ gint tmp_episode_number = -1;
+ gchar *tmp_title;
+ gboolean episode_found = FALSE;
+ EpisodeResource *eres;
+
+ eres = g_object_new (EPISODE_TYPE_RESOURCE,
+ "repository", repository,
+ NULL);
+
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ node_data = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
+ if (node_data == NULL)
+ continue;
+
+ if (xmlStrcasecmp (child->name, THETVDB_LANGUAGE) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_LANGUAGE,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_OVERVIEW) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_OVERVIEW,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_FIRST_AIRED) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_FIRST_AIRED,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_IMDB_ID) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_IMDB_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_SEASON_ID) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_SEASON_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_SERIE_ID) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_SERIES_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_DIRECTOR) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_DIRECTOR_NAMES,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_GUEST_STARS) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_GUEST_STARS_NAMES,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_ID) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_EPISODE_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_FILENAME) == 0) {
+ gchar *str = g_strdup_printf (THETVDB_BASE_IMG, (gchar *) node_data);
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_URL_EPISODE_SCREEN,
+ str, NULL);
+ g_free (str);
+
+ } else if (xmlStrcmp (child->name, THETVDB_EPISODE_NAME) == 0) {
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_EPISODE_NAME,
+ (gchar *) node_data, NULL);
+ tmp_title = g_strdup ((gchar *) node_data);
+
+ } else if (xmlStrcmp (child->name, THETVDB_RATING) == 0) {
+ gdouble num_double = g_ascii_strtod ((gchar *) node_data, NULL);
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_RATING, num_double, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_SEASON_NUMBER) == 0) {
+ gint num = g_ascii_strtoull ((gchar *) node_data, NULL, 10);
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_SEASON_NUMBER, num, NULL);
+ tmp_season_number = num;
+
+ } else if (xmlStrcmp (child->name, THETVDB_ABSOLUTE_NUMBER) == 0) {
+ gint num = g_ascii_strtoull ((gchar *) node_data, NULL, 10);
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_ABSOLUTE_NUMBER,
+ num, NULL);
+
+ } else if (xmlStrcmp (child->name, THETVDB_EPISODE_NUMBER) == 0) {
+ gint num = g_ascii_strtoull ((gchar *) node_data, NULL, 10);
+ g_object_set (G_OBJECT (eres), EPISODE_COLUMN_EPISODE_NUMBER,
+ num, NULL);
+ tmp_episode_number = num;
+ }
+
+ if (node_data != NULL) {
+ xmlFree (node_data);
+ node_data = NULL;
+ }
+ }
+
+ /* Check if the episode is the same we want */
+ if (tmp_season_number == season_number
+ && tmp_episode_number == episode_number) {
+ episode_found = TRUE;
+ episode_resource = eres;
+ }
+
+ if (title != NULL && tmp_title != NULL
+ && g_ascii_strncasecmp (title, tmp_title, 0) == 0) {
+ episode_found = TRUE;
+ episode_resource = eres;
+ }
+
+ gom_resource_save_sync (GOM_RESOURCE (eres), &error);
+ if (error != NULL) {
+ GRL_DEBUG ("Failed to store episode '%s' due %s",
+ tmp_title, error->message);
+ g_error_free (error);
+ }
+
+ g_clear_pointer (&tmp_title, g_free);
+ if (!episode_found)
+ g_object_unref (eres);
+ }
+
+ return episode_resource;
+}
+
+static gchar *
+xml_parse_get_series_id (xmlDocPtr doc)
+{
+ xmlNodePtr node;
+ xmlChar *node_data = NULL;
+ gchar *series_id = NULL;
+
+ /* Get ptr to <data> */
+ node = xmlDocGetRootElement (doc);
+ /* Get ptr to <Serie> */
+ node = node->xmlChildrenNode;
+ /* Iterate in the <Serie> */
+ for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
+ node_data = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ if (node_data == NULL)
+ continue;
+
+ if (xmlStrcmp (node->name, THETVDB_ID) == 0) {
+ series_id = g_strdup ((gchar *) node_data);
+ xmlFree (node_data);
+ break;
+ }
+ xmlFree (node_data);
+ }
+ return series_id;
+}
+
+static SeriesResource *
+xml_parse_and_save_serie (GomRepository *repository,
+ xmlDocPtr doc)
+{
+ xmlNodePtr node;
+ xmlChar *node_data = NULL;
+ SeriesResource *sres;
+ GError *error = NULL;
+ gchar *show = NULL;
+
+ sres = g_object_new (SERIES_TYPE_RESOURCE,
+ "repository", repository,
+ NULL);
+
+ /* Get ptr to <data> */
+ node = xmlDocGetRootElement (doc);
+ /* Get ptr to <Serie> */
+ node = node->xmlChildrenNode;
+ /* Iterate in the <Serie> */
+ for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
+ node_data = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ if (node_data == NULL)
+ continue;
+
+ if (xmlStrcasecmp (node->name, THETVDB_LANGUAGE) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_LANGUAGE,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_STATUS) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_STATUS,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_SERIES_NAME) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_SERIES_NAME,
+ (gchar *) node_data, NULL);
+ show = g_strdup ((gchar *) node_data);
+
+ } else if (xmlStrcmp (node->name, THETVDB_OVERVIEW) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_OVERVIEW,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_FIRST_AIRED) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_FIRST_AIRED,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_IMDB_ID) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_IMDB_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_ZAP2IT_ID) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_ZAP2IT_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_ACTORS) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_ACTOR_NAMES,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_GENRE) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_GENRES,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_ID) == 0) {
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_SERIES_ID,
+ (gchar *) node_data, NULL);
+
+ } else if (xmlStrcmp (node->name, THETVDB_BANNER) == 0) {
+ gchar *str = g_strdup_printf (THETVDB_BASE_IMG, (gchar *) node_data);
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_URL_BANNER, str, NULL);
+ g_free (str);
+
+ } else if (xmlStrcmp (node->name, THETVDB_POSTER) == 0) {
+ gchar *str = g_strdup_printf (THETVDB_BASE_IMG, (gchar *) node_data);
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_URL_POSTER, str, NULL);
+ g_free (str);
+
+ } else if (xmlStrcmp (node->name, THETVDB_FANART) == 0) {
+ gchar *str = g_strdup_printf (THETVDB_BASE_IMG, (gchar *) node_data);
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_URL_FANART, str, NULL);
+ g_free (str);
+
+ } else if (xmlStrcmp (node->name, THETVDB_RATING) == 0) {
+ gdouble num_double = g_ascii_strtod ((gchar *) node_data, NULL);
+ g_object_set (G_OBJECT (sres), SERIES_COLUMN_RATING, num_double, NULL);
+ }
+
+ if (node_data != NULL) {
+ xmlFree (node_data);
+ node_data = NULL;
+ }
+ }
+
+ gom_resource_save_sync (GOM_RESOURCE (sres), &error);
+ if (error != NULL) {
+ GRL_DEBUG ("Failed to store series '%s' due %s",
+ show, error->message);
+ g_error_free (error);
+ }
+
+ g_clear_pointer (&show, g_free);
+ return sres;
+}
+
+static void
+thetvdb_add_data_string_unique (GrlData *data,
+ GrlKeyID key_id,
+ gchar **strv)
+{
+ guint i, j;
+ guint data_len;
+ GrlRelatedKeys *related_keys;
+
+ for (i = 0; strv[i] != NULL; i++) {
+ gboolean insert = TRUE;
+
+ /* Do not insert empty strings */
+ if (strv[i] && *strv[i] == '\0')
+ continue;
+
+ /* Check if this value already exists to this key */
+ data_len = grl_data_length (data, key_id);
+ for (j = 0; insert && j < data_len; j++) {
+ const gchar *element;
+
+ related_keys = grl_data_get_related_keys (data, key_id, j);
+ element = grl_related_keys_get_string (related_keys, key_id);
+ if (g_strcmp0 (element, strv[i]) == 0)
+ insert = FALSE;
+ }
+
+ if (insert)
+ grl_data_add_string (data, key_id, strv[i]);
+ }
+}
+
+static gchar *
+get_from_cache_episode_or_series (EpisodeResource *eres,
+ const gchar *ecol,
+ SeriesResource *sres,
+ const gchar *scol)
+{
+ gchar *str = NULL;
+ if (eres != NULL)
+ g_object_get (eres, ecol, &str, NULL);
+
+ if (str == NULL && sres != NULL)
+ g_object_get (sres, scol, &str, NULL);
+
+ return str;
+}
+
+/* Return a pointer to the media with updated values
+ * or NULL if no keys were set
+ *
+ * NOTE: We give preference to an episode over the tv show for those metadatas
+ * that could provide information for both. */
+static GrlMedia *
+thetvdb_update_media_from_resources (GrlMediaVideo *video,
+ GList *keys,
+ SeriesResource *sres,
+ EpisodeResource *eres)
+{
+ gint failed_keys = 0;
+ GList *it;
+
+ if (sres == NULL)
+ return NULL;
+
+ for (it = keys; it != NULL; it = it->next) {
+ GrlKeyID key_id = GRLPOINTER_TO_KEYID (it->data);
+ gint num = -1;
+ gchar *str = NULL;
+
+ switch (key_id) {
+ case GRL_METADATA_KEY_SEASON:
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_SEASON_NUMBER, &num, NULL);
+
+ if (num > 0)
+ grl_media_video_set_season (video, num);
+ else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_EPISODE:
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_EPISODE_NUMBER, &num, NULL);
+
+ if (num > 0)
+ grl_media_video_set_episode (video, num);
+ else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_TITLE:
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_EPISODE_NAME, &str, NULL);
+
+ if (str != NULL) {
+ grl_media_set_title (GRL_MEDIA (video), str);
+ g_free (str);
+ } else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_GENRE:
+ g_object_get (sres, SERIES_COLUMN_GENRES, &str, NULL);
+ if (str != NULL) {
+ gchar **genres;
+ genres = g_strsplit (str, THETVDB_STR_DELIMITER, 0);
+ thetvdb_add_data_string_unique (GRL_DATA (video),
+ GRL_METADATA_KEY_GENRE,
+ genres);
+ g_free (str);
+ g_strfreev (genres);
+ } else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_PERFORMER:
+ g_object_get (sres, SERIES_COLUMN_ACTOR_NAMES, &str, NULL);
+ if (str != NULL) {
+ gchar **actors;
+ actors = g_strsplit (str, THETVDB_STR_DELIMITER, 0);
+ thetvdb_add_data_string_unique (GRL_DATA (video),
+ GRL_METADATA_KEY_PERFORMER,
+ actors);
+ g_free (str);
+ g_strfreev (actors);
+ } else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_DIRECTOR:
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_DIRECTOR_NAMES, &str, NULL);
+
+ if (str != NULL) {
+ gchar **directors;
+ directors = g_strsplit (str, THETVDB_STR_DELIMITER, 0);
+ thetvdb_add_data_string_unique (GRL_DATA (video),
+ GRL_METADATA_KEY_DIRECTOR,
+ directors);
+ g_free (str);
+ g_strfreev (directors);
+ } else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_PUBLICATION_DATE:
+ str = get_from_cache_episode_or_series (eres, EPISODE_COLUMN_FIRST_AIRED,
+ sres, SERIES_COLUMN_FIRST_AIRED);
+ if (str != NULL) {
+ GDateTime *date;
+ gint year, month, day;
+
+ sscanf (str, "%d-%d-%d", &year, &month, &day);
+ date = g_date_time_new_local (year, month, day, 0, 0, 0);
+ grl_media_set_publication_date (GRL_MEDIA (video), date);
+ g_date_time_unref (date);
+ g_free (str);
+ } else
+ failed_keys++;
+ break;
+
+ case GRL_METADATA_KEY_DESCRIPTION:
+ str = get_from_cache_episode_or_series (eres, EPISODE_COLUMN_OVERVIEW,
+ sres, SERIES_COLUMN_OVERVIEW);
+ if (str != NULL) {
+ grl_media_set_description (GRL_MEDIA (video), str);
+ g_free (str);
+ } else
+ failed_keys++;
+ break;
+
+ default:
+ /* THETVDB Keys */
+ if (key_id == GRL_THETVDB_METADATA_KEY_THETVDB_ID) {
+ str = get_from_cache_episode_or_series (eres, EPISODE_COLUMN_EPISODE_ID,
+ sres, SERIES_COLUMN_SERIES_ID);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_THETVDB_ID,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_IMDB_ID) {
+ str = get_from_cache_episode_or_series (eres, EPISODE_COLUMN_IMDB_ID,
+ sres, SERIES_COLUMN_IMDB_ID);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_IMDB_ID,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_ZAP2IT_ID) {
+ g_object_get (sres, SERIES_COLUMN_ZAP2IT_ID, &str, NULL);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_ZAP2IT_ID,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_GUEST_STARS) {
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_GUEST_STARS_NAMES, &str, NULL);
+
+ if (str != NULL) {
+ gchar **stars;
+ stars = g_strsplit (str, THETVDB_STR_DELIMITER, 0);
+ thetvdb_add_data_string_unique (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_GUEST_STARS,
+ stars);
+ g_free (str);
+ g_strfreev (stars);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_FANART) {
+ g_object_get (sres, SERIES_COLUMN_URL_FANART, &str, NULL);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_FANART,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_BANNER) {
+ g_object_get (sres, SERIES_COLUMN_URL_BANNER, &str, NULL);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_BANNER,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_POSTER) {
+ g_object_get (sres, SERIES_COLUMN_URL_POSTER, &str, NULL);
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_POSTER,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+
+ } else if (key_id == GRL_THETVDB_METADATA_KEY_EPISODE_SS) {
+ if (eres != NULL)
+ g_object_get (eres, EPISODE_COLUMN_URL_EPISODE_SCREEN, &str, NULL);
+
+ if (str != NULL) {
+ grl_data_set_string (GRL_DATA (video),
+ GRL_THETVDB_METADATA_KEY_EPISODE_SS,
+ str);
+ g_free (str);
+ } else
+ failed_keys++;
+ } else
+ failed_keys++;
+ }
+ }
+
+ return (failed_keys == g_list_length (keys)) ? NULL : GRL_MEDIA (video);
+}
+
+static gboolean
+xml_load_data (const gchar *str,
+ xmlDocPtr *doc)
+{
+ xmlDocPtr doc_ptr;
+ xmlNodePtr node;
+
+ doc_ptr = xmlReadMemory (str, strlen (str), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!doc_ptr)
+ goto free_resources;
+
+ node = xmlDocGetRootElement (doc_ptr);
+ if (!node)
+ goto free_resources;
+
+ *doc = doc_ptr;
+ return TRUE;
+
+free_resources:
+ xmlFreeDoc (doc_ptr);
+ return FALSE;
+}
+
+static void
+web_request_succeed (const OperationSpec *os,
+ SeriesResource *sres)
+{
+ const gchar *show;
+ GList *wait_list, *it;
+ GrlTheTVDBSource *tvdb_source;
+
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ wait_list = g_hash_table_lookup (tvdb_source->priv->ht_wait_list, show);
+ for (it = wait_list; it != NULL; it = it->next) {
+ OperationSpec *os = (OperationSpec *) it->data;
+
+ GRL_DEBUG ("Request with id %d succeed. Show name is %s",
+ os->operation_id, show);
+
+ /* To avoid fetching again */
+ os->fetched_web = TRUE;
+ os->serie_resource = g_object_ref (sres);
+ cache_find_episode (os);
+ }
+ g_object_unref (sres);
+ g_list_free (wait_list);
+ g_hash_table_remove (tvdb_source->priv->ht_wait_list, show);
+}
+
+static void
+web_request_failed (const OperationSpec *os)
+{
+ const gchar *show;
+ GList *wait_list, *it;
+ GrlTheTVDBSource *tvdb_source;
+
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ wait_list = g_hash_table_lookup (tvdb_source->priv->ht_wait_list, show);
+ for (it = wait_list; it != NULL; it = it->next) {
+ OperationSpec *os = (OperationSpec *) it->data;
+
+ GRL_DEBUG ("Request with id %d failed. Show name is %s",
+ os->operation_id, show);
+
+ os->callback (os->source, os->operation_id, NULL, os->user_data, NULL);
+ }
+ g_list_free_full (wait_list, free_operation_spec);
+ g_hash_table_remove (tvdb_source->priv->ht_wait_list, show);
+}
+
+static gboolean
+unzip_all_series_data (gchar *zip,
+ gsize zip_len,
+ gchar *language,
+ gchar **dest)
+{
+ struct archive *a;
+ struct archive_entry *entry;
+ gint r;
+ gboolean ret = FALSE;
+ gchar *target_xml;
+
+ a = archive_read_new ();
+ archive_read_support_format_zip (a);
+ r = archive_read_open_memory (a, zip, zip_len);
+ if (r != ARCHIVE_OK) {
+ GRL_DEBUG ("Fail to open archive.");
+ goto unzip_all_series_end;
+ }
+
+ target_xml = g_strdup_printf (THETVDB_ALL_DATA_XML, language);
+ while ((r = archive_read_next_header (a, &entry)) == ARCHIVE_OK) {
+ const char *name;
+
+ name = archive_entry_pathname (entry);
+ GRL_DEBUG ("[ZIP] ENTRY-NAME: '%s'", name);
+ if (g_strcmp0 (name, target_xml) == 0) {
+ size_t size = archive_entry_size (entry);
+ char *buf;
+ ssize_t read;
+
+ buf = g_malloc (size + 1);
+ buf[size] = 0;
+ read = archive_read_data (a, buf, size);
+ if (read <= 0) {
+ g_free (buf);
+ if (read < 0)
+ GRL_DEBUG ("Fatal error reading '%s' in archive: %s",
+ name, archive_error_string (a));
+ else
+ GRL_DEBUG ("Read an empty file from the archive");
+ } else {
+ *dest = buf;
+ break;
+ }
+ }
+ archive_read_data_skip (a);
+ }
+
+ g_free (target_xml);
+ if (r != ARCHIVE_OK) {
+ if (r == ARCHIVE_FATAL) {
+ GRL_WARNING ("Fatal error handling archive: %s",
+ archive_error_string (a));
+ }
+ goto unzip_all_series_end;
+ }
+
+ ret = TRUE;
+unzip_all_series_end:
+ archive_read_free (a);
+ return ret;
+}
+
+static void
+web_get_all_zipped_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gchar *zip_data;
+ gchar *xml_data;
+ gsize zip_len;
+ GError *err = NULL;
+ xmlDocPtr doc;
+ SeriesResource *sres = NULL;
+ EpisodeResource *eres;
+ OperationSpec *os;
+ GrlMediaVideo *video;
+ GrlTheTVDBSource *tvdb_source;
+
+ os = (OperationSpec *) user_data;
+ video = GRL_MEDIA_VIDEO (os->media);
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+
+ grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res, &zip_data, &zip_len, &err);
+ if (err != NULL) {
+ GRL_DEBUG ("Resolve operation failed due '%s'", err->message);
+ g_error_free (err);
+ goto get_all_zipped_end;
+ }
+
+ if (!unzip_all_series_data (zip_data, zip_len, os->lang, &xml_data)) {
+ GRL_DEBUG ("Resolve operation failed while unzipping data");
+ goto get_all_zipped_end;
+ }
+
+ if (!xml_load_data (xml_data, &doc)) {
+ GRL_DEBUG ("Resolve operation failed while loading xml");
+ g_free (xml_data);
+ goto get_all_zipped_end;
+ }
+ g_free (xml_data);
+
+ sres = xml_parse_and_save_serie (tvdb_source->priv->repository, doc);
+ eres = xml_parse_and_save_episodes (tvdb_source->priv->repository, doc,
+ grl_media_get_title (os->media),
+ grl_media_video_get_season (video),
+ grl_media_video_get_episode (video));
+ xmlFreeDoc (doc);
+
+get_all_zipped_end:
+ if (sres != NULL) {
+ web_request_succeed (os, sres);
+ if (eres)
+ g_clear_object (&eres);
+ } else {
+ web_request_failed (os);
+ }
+}
+
+static void
+web_get_series_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gchar *data;
+ gsize len;
+ gchar *url;
+ gchar *serie_id;
+ GError *err = NULL;
+ xmlDocPtr doc;
+ GrlNetWc *wc;
+ OperationSpec *os;
+ GrlTheTVDBSource *tvdb_source;
+
+ os = (OperationSpec *) user_data;
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+
+ grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res, &data, &len, &err);
+ if (err != NULL) {
+ GRL_WARNING ("Resolve operation failed due '%s'", err->message);
+ g_error_free (err);
+ goto get_series_done_error;
+ }
+
+ if (!xml_load_data (data, &doc)) {
+ GRL_WARNING ("Resolve operation failed while loading xml");
+ goto get_series_done_error;
+ }
+
+ serie_id = xml_parse_get_series_id (doc);
+ wc = grl_net_wc_new ();
+ url = g_strdup_printf (THETVDB_GET_EPISODES, tvdb_source->priv->api_key,
+ serie_id, os->lang);
+ g_free (serie_id);
+
+ GRL_DEBUG ("url[2] %s", url);
+ grl_net_wc_request_async (wc, url, NULL, web_get_all_zipped_done, os);
+ g_free (url);
+ g_object_unref (wc);
+ xmlFreeDoc (doc);
+ return;
+
+get_series_done_error:
+ os->callback (os->source, os->operation_id, NULL, os->user_data, NULL);
+ web_request_failed (os);
+}
+
+static void
+thetvdb_execute_resolve_web (OperationSpec *os)
+{
+ GrlNetWc *wc;
+ gchar *url;
+ const gchar *show;
+ GrlTheTVDBSource *tvdb_source;
+ GList *wait_list;
+
+ GRL_DEBUG ("thetvdb_resolve_web");
+
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ /* If there is a request on this show already, wait. */
+ wait_list = g_hash_table_lookup (tvdb_source->priv->ht_wait_list, show);
+ if (wait_list == NULL) {
+ wait_list = g_list_append (wait_list, os);
+ g_hash_table_insert (tvdb_source->priv->ht_wait_list,
+ g_strdup (show),
+ wait_list);
+ wc = grl_net_wc_new ();
+ url = g_strdup_printf (THETVDB_GET_SERIES, show);
+
+ GRL_DEBUG ("url[1] %s", url);
+ grl_net_wc_request_async (wc, url, NULL, web_get_series_done, os);
+ g_free (url);
+ g_object_unref (wc);
+ } else {
+ wait_list = g_list_append (wait_list, os);
+ GRL_DEBUG ("[%s] Add to wait list: %d", show, os->operation_id);
+ }
+}
+
+static void
+cache_find_episode_done (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ const gchar *show;
+ OperationSpec *os;
+ GomResource *resource;
+ GrlMedia *media;
+ GError *err = NULL;
+
+ os = (OperationSpec *) user_data;
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ resource = gom_repository_find_one_finish (GOM_REPOSITORY (object),
+ res,
+ &err);
+ if (resource == NULL) {
+ GRL_DEBUG ("[Episode] Cache miss with '%s' due '%s'", show, err->message);
+ g_error_free (err);
+
+ if (os->fetched_web == FALSE) {
+ /* Fetch web API in order to update current cache */
+ thetvdb_execute_resolve_web (os);
+ }
+ return;
+ }
+
+ media = thetvdb_update_media_from_resources (GRL_MEDIA_VIDEO (os->media),
+ os->keys,
+ os->serie_resource,
+ EPISODE_RESOURCE (resource));
+ os->callback (os->source, os->operation_id, media, os->user_data, NULL);
+ g_object_unref (resource);
+ free_operation_spec (os);
+}
+
+static void
+cache_find_episode (OperationSpec *os)
+{
+ GrlTheTVDBSource *tvdb_source;
+ GomFilter *query, *by_series_id, *by_episode;
+ GValue value_str = { 0, };
+ GrlMedia *media = NULL;
+ gchar *series_id;
+ gchar *show;
+ const gchar *title;
+ gint season_number, episode_number;
+
+ GRL_DEBUG ("cache_find_episode");
+
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+ title = grl_media_get_title (os->media);
+ season_number = grl_media_video_get_season (GRL_MEDIA_VIDEO (os->media));
+ episode_number = grl_media_video_get_episode (GRL_MEDIA_VIDEO (os->media));
+
+ g_object_get (os->serie_resource,
+ SERIES_COLUMN_SERIES_ID, &series_id,
+ SERIES_COLUMN_SERIES_NAME, &show,
+ NULL);
+
+ /* Check if this media is an Episode of a TV Show */
+ if (title == NULL && (season_number == 0 || episode_number == 0)) {
+ goto cache_episode_end;
+ }
+
+ /* Find the exactly episode using series-id and some episode specific info */
+ g_value_init (&value_str, G_TYPE_STRING);
+ g_value_set_string (&value_str, series_id);
+ by_series_id = gom_filter_new_eq (EPISODE_TYPE_RESOURCE,
+ EPISODE_COLUMN_SERIES_ID,
+ &value_str);
+ g_value_unset (&value_str);
+
+ if (season_number != 0 && episode_number != 0) {
+ GomFilter *filter1, *filter2;
+ GValue value_num = { 0, };
+
+ g_value_init (&value_num, G_TYPE_UINT);
+ g_value_set_uint (&value_num, season_number);
+ filter1 = gom_filter_new_eq (EPISODE_TYPE_RESOURCE,
+ EPISODE_COLUMN_SEASON_NUMBER,
+ &value_num);
+ g_value_set_uint (&value_num, episode_number);
+ filter2 = gom_filter_new_eq (EPISODE_TYPE_RESOURCE,
+ EPISODE_COLUMN_EPISODE_NUMBER,
+ &value_num);
+ g_value_unset (&value_num);
+
+ /* Find episode by season number and episode number */
+ by_episode = gom_filter_new_and (filter1, filter2);
+ g_object_unref (filter1);
+ g_object_unref (filter2);
+ } else {
+ g_value_init (&value_str, G_TYPE_STRING);
+ g_value_set_string (&value_str, title);
+
+ /* Find episode by its title */
+ by_episode = gom_filter_new_like (EPISODE_TYPE_RESOURCE,
+ EPISODE_COLUMN_EPISODE_NAME,
+ &value_str);
+ g_value_unset (&value_str);
+ }
+
+ query = gom_filter_new_and (by_series_id, by_episode);
+ g_object_unref (by_series_id);
+ g_object_unref (by_episode);
+
+ gom_repository_find_one_async (tvdb_source->priv->repository,
+ EPISODE_TYPE_RESOURCE,
+ query,
+ cache_find_episode_done,
+ os);
+ g_object_unref (query);
+ g_clear_pointer (&series_id, g_free);
+ g_clear_pointer (&show, g_free);
+ return;
+
+cache_episode_end:
+ /* This media does not specify an episode: return series metadata */
+ media = thetvdb_update_media_from_resources (GRL_MEDIA_VIDEO (os->media),
+ os->keys,
+ os->serie_resource,
+ NULL);
+ os->callback (os->source, os->operation_id, media, os->user_data, NULL);
+ g_clear_pointer (&series_id, g_free);
+ g_clear_pointer (&show, g_free);
+ free_operation_spec (os);
+}
+
+static void
+cache_find_serie_done (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ OperationSpec *os;
+ GomResource *resource;
+ const gchar *show;
+ GError *err = NULL;
+
+ os = (OperationSpec *) user_data;
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ resource = gom_repository_find_one_finish (GOM_REPOSITORY (object),
+ res,
+ &err);
+ if (resource == NULL) {
+ GRL_DEBUG ("[Series] Cache miss with '%s' due '%s'", show, err->message);
+ g_error_free (err);
+ thetvdb_execute_resolve_web (os);
+ return;
+ }
+
+ os->serie_resource = SERIES_RESOURCE (resource);
+ cache_find_episode (os);
+}
+
+static void
+thetvdb_execute_resolve_cache (OperationSpec *os)
+{
+ const gchar *show;
+ GrlTheTVDBSource *tvdb_source;
+ GomFilter *query;
+ GValue value = { 0, };
+
+ GRL_DEBUG ("thetvdb_resolve_cache");
+
+ tvdb_source = GRL_THETVDB_SOURCE (os->source);
+ show = grl_media_video_get_show (GRL_MEDIA_VIDEO (os->media));
+
+ /* Get series async */
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, show);
+ query = gom_filter_new_eq (SERIES_TYPE_RESOURCE,
+ SERIES_COLUMN_SERIES_NAME,
+ &value);
+ g_value_unset (&value);
+ gom_repository_find_one_async (tvdb_source->priv->repository,
+ SERIES_TYPE_RESOURCE,
+ query,
+ cache_find_serie_done,
+ os);
+ g_object_unref (query);
+}
+
+/* ================== API Implementation ================ */
+static void
+grl_thetvdb_source_resolve (GrlSource *source,
+ GrlSourceResolveSpec *rs)
+{
+ OperationSpec *os = NULL;
+
+ GRL_DEBUG ("thetvdb_resolve");
+
+ os = g_slice_new0 (OperationSpec);
+ os->source = rs->source;
+ os->operation_id = rs->operation_id;
+ os->keys = g_list_copy (rs->keys);
+ os->callback = rs->callback;
+ os->media = rs->media;
+ os->user_data = rs->user_data;
+ os->error_code = GRL_CORE_ERROR_RESOLVE_FAILED;
+ os->lang = get_pref_language (GRL_THETVDB_SOURCE (source));
+ os->fetched_web = FALSE;
+
+ thetvdb_execute_resolve_cache (os);
+}
+
+static gboolean
+grl_thetvdb_source_may_resolve (GrlSource *source,
+ GrlMedia *media,
+ GrlKeyID key_id,
+ GList **missing_keys)
+{
+ GrlTheTVDBSource *tvdb_source = GRL_THETVDB_SOURCE (source);
+
+ GRL_DEBUG ("thetvdb_may_resolve");
+
+ /* Check if this key is supported */
+ if (!g_list_find (tvdb_source->priv->supported_keys,
+ GRLKEYID_TO_POINTER (key_id)))
+ return FALSE;
+
+ /* Check if resolve type and media type match */
+ if (media && !GRL_IS_MEDIA_VIDEO (media))
+ return FALSE;
+
+ /* Check if the media has a show */
+ if (!media || !grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_SHOW)) {
+ if (missing_keys)
+ *missing_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_SHOW, NULL);
+
+ return FALSE;
+ }
+
+ /* For season and episode number, we need the the title of the episode */
+ if ((key_id == GRL_METADATA_KEY_SEASON || key_id == GRL_METADATA_KEY_EPISODE)
+ && !grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_TITLE)) {
+ if (missing_keys)
+ *missing_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, NULL);
+
+ return FALSE;
+ }
+
+ /* For the title of the episode, we need season and episode number */
+ if (key_id == GRL_METADATA_KEY_TITLE) {
+ GList *l = NULL;
+ if (!grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_SEASON))
+ l = g_list_prepend (l, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_SEASON));
+
+ if (!grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_EPISODE))
+ l = g_list_prepend (l, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_EPISODE));
+
+ if (l != NULL) {
+ if (missing_keys)
+ *missing_keys = l;
+
+ return FALSE;
+ }
+ }
+
+ /* For episode's specific metadata, we need the season
+ * and episode number or the title of the episode */
+ if ((key_id == GRL_METADATA_KEY_DIRECTOR
+ || key_id == GRL_THETVDB_METADATA_KEY_GUEST_STARS)
+ && !grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_TITLE)) {
+ GList *l = NULL;
+ if (!grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_SEASON))
+ l = g_list_prepend (l, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_SEASON));
+
+ if (!grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_EPISODE))
+ l = g_list_prepend (l, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_EPISODE));
+
+ if (l != NULL) {
+ if (missing_keys)
+ *missing_keys = l;
+
+ return FALSE;
+ }
+ }
+
+ /* Note: The following keys could be related for both show and episode.
+ * To the may-resolve operation we consider the request of those keys as
+ * for the show because we only need the show name to retrieve that
+ * information.
+ * If the GrlMedia has Title or Episode and Season, we retrieve those keys
+ * for the episode.
+ *
+ * GRL_METADATA_KEY_PUBLICATION_DATE
+ * GRL_METADATA_KEY_DESCRIPTION
+ * GRL_THETVDB_METADATA_KEY_THETVDB_ID
+ * GRL_THETVDB_METADATA_KEY_IMDB_ID
+ */
+
+ return TRUE;
+}
+
+static const GList *
+grl_thetvdb_source_supported_keys (GrlSource *source)
+{
+ GrlTheTVDBSource *tvdb_source = GRL_THETVDB_SOURCE (source);
+
+ return tvdb_source->priv->supported_keys;
+}
diff --git a/src/thetvdb/grl-thetvdb.h b/src/thetvdb/grl-thetvdb.h
new file mode 100644
index 0000000..45a0a1f
--- /dev/null
+++ b/src/thetvdb/grl-thetvdb.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 Victor Toso.
+ *
+ * Contact: Victor Toso <me victortoso com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_THETVDB_SOURCE_H_
+#define _GRL_THETVDB_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_THETVDB_SOURCE_TYPE \
+ (grl_thetvdb_source_get_type ())
+
+#define GRL_THETVDB_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ GRL_THETVDB_SOURCE_TYPE, \
+ GrlTheTVDBSource))
+
+#define GRL_THETVDB_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_THETVDB_SOURCE_TYPE, \
+ GrlTheTVDBSourceClass))
+
+#define GRL_IS_THETVDB_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_THETVDB_SOURCE_TYPE))
+
+#define GRL_THETVDB_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ GRL_THETVDB_SOURCE_TYPE, \
+ GrlTheTVDBSourceClass))
+
+typedef struct _GrlTheTVDBPrivate GrlTheTVDBPrivate;
+typedef struct _GrlTheTVDBSource GrlTheTVDBSource;
+
+struct _GrlTheTVDBSource {
+ GrlSource parent;
+ GrlTheTVDBPrivate *priv;
+};
+
+typedef struct _GrlTheTVDBSourceClass GrlTheTVDBSourceClass;
+
+struct _GrlTheTVDBSourceClass {
+ GrlSourceClass parent_class;
+};
+
+GType grl_thetvdb_source_get_type (void);
+
+#endif /* _GRL_THETVDB_SOURCE_H_ */
diff --git a/src/thetvdb/grl-thetvdb.xml b/src/thetvdb/grl-thetvdb.xml
new file mode 100644
index 0000000..fa12fc5
--- /dev/null
+++ b/src/thetvdb/grl-thetvdb.xml
@@ -0,0 +1,10 @@
+<plugin>
+ <info>
+ <name>The TVDB</name>
+ <module>libgrlthetvdb</module>
+ <description>A plugin for fetching metadata of television shows</description>
+ <author>Victor Toso</author>
+ <license>LGPL</license>
+ <site>http://victortoso.com</site>
+ </info>
+</plugin>
diff --git a/src/thetvdb/thetvdb-resources-episodes.c b/src/thetvdb/thetvdb-resources-episodes.c
new file mode 100644
index 0000000..d760a01
--- /dev/null
+++ b/src/thetvdb/thetvdb-resources-episodes.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2014 Victor Toso.
+ *
+ * Contact: Victor Toso <me victortoso com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include "thetvdb-resources.h"
+
+G_DEFINE_TYPE (EpisodeResource, episode_resource, GOM_TYPE_RESOURCE)
+
+struct _EpisodeResourcePrivate {
+ gint64 db_id;
+ gdouble rating;
+ gchar *series_id;
+ gchar *overview;
+ gchar *language;
+ gchar *imdb_id;
+ gchar *first_aired;
+ guint season_number;
+ guint episode_number;
+ guint absolute_number;
+ gchar *season_id;
+ gchar *episode_id;
+ gchar *episode_name;
+ gchar *url_episode_screen;
+ gchar *director_names;
+ gchar *guest_stars_names;
+};
+
+enum {
+ PROP_0,
+ PROP_DB_ID,
+ PROP_LANGUAGE,
+ PROP_SERIES_ID,
+ PROP_OVERVIEW,
+ PROP_IMDB_ID,
+ PROP_FIRST_AIRED,
+ PROP_RATING,
+ PROP_SEASON_NUMBER,
+ PROP_EPISODE_NUMBER,
+ PROP_ABSOLUTE_NUMBER,
+ PROP_SEASON_ID,
+ PROP_EPISODE_ID,
+ PROP_EPISODE_NAME,
+ PROP_URL_EPISODE_SCREEN,
+ PROP_DIRECTOR_NAMES,
+ PROP_GUEST_STARS_NAMES,
+ LAST_PROP
+};
+
+static GParamSpec *specs[LAST_PROP];
+
+static void
+episode_resource_finalize (GObject *object)
+{
+ EpisodeResourcePrivate *priv = EPISODE_RESOURCE(object)->priv;
+
+ g_clear_pointer (&priv->language, g_free);
+ g_clear_pointer (&priv->series_id, g_free);
+ g_clear_pointer (&priv->overview, g_free);
+ g_clear_pointer (&priv->imdb_id, g_free);
+ g_clear_pointer (&priv->first_aired, g_free);
+ g_clear_pointer (&priv->season_id, g_free);
+ g_clear_pointer (&priv->episode_id, g_free);
+ g_clear_pointer (&priv->episode_name, g_free);
+ g_clear_pointer (&priv->url_episode_screen, g_free);
+ g_clear_pointer (&priv->director_names, g_free);
+ g_clear_pointer (&priv->guest_stars_names, g_free);
+
+ G_OBJECT_CLASS(episode_resource_parent_class)->finalize(object);
+}
+
+static void
+episode_resource_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EpisodeResource *resource = EPISODE_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ g_value_set_int64 (value, resource->priv->db_id);
+ break;
+ case PROP_LANGUAGE:
+ g_value_set_string (value, resource->priv->language);
+ break;
+ case PROP_SERIES_ID:
+ g_value_set_string (value, resource->priv->series_id);
+ break;
+ case PROP_OVERVIEW:
+ g_value_set_string (value, resource->priv->overview);
+ break;
+ case PROP_IMDB_ID:
+ g_value_set_string (value, resource->priv->imdb_id);
+ break;
+ case PROP_FIRST_AIRED:
+ g_value_set_string (value, resource->priv->first_aired);
+ break;
+ case PROP_RATING:
+ g_value_set_double (value, resource->priv->rating);
+ break;
+ case PROP_SEASON_NUMBER:
+ g_value_set_uint (value, resource->priv->season_number);
+ break;
+ case PROP_EPISODE_NUMBER:
+ g_value_set_uint (value, resource->priv->episode_number);
+ break;
+ case PROP_ABSOLUTE_NUMBER:
+ g_value_set_uint (value, resource->priv->absolute_number);
+ break;
+ case PROP_SEASON_ID:
+ g_value_set_string (value, resource->priv->season_id);
+ break;
+ case PROP_EPISODE_ID:
+ g_value_set_string (value, resource->priv->episode_id);
+ break;
+ case PROP_EPISODE_NAME:
+ g_value_set_string (value, resource->priv->episode_name);
+ break;
+ case PROP_URL_EPISODE_SCREEN:
+ g_value_set_string (value, resource->priv->url_episode_screen);
+ break;
+ case PROP_DIRECTOR_NAMES:
+ g_value_set_string (value, resource->priv->director_names);
+ break;
+ case PROP_GUEST_STARS_NAMES:
+ g_value_set_string (value, resource->priv->guest_stars_names);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+episode_resource_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EpisodeResource *resource = EPISODE_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ resource->priv->db_id = g_value_get_int64 (value);
+ break;
+ case PROP_LANGUAGE:
+ g_clear_pointer (&resource->priv->language, g_free);
+ resource->priv->language = g_value_dup_string (value);
+ break;
+ case PROP_SERIES_ID:
+ g_clear_pointer (&resource->priv->series_id, g_free);
+ resource->priv->series_id = g_value_dup_string (value);
+ break;
+ case PROP_OVERVIEW:
+ g_clear_pointer (&resource->priv->overview, g_free);
+ resource->priv->overview = g_value_dup_string (value);
+ break;
+ case PROP_IMDB_ID:
+ g_clear_pointer (&resource->priv->imdb_id, g_free);
+ resource->priv->imdb_id = g_value_dup_string (value);
+ break;
+ case PROP_FIRST_AIRED:
+ g_clear_pointer (&resource->priv->first_aired, g_free);
+ resource->priv->first_aired = g_value_dup_string (value);
+ break;
+ case PROP_RATING:
+ resource->priv->rating = g_value_get_double (value);
+ break;
+ case PROP_SEASON_NUMBER:
+ resource->priv->season_number = g_value_get_uint (value);
+ break;
+ case PROP_EPISODE_NUMBER:
+ resource->priv->episode_number = g_value_get_uint (value);
+ break;
+ case PROP_ABSOLUTE_NUMBER:
+ resource->priv->absolute_number = g_value_get_uint (value);
+ break;
+ case PROP_SEASON_ID:
+ g_clear_pointer (&resource->priv->season_id, g_free);
+ resource->priv->season_id = g_value_dup_string (value);
+ break;
+ case PROP_EPISODE_ID:
+ g_clear_pointer (&resource->priv->episode_id, g_free);
+ resource->priv->episode_id = g_value_dup_string (value);
+ break;
+ case PROP_EPISODE_NAME:
+ g_clear_pointer (&resource->priv->episode_name, g_free);
+ resource->priv->episode_name = g_value_dup_string (value);
+ break;
+ case PROP_URL_EPISODE_SCREEN:
+ g_clear_pointer (&resource->priv->url_episode_screen, g_free);
+ resource->priv->url_episode_screen = g_value_dup_string (value);
+ break;
+ case PROP_DIRECTOR_NAMES:
+ g_clear_pointer (&resource->priv->director_names, g_free);
+ resource->priv->director_names = g_value_dup_string (value);
+ break;
+ case PROP_GUEST_STARS_NAMES:
+ g_clear_pointer (&resource->priv->guest_stars_names, g_free);
+ resource->priv->guest_stars_names = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+episode_resource_class_init (EpisodeResourceClass *klass)
+{
+ GObjectClass *object_class;
+ GomResourceClass *resource_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = episode_resource_finalize;
+ object_class->get_property = episode_resource_get_property;
+ object_class->set_property = episode_resource_set_property;
+ g_type_class_add_private(object_class, sizeof(EpisodeResourcePrivate));
+
+ resource_class = GOM_RESOURCE_CLASS(klass);
+ gom_resource_class_set_table(resource_class, "episodes");
+
+ specs[PROP_DB_ID] = g_param_spec_int64 (EPISODE_COLUMN_ID,
+ NULL, NULL,
+ 0, G_MAXINT64,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DB_ID,
+ specs[PROP_DB_ID]);
+ gom_resource_class_set_primary_key (resource_class, EPISODE_COLUMN_ID);
+
+ specs[PROP_LANGUAGE] = g_param_spec_string (EPISODE_COLUMN_LANGUAGE,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_LANGUAGE,
+ specs[PROP_LANGUAGE]);
+
+ specs[PROP_SERIES_ID] = g_param_spec_string (EPISODE_COLUMN_SERIES_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SERIES_ID,
+ specs[PROP_SERIES_ID]);
+
+ specs[PROP_OVERVIEW] = g_param_spec_string (EPISODE_COLUMN_OVERVIEW,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_OVERVIEW,
+ specs[PROP_OVERVIEW]);
+
+ specs[PROP_IMDB_ID] = g_param_spec_string (EPISODE_COLUMN_IMDB_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_IMDB_ID,
+ specs[PROP_IMDB_ID]);
+
+ specs[PROP_FIRST_AIRED] = g_param_spec_string (EPISODE_COLUMN_FIRST_AIRED,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_FIRST_AIRED,
+ specs[PROP_FIRST_AIRED]);
+
+ specs[PROP_RATING] = g_param_spec_double (EPISODE_COLUMN_RATING,
+ NULL, NULL,
+ 0, G_MAXDOUBLE,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_RATING,
+ specs[PROP_RATING]);
+
+ specs[PROP_SEASON_NUMBER] = g_param_spec_uint (EPISODE_COLUMN_SEASON_NUMBER,
+ NULL, NULL,
+ 0, G_MAXUINT,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SEASON_NUMBER,
+ specs[PROP_SEASON_NUMBER]);
+
+ specs[PROP_EPISODE_NUMBER] = g_param_spec_uint (EPISODE_COLUMN_EPISODE_NUMBER,
+ NULL, NULL,
+ 0, G_MAXUINT,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_EPISODE_NUMBER,
+ specs[PROP_EPISODE_NUMBER]);
+
+ specs[PROP_ABSOLUTE_NUMBER] = g_param_spec_uint (EPISODE_COLUMN_ABSOLUTE_NUMBER,
+ NULL, NULL,
+ 0, G_MAXUINT,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_ABSOLUTE_NUMBER,
+ specs[PROP_ABSOLUTE_NUMBER]);
+
+ specs[PROP_SEASON_ID] = g_param_spec_string (EPISODE_COLUMN_SEASON_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SEASON_ID,
+ specs[PROP_SEASON_ID]);
+
+ specs[PROP_EPISODE_ID] = g_param_spec_string (EPISODE_COLUMN_EPISODE_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_EPISODE_ID,
+ specs[PROP_EPISODE_ID]);
+ gom_resource_class_set_unique (resource_class, EPISODE_COLUMN_EPISODE_ID);
+
+ specs[PROP_EPISODE_NAME] = g_param_spec_string (EPISODE_COLUMN_EPISODE_NAME,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_EPISODE_NAME,
+ specs[PROP_EPISODE_NAME]);
+
+ specs[PROP_URL_EPISODE_SCREEN] = g_param_spec_string (EPISODE_COLUMN_URL_EPISODE_SCREEN,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_URL_EPISODE_SCREEN,
+ specs[PROP_URL_EPISODE_SCREEN]);
+
+ specs[PROP_DIRECTOR_NAMES] = g_param_spec_string (EPISODE_COLUMN_DIRECTOR_NAMES,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DIRECTOR_NAMES,
+ specs[PROP_DIRECTOR_NAMES]);
+
+ specs[PROP_GUEST_STARS_NAMES] = g_param_spec_string (EPISODE_COLUMN_GUEST_STARS_NAMES,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_GUEST_STARS_NAMES,
+ specs[PROP_GUEST_STARS_NAMES]);
+}
+
+static void
+episode_resource_init (EpisodeResource *resource)
+{
+ resource->priv = G_TYPE_INSTANCE_GET_PRIVATE(resource,
+ EPISODE_TYPE_RESOURCE,
+ EpisodeResourcePrivate);
+}
diff --git a/src/thetvdb/thetvdb-resources-series.c b/src/thetvdb/thetvdb-resources-series.c
new file mode 100644
index 0000000..25f9e49
--- /dev/null
+++ b/src/thetvdb/thetvdb-resources-series.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 Victor Toso.
+ *
+ * Contact: Victor Toso <me victortoso com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include "thetvdb-resources.h"
+
+G_DEFINE_TYPE (SeriesResource, series_resource, GOM_TYPE_RESOURCE)
+
+struct _SeriesResourcePrivate {
+ gint64 db_id;
+ gdouble rating;
+ gchar *series_id;
+ gchar *overview;
+ gchar *language;
+ gchar *imdb_id;
+ gchar *first_aired;
+ gchar *series_name;
+ gchar *status;
+ gchar *url_banner;
+ gchar *url_fanart;
+ gchar *url_poster;
+ gchar *zap2it_id;
+ gchar *actor_names;
+ gchar *alias_names;
+ gchar *genres;
+};
+
+enum {
+ PROP_0,
+ PROP_DB_ID,
+ PROP_LANGUAGE,
+ PROP_SERIES_NAME,
+ PROP_SERIES_ID,
+ PROP_STATUS,
+ PROP_OVERVIEW,
+ PROP_IMDB_ID,
+ PROP_ZAP2IT_ID,
+ PROP_FIRST_AIRED,
+ PROP_RATING,
+ PROP_ACTOR_NAMES,
+ PROP_GENRES,
+ PROP_URL_BANNER,
+ PROP_URL_FANART,
+ PROP_URL_POSTER,
+ LAST_PROP
+};
+
+static GParamSpec *specs[LAST_PROP];
+
+static void
+series_resource_finalize (GObject *object)
+{
+ SeriesResourcePrivate *priv = SERIES_RESOURCE(object)->priv;
+
+ g_clear_pointer (&priv->language, g_free);
+ g_clear_pointer (&priv->series_name, g_free);
+ g_clear_pointer (&priv->series_id, g_free);
+ g_clear_pointer (&priv->status, g_free);
+ g_clear_pointer (&priv->overview, g_free);
+ g_clear_pointer (&priv->imdb_id, g_free);
+ g_clear_pointer (&priv->zap2it_id, g_free);
+ g_clear_pointer (&priv->first_aired, g_free);
+ g_clear_pointer (&priv->actor_names, g_free);
+ g_clear_pointer (&priv->alias_names, g_free);
+ g_clear_pointer (&priv->genres, g_free);
+ g_clear_pointer (&priv->url_banner, g_free);
+ g_clear_pointer (&priv->url_fanart, g_free);
+ g_clear_pointer (&priv->url_poster, g_free);
+
+ G_OBJECT_CLASS(series_resource_parent_class)->finalize(object);
+}
+
+static void
+series_resource_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SeriesResource *resource = SERIES_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ g_value_set_int64 (value, resource->priv->db_id);
+ break;
+ case PROP_LANGUAGE:
+ g_value_set_string (value, resource->priv->language);
+ break;
+ case PROP_SERIES_NAME:
+ g_value_set_string (value, resource->priv->series_name);
+ break;
+ case PROP_SERIES_ID:
+ g_value_set_string (value, resource->priv->series_id);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value, resource->priv->status);
+ break;
+ case PROP_OVERVIEW:
+ g_value_set_string (value, resource->priv->overview);
+ break;
+ case PROP_IMDB_ID:
+ g_value_set_string (value, resource->priv->imdb_id);
+ break;
+ case PROP_ZAP2IT_ID:
+ g_value_set_string (value, resource->priv->zap2it_id);
+ break;
+ case PROP_FIRST_AIRED:
+ g_value_set_string (value, resource->priv->first_aired);
+ break;
+ case PROP_RATING:
+ g_value_set_double (value, resource->priv->rating);
+ break;
+ case PROP_ACTOR_NAMES:
+ g_value_set_string (value, resource->priv->actor_names);
+ break;
+ case PROP_GENRES:
+ g_value_set_string (value, resource->priv->genres);
+ break;
+ case PROP_URL_BANNER:
+ g_value_set_string (value, resource->priv->url_banner);
+ break;
+ case PROP_URL_FANART:
+ g_value_set_string (value, resource->priv->url_fanart);
+ break;
+ case PROP_URL_POSTER:
+ g_value_set_string (value, resource->priv->url_poster);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+series_resource_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SeriesResource *resource = SERIES_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ resource->priv->db_id = g_value_get_int64 (value);
+ break;
+ case PROP_LANGUAGE:
+ g_clear_pointer (&resource->priv->language, g_free);
+ resource->priv->language = g_value_dup_string (value);
+ break;
+ case PROP_SERIES_NAME:
+ g_clear_pointer (&resource->priv->series_name, g_free);
+ resource->priv->series_name = g_value_dup_string (value);
+ break;
+ case PROP_SERIES_ID:
+ g_clear_pointer (&resource->priv->series_id, g_free);
+ resource->priv->series_id = g_value_dup_string (value);
+ break;
+ case PROP_STATUS:
+ g_clear_pointer (&resource->priv->status, g_free);
+ resource->priv->status = g_value_dup_string (value);
+ break;
+ case PROP_OVERVIEW:
+ g_clear_pointer (&resource->priv->overview, g_free);
+ resource->priv->overview = g_value_dup_string (value);
+ break;
+ case PROP_IMDB_ID:
+ g_clear_pointer (&resource->priv->imdb_id, g_free);
+ resource->priv->imdb_id = g_value_dup_string (value);
+ break;
+ case PROP_ZAP2IT_ID:
+ g_clear_pointer (&resource->priv->zap2it_id, g_free);
+ resource->priv->zap2it_id = g_value_dup_string (value);
+ break;
+ case PROP_FIRST_AIRED:
+ g_clear_pointer (&resource->priv->first_aired, g_free);
+ resource->priv->first_aired = g_value_dup_string (value);
+ break;
+ case PROP_RATING:
+ resource->priv->rating = g_value_get_double (value);
+ break;
+ case PROP_ACTOR_NAMES:
+ g_clear_pointer (&resource->priv->actor_names, g_free);
+ resource->priv->actor_names = g_value_dup_string (value);
+ break;
+ case PROP_GENRES:
+ g_clear_pointer (&resource->priv->genres, g_free);
+ resource->priv->genres = g_value_dup_string (value);
+ break;
+ case PROP_URL_BANNER:
+ g_clear_pointer (&resource->priv->url_banner, g_free);
+ resource->priv->url_banner = g_value_dup_string (value);
+ break;
+ case PROP_URL_FANART:
+ g_clear_pointer (&resource->priv->url_fanart, g_free);
+ resource->priv->url_fanart = g_value_dup_string (value);
+ break;
+ case PROP_URL_POSTER:
+ g_clear_pointer (&resource->priv->url_poster, g_free);
+ resource->priv->url_poster = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+series_resource_class_init (SeriesResourceClass *klass)
+{
+ GObjectClass *object_class;
+ GomResourceClass *resource_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = series_resource_finalize;
+ object_class->get_property = series_resource_get_property;
+ object_class->set_property = series_resource_set_property;
+ g_type_class_add_private(object_class, sizeof(SeriesResourcePrivate));
+
+ resource_class = GOM_RESOURCE_CLASS(klass);
+ gom_resource_class_set_table(resource_class, "series");
+
+ specs[PROP_DB_ID] = g_param_spec_int64 (SERIES_COLUMN_ID,
+ NULL, NULL,
+ 0, G_MAXINT64,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DB_ID,
+ specs[PROP_DB_ID]);
+ gom_resource_class_set_primary_key (resource_class, SERIES_COLUMN_ID);
+
+ specs[PROP_LANGUAGE] = g_param_spec_string (SERIES_COLUMN_LANGUAGE,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_LANGUAGE,
+ specs[PROP_LANGUAGE]);
+
+ specs[PROP_SERIES_NAME] = g_param_spec_string (SERIES_COLUMN_SERIES_NAME,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SERIES_NAME,
+ specs[PROP_SERIES_NAME]);
+
+ specs[PROP_SERIES_ID] = g_param_spec_string (SERIES_COLUMN_SERIES_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SERIES_ID,
+ specs[PROP_SERIES_ID]);
+ gom_resource_class_set_unique (resource_class, SERIES_COLUMN_SERIES_ID);
+
+ specs[PROP_STATUS] = g_param_spec_string (SERIES_COLUMN_STATUS,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_STATUS,
+ specs[PROP_STATUS]);
+
+ specs[PROP_OVERVIEW] = g_param_spec_string (SERIES_COLUMN_OVERVIEW,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_OVERVIEW,
+ specs[PROP_OVERVIEW]);
+
+ specs[PROP_IMDB_ID] = g_param_spec_string (SERIES_COLUMN_IMDB_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_IMDB_ID,
+ specs[PROP_IMDB_ID]);
+
+ specs[PROP_ZAP2IT_ID] = g_param_spec_string (SERIES_COLUMN_ZAP2IT_ID,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_ZAP2IT_ID,
+ specs[PROP_ZAP2IT_ID]);
+
+ specs[PROP_FIRST_AIRED] = g_param_spec_string (SERIES_COLUMN_FIRST_AIRED,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_FIRST_AIRED,
+ specs[PROP_FIRST_AIRED]);
+
+ specs[PROP_RATING] = g_param_spec_double (SERIES_COLUMN_RATING,
+ NULL, NULL,
+ 0, G_MAXDOUBLE,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_RATING,
+ specs[PROP_RATING]);
+
+ specs[PROP_ACTOR_NAMES] = g_param_spec_string (SERIES_COLUMN_ACTOR_NAMES,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_ACTOR_NAMES,
+ specs[PROP_ACTOR_NAMES]);
+
+ specs[PROP_GENRES] = g_param_spec_string (SERIES_COLUMN_GENRES,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_GENRES,
+ specs[PROP_GENRES]);
+
+ specs[PROP_URL_BANNER] = g_param_spec_string (SERIES_COLUMN_URL_BANNER,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_URL_BANNER,
+ specs[PROP_URL_BANNER]);
+
+ specs[PROP_URL_FANART] = g_param_spec_string (SERIES_COLUMN_URL_FANART,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_URL_FANART,
+ specs[PROP_URL_FANART]);
+
+ specs[PROP_URL_POSTER] = g_param_spec_string (SERIES_COLUMN_URL_POSTER,
+ NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_URL_POSTER,
+ specs[PROP_URL_POSTER]);
+}
+
+static void
+series_resource_init (SeriesResource *resource)
+{
+ resource->priv = G_TYPE_INSTANCE_GET_PRIVATE(resource,
+ SERIES_TYPE_RESOURCE,
+ SeriesResourcePrivate);
+}
diff --git a/src/thetvdb/thetvdb-resources.h b/src/thetvdb/thetvdb-resources.h
new file mode 100644
index 0000000..81d2eb3
--- /dev/null
+++ b/src/thetvdb/thetvdb-resources.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 Victor Toso.
+ *
+ * Contact: Victor Toso <me victortoso com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_THETVDB_RESOURCEs_H_
+#define _GRL_THETVDB_RESOURCEs_H_
+
+#include <gom/gom.h>
+
+/*----- Series ----- */
+#define SERIES_TYPE_RESOURCE \
+ (series_resource_get_type())
+
+#define SERIES_TYPE_TYPE \
+ (series_type_get_type())
+
+#define SERIES_RESOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ SERIES_TYPE_RESOURCE, \
+ SeriesResource))
+
+#define SERIES_RESOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ SERIES_TYPE_RESOURCE, \
+ SeriesResourceClass))
+
+#define SERIES_IS_RESOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ SERIES_TYPE_RESOURCE))
+
+#define SERIES_IS_RESOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ SERIES_TYPE_RESOURCE))
+
+#define SERIES_RESOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ SERIES_TYPE_RESOURCE, \
+ SeriesResourceClass))
+
+#define SERIES_COLUMN_ID "id"
+#define SERIES_COLUMN_LANGUAGE "language"
+#define SERIES_COLUMN_SERIES_NAME "series-name"
+#define SERIES_COLUMN_SERIES_ID "series-id"
+#define SERIES_COLUMN_STATUS "status"
+#define SERIES_COLUMN_OVERVIEW "overview"
+#define SERIES_COLUMN_IMDB_ID "imdb-id"
+#define SERIES_COLUMN_ZAP2IT_ID "zap2it-id"
+#define SERIES_COLUMN_FIRST_AIRED "first-aired"
+#define SERIES_COLUMN_RATING "rating"
+#define SERIES_COLUMN_ACTOR_NAMES "actor-names"
+#define SERIES_COLUMN_GENRES "genres"
+#define SERIES_COLUMN_URL_BANNER "url-banner"
+#define SERIES_COLUMN_URL_FANART "url-fanart"
+#define SERIES_COLUMN_URL_POSTER "url-poster"
+
+typedef struct _SeriesResource SeriesResource;
+typedef struct _SeriesResourceClass SeriesResourceClass;
+typedef struct _SeriesResourcePrivate SeriesResourcePrivate;
+
+struct _SeriesResource
+{
+ GomResource parent;
+ SeriesResourcePrivate *priv;
+};
+
+struct _SeriesResourceClass
+{
+ GomResourceClass parent_class;
+};
+
+GType series_resource_get_type (void);
+
+/*----- Episodes ----- */
+#define EPISODE_TYPE_RESOURCE \
+ (episode_resource_get_type())
+
+#define EPISODE_TYPE_TYPE \
+ (episode_type_get_type())
+
+#define EPISODE_RESOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ EPISODE_TYPE_RESOURCE, \
+ EpisodeResource))
+
+#define EPISODE_RESOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ EPISODE_TYPE_RESOURCE, \
+ EpisodeResourceClass))
+
+#define EPISODE_IS_RESOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ EPISODE_TYPE_RESOURCE))
+
+#define EPISODE_IS_RESOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ EPISODE_TYPE_RESOURCE))
+
+#define EPISODE_RESOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EPISODE_TYPE_RESOURCE, \
+ EpisodeResourceClass))
+
+#define EPISODE_COLUMN_ID "id"
+#define EPISODE_COLUMN_LANGUAGE "language"
+#define EPISODE_COLUMN_SERIES_ID "series-id"
+#define EPISODE_COLUMN_OVERVIEW "overview"
+#define EPISODE_COLUMN_IMDB_ID "imdb-id"
+#define EPISODE_COLUMN_FIRST_AIRED "first-aired"
+#define EPISODE_COLUMN_RATING "rating"
+#define EPISODE_COLUMN_SEASON_NUMBER "season-number"
+#define EPISODE_COLUMN_EPISODE_NUMBER "episode-number"
+#define EPISODE_COLUMN_ABSOLUTE_NUMBER "absolute-number"
+#define EPISODE_COLUMN_SEASON_ID "season-id"
+#define EPISODE_COLUMN_EPISODE_ID "episode-id"
+#define EPISODE_COLUMN_EPISODE_NAME "episode-name"
+#define EPISODE_COLUMN_URL_EPISODE_SCREEN "url-episode-screen"
+#define EPISODE_COLUMN_DIRECTOR_NAMES "director-names"
+#define EPISODE_COLUMN_GUEST_STARS_NAMES "guest-stars-names"
+
+typedef struct _EpisodeResource EpisodeResource;
+typedef struct _EpisodeResourceClass EpisodeResourceClass;
+typedef struct _EpisodeResourcePrivate EpisodeResourcePrivate;
+
+struct _EpisodeResource
+{
+ GomResource parent;
+ EpisodeResourcePrivate *priv;
+};
+
+struct _EpisodeResourceClass
+{
+ GomResourceClass parent_class;
+};
+
+GType episode_resource_get_type (void);
+
+#endif /* _GRL_THETVDB_RESOURCES_H_ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]