Added tmdb-image support
[maevies] / src / mvs-minfo-provider.c
1 /*
2  * mvs-minfo-provider.c
3  *
4  * This file is part of maevies
5  * Copyright (C) 2010 Simón Pena <spenap@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  */
18
19 #include "mvs-minfo-provider.h"
20
21 #include <libxml/parser.h>
22 #include <libxml/xpath.h>
23 #include <json-glib/json-glib.h>
24
25 #include "mvs-tmdb-movie.h"
26 #include "mvs-tmdb-image.h"
27 #include "mvs-watc-movie.h"
28
29 #define TMDB_API_KEY "249e1a42df9bee09fac5e92d3a51396b"
30 #define TMDB_LANGUAGE "en"
31 #define TMDB_FORMAT "xml"
32 #define TMDB_METHOD "Movie.search"
33 #define TMDB_BASE_URL "http://api.themoviedb.org/2.1/%s/%s/%s/%s/%s"
34 #define TMDB_MOVIE_XPATH "/OpenSearchDescription/movies/movie"
35
36 #define WATC_BASE_URL "http://whatsafterthecredits.com/api.php?action=%s&format=%s&search=%s"
37 #define WATC_ACTION "opensearch"
38 #define WATC_FORMAT "json"
39
40 G_DEFINE_TYPE (MvsMInfoProvider, mvs_minfo_provider, G_TYPE_OBJECT)
41
42 enum {
43         PROP_0,
44         PROP_FORMAT,
45 };
46
47 #define GET_PRIVATE(o) \
48   (G_TYPE_INSTANCE_GET_PRIVATE ((o), MVS_TYPE_MINFO_PROVIDER, MvsMInfoProviderPrivate))
49
50 struct _MvsMInfoProviderPrivate {
51         gchar *format;
52         MvsService service;
53 };
54
55 enum {
56         RESPONSE_RECEIVED,
57         LAST_SIGNAL
58 };
59
60 static guint
61 signals[LAST_SIGNAL] = { 0 };
62
63
64 static void
65 mvs_minfo_provider_get_property (GObject *object, guint property_id,
66                          GValue *value, GParamSpec *pspec)
67 {
68         MvsMInfoProvider *self = MVS_MINFO_PROVIDER (object);
69
70         switch (property_id) {
71         case PROP_FORMAT:
72                 g_value_set_string (value, self->priv->format);
73                 break;
74         default:
75                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
76         }
77 }
78
79 static void
80 mvs_minfo_provider_set_property (GObject *object, guint property_id,
81                          const GValue *value, GParamSpec *pspec)
82 {
83         MvsMInfoProvider *self = MVS_MINFO_PROVIDER (object);
84
85         switch (property_id) {
86         case PROP_FORMAT:
87                 mvs_minfo_provider_set_format (self,
88                                 g_value_get_string (value));
89                 break;
90         default:
91                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
92         }
93 }
94
95 static void
96 mvs_minfo_provider_finalize (GObject *object)
97 {
98         MvsMInfoProvider *self = MVS_MINFO_PROVIDER (object);
99
100         g_free (self->priv->format);
101
102         G_OBJECT_CLASS (mvs_minfo_provider_parent_class)->finalize (object);
103 }
104
105 static void
106 mvs_minfo_provider_class_init (MvsMInfoProviderClass *klass)
107 {
108         GObjectClass *object_class = G_OBJECT_CLASS (klass);
109
110         g_type_class_add_private (klass, sizeof (MvsMInfoProviderPrivate));
111
112         object_class->get_property = mvs_minfo_provider_get_property;
113         object_class->set_property = mvs_minfo_provider_set_property;
114         object_class->finalize = mvs_minfo_provider_finalize;
115
116         g_object_class_install_property
117                 (object_class, PROP_FORMAT,
118                  g_param_spec_string ("format", "The format", "The format",
119                                       TMDB_FORMAT,
120                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
121
122         signals[RESPONSE_RECEIVED] = g_signal_new ("response-received", MVS_TYPE_MINFO_PROVIDER,
123                         G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
124                         0,
125                         NULL,
126                         NULL,
127                         g_cclosure_marshal_VOID__POINTER,
128                         G_TYPE_NONE,
129                         1,
130                         G_TYPE_POINTER);
131 }
132
133 static void
134 mvs_minfo_provider_init (MvsMInfoProvider *self)
135 {
136         self->priv = GET_PRIVATE (self);
137         self->priv->format = NULL;
138         self->priv->service = MVS_SERVICE_TMDB;
139 }
140
141 MvsMInfoProvider*
142 mvs_minfo_provider_new (void)
143 {
144         return g_object_new (MVS_TYPE_MINFO_PROVIDER, NULL);
145 }
146
147 static MvsTmdbImage*
148 create_tmdb_image (xmlNodePtr node)
149 {
150         MvsTmdbImage *image = mvs_tmdb_image_new ();
151         gchar *value = NULL;
152         int i;
153
154         /* <image type="poster"
155          * url="http://....jpg"
156          * size="original"
157          * id="4bc91...e007304"/> */
158
159         for (i = 0; i < LAST_FIELD; i ++) {
160               value = xmlGetProp (node, image_fields[i]);
161
162               g_object_set (image, image_fields[i], value, NULL);
163
164               g_free (value);
165         }
166
167         return image;
168 }
169
170 static MvsTmdbMovie*
171 create_tmdb_movie (xmlNodePtr node)
172 {
173         xmlNodePtr cur_node = NULL;
174         MvsTmdbMovie *movie_info = mvs_tmdb_movie_new ();
175         GList *image_list = NULL;
176
177         /* We use the loop to append each property to the movie object */
178         for (cur_node = node; cur_node; cur_node = cur_node->next) {
179                 if (cur_node->type == XML_ELEMENT_NODE) {
180                         gchar *value = NULL;
181
182                         if (g_strcmp0 (cur_node->name, "images") == 0) {
183                                 xmlNodePtr cur_image = NULL;
184                                 for (cur_image = cur_node->children; cur_image;
185                                                 cur_image = cur_image->next) {
186
187                                         MvsTmdbImage *tmdb_image = create_tmdb_image (cur_image);
188                                         image_list = g_list_append (image_list, tmdb_image);
189                                 }
190                         }
191                         else {
192                                 value = xmlNodeGetContent (cur_node);
193                                 g_object_set (movie_info, cur_node->name, value, NULL);
194                                 g_free (value);
195                         }
196                 }
197         }
198
199         mvs_tmdb_movie_set_images (movie_info, image_list);
200
201         return movie_info;
202 }
203
204 static GList*
205 generate_list (xmlNodeSetPtr node_set)
206 {
207         int i = 0;
208         GList *list = NULL;
209
210         for (i = 0; i < node_set->nodeNr; i++) {
211                 xmlNodePtr node = node_set->nodeTab[i];
212                 if (node->type == XML_ELEMENT_NODE) {
213                         MvsTmdbMovie *movie_info =
214                                         create_tmdb_movie (node->children);
215                         if (movie_info)
216                                 list = g_list_prepend (list, movie_info);
217                 }
218         }
219
220         if (list)
221                 list = g_list_reverse (list);
222
223         return list;
224 }
225
226 static GList*
227 parse_xml (const char *xml_data, goffset length)
228 {
229         GList *list = NULL;
230         xmlDocPtr document = xmlReadMemory (xml_data, length,
231                         NULL,
232                         NULL,
233                         XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
234         g_return_if_fail (document);
235
236         xmlXPathContextPtr context_ptr = xmlXPathNewContext (document);
237
238         xmlXPathObjectPtr xpath_obj =
239                         xmlXPathEvalExpression (TMDB_MOVIE_XPATH, context_ptr);
240
241         xmlNodeSetPtr nodeset = xpath_obj->nodesetval;
242
243         if (nodeset->nodeNr > 0) {
244                 list = generate_list (nodeset);
245         }
246
247         xmlXPathFreeObject (xpath_obj);
248         xmlXPathFreeContext (context_ptr);
249         xmlFreeDoc (document);
250
251         return list;
252 }
253
254 static GList *
255 parse_json (const char *json_data, goffset length)
256 {
257         JsonParser *parser = NULL;
258         JsonNode *root = NULL;
259         GError *error = NULL;
260         GList *list = NULL;
261
262         parser = json_parser_new ();
263
264         json_parser_load_from_data (parser, json_data, length, &error);
265         if (error)
266         {
267                 g_warning ("Unable to parse data '%s': %s\n",
268                                 json_data, error->message);
269                 g_error_free (error);
270                 g_object_unref (parser);
271                 return list;
272         }
273
274         /* Don't free */
275         root = json_parser_get_root (parser);
276         JsonArray *response = json_node_get_array (root);
277
278         /* The response is expected with the following format:
279          * [ SEARCH_TERM ,[ SEARCH_RESULT_1, SEARCH_RESULT_N]] */
280
281         if (json_array_get_length (response) != 2) {
282
283                 g_warning ("Wrong response format: %s\n", json_data);
284
285                 g_object_unref (parser);
286                 return list;
287         }
288
289         const gchar *search_term = json_array_get_string_element (response, 0);
290         g_message ("Searched for: %s\n", search_term);
291
292         JsonArray *results = json_array_get_array_element (response, 1);
293         int i;
294         int array_length = json_array_get_length (results);
295
296         for (i = 0; i < array_length; i++) {
297                 const gchar *result =
298                                 json_array_get_string_element (results, i);
299                 MvsWatcMovie *watc_movie = mvs_watc_movie_new (result);
300                 list = g_list_prepend (list, watc_movie);
301         }
302
303         g_object_unref (parser);
304
305         if (list)
306                 list = g_list_reverse (list);
307
308         return list;
309 }
310
311 static void
312 process_response_cb (SoupSession *session, SoupMessage *message,
313                     gpointer user_data)
314 {
315         MvsMInfoProvider *self = MVS_MINFO_PROVIDER (user_data);
316         const gchar *mime = NULL;
317         GList *list = NULL;
318
319         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code) ||
320                         message->response_body->length <= 0) {
321
322                 g_print ("%s\n", message->reason_phrase);
323         }
324         else {
325
326                 mime = soup_message_headers_get_content_type
327                                 (message->response_headers, NULL);
328                 g_message ("Mime type: %s\n", mime);
329
330                 if (g_strcmp0 (mime, "text/xml") == 0)
331                         list = parse_xml (message->response_body->data,
332                                         message->response_body->length);
333                 else if (g_strcmp0 (mime, "application/json") == 0)
334                         list = parse_json (message->response_body->data,
335                                         message->response_body->length);
336         }
337
338         g_signal_emit (self, signals[RESPONSE_RECEIVED], 0, list);
339 }
340
341 static gchar *
342 get_query_uri (MvsMInfoProvider *self, const char *query)
343 {
344         gchar *uri = NULL;
345
346         if (self->priv->service == MVS_SERVICE_TMDB) {
347                 /* METHOD/LANGUAGE/FORMAT/APIKEY/MOVIENAME */
348                 uri = g_strdup_printf (TMDB_BASE_URL, TMDB_METHOD,
349                                 TMDB_LANGUAGE,
350                                 self->priv->format,
351                                 TMDB_API_KEY,
352                                 query);
353
354         }
355         else if (self->priv->service == MVS_SERVICE_WATC) {
356                 /* WATCBASE_URL/ACTION/FORMAT/QUERY */
357                 uri = g_strdup_printf (WATC_BASE_URL,
358                                 WATC_ACTION,
359                                 WATC_FORMAT,
360                                 query);
361         }
362         else {
363                 g_warning ("Service unsupported\n");
364         }
365
366         g_message ("%s", uri);
367         return uri;
368 }
369
370 gboolean
371 mvs_minfo_provider_query (MvsMInfoProvider *self, MvsService service,
372                           const gchar *query)
373 {
374         g_return_val_if_fail (MVS_IS_MINFO_PROVIDER (self), FALSE);
375
376         self->priv->service = service;
377
378         SoupSession *session = NULL;
379         SoupMessage *message = NULL;
380         gboolean message_queued = FALSE;
381
382         gchar *uri = get_query_uri (self, query);
383
384         g_return_val_if_fail (uri, FALSE);
385
386         session = soup_session_async_new ();
387         message = soup_message_new ("GET", uri);
388
389         if (message) {
390                 soup_session_queue_message (session, message,
391                                 process_response_cb, self);
392                 message_queued = TRUE;
393         }
394
395         g_free (uri);
396
397         return message_queued;
398 }
399
400 gboolean
401 mvs_minfo_provider_set_format (MvsMInfoProvider *self,
402                                const gchar *format)
403 {
404         g_return_val_if_fail (MVS_IS_MINFO_PROVIDER (self), FALSE);
405
406         g_free (self->priv->format);
407
408         self->priv->format = g_strdup (format);
409
410         return TRUE;
411 }