changeset 2825:9cdd04a31ab2

branch merge.
author William Pitcock <nenolod@atheme.org>
date Sun, 13 Jul 2008 15:17:29 -0500
parents 165d390cbdaa (diff) 5389a40f3566 (current diff)
children 615a87ac7a96
files
diffstat 16 files changed, 422 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/src/skins/plugin.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/skins/plugin.c	Sun Jul 13 15:17:29 2008 -0500
@@ -31,17 +31,17 @@
 
 gchar *skins_paths[SKINS_PATH_COUNT] = {};
 
-static Interface skins_gp =
+Interface skins_interface =
 {
     .id = "skinned",
     .desc = "Audacious Skinned GUI",
     .init = skins_init,
-    .fini = skins_cleanup,
-    .conf = skins_configure
+    .fini = skins_cleanup
 };
 
-SIMPLE_INTERFACE_PLUGIN("skinned", &skins_gp);
+SIMPLE_INTERFACE_PLUGIN("skinned", &skins_interface);
 gboolean plugin_is_active = FALSE;
+static GtkWidget *cfgdlg;
 
 static void skins_free_paths(void) {
     int i;
@@ -86,6 +86,10 @@
     init_skins(config.skin);
     mainwin_setup_menus();
 
+    skins_interface.ops->create_prefs_window();
+    cfgdlg = skins_configure();
+    aud_prefswin_page_new(cfgdlg, N_("Skinned Interface"), DATA_DIR "/images/appearance.png");
+
     aud_hook_call("create prefswin", NULL);
 
     if (config.player_visible) mainwin_real_show();
--- a/src/skins/plugin.h	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/skins/plugin.h	Sun Jul 13 15:17:29 2008 -0500
@@ -39,6 +39,7 @@
 };
 
 extern gchar *skins_paths[];
+extern Interface skins_interface;
 
 gboolean skins_init(void);
 gboolean skins_cleanup(void);
--- a/src/skins/ui_main.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/skins/ui_main.c	Sun Jul 13 15:17:29 2008 -0500
@@ -57,6 +57,7 @@
 #include "ui_playlist.h"
 #include "ui_hints.h"
 #include "dnd.h"
+#include "plugin.h"
 #if 0
 #include "configdb.h"
 #include "input.h"
@@ -1697,8 +1698,7 @@
             action_about_audacious();
             break;
         case MAINWIN_GENERAL_PLAYFILE: {
-            gboolean button = FALSE; /* FALSE = NO_PLAY_BUTTON */
-            aud_hook_call("filebrowser show", &button);
+            skins_interface.ops->filebrowser_show(FALSE); /* FALSE = NO_PLAY_BUTTON */
             break;
         }
         case MAINWIN_GENERAL_PLAYLOCATION:
@@ -2702,14 +2702,13 @@
 void
 action_play_file( void )
 {
-    gboolean button = TRUE; /* TRUE = PLAY_BUTTON */
-    aud_hook_call("filebrowser show", &button);
+    skins_interface.ops->filebrowser_show(TRUE); /* TRUE = PLAY_BUTTON */
 }
 
 void
 action_play_location( void )
 {
-    aud_hook_call("urlopener show", NULL);
+    skins_interface.ops->urlopener_show();
 }
 
 void
@@ -2811,8 +2810,7 @@
 void
 action_preferences( void )
 {
-    gboolean show = TRUE;
-    aud_hook_call("prefswin show", &show);
+    skins_interface.ops->show_prefs_window();
 }
 
 void
--- a/src/skins/ui_playlist.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/skins/ui_playlist.c	Sun Jul 13 15:17:29 2008 -0500
@@ -41,6 +41,7 @@
 
 #include "actions-playlist.h"
 #include "dnd.h"
+#include "plugin.h"
 #if 0
 #include "input.h"
 #include "main.h"
@@ -1793,14 +1794,13 @@
 void
 action_playlist_add_files(void)
 {
-    gboolean button = FALSE; /* FALSE = NO_PLAY_BUTTON */
-    aud_hook_call("filebrowser show", &button);
+    skins_interface.ops->filebrowser_show(FALSE); /* FALSE = NO_PLAY_BUTTON */
 }
 
 void
 action_playlist_add_url(void)
 {
-    aud_hook_call("urlopener show", NULL);
+    skins_interface.ops->urlopener_show();
 }
 
 void
--- a/src/streambrowser/Makefile	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/Makefile	Sun Jul 13 15:17:29 2008 -0500
@@ -3,10 +3,12 @@
 SRCS = streambrowser.c	\
        streamdir.c \
        shoutcast.c \
+       xiph.c \
        gui/about_win.c \
        gui/streambrowser_win.c
 
 DATA = images/shoutcast.png \
+       images/xiph.png \
        images/streambrowser-16x16.png \
        images/streambrowser-64x64.png
 
--- a/src/streambrowser/gui/streambrowser_win.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/gui/streambrowser_win.c	Sun Jul 13 15:17:29 2008 -0500
@@ -38,9 +38,6 @@
 static streamdir_gui_t*	find_streamdir_gui_by_streamdir(streamdir_t *streamdir);
 static gboolean			tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data);
 
-static gboolean			mystrcasestr(const char *haystack, const char *needle);
-
-
 static GtkWidget*		notebook;
 static GtkWidget*		search_entry;
 static GtkWidget*		add_button;
@@ -191,6 +188,8 @@
 		gtk_tree_store_append(store, &new_iter, &iter);
 		gtk_tree_store_set(store, &new_iter, 0, "gtk-directory", 1, streaminfo->name, 2, streaminfo->current_track, -1);
 	}
+	
+	gtk_tree_path_free(path);
 }
 
 void streambrowser_win_set_update_function(void (*function) (streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo))
@@ -237,10 +236,11 @@
 	if (fetching) {
 		gchar temp[DEF_STRING_LEN];
 		sprintf(temp, "<span style='italic' weight='heavy'>%s</span>", streaminfo->name);
-		gtk_tree_store_set(store, &iter, 0, "gtk-refresh", 1, temp, 2, "", -1);
+		
+		gtk_tree_store_set(store, &iter, 0, "gtk-refresh", 1, temp, 2, streaminfo->current_track, -1);
 	}
 	else {
-		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, streaminfo->name, 2, "", -1);
+		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, streaminfo->name, 2, streaminfo->current_track, -1);
 	}
 }
 
@@ -309,13 +309,20 @@
 {
 	if (page_num < 0)
 		return FALSE;
+		
+	/* only enable searching in the current tree (tab) */
 
-	/* update the current selected stream */
+	streamdir_gui_t *streamdir_gui;
+	int i;
 
-	/*
-	streamdir_gui_t *streamdir_gui = g_list_nth_data(streamdir_gui_list, page_num);
-	update_function(streamdir_gui->streamdir, NULL, NULL);
-	*/
+	for (i = 0; i < g_list_length(streamdir_gui_list); i++) {
+		streamdir_gui = g_list_nth_data(streamdir_gui_list, i);
+
+		if (i == page_num) 
+			gtk_tree_view_set_search_column(GTK_TREE_VIEW(streamdir_gui->tree_view), 1);
+		else
+			gtk_tree_view_set_search_column(GTK_TREE_VIEW(streamdir_gui->tree_view), -1);
+	}
 
 	/* clear the search box */
 	gtk_entry_set_text(GTK_ENTRY(search_entry), "");
@@ -344,6 +351,7 @@
 	model = gtk_tree_view_get_model(tree_view);
 	gtk_tree_model_get_iter(model, &iter, path);
 	
+	/* don't fetch a category that has been already fetched */
 	if (gtk_tree_model_iter_has_child(model, &iter)) {
 		gtk_tree_path_free(path);
 		return TRUE;
@@ -420,7 +428,7 @@
 {
 	if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
 		on_add_button_clicked(GTK_BUTTON(add_button), NULL);
-		return FALSE;
+		return TRUE;
 	}
 
 	if (event->keyval == GDK_Escape) {
@@ -428,50 +436,23 @@
 		return FALSE;
 	}
 	
-	/* todo : remove this
-	GtkWidget *table = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)));
-	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_table(GTK_TABLE(table));
-	if (streamdir_gui == NULL)
-		return FALSE;
-
-	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
-	GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
-	GtkTreeIter iter;
-	GtkTreePath *path;
-	const gchar *key = gtk_entry_get_text(GTK_ENTRY(search_entry));
-	
-	if (!gtk_tree_model_get_iter_first(model, &iter))
-		return FALSE;
-		
-	while (gtk_tree_model_iter_next(model, &iter)) {
-		GValue value = {0, };
-
-		gtk_tree_model_get_value(model, &iter, 1, &value);
-		const gchar *string = g_value_get_string(&value);
-	
-		if (string == NULL || string[0] == '\0' || key == NULL || key[0] == '\0')
-			return FALSE;
-
-		if (mystrcasestr(string, key)) {		gtk_entry_set_text(GTK_ENTRY(search_entry), "");
-
-			path = gtk_tree_model_get_path(model, &iter);	
-			gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE);
-			gtk_tree_path_free(path);
-		}
-
-		g_value_unset(&value);		
-	}
-	*/
-
 	return FALSE;
 }
 
 static gboolean on_tree_view_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
+	if (event->keyval == GDK_Down || event->keyval == GDK_Up)
+		return FALSE;
+	else
+		if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
+			on_add_button_clicked(GTK_BUTTON(add_button), NULL);
+			return FALSE;
+		}
+
 	gtk_widget_grab_focus(search_entry);
 	on_search_entry_key_pressed(widget, event, data);
 
-	return FALSE;
+	return TRUE;
 }
 
 static streamdir_gui_t *find_streamdir_gui_by_name(gchar *name)
@@ -536,6 +517,9 @@
 
 static gboolean tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data)
 {
+	if (column == -1)
+		return TRUE;
+
 	GValue value = {0, };
 	gboolean ret;
 	
@@ -552,25 +536,3 @@
 	return ret;
 }
 
-static 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;
-}
-
Binary file src/streambrowser/images/shoutcast.png has changed
Binary file src/streambrowser/images/xiph.png has changed
--- a/src/streambrowser/shoutcast.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/shoutcast.c	Sun Jul 13 15:17:29 2008 -0500
@@ -57,6 +57,10 @@
 		return FALSE;
 	}
 	
+	/* free/remove any existing streaminfos in this category */
+	while (streaminfo_get_count(category) > 0)
+		streaminfo_remove(category, streaminfo_get_by_index(category, 0));
+	
 	xmlNode *root_node = xmlDocGetRootElement(doc);
 	xmlNode *node;
 	
@@ -73,7 +77,7 @@
 
 			debug("shoutcast: fetching stream info for '%s/%d' from '%s'\n", streaminfo_name, streaminfo_id, url);
 
-			streaminfo_t *streaminfo = streaminfo_new(streaminfo_name, streaminfo_playlist_url, streaminfo_current_track);
+			streaminfo_t *streaminfo = streaminfo_new(streaminfo_name, streaminfo_playlist_url, "", streaminfo_current_track);
 			streaminfo_add(category, streaminfo);
 			
 			xmlFree(streaminfo_name);
@@ -84,12 +88,13 @@
 		}
 	}
 	
+	xmlFreeDoc(doc);
+	
 	if (remove(temp_filename) != 0) {
 		failure("shoutcast: cannot remove the temporary file: %s\n", strerror(errno));
 	}
 	free(temp_filename);
-	// todo: free the xml mallocs()
-	
+
 	return TRUE;
 }
 
@@ -143,7 +148,7 @@
 		}
 	}
 
-	// todo: free the xml mallocs()
+	xmlFreeDoc(doc);
 	
 	if (remove(temp_filename) != 0) {
 		failure("shoutcast: cannot remove the temporary file: %s\n", strerror(errno));
--- a/src/streambrowser/shoutcast.h	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/shoutcast.h	Sun Jul 13 15:17:29 2008 -0500
@@ -28,7 +28,6 @@
 #define SHOUTCAST_STREAMDIR_URL		"http://www.shoutcast.com/sbin/newxml.phtml"
 #define SHOUTCAST_CATEGORY_URL		"http://www.shoutcast.com/sbin/newxml.phtml?genre=%s"
 #define SHOUTCAST_STREAMINFO_URL	"http://www.shoutcast.com/sbin/shoutcast-playlist.pls?rn=%s&file=filename.pls"
-#define SHOUTCAST_BUFFER_SIZE		256
 
 
 gboolean							shoutcast_category_fetch(category_t *category);
--- a/src/streambrowser/streambrowser.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/streambrowser.c	Sun Jul 13 15:17:29 2008 -0500
@@ -25,6 +25,7 @@
 #include "streambrowser.h"
 #include "streamdir.h"
 #include "shoutcast.h"
+#include "xiph.h"
 #include "gui/streambrowser_win.h"
 #include "gui/about_win.h"
 
@@ -55,7 +56,6 @@
 static GtkWidget *main_menu_item;
 static GQueue *update_thread_data_queue = NULL;
 static GMutex *update_thread_mutex = NULL;
-static gint update_thread_count = 0;
 
 streambrowser_cfg_t streambrowser_cfg;
 
@@ -139,6 +139,28 @@
     return TRUE;
 }
 
+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()
 {
@@ -256,14 +278,14 @@
           category == NULL ? "" : category->name,
           streaminfo == NULL ? "" : streaminfo->name);
 
