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