Remove sqlite3 build dep.
[gmpc.git] / src / Tools / gmpc_easy_download.c
blob0b18f19847340f229a16a0e810936b84747dce61
1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <glib.h>
24 #include <libsoup/soup.h>
25 #include <zlib.h>
26 #include "gmpc_easy_download.h"
27 #include "main.h"
29 #define LOG_DOMAIN "EasyDownload"
30 /****
31 * ZIP MISC
32 * **********/
33 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
34 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
35 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
36 #define COMMENT 0x10 /* bit 4 set: file comment present */
37 static char gz_magic[2] = { 0x1f, 0x8b };
39 static int skip_gzip_header(const char *src, gsize size)
41 int idx;
42 if (size < 10 || memcmp(src, gz_magic, 2))
43 return -1;
44 if (src[2] != Z_DEFLATED)
46 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING,
47 "unsupported compression method (%d).\n", (int)src[3]);
48 return -1;
50 idx = 10;
51 /* skip past header, mtime and xos */
53 if (src[3] & EXTRA_FIELD)
54 idx += src[idx] + (src[idx + 1] << 8) + 2;
55 if (src[3] & ORIG_NAME)
57 while (src[idx])
58 idx++;
59 idx++;
61 if (src[3] & COMMENT)
63 while (src[idx])
64 idx++;
65 idx++;
67 if (src[3] & HEAD_CRC)
68 idx += 2;
69 return idx;
72 static int read_cb(void *z, char *buffer, int size)
74 int r = 0;
75 z_stream *zs = z;
76 if (zs)
78 zs->next_out = (void *)buffer;
79 zs->avail_out = size;
80 r = inflate(zs, Z_SYNC_FLUSH);
81 if (r == Z_OK || r == Z_STREAM_END || r == Z_NEED_DICT || r == Z_BUF_ERROR)
83 return size - zs->avail_out;
86 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "failed unzipping stream: %i\n", r);
87 return -1;
90 static int close_cb(void *z)
92 z_stream *zs = z;
93 inflateEnd(zs);
94 g_free(zs);
95 return 0;
98 static SoupSession *soup_session = NULL;
100 static void gmpc_easy_download_set_proxy(SoupSession * session)
102 if (session == NULL)
103 return;
105 if (cfg_get_single_value_as_int_with_default(config, "Network Settings", "Use Proxy", FALSE))
107 char *value = cfg_get_single_value_as_string(config, "Network Settings", "Proxy Address");
108 char *username = NULL;
109 char *password = NULL;
110 gint port = cfg_get_single_value_as_int_with_default(config, "Network Settings", "Proxy Port", 8080);
111 if (cfg_get_single_value_as_int_with_default(config, "Network Settings", "Use authentication", FALSE))
113 password = cfg_get_single_value_as_string(config, "Network Settings", "Proxy authentication password");
114 username = cfg_get_single_value_as_string(config, "Network Settings", "Proxy authentication username");
116 if (value)
118 SoupURI *uri = NULL;
119 gchar *ppath = NULL;
120 if (username && username[0] != '\0' && password && password[0] != '\0')
122 gchar *usere = gmpc_easy_download_uri_escape(username);
123 gchar *passe = gmpc_easy_download_uri_escape(password);
124 ppath = g_strdup_printf("http://%s:%s@%s:%i", usere, passe, value, port);
125 g_free(usere);
126 g_free(passe);
127 } else if (username && username[0] != '\0')
129 gchar *usere = gmpc_easy_download_uri_escape(username);
130 ppath = g_strdup_printf("http://%s@%s:%i", usere, value, port);
131 g_free(usere);
132 } else
134 ppath = g_strdup_printf("http://%s:%i", value, port);
136 uri = soup_uri_new(ppath);
137 g_object_set(G_OBJECT(session), SOUP_SESSION_PROXY_URI, uri, NULL);
138 soup_uri_free(uri);
139 g_free(ppath);
141 g_free(username);
142 g_free(password);
143 g_free(value);
144 } else
146 g_object_set(G_OBJECT(session), SOUP_SESSION_PROXY_URI, NULL, NULL);
150 /***
151 * preferences window
153 /* for gtkbuilder */
154 void proxy_pref_use_proxy_toggled(GtkWidget * toggle_button);
155 void proxy_pref_http_address_changed(GtkWidget * entry);
156 void proxy_pref_http_port_changed(GtkWidget * entry);
157 void proxy_pref_use_auth_toggled(GtkWidget * toggle_button);
158 void proxy_pref_auth_username_changed(GtkWidget * entry);
159 void proxy_pref_auth_password_changed(GtkWidget * entry);
161 static GtkBuilder *proxy_pref_xml = NULL;
162 static void proxy_pref_destroy(GtkWidget * container)
164 GObject *temp = gtk_builder_get_object(proxy_pref_xml, "frame_proxy_settings");
165 gtk_container_remove(GTK_CONTAINER(container), GTK_WIDGET(temp));
166 g_object_unref(proxy_pref_xml);
167 proxy_pref_xml = NULL;
170 void proxy_pref_use_proxy_toggled(GtkWidget * toggle_button)
172 cfg_set_single_value_as_int(config, "Network Settings", "Use Proxy",
173 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button)));
174 gmpc_easy_download_set_proxy(soup_session);
177 void proxy_pref_http_address_changed(GtkWidget * entry)
179 cfg_set_single_value_as_string(config, "Network Settings", "Proxy Address",
180 (char *)gtk_entry_get_text(GTK_ENTRY(entry)));
181 gmpc_easy_download_set_proxy(soup_session);
184 void proxy_pref_http_port_changed(GtkWidget * entry)
186 cfg_set_single_value_as_int(config, "Network Settings", "Proxy Port",
187 gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(entry)));
188 gmpc_easy_download_set_proxy(soup_session);
191 void proxy_pref_use_auth_toggled(GtkWidget * toggle_button)
193 cfg_set_single_value_as_int(config, "Network Settings", "Use authentication",
194 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button)));
195 gmpc_easy_download_set_proxy(soup_session);
198 void proxy_pref_auth_username_changed(GtkWidget * entry)
200 cfg_set_single_value_as_string(config, "Network Settings", "Proxy authentication username",
201 (char *)gtk_entry_get_text(GTK_ENTRY(entry)));
202 gmpc_easy_download_set_proxy(soup_session);
205 void proxy_pref_auth_password_changed(GtkWidget * entry)
207 cfg_set_single_value_as_string(config, "Network Settings", "Proxy authentication password",
208 gtk_entry_get_text(GTK_ENTRY(entry)));
209 gmpc_easy_download_set_proxy(soup_session);
212 static void proxy_pref_construct(GtkWidget * container)
214 GObject *temp = NULL;
215 gchar *string;
216 GError *error = NULL;
217 gchar *path = gmpc_get_full_glade_path("preferences-proxy.ui");
218 proxy_pref_xml = gtk_builder_new();
219 gtk_builder_add_from_file(proxy_pref_xml, path, &error);
221 if (error)
223 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to load %s: '%s'", path, error->message);
224 g_error_free(error);
225 g_free(path);
227 return;
230 q_free(path);
231 /* use proxy */
232 temp = gtk_builder_get_object(proxy_pref_xml, "checkbutton_use_proxy");
233 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(temp),
234 cfg_get_single_value_as_int_with_default(config, "Network Settings", "Use Proxy",
235 FALSE));
237 /* hostname */
238 temp = gtk_builder_get_object(proxy_pref_xml, "entry_http_hostname");
239 string = cfg_get_single_value_as_string(config, "Network Settings", "Proxy Address");
240 if (string)
242 gtk_entry_set_text(GTK_ENTRY(temp), string);
243 g_free(string);
246 /* port */
247 temp = gtk_builder_get_object(proxy_pref_xml, "spinbutton_http_port");
248 gtk_spin_button_set_value(GTK_SPIN_BUTTON(temp),
249 cfg_get_single_value_as_int_with_default(config, "Network Settings", "Proxy Port", 8080));
251 /* use auth */
252 temp = gtk_builder_get_object(proxy_pref_xml, "checkbutton_use_auth");
253 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(temp),
254 cfg_get_single_value_as_int_with_default(config, "Network Settings",
255 "Use authentication", FALSE));
257 /* username */
258 temp = gtk_builder_get_object(proxy_pref_xml, "entry_auth_username");
259 string = cfg_get_single_value_as_string(config, "Network Settings", "Proxy authentication username");
260 if (string)
262 gtk_entry_set_text(GTK_ENTRY(temp), string);
263 g_free(string);
266 /* username */
267 temp = gtk_builder_get_object(proxy_pref_xml, "entry_auth_password");
268 string = cfg_get_single_value_as_string(config, "Network Settings", "Proxy authentication password");
269 if (string)
271 gtk_entry_set_text(GTK_ENTRY(temp), string);
272 g_free(string);
275 /* signal autoconnect */
276 gtk_builder_connect_signals(proxy_pref_xml, NULL);
277 /* Add to parent */
278 temp = gtk_builder_get_object(proxy_pref_xml, "frame_proxy_settings");
279 gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(temp));
280 gtk_widget_show_all(container);
283 gmpcPrefPlugin proxyplug_pref = {
284 .construct = proxy_pref_construct,
285 .destroy = proxy_pref_destroy
288 gmpcPlugin proxyplug = {
289 .name = N_("Proxy"),
290 .version = {0, 0, 0}
292 .plugin_type = GMPC_INTERNALL,
293 .pref = &proxyplug_pref
297 * LIBSOUP BASED ASYNC DOWNLOADER
300 static void gmpc_easy_async_free_handler_real(GEADAsyncHandler * handle);
301 typedef struct
303 SoupMessage *msg;
304 gchar *uri;
305 GEADAsyncCallback callback;
306 gpointer userdata;
307 gchar *data;
308 goffset length;
309 int is_gzip;
310 int is_deflate;
311 z_stream *z;
312 gpointer extra_data;
313 guint uid;
314 guint old_status_code;
315 } _GEADAsyncHandler;
317 static guint uid = 0;
318 static void gmpc_easy_async_headers_update(SoupMessage * msg, gpointer data)
320 _GEADAsyncHandler *d = data;
321 const gchar *encoding = soup_message_headers_get(msg->response_headers, "Content-Encoding");
322 goffset size = soup_message_headers_get_content_length(msg->response_headers);
323 g_log("EasyDownload", G_LOG_LEVEL_DEBUG,
324 "Expected download length: %u",(guint)size);
325 if (encoding)
327 if (strcmp(encoding, "gzip") == 0)
329 d->is_gzip = 1;
330 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Url is gzipped");
331 } else if (strcmp(encoding, "deflate") == 0)
333 d->is_deflate = 1;
334 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Url is enflated");
338 /* If a second set comes in, close that */
339 else
341 d->is_gzip = 0;
342 d->is_deflate = 0;
346 * Don't record data from redirection, in a while it _will_ be redirected,
347 * We care about that
349 if (d->old_status_code != 0 && d->old_status_code != msg->status_code)
351 g_log("EasyDownload", G_LOG_LEVEL_DEBUG,
352 "Cleaning out the previous block of data: status_code: %i(old) ->%i(new)",
353 d->old_status_code, msg->status_code);
354 /* Clear buffer */
355 g_free(d->data);
356 d->data = NULL;
357 d->length = 0;
358 if (d->z)
359 close_cb(d->z);
360 d->z = NULL;
362 d->old_status_code = msg->status_code;
365 static void gmpc_easy_async_status_update(SoupMessage * msg, SoupBuffer * buffer, gpointer data)
367 _GEADAsyncHandler *d = data;
368 /* don't store error data, not used anyway */
369 if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code))
371 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
372 "Error mesg status code: %i\n", msg->status_code);
373 return;
375 if (d->is_gzip || d->is_deflate)
377 if (d->z == NULL)
379 long data_start;
380 d->z = g_malloc0(sizeof(*d->z));
381 data_start = (d->is_gzip == 1) ? skip_gzip_header(buffer->data, buffer->length) : 0;
382 d->z->next_in = (void *)((buffer->data) + data_start);
383 d->z->avail_in = buffer->length - data_start;
384 d->z->zalloc = NULL;
385 d->z->zfree = NULL;
386 d->z->opaque = NULL;
387 if (inflateInit2(d->z, -MAX_WBITS) == Z_OK)
389 int res = 0;
392 d->data = g_realloc(d->data, d->length + 12 * 1024 + 1);
393 res = read_cb(d->z, &(d->data[d->length]), 12 * 1024);
395 if (res > 0)
396 d->length += res;
398 d->data[d->length] = '\0';
399 } while (res > 0);
400 if (res < 0)
402 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failure during unzipping 1: %s", d->uri);
403 soup_session_cancel_message(soup_session, d->msg, SOUP_STATUS_MALFORMED);
405 } else
407 /* give error */
408 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failure during inflateInit2: %s", d->uri);
409 soup_session_cancel_message(soup_session, d->msg, SOUP_STATUS_MALFORMED);
411 } else
413 int res = 0;
414 d->z->next_in = (void *)((buffer->data));
415 d->z->avail_in = buffer->length;
418 d->data = g_realloc(d->data, d->length + 12 * 1024 + 1);
419 res = read_cb(d->z, &(d->data[d->length]), 12 * 1024);
421 if (res > 0)
422 d->length += res;
424 d->data[d->length] = '\0';
425 } while (res > 0);
426 if (res < 0)
428 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failure during unzipping 2: %s", d->uri);
429 soup_session_cancel_message(soup_session, d->msg, SOUP_STATUS_MALFORMED);
433 } else
435 d->data = g_realloc(d->data, d->length + buffer->length + 1);
436 g_memmove(&(d->data[d->length]), buffer->data, (size_t) buffer->length);
437 d->length += buffer->length;
438 d->data[d->length] = '\0';
440 d->callback((GEADAsyncHandler *) d, GEAD_PROGRESS, d->userdata);
443 static void gmpc_easy_async_callback(SoupSession * session, SoupMessage * msg, gpointer data)
445 _GEADAsyncHandler *d = data;
446 if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code))
448 d->callback((GEADAsyncHandler *) d, GEAD_DONE, d->userdata);
449 } else if (msg->status_code == SOUP_STATUS_CANCELLED)
451 d->callback((GEADAsyncHandler *) d, GEAD_CANCELLED, d->userdata);
452 } else
454 d->callback((GEADAsyncHandler *) d, GEAD_FAILED, d->userdata);
456 gmpc_easy_async_free_handler_real((GEADAsyncHandler *) d);
459 static void gmpc_easy_async_free_handler_real(GEADAsyncHandler * handle)
461 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
462 if (d->z)
463 close_cb(d->z);
464 g_free(d->uri);
465 g_free(d->data);
466 g_free(d);
470 * Get the total size of the download, if available.
472 goffset gmpc_easy_handler_get_content_size(const GEADAsyncHandler * handle)
474 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
475 return soup_message_headers_get_content_length(d->msg->response_headers);
478 const char *gmpc_easy_handler_get_uri(const GEADAsyncHandler * handle)
480 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
481 return d->uri;
484 const char *gmpc_easy_handler_get_data(const GEADAsyncHandler * handle, goffset * length)
486 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
487 if (length)
488 *length = d->length;
489 return d->data;
492 const guchar *gmpc_easy_handler_get_data_vala_wrap(const GEADAsyncHandler * handle, gint * length)
494 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
495 if (length)
496 *length = (gint) d->length;
497 return (guchar *) d->data;
499 const char *gmpc_easy_handler_get_data_as_string(const GEADAsyncHandler * handle)
501 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
502 g_assert(d->data[d->length] == '\0');
503 return (gchar *) d->data;
505 void gmpc_easy_handler_set_user_data(const GEADAsyncHandler * handle, gpointer user_data)
507 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
508 d->extra_data = user_data;
511 gpointer gmpc_easy_handler_get_user_data(const GEADAsyncHandler * handle)
513 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
514 return d->extra_data;
517 void gmpc_easy_async_cancel(const GEADAsyncHandler * handle)
519 _GEADAsyncHandler *d = (_GEADAsyncHandler *) handle;
520 soup_session_cancel_message(soup_session, d->msg, SOUP_STATUS_CANCELLED);
523 GEADAsyncHandler *gmpc_easy_async_downloader(const gchar * uri, GEADAsyncCallback callback, gpointer user_data)
525 if (uri == NULL)
527 g_log(LOG_DOMAIN,G_LOG_LEVEL_WARNING, "No download uri specified.");
528 return NULL;
530 return gmpc_easy_async_downloader_with_headers(uri, callback, user_data, NULL);
533 GEADAsyncHandler *gmpc_easy_async_downloader_with_headers(const gchar * uri, GEADAsyncCallback callback,
534 gpointer user_data, ...)
536 SoupMessage *msg;
537 _GEADAsyncHandler *d;
538 va_list ap;
539 char *va_entry;
540 if (soup_session == NULL)
542 soup_session = soup_session_async_new();
543 gmpc_easy_download_set_proxy(soup_session);
544 g_object_set(soup_session, "timeout", 5, NULL);
545 /* Set user agent, to get around wikipedia ban. */
546 g_object_set(soup_session, "user-agent", "gmpc ",NULL);
549 msg = soup_message_new("GET", uri);
550 if (!msg)
551 return NULL;
553 soup_message_headers_append(msg->request_headers, "Accept-Encoding", "deflate,gzip");
554 va_start(ap, user_data);
555 va_entry = va_arg(ap, typeof(va_entry));
556 while (va_entry)
558 char *value = va_arg(ap, typeof(value));
559 soup_message_headers_append(msg->request_headers, va_entry, value);
560 va_entry = va_arg(ap, typeof(va_entry));
562 va_end(ap);
564 d = g_malloc0(sizeof(*d));
565 d->uid = ++uid;
566 d->is_gzip = 0;
567 d->is_deflate = 0;
568 d->z = NULL;
569 d->data = NULL;
570 d->msg = msg;
571 d->uri = g_strdup(uri);
572 d->callback = callback;
573 d->userdata = user_data;
574 d->extra_data = NULL;
575 d->old_status_code = 0;
576 soup_message_body_set_accumulate(msg->response_body, FALSE);
577 g_signal_connect_after(msg, "got-chunk", G_CALLBACK(gmpc_easy_async_status_update), d);
578 g_signal_connect_after(msg, "got-headers", G_CALLBACK(gmpc_easy_async_headers_update), d);
579 soup_session_queue_message(soup_session, msg, gmpc_easy_async_callback, d);
581 return (GEADAsyncHandler *) d;
584 void gmpc_easy_async_quit(void)
586 if (soup_session)
588 soup_session_abort(soup_session);
589 g_object_unref(soup_session);
590 soup_session = NULL;
594 char *gmpc_easy_download_uri_escape(const char *part)
596 return soup_uri_encode(part, "&+");
599 /**************************************************************/
602 typedef struct {
603 void *a;
604 void *b;
605 GEADAsyncCallbackVala callback;
606 } valaf;
608 static void temp_callback(const GEADAsyncHandler *handle, GEADStatus status, gpointer user_data)
610 valaf *f = (valaf*)user_data;
611 f->callback(handle, status, f->b, f->a);
612 if(status != GEAD_PROGRESS) g_free(f);
614 GEADAsyncHandler * gmpc_easy_async_downloader_vala(const char *path, gpointer user_data2, GEADAsyncCallbackVala callback,
615 gpointer user_data
618 valaf *f = g_malloc0(sizeof(*f));
619 f->a = user_data;
620 f->b =user_data2;
621 f->callback = callback;
622 return gmpc_easy_async_downloader(path, temp_callback, f);
626 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */