Mercurial > audlegacy-plugins
view src/streambrowser/streambrowser.c @ 3132:f8686c591e49
neon: More gracefully handle the NEON_READER_ERROR condition.
author | William Pitcock <nenolod@atheme.org> |
---|---|
date | Thu, 07 May 2009 00:30:11 -0500 |
parents | 3134a0987162 |
children |
line wrap: on
line source
/* * Audacious Streambrowser Plugin * * Copyright (c) 2008 Calin Crisan <ccrisan@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses>. */ #include <stdlib.h> #include <gtk/gtk.h> #include <glib.h> #include <audlegacy/plugin.h> #include <audlegacy/ui_plugin_menu.h> #include "streambrowser.h" #include "streamdir.h" #include "shoutcast.h" #include "xiph.h" #include "bookmarks.h" #include "gui/streambrowser_win.h" typedef struct { gboolean debug; bookmark_t *bookmarks; int bookmarks_count; } streambrowser_cfg_t; typedef struct { streamdir_t *streamdir; category_t *category; streaminfo_t *streaminfo; gboolean add_to_playlist; } update_thread_data_t; static void sb_init(); static void sb_about(); static void sb_configure(); static void sb_cleanup(); static void gui_init(); static void gui_done(); static void streamdir_update(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist); static gpointer update_thread_core(gpointer user_data); static void streaminfo_add_to_playlist(streaminfo_t *streaminfo); static void on_plugin_services_menu_item_click(); static GtkWidget *playlist_menu_item; static GtkWidget *main_menu_item; static GQueue *update_thread_data_queue = NULL; static GMutex *update_thread_mutex = NULL; streambrowser_cfg_t streambrowser_cfg; static GeneralPlugin sb_plugin = { .description = "Stream Browser", .init = sb_init, .about = sb_about, .configure = sb_configure, .cleanup = sb_cleanup }; GeneralPlugin *sb_gplist[] = { &sb_plugin, NULL }; SIMPLE_GENERAL_PLUGIN(streambrowser, sb_gplist); void debug(const char *fmt, ...) { if (streambrowser_cfg.debug) { va_list ap; fprintf(stderr, "* streambrowser: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } } void failure(const char *fmt, ...) { va_list ap; fprintf(stderr, "! streambrowser: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } gboolean fetch_remote_to_local_file(gchar *remote_url, gchar *local_url) { VFSFile *remote_file = aud_vfs_fopen(remote_url, "r"); if (remote_file == NULL) { failure("failed to fetch file '%s'\n", remote_url); return FALSE; } VFSFile *local_file = aud_vfs_fopen(local_url, "w"); if (local_file == NULL) { aud_vfs_fclose(remote_file); failure("failed to create local file '%s'\n", local_file); return FALSE; } unsigned char buff[DEF_BUFFER_SIZE]; int size; while (!aud_vfs_feof(remote_file)) { size = aud_vfs_fread(buff, 1, DEF_BUFFER_SIZE, remote_file); // i don't know why aud_vfs_feof() doesn't ever return TRUE // so this is a workaround to properly end the loop if (size == 0) break; size = aud_vfs_fwrite(buff, 1, size, local_file); if (size == 0) { aud_vfs_fclose(local_file); aud_vfs_fclose(remote_file); failure("failed to write to local file '%s'\n", local_file); return FALSE; } } aud_vfs_fclose(local_file); aud_vfs_fclose(remote_file); return TRUE; } void config_load() { streambrowser_cfg.debug = FALSE; streambrowser_cfg.bookmarks = NULL; streambrowser_cfg.bookmarks_count = 0; mcs_handle_t *db; if ((db = aud_cfg_db_open()) == NULL) { failure("failed to load configuration\n"); return; } aud_cfg_db_get_bool(db, "streambrowser", "debug", &streambrowser_cfg.debug); aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &streambrowser_cfg.bookmarks_count); debug("debug = %d\n", streambrowser_cfg.debug); if (streambrowser_cfg.bookmarks_count == 0) streambrowser_cfg.bookmarks = NULL; else streambrowser_cfg.bookmarks = g_malloc(sizeof(bookmark_t) * streambrowser_cfg.bookmarks_count); int i; gchar item[DEF_STRING_LEN]; gchar *value; for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) { g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i); if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) { strncpy(streambrowser_cfg.bookmarks[i].streamdir_name, value, DEF_STRING_LEN); g_free(value); } else streambrowser_cfg.bookmarks[i].streamdir_name[0] = '\0'; g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i); if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) { strncpy(streambrowser_cfg.bookmarks[i].name, value, DEF_STRING_LEN); g_free(value); } else streambrowser_cfg.bookmarks[i].name[0] = '\0'; g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i); if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) { strncpy(streambrowser_cfg.bookmarks[i].playlist_url, value, DEF_STRING_LEN); g_free(value); } else streambrowser_cfg.bookmarks[i].playlist_url[0] = '\0'; g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i); if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) { strncpy(streambrowser_cfg.bookmarks[i].url, value, DEF_STRING_LEN); g_free(value); } else streambrowser_cfg.bookmarks[i].url[0] = '\0'; debug("loaded a bookmark with streamdir_name = '%s', name = '%s', playlist_url = '%s', url = '%s'\n", streambrowser_cfg.bookmarks[i].streamdir_name, streambrowser_cfg.bookmarks[i].name, streambrowser_cfg.bookmarks[i].playlist_url, streambrowser_cfg.bookmarks[i].url); } debug("loaded %d bookmarks\n", streambrowser_cfg.bookmarks_count); aud_cfg_db_close(db); debug("configuration loaded\n"); } void config_save() { mcs_handle_t *db; if ((db = aud_cfg_db_open()) == NULL) { failure("failed to save configuration\n"); return; } aud_cfg_db_set_bool(db, "streambrowser", "debug", streambrowser_cfg.debug); int old_bookmarks_count, i; gchar item[DEF_STRING_LEN]; aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &old_bookmarks_count); aud_cfg_db_set_int(db, "streambrowser", "bookmarks_count", streambrowser_cfg.bookmarks_count); for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) { debug("saving bookmark with streamdir_name = '%s', name = '%s', playlist_url = '%s', url = '%s'\n", streambrowser_cfg.bookmarks[i].streamdir_name, streambrowser_cfg.bookmarks[i].name, streambrowser_cfg.bookmarks[i].playlist_url, streambrowser_cfg.bookmarks[i].url); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i); aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].streamdir_name); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i); aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].name); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i); aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].playlist_url); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i); aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].url); } for (i = streambrowser_cfg.bookmarks_count; i < old_bookmarks_count; i++) { g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i); aud_cfg_db_unset_key(db, "streambrowser", item); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i); aud_cfg_db_unset_key(db, "streambrowser", item); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i); aud_cfg_db_unset_key(db, "streambrowser", item); g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i); aud_cfg_db_unset_key(db, "streambrowser", item); } aud_cfg_db_close(db); debug("configuration saved\n"); } gboolean mystrcasestr(const char *haystack, const char *needle) { int len_h = strlen(haystack) + 1; int len_n = strlen(needle) + 1; int i; char *upper_h = malloc(len_h); char *upper_n = malloc(len_n); for (i = 0; i < len_h; i++) upper_h[i] = toupper(haystack[i]); for (i = 0; i < len_n; i++) upper_n[i] = toupper(needle[i]); char *p = strstr(upper_h, upper_n); free(upper_h); free(upper_n); return (gboolean) p; } static void sb_init() { /* workaround to print sb_init() */ streambrowser_cfg.debug = TRUE; debug("sb_init()\n"); streambrowser_cfg.debug = FALSE; config_load(); gui_init(); } static void sb_about() { debug("sb_about()\n"); static GtkWidget* about_window = NULL; if (about_window != NULL) { gtk_window_present(GTK_WINDOW(about_window)); } else { about_window = audacious_info_dialog(_("About Stream Browser"), _("Copyright (c) 2008, by Calin Crisan <ccrisan@gmail.com> and The Audacious Team.\n\n" "This is a simple stream browser that includes the most popular streaming directories.\n" "Many thanks to the Streamtuner developers <http://www.nongnu.org/streamtuner>,\n" "\tand of course to the whole Audacious community.\n\n" "Also thank you Tony Vroon for mentoring & guiding me, again.\n\n" "This was a Google Summer of Code 2008 project."), _("OK"), FALSE, NULL, NULL); g_signal_connect(G_OBJECT(about_window), "destroy", G_CALLBACK(gtk_widget_destroyed), &about_window); } } static void sb_configure() { debug("sb_configure()\n"); } static void sb_cleanup() { debug("sb_cleanup()\n"); gui_done(); config_save(); } static void gui_init() { /* the plugin services menu */ playlist_menu_item = gtk_image_menu_item_new_with_label(_("Streambrowser")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(playlist_menu_item), gtk_image_new_from_file(STREAMBROWSER_ICON_SMALL)); gtk_widget_show(playlist_menu_item); g_signal_connect(G_OBJECT(playlist_menu_item), "activate", G_CALLBACK(on_plugin_services_menu_item_click), NULL); audacious_menu_plugin_item_add(AUDACIOUS_MENU_PLAYLIST_RCLICK, playlist_menu_item); main_menu_item = gtk_image_menu_item_new_with_label(_("Streambrowser")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(main_menu_item), gtk_image_new_from_file(STREAMBROWSER_ICON_SMALL)); gtk_widget_show(main_menu_item); g_signal_connect(G_OBJECT(main_menu_item), "activate", G_CALLBACK(on_plugin_services_menu_item_click), NULL); audacious_menu_plugin_item_add(AUDACIOUS_MENU_MAIN, main_menu_item); /* main streambrowser window */ streambrowser_win_init(); streambrowser_win_set_update_function(streamdir_update); /* others */ update_thread_mutex = g_mutex_new(); update_thread_data_queue = g_queue_new(); debug("gui initialized\n"); } static void gui_done() { /* the plugin services menu */ audacious_menu_plugin_item_remove(AUDACIOUS_MENU_PLAYLIST_RCLICK, playlist_menu_item); audacious_menu_plugin_item_remove(AUDACIOUS_MENU_MAIN, main_menu_item); /* main streambrowser window */ streambrowser_win_hide(); streambrowser_win_done(); /* others */ if (update_thread_mutex) g_mutex_free(update_thread_mutex); update_thread_mutex = NULL; if (update_thread_data_queue) g_queue_free(update_thread_data_queue); update_thread_data_queue = NULL; debug("gui destroyed\n"); } static void streamdir_update(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist) { debug("requested streamdir update (streamdir = '%s', category = '%s', streaminfo = '%s', add_to_playlist = %d)\n", streamdir == NULL ? "" : streamdir->name, category == NULL ? "" : category->name, streaminfo == NULL ? "" : streaminfo->name, add_to_playlist); if (g_queue_get_length(update_thread_data_queue) >= MAX_UPDATE_THREADS) { debug("another %d streamdir updates are pending, this request will be dropped\n", g_queue_get_length(update_thread_data_queue)); } else { g_mutex_lock(update_thread_mutex); /* do we have a running thread? */ if (g_queue_get_length(update_thread_data_queue) > 0) { int i; gboolean exists = FALSE; update_thread_data_t *update_thread_data; /* search for another identic update request */ for (i = 0; i < g_queue_get_length(update_thread_data_queue); i++) { update_thread_data = g_queue_peek_nth(update_thread_data_queue, i); if (update_thread_data->streamdir == streamdir && update_thread_data->category == category && update_thread_data->streaminfo == streaminfo && update_thread_data->add_to_playlist == add_to_playlist) { exists = TRUE; break; } } /* if no other similar request exists, we enqueue it */ if (!exists) { debug("another %d streamdir updates are pending, this request will be queued\n", g_queue_get_length(update_thread_data_queue)); update_thread_data = g_malloc(sizeof(update_thread_data_t)); update_thread_data->streamdir = streamdir; update_thread_data->category = category; update_thread_data->streaminfo = streaminfo; update_thread_data->add_to_playlist = add_to_playlist; g_queue_push_tail(update_thread_data_queue, update_thread_data); } else { debug("this request is already present in the queue, dropping\n"); } } /* no thread is currently running, we start one */ else { debug("no other streamdir updates are pending, starting to process this request immediately\n"); update_thread_data_t *data = g_malloc(sizeof(update_thread_data_t)); data->streamdir = streamdir; data->category = category; data->streaminfo = streaminfo; data->add_to_playlist = add_to_playlist; g_queue_push_tail(update_thread_data_queue, data); g_thread_create((GThreadFunc) update_thread_core, NULL, FALSE, NULL); } g_mutex_unlock(update_thread_mutex); } } static gpointer update_thread_core(gpointer user_data) { debug("entering update thread core\n"); /* try to get the last item in the queue, but don't remove it */ g_mutex_lock(update_thread_mutex); update_thread_data_t *data = NULL; if (g_queue_get_length(update_thread_data_queue) > 0) { data = g_queue_peek_head(update_thread_data_queue); } g_mutex_unlock(update_thread_mutex); /* repetitively process the queue elements, until queue is empty */ while (data != NULL && g_queue_get_length(update_thread_data_queue) > 0) { /* update a streaminfo */ if (data->streaminfo != NULL) { gdk_threads_enter(); streambrowser_win_set_streaminfo_state(data->streamdir, data->category, data->streaminfo, TRUE); gdk_threads_leave(); if (data->add_to_playlist) streaminfo_add_to_playlist(data->streaminfo); else { /* shoutcast */ if (strncmp(data->streamdir->name, SHOUTCAST_NAME, strlen(SHOUTCAST_NAME)) == 0) { shoutcast_streaminfo_fetch(data->category, data->streaminfo); } /* xiph */ else if (strncmp(data->streamdir->name, XIPH_NAME, strlen(XIPH_NAME)) == 0) { xiph_streaminfo_fetch(data->category, data->streaminfo); } /* bookmarks */ else if (strncmp(data->streamdir->name, BOOKMARKS_NAME, strlen(BOOKMARKS_NAME)) == 0) { bookmarks_streaminfo_fetch(data->category, data->streaminfo); } } gdk_threads_enter(); if (!data->add_to_playlist) streambrowser_win_set_streaminfo(data->streamdir, data->category, data->streaminfo); streambrowser_win_set_streaminfo_state(data->streamdir, data->category, data->streaminfo, FALSE); gdk_threads_leave(); } /* update a category */ else if (data->category != NULL) { gdk_threads_enter(); streambrowser_win_set_category_state(data->streamdir, data->category, TRUE); gdk_threads_leave(); /* shoutcast */ if (strncmp(data->streamdir->name, SHOUTCAST_NAME, strlen(SHOUTCAST_NAME)) == 0) { shoutcast_category_fetch(data->streamdir, data->category); } /* xiph */ else if (strncmp(data->streamdir->name, XIPH_NAME, strlen(XIPH_NAME)) == 0) { xiph_category_fetch(data->streamdir, data->category); } /* bookmarks */ else if (strncmp(data->streamdir->name, BOOKMARKS_NAME, strlen(BOOKMARKS_NAME)) == 0) { bookmarks_category_fetch(data->streamdir, data->category); } gdk_threads_enter(); streambrowser_win_set_category(data->streamdir, data->category); streambrowser_win_set_category_state(data->streamdir, data->category, FALSE); gdk_threads_leave(); } /* update a streamdir */ else if (data->streamdir != NULL) { /* shoutcast */ if (strncmp(data->streamdir->name, SHOUTCAST_NAME, strlen(SHOUTCAST_NAME)) == 0) { streamdir_t *streamdir = shoutcast_streamdir_fetch(); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, SHOUTCAST_ICON); gdk_threads_leave(); } } /* xiph */ else if (strncmp(data->streamdir->name, XIPH_NAME, strlen(XIPH_NAME)) == 0) { streamdir_t *streamdir = xiph_streamdir_fetch(); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, XIPH_ICON); gdk_threads_leave(); } } /* bookmarks */ else if (strncmp(data->streamdir->name, BOOKMARKS_NAME, strlen(BOOKMARKS_NAME)) == 0) { streamdir_t *streamdir = bookmarks_streamdir_fetch(&streambrowser_cfg.bookmarks, &streambrowser_cfg.bookmarks_count); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, BOOKMARKS_ICON); gdk_threads_leave(); } } } /* update all streamdirs */ else { /* shoutcast */ streamdir_t *streamdir = shoutcast_streamdir_fetch(); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, SHOUTCAST_ICON); gdk_threads_leave(); } /* xiph */ streamdir = xiph_streamdir_fetch(); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, XIPH_ICON); gdk_threads_leave(); } /* bookmarks */ streamdir = bookmarks_streamdir_fetch(&streambrowser_cfg.bookmarks, &streambrowser_cfg.bookmarks_count); if (streamdir != NULL) { gdk_threads_enter(); streambrowser_win_set_streamdir(streamdir, BOOKMARKS_ICON); gdk_threads_leave(); int i; for (i = 0; i < category_get_count(streamdir); i++) streamdir_update(streamdir, category_get_by_index(streamdir, i), NULL, FALSE); } } g_free(data); g_mutex_lock(update_thread_mutex); /* remove the just processed data from the queue */ g_queue_pop_head(update_thread_data_queue); /* try to get the last item in the queue */ if (g_queue_get_length(update_thread_data_queue) > 0) data = g_queue_peek_head(update_thread_data_queue); else data = NULL; g_mutex_unlock(update_thread_mutex); } debug("leaving update thread core\n"); return NULL; } static void streaminfo_add_to_playlist(streaminfo_t *streaminfo) { if (strlen(streaminfo->playlist_url) > 0) { debug("fetching stream playlist for station '%s' from '%s'\n", streaminfo->name, streaminfo->playlist_url); if (!fetch_remote_to_local_file(streaminfo->playlist_url, PLAYLIST_TEMP_FILE)) { failure("shoutcast: stream playlist '%s' could not be downloaded to '%s'\n", streaminfo->playlist_url, PLAYLIST_TEMP_FILE); return; } debug("stream playlist '%s' successfuly downloaded to '%s'\n", streaminfo->playlist_url, PLAYLIST_TEMP_FILE); aud_playlist_add(aud_playlist_get_active(), PLAYLIST_TEMP_FILE); debug("stream playlist '%s' added\n", streaminfo->playlist_url); } if (strlen(streaminfo->url) > 0) { aud_playlist_add(aud_playlist_get_active(), streaminfo->url); debug("stream '%s' added\n", streaminfo->url); } } static void on_plugin_services_menu_item_click() { debug("on_plugin_services_menu_item_click()\n"); streambrowser_win_show(); streamdir_update(NULL, NULL, NULL, FALSE); }