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.
23 #include <libmpd/libmpd.h>
24 #include <glib/gstdio.h>
26 #include "playlist3.h"
27 #include "gmpc_easy_download.h"
28 #include "gmpc-extras.h"
30 #define LOG_DOMAIN "UrlFetcher"
37 #include <spiff/spiff_c.h>
40 #define MAX_PLAYLIST_SIZE 12*1024
45 typedef struct _UrlParseData
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
);
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
)
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
;
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
);
92 static GList
* url_parse_pls_file(const char *data
, int size
)
95 gchar
**tokens
= g_regex_split_simple("\n", data
, G_REGEX_MULTILINE
, G_REGEX_MATCH_NEWLINE_ANY
); // g_strsplit(data, "\n", -1);
99 for (i
= 0; tokens
[i
]; i
++)
102 if (!strncmp(tokens
[i
], "File", 4))
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]));
116 return g_list_reverse(retv
);
120 * Parse EXTM3U Files:
122 static GList
* url_parse_extm3u_file(const char *data
, int size
)
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);
129 for (i
= 0; tokens
[i
]; i
++)
132 if (!strncmp(tokens
[i
], "http://", 7))
134 retv
= g_list_prepend(retv
, g_strdup(tokens
[i
]));
139 return g_list_reverse(retv
);
146 static GList
*url_parse_xspf_file(const char *data
, int size
, const char *uri
)
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
);
155 for (i
= 0; handlers
&& handlers
[i
]; i
++)
157 if (strcmp(handlers
[i
], "http://") == 0)
160 } else if (strcmp(handlers
[i
], "file://") == 0)
166 g_strfreev(handlers
);
168 slist
= xspf_parse_memory(data
, (int)size
, uri
);
171 XSPF_LIST_FOREACH_TRACK(slist
, strack
)
173 XSPF_TRACK_FOREACH_LOCATION(strack
, sloc
)
175 char *scheme
= g_uri_parse_scheme(sloc
->value
);
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
));
189 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Failed to parse scheme: %s", sloc
->value
);
195 return g_list_reverse(retv
);
202 static GList
*url_parse_spiff_file(const char *data
, int size
, const gchar
* uri
)
205 const gchar
*tempdir
= g_get_tmp_dir();
206 gchar
*filename
= g_build_filename(tempdir
, "gmpc-temp-spiff-file", NULL
);
209 GError
*error
= NULL
;
210 int has_http
= FALSE
, has_file
= FALSE
;
211 char **handlers
= mpd_server_get_url_handlers(connection
);
213 for (i
= 0; handlers
&& handlers
[i
]; i
++)
215 if (strcmp(handlers
[i
], "http://") == 0)
218 } else if (strcmp(handlers
[i
], "file://") == 0)
224 g_strfreev(handlers
);
226 g_file_set_contents(filename
, data
, (gssize
) size
, &error
);
229 struct spiff_track
*strack
;
230 struct spiff_mvalue
*sloc
;
231 struct spiff_list
*slist
= spiff_parse(filename
);
234 SPIFF_LIST_FOREACH_TRACK(slist
, strack
)
236 SPIFF_TRACK_FOREACH_LOCATION(strack
, sloc
)
238 char *scheme
= g_uri_parse_scheme(sloc
->value
);
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
));
252 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Failed to parse scheme: %s", sloc
->value
);
261 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Error message: %s", error
->message
);
272 * Check url for correctness
274 static gboolean
url_validate_url(const gchar
* text
)
278 gchar
**handlers
= NULL
;
279 /** test if text has a length */
280 if (!text
|| text
[0] == '\0')
282 /* Get the scheme of the url */
283 scheme
= g_uri_parse_scheme(text
);
284 /* If no scheme, then it is not valid */
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 */
298 g_strfreev(handlers
);
304 g_strfreev(handlers
);
311 static int url_check_binary(const char *data
, const int size
)
314 binary
= !g_utf8_validate(data
, size
, NULL
);
316 printf("Binary data found\n");
320 static GList
*parse_data(const char *data
, guint size
, const char *text
)
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 */
331 urls
= url_parse_xspf_file(data
, size
, text
);
334 urls
= url_parse_spiff_file(data
, size
, text
);
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 */
359 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Adding url: %s\n", text
);
360 urls
= g_list_append(urls
, g_strdup(text
));
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
)
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
);
378 } else if (status
== GEAD_CANCELLED
)
380 upd
->result_callback(upd
->result
, upd
->user_data
);
381 url_parse_data_free(upd
);
383 } else if (status
== GEAD_PROGRESS
)
386 goffset total
= gmpc_easy_handler_get_content_size(handle
);
387 const char *ddata
= gmpc_easy_handler_get_data(handle
, &length
);
392 gdouble prog
= (length
/ (double)total
);
393 if(upd
->progress_callback
!= NULL
)
394 upd
->progress_callback(prog
, upd
->user_data
);
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
);
408 upd
->result_callback(upd
->result
, upd
->user_data
);
409 url_parse_data_free(upd
);
414 /****************************************
418 static void parse_uri(const char *uri
, UrlParseData
*upd
)
421 g_assert(upd
!= NULL
);
422 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Trying url: %s", uri
);
426 upd
->result_callback(upd
->result
, upd
->user_data
);
427 url_parse_data_free(upd
);
430 /* Check local path */
431 scheme
= g_uri_parse_scheme(uri
);
436 if (g_file_test(uri
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_REGULAR
))
438 FILE *fp
= g_fopen(uri
, "r");
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 */
445 upd
->result
= parse_data(buffer
, (guint
) t
, uri
);
446 upd
->result_callback(upd
->result
, upd
->user_data
);
447 url_parse_data_free(upd
);
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
);
463 if (url_validate_url(uri
))
465 if (strcasecmp(scheme
, "http") == 0)
467 gmpc_easy_async_downloader(uri
, url_fetcher_download_callback
, upd
);
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
);
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
);
485 /* Dialog needs to be kept running */
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
)
505 UrlParseData
*data
= url_parse_data_new();
506 gmpc_url_fetching_gui_set_processing(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
)
526 static void gufg_parse_callback_0160(GmpcUrlFetchingGui
* a
, const gchar
* url
, void *user_data
)
530 if (mpd_playlist_load(connection
, url
) == MPD_PLAYLIST_LOAD_FAILED
)
532 gufg_parse_callback(a
, url
, user_data
);
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
);
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
);
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();
568 UrlParseData
*upd
= url_parse_data_new();
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
),
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: */