-    if (update_thread_count >= MAX_UPDATE_THREADS) {
-        debug("another %d streamdir updates are pending, this request will be dropped\n", update_thread_count);
+    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 (update_thread_count > 0) {
+        if (g_queue_get_length(update_thread_data_queue) > 0) {
             int i;
             gboolean exists = FALSE;
             update_thread_data_t *update_thread_data;
@@ -281,7 +303,7 @@
             
             /* if no other similar request exists, we enqueue it */
             if (!exists) {
-                debug("another %d streamdir updates are pending, this request will be queued\n", update_thread_count);
+                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));
 
@@ -290,7 +312,6 @@
                 update_thread_data->streaminfo = streaminfo;
  
                 g_queue_push_tail(update_thread_data_queue, update_thread_data);
-                update_thread_count++;
             }
             else {
                 debug("this request is already present in the queue, dropping\n");          
@@ -307,7 +328,6 @@
             data->streaminfo = streaminfo;
  
             g_queue_push_tail(update_thread_data_queue, data);
-            update_thread_count++;
 
 			g_thread_create((GThreadFunc) update_thread_core, NULL, FALSE, NULL);
         }
@@ -320,16 +340,16 @@
 {
 	debug("entering update thread core\n");
 
-	/* try to get the last item in the queue */
+	/* 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 (update_thread_count > 0) {
-		data = g_queue_pop_head(update_thread_data_queue);
+	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 && update_thread_count > 0) {
+	while (data != NULL && g_queue_get_length(update_thread_data_queue) > 0) {
 	    /* update a streaminfo - that is - add this streaminfo to playlist */
 		if (data->streaminfo != NULL) {
 	    	gdk_threads_enter();
@@ -357,6 +377,19 @@
 				streambrowser_win_set_category_state(data->streamdir, data->category, FALSE);
 		        gdk_threads_leave();
 		    }
+		    /* xiph */
+		    else if (strncmp(data->streamdir->name, XIPH_NAME, strlen(XIPH_NAME)) == 0) {
+		    	gdk_threads_enter();
+				streambrowser_win_set_category_state(data->streamdir, data->category, TRUE);
+		    	gdk_threads_leave();
+		    	
+		        xiph_category_fetch(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) {
@@ -369,14 +402,30 @@
 		            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();
+		        }
+		    }
 		}
 		/* update all streamdirs */
 		else {
 		    /* shoutcast */
-		    streamdir_t *shoutcast_streamdir = shoutcast_streamdir_fetch();
-		    if (shoutcast_streamdir != NULL) {
+		    streamdir_t *streamdir = shoutcast_streamdir_fetch();
+		    if (streamdir != NULL) {
 		        gdk_threads_enter();
-		        streambrowser_win_set_streamdir(shoutcast_streamdir, SHOUTCAST_ICON);
+		        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();
 		    }
 		}
@@ -384,13 +433,16 @@
 		g_free(data);
 
 		g_mutex_lock(update_thread_mutex);
-		update_thread_count--;	
+
+		/* 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 (update_thread_count > 0)
-			data = g_queue_pop_head(update_thread_data_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);
 	}
 
@@ -401,14 +453,22 @@
 
 static void streaminfo_add_to_playlist(streaminfo_t *streaminfo)
 {
-    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);
+    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_url(aud_playlist_get_active(), 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()
--- a/src/streambrowser/streambrowser.h	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/streambrowser.h	Sun Jul 13 15:17:29 2008 -0500
@@ -46,6 +46,9 @@
 void				failure(const char *fmt, ...);
 gboolean			fetch_remote_to_local_file(gchar *remote_url, gchar *local_url);
 
+	/* returns true if the substring has been found, false otherwise */
+gboolean			mystrcasestr(const char *haystack, const char *needle);
+
 
 #endif	// STREAMBROWSER_H
 
--- a/src/streambrowser/streamdir.c	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/streamdir.c	Sun Jul 13 15:17:29 2008 -0500
@@ -111,11 +111,23 @@
 }
 
 
-streaminfo_t* streaminfo_new(gchar *name, gchar *playlist_url, gchar *current_track)
+streaminfo_t* streaminfo_new(gchar *name, gchar *playlist_url, gchar *url, gchar *current_track)
 {
+	/* replace ampersands with slashes, to avoit gtk/pango markup confusions */
+	int i, count = strlen(name);
+	for (i = 0; i < count; i++)
+		if (name[i] == '&')
+			name[i] = '/';
+
+	count = strlen(current_track);
+	for (i = 0; i < count; i++)
+		if (current_track[i] == '&')
+			current_track[i] = '/';
+
 	streaminfo_t *streaminfo = (streaminfo_t*) g_malloc(sizeof(streaminfo_t));
 	strncpy(streaminfo->name, name, DEF_STRING_LEN);
 	strncpy(streaminfo->playlist_url, playlist_url, DEF_STRING_LEN);
+	strncpy(streaminfo->url, url, DEF_STRING_LEN);
 	strncpy(streaminfo->current_track, current_track, DEF_STRING_LEN);
 
 	return streaminfo;
--- a/src/streambrowser/streamdir.h	Sun Jul 13 15:17:16 2008 -0500
+++ b/src/streambrowser/streamdir.h	Sun Jul 13 15:17:29 2008 -0500
@@ -29,6 +29,7 @@
 
 	gchar			name[DEF_STRING_LEN];
 	gchar			playlist_url[DEF_STRING_LEN];
+	gchar			url[DEF_STRING_LEN];
 	gchar			current_track[DEF_STRING_LEN];
 
 } streaminfo_t;
@@ -60,7 +61,7 @@
 gint				category_get_count(streamdir_t *streamdir);
 gint				category_get_index(streamdir_t *streamdir, category_t *category);
 
-streaminfo_t*		streaminfo_new(gchar *name, gchar *playlist_url, gchar *current_track);
+streaminfo_t*		streaminfo_new(gchar *name, gchar *playlist_url, gchar *url, gchar *current_track);
 void				streaminfo_delete(streaminfo_t *streaminfo);
 void				streaminfo_free(streaminfo_t *streaminfo);
 void				streaminfo_add(category_t *category, streaminfo_t *streaminfo);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/streambrowser/xiph.c	Sun Jul 13 15:17:29 2008 -0500
@@ -0,0 +1,223 @@
+/*
+ * 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 <string.h>
+#include <glib.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <audacious/plugin.h>
+
+#include "streambrowser.h"
+#include "xiph.h"
+
+
+typedef struct {
+	gchar name[DEF_STRING_LEN];
+	gchar url[DEF_STRING_LEN];
+	gchar current_song[DEF_STRING_LEN];
+	gchar genre[DEF_STRING_LEN];
+} xiph_entry_t;
+
+
+static xiph_entry_t *xiph_entries = NULL;
+static int xiph_entry_count = 0;
+
+typedef struct {
+	gchar *name;
+	gchar *match_string;
+} xiph_category_t;
+
+/* inspired from streamtuner's xiph plugin */
+static xiph_category_t xiph_categories[] = {
+	{ "Alternative", "alternative indie goth college industrial punk hardcore ska" },
+	{ "Electronic", "electronic ambient drum trance techno house downtempo breakbeat jungle garage" },
+	{ "Classical", "classical opera symphonic" },
+	{ "Country", "country swing" },
+	{ "Hip-Hop/Rap", "hip hop rap turntable" },
+	{ "Jazz", "jazz swing" },
+	{ "Oldies", "oldies disco 50s 60s 70s 80s 90s" },
+	{ "Rop", "top pop" },
+	{ "Rock", "rock metal" },
+	{ "R&amp;B/Soul", "r&b funk soul urban" },
+	{ "Spiritual", "spiritual gospel christian muslim jewish religio" },
+	{ "Spoken", "spoken talk comedy" },
+	{ "World", "world reggae island african european east asia" },
+	{ "Other", "various mixed misc eclectic film show instrumental" }
+};
+
+
+// todo: call refresh_streamdir() more often to refresh the current track
+static void refresh_streamdir();
+	/* returns true if any of the words in string1 is present in string2 */
+static gboolean genre_match(gchar *string1, gchar *string2);
+
+
+gboolean xiph_category_fetch(category_t *category)
+{
+	int entryno, categoryno;
+	int xiph_category_count = sizeof(xiph_categories) / sizeof(xiph_category_t);
+	xiph_category_t *xiph_category = NULL;
+	
+	for (categoryno = 0; categoryno < xiph_category_count; categoryno++)
+		if (strcmp(xiph_categories[categoryno].name, category->name) == 0) {
+			xiph_category = xiph_categories + categoryno;
+			break;
+		}
+	
+	/* somehow we've got an invalid/unrecognized category */
+	if (xiph_category == NULL) {
+		failure("xiph: got an unrecognized category: '%s'\n", category->name);
+		return FALSE;
+	}
+	
+	/* free/remove any existing streaminfos in this category */
+	while (streaminfo_get_count(category) > 0)
+		streaminfo_remove(category, streaminfo_get_by_index(category, 0));	
+	
+	/* see what entries match this category */
+	for (entryno = 0; entryno < xiph_entry_count; entryno++) {
+		if (genre_match(xiph_category->match_string, xiph_entries[entryno].genre)) {
+			streaminfo_t *streaminfo = streaminfo_new(xiph_entries[entryno].name, "", xiph_entries[entryno].url, xiph_entries[entryno].current_song);
+			streaminfo_add(category, streaminfo);
+		}
+	}
+	
+	/* if the requested category is the last one in the list ("other"), 
+	   we fill it with all the entries that don't match the rest of categories */
+	if (xiph_category == &xiph_categories[xiph_category_count - 1]) {
+		for (entryno = 0; entryno < xiph_entry_count; entryno++) {
+			gboolean matched = FALSE;
+			
+			for (categoryno = 0; categoryno < xiph_category_count; categoryno++)
+				if (genre_match(xiph_entries[entryno].genre, xiph_categories[categoryno].match_string)) {
+					matched = TRUE;
+					break;
+				}
+			
+			if (!matched) {
+				streaminfo_t *streaminfo = streaminfo_new(xiph_entries[entryno].name, "", xiph_entries[entryno].url, xiph_entries[entryno].current_song);
+				streaminfo_add(category, streaminfo);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+
+streamdir_t* xiph_streamdir_fetch()
+{
+	streamdir_t *streamdir = streamdir_new(XIPH_NAME);
+	int categno;
+	
+	refresh_streamdir();
+	
+	for (categno = 0; categno < sizeof(xiph_categories) / sizeof(xiph_category_t); categno++) {
+		category_t *category = category_new(xiph_categories[categno].name);
+		category_add(streamdir, category);
+	}
+
+	return streamdir;
+}
+
+static void refresh_streamdir()
+{
+	/* free any previously fetched streamdir data */
+	if (xiph_entries != NULL)
+		free(xiph_entries);
+	xiph_entry_count = 0;
+
+	debug("xiph: fetching streaming directory file '%s'\n", XIPH_STREAMDIR_URL);
+	if (!fetch_remote_to_local_file(XIPH_STREAMDIR_URL, XIPH_TEMP_FILENAME)) {
+		failure("xiph: stream directory file '%s' could not be downloaded to '%s'\n", XIPH_STREAMDIR_URL, XIPH_TEMP_FILENAME);
+		return;
+	}
+	debug("xiph: stream directory file '%s' successfuly downloaded to '%s'\n", XIPH_STREAMDIR_URL, XIPH_TEMP_FILENAME);
+
+	xmlDoc *doc = xmlReadFile(XIPH_TEMP_FILENAME, NULL, 0);
+	if (doc == NULL) {
+		failure("xiph: failed to read stream directory file\n");
+		return;
+	}
+	
+	xmlNode *root_node = xmlDocGetRootElement(doc);
+	xmlNode *node, *child;
+	gchar *content;
+	
+	root_node = root_node->children;
+
+	for (node = root_node; node != NULL; node = node->next) {
+		if (node->type == XML_ELEMENT_NODE) {
+			xiph_entries = realloc(xiph_entries, sizeof(xiph_entry_t) * (xiph_entry_count + 1));
+		
+			for (child = node->children; child != NULL; child = child->next) {
+				if (strcmp((gchar *)child->name, "server_name") == 0) {
+					content = (gchar *) xmlNodeGetContent(child);
+					strcpy(xiph_entries[xiph_entry_count].name, content);
+					xmlFree(content);
+				}
+				else if (strcmp((gchar *)child->name, "listen_url") == 0) {
+					content = (gchar *) xmlNodeGetContent(child);
+					strcpy(xiph_entries[xiph_entry_count].url, content);
+					xmlFree(content);
+				}
+				else if (strcmp((gchar *)child->name, "current_song") == 0) {
+					content = (gchar *) xmlNodeGetContent(child);
+					strcpy(xiph_entries[xiph_entry_count].current_song, content);
+					xmlFree(content);
+				}
+				else if (strcmp((gchar *)child->name, "genre") == 0) {
+					content = (gchar *) xmlNodeGetContent(child);
+					strcpy(xiph_entries[xiph_entry_count].genre, content);
+					xmlFree(content);
+				}
+			}
+			
+			xiph_entry_count++;
+		}
+	}
+
+	xmlFreeDoc(doc);
+	
+	debug("xiph: streaming directory successfuly loaded\n");
+}
+
+static gboolean genre_match(gchar *string1, gchar *string2)
+{
+	char *saveptr = NULL;
+	char *token;
+	gboolean matched = FALSE;
+	char temp1[strlen(string1) + 1];
+	char temp2[strlen(string2) + 1];
+
+	/* these are required for strtok_r to work properly */	
+	strcpy(temp1, string1);
+	strcpy(temp2, string2);
+	
+	token = strtok_r(temp1, " ", &saveptr);
+	while (token != NULL) {
+		if (mystrcasestr(temp2, token))
+			matched = TRUE;
+			
+		token = strtok_r(NULL, " ", &saveptr);
+	}
+	
+	return matched;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/streambrowser/xiph.h	Sun Jul 13 15:17:29 2008 -0500
@@ -0,0 +1,37 @@
+/*
+ * 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>.
+ */
+
+
+#ifndef XIPH_H
+#define XIPH_H
+
+#include "streambrowser.h"
+#include "streamdir.h"
+
+#define XIPH_NAME				"Xiph"
+#define XIPH_ICON				DATA_DIR G_DIR_SEPARATOR_S "images" G_DIR_SEPARATOR_S "xiph.png"
+#define XIPH_STREAMDIR_URL		"http://dir.xiph.org/yp.xml"
+#define XIPH_TEMP_FILENAME		"file:///tmp/xiph_yp.xml"
+
+
+gboolean							xiph_category_fetch(category_t *category);
+streamdir_t*						xiph_streamdir_fetch();
+
+
+#endif	// SHOUTCAST_H
+