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