Remove sqlite3 build dep.
[gmpc.git] / src / Tools / url-fetcher.c
blobc49c9afc6fcfa01ea3407ee8508386209d4e9397
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 <unistd.h>
22 #include <string.h>
23 #include <libmpd/libmpd.h>
24 #include <glib/gstdio.h>
25 #include "main.h"
26 #include "playlist3.h"
27 #include "gmpc_easy_download.h"
28 #include "gmpc-extras.h"
30 #define LOG_DOMAIN "UrlFetcher"
32 #ifdef XSPF
33 #include <xspf_c.h>
34 #endif
36 #ifdef SPIFF
37 #include <spiff/spiff_c.h>
38 #endif
40 #define MAX_PLAYLIST_SIZE 12*1024
45 typedef struct _UrlParseData
47 GList *result;
48 void (*error_callback)(const gchar *error_msg, gpointer user_data);
49 void (*result_callback)(GList *result,gpointer user_data);
50 void (*progress_callback)(gdouble progress, gpointer user_data);
51 gpointer user_data;
53 } UrlParseData;
56 /**
57 * Default callback adds to play_queue
59 static void url_parse_default_error_callback(const gchar *message, gpointer user_data)
61 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "UrlFetcher Default error callback: %s", message);
63 static void url_parse_default_callback(GList *result, gpointer user_data)
65 GList *iter;
66 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "UrlFetcher Default callback called: %i items", g_list_length(result));
67 for(iter = g_list_first(result); iter != NULL; iter = g_list_next(iter))
69 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "MPD Playlist add: %s", (const char *)iter->data);
70 mpd_playlist_add(connection, (const char *)iter->data);
74 static UrlParseData *url_parse_data_new(void)
76 UrlParseData *d = g_slice_new0(UrlParseData);
77 /* Set default callback */
78 d->result_callback = url_parse_default_callback;
79 d->error_callback = url_parse_default_error_callback;
80 return d;
82 static void url_parse_data_free(UrlParseData *data)
84 g_list_foreach(data->result, (GFunc)g_free, NULL);
85 g_list_free(data->result);
86 g_slice_free(UrlParseData, data);
89 /***
90 * Parse PLS files:
92 static GList * url_parse_pls_file(const char *data, int size)
94 int i = 0;
95 gchar **tokens = g_regex_split_simple("\n", data, G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY); // g_strsplit(data, "\n", -1);
96 GList *retv = NULL;
97 if (tokens)
99 for (i = 0; tokens[i]; i++)
101 /* Check for File */
102 if (!strncmp(tokens[i], "File", 4))
104 int del = 0;
105 /* split the string, look for delimiter = */
106 for (del = 3; tokens[i][del] != '\0' && tokens[i][del] != '='; del++) ;
107 /** if delimiter is found, and the url behind it starts with http:// add it*/
108 if (tokens[i][del] == '=' && strncmp(&tokens[i][del + 1], "http://", 7) == 0)
110 retv = g_list_prepend(retv, g_strdup(&tokens[i][del+1]));
114 g_strfreev(tokens);
116 return g_list_reverse(retv);
119 /***
120 * Parse EXTM3U Files:
122 static GList * url_parse_extm3u_file(const char *data, int size)
124 int i = 0;
125 GList *retv = NULL;
126 gchar **tokens = g_regex_split_simple("(\r\n|\n|\r)", data, G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY); // g_strsplit(data, "\n", -1);
127 if (tokens)
129 for (i = 0; tokens[i]; i++)
131 /* Check for File */
132 if (!strncmp(tokens[i], "http://", 7))
134 retv = g_list_prepend(retv, g_strdup(tokens[i]));
137 g_strfreev(tokens);
139 return g_list_reverse(retv);
142 #ifdef XSPF
143 /***
144 * parse xspf file
146 static GList *url_parse_xspf_file(const char *data, int size, const char *uri)
148 GList *retv = NULL;
149 int has_http = FALSE, has_file = FALSE;
150 struct xspf_track *strack;
151 struct xspf_mvalue *sloc;
152 struct xspf_list *slist;
153 char **handlers = mpd_server_get_url_handlers(connection);
154 int i = 0;
155 for (i = 0; handlers && handlers[i]; i++)
157 if (strcmp(handlers[i], "http://") == 0)
159 has_http = TRUE;
160 } else if (strcmp(handlers[i], "file://") == 0)
162 has_file = TRUE;
165 if (handlers)
166 g_strfreev(handlers);
168 slist = xspf_parse_memory(data, (int)size, uri);
169 if (slist != NULL)
171 XSPF_LIST_FOREACH_TRACK(slist, strack)
173 XSPF_TRACK_FOREACH_LOCATION(strack, sloc)
175 char *scheme = g_uri_parse_scheme(sloc->value);
176 if (scheme)
178 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Trying to add url: %s", sloc->value);
179 if (strcmp(scheme, "http") == 0 && has_http)
181 retv = g_list_prepend(retv, g_strdup(sloc->value));
182 } else if (strcmp(scheme, "file") == 0 && has_file)
184 retv = g_list_prepend(retv, g_strdup(sloc->value));
186 g_free(scheme);
187 } else
189 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Failed to parse scheme: %s", sloc->value);
193 xspf_free(slist);
195 return g_list_reverse(retv);
197 #else
198 #ifdef SPIFF
199 /***
200 * parse spiff file
202 static GList *url_parse_spiff_file(const char *data, int size, const gchar * uri)
204 GList *retv = NULL;
205 const gchar *tempdir = g_get_tmp_dir();
206 gchar *filename = g_build_filename(tempdir, "gmpc-temp-spiff-file", NULL);
207 if (filename)
209 GError *error = NULL;
210 int has_http = FALSE, has_file = FALSE;
211 char **handlers = mpd_server_get_url_handlers(connection);
212 int i = 0;
213 for (i = 0; handlers && handlers[i]; i++)
215 if (strcmp(handlers[i], "http://") == 0)
217 has_http = TRUE;
218 } else if (strcmp(handlers[i], "file://") == 0)
220 has_file = TRUE;
223 if (handlers)
224 g_strfreev(handlers);
226 g_file_set_contents(filename, data, (gssize) size, &error);
227 if (!error)
229 struct spiff_track *strack;
230 struct spiff_mvalue *sloc;
231 struct spiff_list *slist = spiff_parse(filename);
232 if (slist != NULL)
234 SPIFF_LIST_FOREACH_TRACK(slist, strack)
236 SPIFF_TRACK_FOREACH_LOCATION(strack, sloc)
238 char *scheme = g_uri_parse_scheme(sloc->value);
239 if (scheme)
241 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Trying to add url: %s", sloc->value);
242 if (strcmp(scheme, "http") == 0 && has_http)
244 retv = g_list_prepend(retv, g_strdup(sloc->value));
245 } else if (strcmp(scheme, "file") == 0 && has_file)
247 retv = g_list_prepend(retv, g_strdup(sloc->value));
249 g_free(scheme);
250 } else
252 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Failed to parse scheme: %s", sloc->value);
256 spiff_free(slist);
258 g_unlink(filename);
259 } else
261 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Error message: %s", error->message);
262 g_error_free(error);
265 g_free(filename);
267 return retv;
269 #endif
270 #endif
272 * Check url for correctness
274 static gboolean url_validate_url(const gchar * text)
276 int i;
277 gchar *scheme;
278 gchar **handlers = NULL;
279 /** test if text has a length */
280 if (!text || text[0] == '\0')
281 return FALSE;
282 /* Get the scheme of the url */
283 scheme = g_uri_parse_scheme(text);
284 /* If no scheme, then it is not valid */
285 if (scheme == NULL)
287 return FALSE;
289 handlers = mpd_server_get_url_handlers(connection);
290 /* iterate all entries and find matching handler */
291 for (i = 0; handlers && handlers[i]; i++)
293 if (strncasecmp(handlers[i], scheme, strlen(handlers[i] - 3)) == 0)
295 /* If we found a match, the url is valid */
296 g_free(scheme);
297 if (handlers)
298 g_strfreev(handlers);
299 return TRUE;
302 g_free(scheme);
303 if (handlers)
304 g_strfreev(handlers);
305 return FALSE;
309 * Handle user input
311 static int url_check_binary(const char *data, const int size)
313 int binary = FALSE;
314 binary = !g_utf8_validate(data, size, NULL);
315 if (binary)
316 printf("Binary data found\n");
317 return binary;
320 static GList *parse_data(const char *data, guint size, const char *text)
322 GList *urls = NULL;
323 if (url_check_binary(data, size))
325 urls = g_list_append(urls, g_strdup(text));
326 } else if (!strncasecmp(data, "<?xml", 5))
328 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Detected a xml file, might be xspf");
329 /* This might just be a xspf file */
330 #ifdef XSPF
331 urls = url_parse_xspf_file(data, size, text);
332 #else
333 #ifdef SPIFF
334 urls = url_parse_spiff_file(data, size, text);
335 #endif
336 #endif
338 /** pls file: */
339 else if (!strncasecmp(data, "[playlist]", 10))
341 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Detected a PLS\n");
342 urls = url_parse_pls_file(data, size);
344 /** Extended M3U file */
345 else if (!strncasecmp(data, "#EXTM3U", 7))
347 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Detected a Extended M3U\n");
348 urls = url_parse_extm3u_file(data, size);
350 /** Hack to detect most non-extended m3u files */
351 else if (!strncasecmp(data, "http://", 7))
353 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Might be a M3U, or generic list\n");
354 urls = url_parse_extm3u_file(data, size);
356 /** Assume Binary file */
357 else
359 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Adding url: %s\n", text);
360 urls = g_list_append(urls, g_strdup(text));
363 return urls;
366 static void url_fetcher_download_callback(const GEADAsyncHandler * handle, const GEADStatus status, gpointer data)
368 UrlParseData *upd = (UrlParseData*)data;
369 const gchar *uri = gmpc_easy_handler_get_uri(handle);
370 if (status == GEAD_DONE)
372 goffset length;
373 const char *ddata = gmpc_easy_handler_get_data(handle, &length);
374 upd->result = parse_data(ddata, (guint) length, uri);
375 upd->result_callback(upd->result, upd->user_data);
376 url_parse_data_free(upd);
377 upd = NULL;
378 } else if (status == GEAD_CANCELLED)
380 upd->result_callback(upd->result, upd->user_data);
381 url_parse_data_free(upd);
382 upd = NULL;
383 } else if (status == GEAD_PROGRESS)
385 goffset length;
386 goffset total = gmpc_easy_handler_get_content_size(handle);
387 const char *ddata = gmpc_easy_handler_get_data(handle, &length);
388 if (data)
390 if (total > 0)
392 gdouble prog = (length / (double)total);
393 if(upd->progress_callback != NULL)
394 upd->progress_callback(prog, upd->user_data);
395 } else
397 if(upd->progress_callback != NULL)
398 upd->progress_callback(-1, upd->user_data);
401 if (length > MAX_PLAYLIST_SIZE)
403 upd->result = parse_data(ddata, (guint) length, uri);
404 gmpc_easy_async_cancel(handle);
406 } else
408 upd->result_callback(upd->result, upd->user_data);
409 url_parse_data_free(upd);
410 upd = NULL;
414 /****************************************
415 * Parsing uri
418 static void parse_uri(const char *uri, UrlParseData *upd)
420 gchar *scheme;
421 g_assert(upd != NULL);
422 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Trying url: %s", uri);
423 /* Check NULL */
424 if (uri == NULL)
426 upd->result_callback(upd->result, upd->user_data);
427 url_parse_data_free(upd);
428 return;
430 /* Check local path */
431 scheme = g_uri_parse_scheme(uri);
433 if (scheme == NULL)
435 /* local uri */
436 if (g_file_test(uri, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
438 FILE *fp = g_fopen(uri, "r");
439 if (fp)
441 char buffer[MAX_PLAYLIST_SIZE];
442 ssize_t t = fread(buffer, 1, MAX_PLAYLIST_SIZE - 1, fp);
443 /* Make sure it is NULL terminated */
444 buffer[t] = '\0';
445 upd->result = parse_data(buffer, (guint) t, uri);
446 upd->result_callback(upd->result, upd->user_data);
447 url_parse_data_free(upd);
449 fclose(fp);
451 } else
453 gchar *temp = g_strdup_printf("%s: '%s'", _("Failed to open local file"), uri);
454 if(upd->error_callback != NULL)
455 upd->error_callback(temp, upd->user_data);
456 url_parse_data_free(upd);
457 g_free(temp);
460 else
462 /* remote uri */
463 if (url_validate_url(uri))
465 if (strcasecmp(scheme, "http") == 0)
467 gmpc_easy_async_downloader(uri, url_fetcher_download_callback, upd);
468 } else
470 upd->result = g_list_append(NULL, g_strdup(uri));
471 upd->result_callback(upd->result, upd->user_data);
472 url_parse_data_free(upd);
474 } else
476 gchar *temp = g_strdup_printf("%s: '%s'", _("Uri scheme not supported"), scheme);
477 if(upd->error_callback != NULL)
478 upd->error_callback(temp, upd->user_data);
479 url_parse_data_free(upd);
480 g_free(temp);
483 if (scheme)
484 g_free(scheme);
485 /* Dialog needs to be kept running */
486 return;
488 static void gufg_set_progress(gdouble prog, gpointer a)
490 gmpc_url_fetching_gui_set_progress(GMPC_URL_FETCHING_GUI(a), prog);
492 static void gufg_set_error(const gchar *error_msg, gpointer a)
494 gmpc_url_fetching_gui_set_error(GMPC_URL_FETCHING_GUI(a), error_msg);
496 static void gufg_set_result(GList *result, gpointer a)
498 url_parse_default_callback(result, a);
499 gmpc_url_fetching_gui_set_completed(GMPC_URL_FETCHING_GUI(a));
501 static void gufg_parse_callback(GmpcUrlFetchingGui * a, const gchar * url, void *user_data)
503 if(url != NULL)
505 UrlParseData *data = url_parse_data_new();
506 gmpc_url_fetching_gui_set_processing(a);
507 data->user_data = a;
508 data->progress_callback = gufg_set_progress;
509 data->error_callback = gufg_set_error;
510 data->result_callback = gufg_set_result;
512 parse_uri(url, data);
516 static gboolean gufg_validate_callback(GmpcUrlFetchingGui * a, const gchar * url, void *user_data)
518 return (strlen(url) > 0 && (G_IS_DIR_SEPARATOR(url[0]) || url_validate_url(url)));
521 static gboolean gufg_validate_callback_0160(GmpcUrlFetchingGui * a, const gchar * url, void *user_data)
523 return TRUE;
526 static void gufg_parse_callback_0160(GmpcUrlFetchingGui * a, const gchar * url, void *user_data)
528 if(url != NULL)
530 if (mpd_playlist_load(connection, url) == MPD_PLAYLIST_LOAD_FAILED)
532 gufg_parse_callback(a, url, user_data);
533 return;
536 gmpc_url_fetching_gui_set_completed(a);
539 void url_start_easy_command(void *data,char *param, void *d )
541 g_debug("Url easy command received: %s\n", param);
542 url_start_real(param);
544 void url_start(void)
546 if (mpd_server_check_version(connection, 0, 16, 0))
548 gmpc_url_fetching_gui_new(gufg_parse_callback_0160, NULL, gufg_validate_callback_0160, NULL, g_object_unref);
549 } else
551 gmpc_url_fetching_gui_new(gufg_parse_callback, NULL, gufg_validate_callback, NULL, g_object_unref);
555 void url_start_real(const gchar * url)
557 if (mpd_server_check_version(connection, 0, 16, 0))
559 printf("add url2: '%s'\n", url);
560 if (mpd_playlist_load(connection, url) == MPD_PLAYLIST_LOAD_FAILED)
562 UrlParseData *upd = url_parse_data_new();
563 parse_uri(url, upd);
564 return;
566 } else
568 UrlParseData *upd = url_parse_data_new();
569 parse_uri(url, upd);
573 void url_start_custom(const gchar *url,
574 void (*error_callback)(const gchar *error_msg, gpointer user_data),
575 void (*result_callback)(GList *result,gpointer user_data),
576 void (*progress_callback)(gdouble progress, gpointer user_data),
577 gpointer user_data)
579 UrlParseData *data = url_parse_data_new();
580 data->user_data = user_data;
581 data->progress_callback = progress_callback;
582 data->error_callback = error_callback;
583 data->result_callback = result_callback;
585 parse_uri(url, data);
589 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */