Misc code fixes
[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",
123                         MVS_TYPE_MINFO_PROVIDER,
124                         G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
125                         0,
126                         NULL,
127                         NULL,
128                         g_cclosure_marshal_VOID__UINT_POINTER,
129                         G_TYPE_NONE,
130                         2,
131                         G_TYPE_UINT,
132                         G_TYPE_POINTER,
133                         NULL);
134 }
135
136 static void
137 mvs_minfo_provider_init (MvsMInfoProvider *self)
138 {
139         self->priv = GET_PRIVATE (self);
140         self->priv->format = NULL;
141         self->priv->service = MVS_SERVICE_TMDB;
142 }
143
144 MvsMInfoProvider*
145 mvs_minfo_provider_new (void)
146 {
147         return g_object_new (MVS_TYPE_MINFO_PROVIDER, NULL);
148 }
149
150 static MvsTmdbImage*
151 create_tmdb_image (xmlNodePtr node)
152 {
153         MvsTmdbImage *image = mvs_tmdb_image_new ();
154         gchar *value = NULL;
155         int i;
156
157         /* <image type="poster"
158          * url="http://....jpg"
159          * size="original"
160          * id="4bc91...e007304"/> */
161
162         for (i = 0; i < LAST_FIELD; i ++) {
163
164                 const gchar *image_field = mvs_tmdb_image_get_field (i);
165
166                 value = xmlGetProp (node, image_field);
167                 g_object_set (image, image_field, value, NULL);
168
169                 g_free (value);
170         }
171
172         return image;
173 }
174
175 static MvsTmdbMovie*
176 create_tmdb_movie (xmlNodePtr node)
177 {
178         xmlNodePtr cur_node = NULL;
179         MvsTmdbMovie *movie_info = mvs_tmdb_movie_new ();
180         GList *image_list = NULL;
181
182         /* We use the loop to append each property to the movie object */
183         for (cur_node = node; cur_node; cur_node = cur_node->next) {
184                 if (cur_node->type == XML_ELEMENT_NODE) {
185                         gchar *value = NULL;
186
187                         if (g_strcmp0 (cur_node->name, "images") == 0) {
188                                 xmlNodePtr cur_image = NULL;
189                                 for (cur_image = cur_node->children; cur_image;
190                                                 cur_image = cur_image->next) {
191
192                                         MvsTmdbImage *tmdb_image = create_tmdb_image (cur_image);
193                                         image_list = g_list_append (image_list, tmdb_image);
194                                 }
195                         }
196                         else {
197                                 value = xmlNodeGetContent (cur_node);
198                                 g_object_set (movie_info, cur_node->name, value, NULL);
199                                 g_free (value);
200                         }
201                 }
202         }
203
204         mvs_tmdb_movie_set_images (movie_info, image_list);
205
206         return movie_info;
207 }
208
209 static GList*
210 generate_list (xmlNodeSetPtr node_set)
211 {
212         int i = 0;
213         GList *list = NULL;
214
215         for (i = 0; i < node_set->nodeNr; i++) {
216                 xmlNodePtr node = node_set->nodeTab[i];
217                 if (node->type == XML_ELEMENT_NODE) {
218                         MvsTmdbMovie *movie_info =
219                                         create_tmdb_movie (node->children);
220                         if (movie_info)
221                                 list = g_list_prepend (list, movie_info);
222                 }
223         }
224
225         if (list)
226                 list = g_list_reverse (list);
227
228         return list;
229 }
230
231 static GList*
232 parse_xml (const char *xml_data, goffset length)
233 {
234         GList *list = NULL;
235         xmlDocPtr document = xmlReadMemory (xml_data, length,
236                         NULL,
237                         NULL,
238                         XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
239         g_return_val_if_fail (document, NULL);
240
241         xmlXPathContextPtr context_ptr = xmlXPathNewContext (document);
242
243         xmlXPathObjectPtr xpath_obj =
244                         xmlXPathEvalExpression (TMDB_MOVIE_XPATH, context_ptr);
245
246         xmlNodeSetPtr nodeset = xpath_obj->nodesetval;
247
248         if (nodeset->nodeNr > 0) {
249                 list = generate_list (nodeset);
250         }
251
252         xmlXPathFreeObject (xpath_obj);
253         xmlXPathFreeContext (context_ptr);
254         xmlFreeDoc (document);
255
256         return list;
257 }
258
259 static GList *
260 parse_json (const char *json_data, goffset length)
261 {
262         JsonParser *parser = NULL;
263         JsonNode *root = NULL;
264         GError *error = NULL;
265         GList *list = NULL;
266
267         parser = json_parser_new ();
268
269         json_parser_load_from_data (parser, json_data, length, &error);
270         if (error)
271         {
272                 g_warning ("Unable to parse data '%s': %s\n",
273                                 json_data, error->message);
274                 g_error_free (error);
275                 g_object_unref (parser);
276                 return list;
277         }
278
279         /* Don't free */
280         root = json_parser_get_root (parser);
281         JsonArray *response = json_node_get_array (root);
282
283         /* The response is expected with the following format:
284          * [ SEARCH_TERM ,[ SEARCH_RESULT_1, SEARCH_RESULT_N]] */
285
286         if (json_array_get_length (response) != 2) {
287
288                 g_warning ("Wrong response format: %s\n", json_data);
289
290                 g_object_unref (parser);
291                 return list;
292         }
293
294         const gchar *search_term = json_array_get_string_element (response, 0);
295         g_message ("Searched for: %s\n", search_term);
296
297         JsonArray *results = json_array_get_array_element (response, 1);
298         int i;
299         int array_length = json_array_get_length (results);
300
301         for (i = 0; i < array_length; i++) {
302                 const gchar *result =
303                                 json_array_get_string_element (results, i);
304                 MvsWatcMovie *watc_movie = mvs_watc_movie_new (result);
305                 list = g_list_prepend (list, watc_movie);
306         }
307
308         g_object_unref (parser);
309
310         if (list)
311                 list = g_list_reverse (list);
312
313         return list;
314 }
315
316 static void
317 process_response_cb (SoupSession *session, SoupMessage *message,
318                     gpointer user_data)
319 {
320         MvsMInfoProvider *self = MVS_MINFO_PROVIDER (user_data);
321         const gchar *mime = NULL;
322         GList *list = NULL;
323         guint service;
324
325         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code) ||
326                         message->response_body->length <= 0) {
327
328                 g_print ("%s\n", message->reason_phrase);
329         }
330         else {
331
332                 mime = soup_message_headers_get_content_type
333                                 (message->response_headers, NULL);
334                 g_message ("Mime type: %s\n", mime);
335
336                 if (g_strcmp0 (mime, "text/xml") == 0) {
337                         list = parse_xml (message->response_body->data,
338                                         message->response_body->length);
339                         service = MVS_SERVICE_TMDB;
340                 }
341                 else if (g_strcmp0 (mime, "application/json") == 0) {
342                         list = parse_json (message->response_body->data,
343                                         message->response_body->length);
344                         service = MVS_SERVICE_WATC;
345                 }
346         }
347
348         g_signal_emit (self, signals[RESPONSE_RECEIVED], 0, service, list);
349 }
350
351 static gchar *
352 get_query_uri (MvsMInfoProvider *self, const char *query)
353 {
354         gchar *uri = NULL;
355
356         if (self->priv->service == MVS_SERVICE_TMDB) {
357                 /* METHOD/LANGUAGE/FORMAT/APIKEY/MOVIENAME */
358                 uri = g_strdup_printf (TMDB_BASE_URL, TMDB_METHOD,
359                                 TMDB_LANGUAGE,
360                                 self->priv->format,
361                                 TMDB_API_KEY,
362                                 query);
363
364         }
365         else if (self->priv->service == MVS_SERVICE_WATC) {
366                 /* WATCBASE_URL/ACTION/FORMAT/QUERY */
367                 uri = g_strdup_printf (WATC_BASE_URL,
368                                 WATC_ACTION,
369                                 WATC_FORMAT,
370                                 query);
371         }
372         else {
373                 g_warning ("Service unsupported\n");
374         }
375
376         g_message ("%s", uri);
377         return uri;
378 }
379
380 gboolean
381 mvs_minfo_provider_query (MvsMInfoProvider *self, MvsService service,
382                           const gchar *query)
383 {
384         g_return_val_if_fail (MVS_IS_MINFO_PROVIDER (self), FALSE);
385
386         self->priv->service = service;
387
388         SoupSession *session = NULL;
389         SoupMessage *message = NULL;
390         gboolean message_queued = FALSE;
391
392         gchar *uri = get_query_uri (self, query);
393
394         g_return_val_if_fail (uri, FALSE);
395
396         session = soup_session_async_new ();
397         message = soup_message_new ("GET", uri);
398
399         if (message) {
400                 soup_session_queue_message (session, message,
401                                 process_response_cb, self);
402                 message_queued = TRUE;
403         }
404
405         g_free (uri);
406
407         return message_queued;
408 }
409
410 gboolean
411 mvs_minfo_provider_set_format (MvsMInfoProvider *self,
412                                const gchar *format)
413 {
414         g_return_val_if_fail (MVS_IS_MINFO_PROVIDER (self), FALSE);
415
416         g_free (self->priv->format);
417
418         self->priv->format = g_strdup (format);
419
420         return TRUE;
421 }