changeset 2815:cc6f02424609

added initial support for xiph streaming directory; small bug fixes & code cleanups
author Calin Crisan ccrisan@gmail.com
date Sun, 13 Jul 2008 04:00:04 +0300
parents a03d670e8752
children a7a260032a5e 779125caa3ac
files src/streambrowser/Makefile src/streambrowser/gui/streambrowser_win.c src/streambrowser/shoutcast.c src/streambrowser/shoutcast.h src/streambrowser/streambrowser.c src/streambrowser/streambrowser.h src/streambrowser/streamdir.c src/streambrowser/streamdir.h src/streambrowser/xiph.c src/streambrowser/xiph.h
diffstat 10 files changed, 296 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/src/streambrowser/Makefile	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/Makefile	Sun Jul 13 04:00:04 2008 +0300
@@ -3,6 +3,7 @@
 SRCS = streambrowser.c	\
        streamdir.c \
        shoutcast.c \
+       xiph.c \
        gui/about_win.c \
        gui/streambrowser_win.c
 
--- a/src/streambrowser/gui/streambrowser_win.c	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/gui/streambrowser_win.c	Sun Jul 13 04:00:04 2008 +0300
@@ -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))
@@ -309,13 +308,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 +350,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;
@@ -428,41 +435,6 @@
 		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;
 }
 
@@ -536,6 +508,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 +527,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;
-}
-
--- a/src/streambrowser/shoutcast.c	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/shoutcast.c	Sun Jul 13 04:00:04 2008 +0300
@@ -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	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/shoutcast.h	Sun Jul 13 04:00:04 2008 +0300
@@ -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	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/streambrowser.c	Sun Jul 13 04:00:04 2008 +0300
@@ -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"
 
@@ -139,6 +140,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()
 {
@@ -261,7 +284,7 @@
     }
     else {
         g_mutex_lock(update_thread_mutex);
-            
+        
     	/* do we have a running thread? */
         if (update_thread_count > 0) {
             int i;
@@ -320,11 +343,11 @@
 {
 	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);
+		data = g_queue_peek_head(update_thread_data_queue);
 	}
 	g_mutex_unlock(update_thread_mutex);
 
@@ -357,6 +380,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 +405,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,6 +436,9 @@
 		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);
 		update_thread_count--;	
 
 		/* try to get the last item in the queue */
@@ -401,14 +456,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	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/streambrowser.h	Sun Jul 13 04:00:04 2008 +0300
@@ -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	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/streamdir.c	Sun Jul 13 04:00:04 2008 +0300
@@ -111,11 +111,12 @@
 }
 
 
-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)
 {
 	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	Sat Jul 12 16:00:03 2008 +0200
+++ b/src/streambrowser/streamdir.h	Sun Jul 13 04:00:04 2008 +0300
@@ -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 04:00:04 2008 +0300
@@ -0,0 +1,148 @@
+/*
+ * 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;
+static gchar *categories[] = {
+	// todo: complete this list
+	"alternative",
+	"dance",
+	"techno",
+	"rock",
+	"pop"
+};
+
+
+void refresh_streamdir();
+
+
+
+gboolean xiph_category_fetch(category_t *category)
+{
+	int entryno;
+	
+	/* see what entries match this category */
+	for (entryno = 0; entryno < xiph_entry_count; entryno++) {
+
+		if (mystrcasestr(xiph_entries[entryno].genre, category->name)) {
+			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(categories) / sizeof(gchar *); categno++) {
+		category_t *category = category_new(categories[categno]);
+		category_add(streamdir, category);
+	}
+
+	return streamdir;
+}
+
+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");
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/streambrowser/xiph.h	Sun Jul 13 04:00:04 2008 +0300
@@ -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
+