[xiph-commits] r14664 - in branches/dir.xiph.org: . cgi-bin cronjobs css images inc inc/smarty-plugins templates

balbinus at svn.xiph.org balbinus at svn.xiph.org
Mon Apr 7 11:48:08 PDT 2008


Author: balbinus
Date: 2008-04-07 11:48:08 -0700 (Mon, 07 Apr 2008)
New Revision: 14664

Added:
   branches/dir.xiph.org/by_format.php
   branches/dir.xiph.org/by_genre.php
   branches/dir.xiph.org/c_templates/
   branches/dir.xiph.org/cgi-bin/
   branches/dir.xiph.org/cgi-bin/yp.php
   branches/dir.xiph.org/cronjobs/
   branches/dir.xiph.org/cronjobs/check_servers.php
   branches/dir.xiph.org/cronjobs/generate_tagcloud.php
   branches/dir.xiph.org/cronjobs/generate_top.php
   branches/dir.xiph.org/cronjobs/generate_xml.php
   branches/dir.xiph.org/cronjobs/import_test_data.php
   branches/dir.xiph.org/cronjobs/prune_mountpoints.php
   branches/dir.xiph.org/cronjobs/update_stats.php
   branches/dir.xiph.org/css/
   branches/dir.xiph.org/css/style.css
   branches/dir.xiph.org/images/
   branches/dir.xiph.org/images/content-bg.png
   branches/dir.xiph.org/images/default_icon.jpg
   branches/dir.xiph.org/images/header-bg.png
   branches/dir.xiph.org/images/header-title.png
   branches/dir.xiph.org/images/logo-dirxiphorg.png
   branches/dir.xiph.org/images/music.png
   branches/dir.xiph.org/images/page-bg.png
   branches/dir.xiph.org/images/search-box.png
   branches/dir.xiph.org/inc/
   branches/dir.xiph.org/inc/class.db.php
   branches/dir.xiph.org/inc/class.izterator.php
   branches/dir.xiph.org/inc/class.izteratorbuilder.php
   branches/dir.xiph.org/inc/class.mc.php
   branches/dir.xiph.org/inc/class.mountpoint.php
   branches/dir.xiph.org/inc/class.server.php
   branches/dir.xiph.org/inc/class.tag.php
   branches/dir.xiph.org/inc/class.ypclient.php
   branches/dir.xiph.org/inc/inc.db.php
   branches/dir.xiph.org/inc/inc.mc.php
   branches/dir.xiph.org/inc/inc.templating.php
   branches/dir.xiph.org/inc/lib.dir.php
   branches/dir.xiph.org/inc/lib.uuidgen.php
   branches/dir.xiph.org/inc/prepend.php
   branches/dir.xiph.org/inc/smarty-plugins/
   branches/dir.xiph.org/inc/smarty-plugins/modifier.get_media_type.php
   branches/dir.xiph.org/inc/smarty-plugins/modifier.get_mime_type.php
   branches/dir.xiph.org/index.php
   branches/dir.xiph.org/listen.php
   branches/dir.xiph.org/search.php
   branches/dir.xiph.org/templates/
   branches/dir.xiph.org/templates/foot.tpl
   branches/dir.xiph.org/templates/head.tpl
   branches/dir.xiph.org/templates/index.tpl
   branches/dir.xiph.org/templates/menu_right.tpl
   branches/dir.xiph.org/templates/search.tpl
   branches/dir.xiph.org/templates/streams_list.tpl
   branches/dir.xiph.org/templates/yp.xml.tpl
Log:
Initial import of dir.xiph.org NG.

Added: branches/dir.xiph.org/by_format.php
===================================================================
--- branches/dir.xiph.org/by_format.php	                        (rev 0)
+++ branches/dir.xiph.org/by_format.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,93 @@
+<?php
+
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Get the args
+if (array_key_exists('PATH_INFO', $_SERVER))
+{
+	$search_string = preg_replace('|^/([^\s/]+).*$|', '$1', $_SERVER['PATH_INFO']);
+	$search_string = substr($search_string, 0, 15);
+	$tpl->assign('search_keyword', str_replace('_', ' ', $search_string));
+	$search_string = preg_replace('/[^A-Za-z0-9+_\-]/', '_', $search_string);
+	$search_string_hash = jenkins_hash_hex($search_string);
+	
+	// Memcache connection
+    $memcache = DirXiphOrgMCC::getInstance();
+	
+	// Get the data from the Memcache server
+	if (($results = $memcache->get('prod_search_format_'.$search_string_hash)) === false)
+	{
+		// Database connection
+		$db = DirXiphOrgDBC::getInstance();
+	
+		// Cache miss. Now query the database.
+		try
+		{
+			$query = ' SELECT m.* FROM `mountpoint` AS m INNER JOIN `media_type` AS mty ON m.`media_type_id` = mty.`id` WHERE mty.`media_type_url` = "%s" ORDER BY m.`listeners` DESC LIMIT %d;';
+			$query = sprintf($query, mysql_real_escape_string($search_string), MAX_SEARCH_RESULTS);
+			$results = $db->selectQuery($query);
+			$res = array();
+			while (!$results->endOf())
+			{
+				$res[] = Mountpoint::retrieveByPk($results->current('id'));
+				$results->next();
+			}
+			$results = $res;
+		}
+		catch (SQLNoResultException $e)
+		{
+			$results = array();
+		}
+	
+		// Cache the resultset
+		$memcache->set('prod_search_format_'.$search_string_hash, $results, false, 60);
+	}
+	
+	if ($results !== false && $results !== array())
+	{
+	    $n_results = count($results);
+		$results_pages = $n_results / MAX_RESULTS_PER_PAGE;
+		if ($page_n > $results_pages)
+		{
+		    $page_n = 0;
+		}
+		$offset = $page_n * MAX_RESULTS_PER_PAGE;
+	    $results = array_slice($results, $offset,
+	                                     MAX_RESULTS_PER_PAGE);
+		$tpl->assign_by_ref('results', $results);
+		$tpl->assign_by_ref('results_pages', $results_pages);
+		$tpl->assign_by_ref('results_page_no', $page_n);
+	}
+}
+else
+{
+	// Tag cloud
+//	$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+}
+
+// Header
+$tpl->display("head.tpl");
+
+// Display the results
+$tpl->assign('servers_total', $memcache->get('servers_total'));
+$tpl->assign('servers_mp3', $memcache->get('servers_'.CONTENT_TYPE_MP3));
+$tpl->assign('servers_vorbis', $memcache->get('servers_'.CONTENT_TYPE_OGG_VORBIS));
+$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+$tpl->display('search.tpl');
+
+// Footer
+$tpl->assign('generation_time', (microtime(true) - $begin_time) * 1000);
+$tpl->assign('sql_queries', isset($db) ? $db->queries : 0);
+if (isset($db))
+{
+	$tpl->assign('sql_debug', $db->log);	
+}
+$tpl->assign('mc_gets', isset($memcache) ? $memcache->gets : 0);
+$tpl->assign('mc_sets', isset($memcache) ? $memcache->sets : 0);
+if (isset($memcache))
+{
+    $tpl->assign('mc_debug', $memcache->log);
+}
+$tpl->display('foot.tpl');
+
+?>

Added: branches/dir.xiph.org/by_genre.php
===================================================================
--- branches/dir.xiph.org/by_genre.php	                        (rev 0)
+++ branches/dir.xiph.org/by_genre.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,102 @@
+<?php
+
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Get the args
+$page_n = array_key_exists('page', $_GET) ? intval($_GET['page']) : 0;
+if ($page_n > (MAX_SEARCH_RESULTS / MAX_RESULTS_PER_PAGE))
+{
+    $page_n = 0;
+}
+if (array_key_exists('PATH_INFO', $_SERVER))
+{
+	$search_string = preg_replace('|^/([^\s/]+).*$|', '$1', $_SERVER['PATH_INFO']);
+	$search_string = substr($search_string, 0, 15);
+	$tpl->assign('search_keyword', $search_string);
+	$search_string = str_replace(array('+', '-', '*', '<', '>', '~', '"', '(', ')', '&', '|'),
+								 array('', '', '', '', '', '', '', '', '', '', '', ''),
+								 $search_string);
+	$search_string_hash = jenkins_hash_hex($search_string);
+	
+	// Get the data from the Memcache server
+	if (($results = $memcache->get('prod_search_genre_'.$search_string_hash)) === false)
+	{
+		// Database connection
+		$db = DirXiphOrgDBC::getInstance();
+	
+		// Cache miss. Now query the database.
+		try
+		{
+//			$query = 'SELECT * FROM `mountpoints` WHERE MATCH `genre` AGAINST ("%s" IN BOOLEAN MODE) GROUP BY `stream_name`, `cluster_id` ORDER BY `listeners` DESC LIMIT 50;';
+            $query = 'SELECT m.`id` FROM `mountpoint` AS m INNER JOIN (SELECT mt.mountpoint_id FROM `mountpoints_tags` AS mt INNER JOIN `tag` AS t ON mt.`tag_id` = t.`id` WHERE t.`tag_name` = "%s" ORDER BY NULL) AS mt1 ON m.`id` = mt1.`mountpoint_id` ORDER BY m.`listeners` DESC LIMIT %d;';
+			$query = sprintf($query, mysql_real_escape_string($search_string), MAX_SEARCH_RESULTS);
+			$results = $db->selectQuery($query);
+			$res = array();
+			while (!$results->endOf())
+			{
+				$res[] = Mountpoint::retrieveByPk($results->current('id'));
+				$results->next();
+			}
+			$results = $res;
+		}
+		catch (SQLNoResultException $e)
+		{
+			$results = array();
+		}
+	
+		// Cache the resultset
+		$memcache->set('prod_search_genre_'.$search_string_hash, $results, false, 60);
+	}
+	
+	if ($results !== false && $results !== array())
+	{
+	    $n_results = count($results);
+		$results_pages = $n_results / MAX_RESULTS_PER_PAGE;
+		if ($page_n > $results_pages)
+		{
+		    $page_n = 0;
+		}
+		$offset = $page_n * MAX_RESULTS_PER_PAGE;
+	    $results = array_slice($results, $offset,
+	                                     MAX_RESULTS_PER_PAGE);
+		$tpl->assign_by_ref('results', $results);
+		$tpl->assign_by_ref('results_pages', $results_pages);
+		$tpl->assign_by_ref('results_page_no', $page_n);
+	}
+}
+else
+{
+	// Tag cloud
+	$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+	$tpl->assign('display_tag_cloud', true);
+}
+
+// Header
+$tpl->display("head.tpl");
+
+// Display the results
+$tpl->assign('servers_total', $memcache->get('servers_total'));
+$tpl->assign('servers_mp3', $memcache->get('servers_'.CONTENT_TYPE_MP3));
+$tpl->assign('servers_vorbis', $memcache->get('servers_'.CONTENT_TYPE_OGG_VORBIS));
+$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+$tpl->display('search.tpl');
+
+// Footer
+$tpl->assign('generation_time', (microtime(true) - $begin_time) * 1000);
+$tpl->assign('sql_queries', isset($db) ? $db->queries : 0);
+if (isset($db))
+{
+	$tpl->assign('sql_debug', $db->log);	
+}
+$tpl->assign('mc_gets', isset($memcache) ? $memcache->gets : 0);
+$tpl->assign('mc_sets', isset($memcache) ? $memcache->sets : 0);
+if (isset($memcache))
+{
+    $tpl->assign('mc_debug', $memcache->log);
+}
+$tpl->display('foot.tpl');
+
+?>

Added: branches/dir.xiph.org/cgi-bin/yp.php
===================================================================
--- branches/dir.xiph.org/cgi-bin/yp.php	                        (rev 0)
+++ branches/dir.xiph.org/cgi-bin/yp.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,321 @@
+<?php
+
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Do we have enough data?
+if (!array_key_exists('action', $_REQUEST))
+{
+	echo("No action key provided.");
+	exit();
+}
+
+// Log the request
+if (defined('DEBUG'))
+{
+	$fp = fopen('/tmp/dxo_'.md5(microtime()).'.test', 'w');
+	ob_start();
+	printf("======================================\n%s\n",
+		   date('Y-m-d H:i:s'));
+	print_r($_REQUEST);
+	$data = ob_get_contents();
+	ob_end_clean();
+	fwrite($fp, $data);
+}
+
+try
+{
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Then process it
+switch ($_REQUEST['action'])
+{
+	case 'add':
+		// TODO: handle case where the index isn't defined.
+		// TODO: check that the data we get is consistent with the previous cluster data.
+		
+		// Check the args are here
+		$mandatory_args = array('sn', 'type', 'genre', 'b', 'listenurl');
+		foreach ($mandatory_args as $a)
+		{
+		    if (!array_key_exists($a, $_REQUEST))
+		    {
+			    // Return failure
+			    header("YPResponse: 0");
+			    header("YPMessage: Not enough arguments.");
+			    header("SID: -1");
+				if (defined('DEBUG'))
+			    	fwrite($fp, "\YPResponse: 0\nYPMessage: Not enough arguments.\nSID: -1");
+		    }
+		}
+		// Remote IP
+		$ip = $_SERVER['REMOTE_ADDR'];
+		// Stream name
+		$stream_name = $_REQUEST['sn'];
+		// Media type
+		$media_type = $_REQUEST['type'];
+		if (array_key_exists('stype', $_REQUEST))
+		{
+			if (preg_match('/vorbis/i', $_REQUEST['stype']))
+			{
+				$media_type .= '+vorbis';
+			}
+			if (preg_match('/theora/i', $_REQUEST['stype']))
+			{
+				$media_type .= '+theora';
+			}
+		}
+		// Genre, space-normalized
+		$genre = $_REQUEST['genre'];
+		$genre = str_replace(array('+', '-', '*', '<', '>', '~', '"', '(', ')', '|', '!', '?'),
+							 array('', '', '', '', '', '', '', '', '', '', '', '', ''),
+							 $genre);
+		$genre = preg_replace('/\s+/', ' ', $genre);
+		$genre_list = array_slice(explode(' ', $genre), 0, 10);
+		// Bitrate
+		$bitrate = $_REQUEST['b'];
+		// Listen URL
+		$listen_url = $_REQUEST['listenurl'];
+		
+		// Cluster password
+		$cluster_password = array_key_exists('cpswd', $_REQUEST) ? $_REQUEST['cpswd'] : null;
+		// Description
+		$description = array_key_exists('desc', $_REQUEST) ? $_REQUEST['desc'] : null;
+		// URL
+		$url = array_key_exists('url', $_REQUEST) ? $_REQUEST['url'] : null;
+		
+		// MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Look for the mountpoint
+		$server_id = $sid = null;
+		$query = 'SELECT `id`, `mountpoint_id` FROM `server` WHERE `listen_url` = "%s";';
+		$query = sprintf($query, mysql_real_escape_string($listen_url));
+		$mp_id = $server_id = false; // MySQL auto-increment keys can't be 0
+		$mp = $server = null;
+		try
+		{
+			// The mountpoint exists yet. Either an error from the Icecast server, or
+			// we didn't wipe out old stuff fast enough.
+			$res = $db->singleQuery($query);
+			$mp_id = $res->current('mountpoint_id');
+			$mp = Mountpoint::retrieveByPk($mp_id);
+			$server_id = $res->current('id');
+			$server = Server::retrieveByPk($server_id);
+		}
+		catch(SQLNoResultException $e)
+		{
+			// The mountpoint doesn't exist yet in our database (it's OK)
+		}
+		
+		// SID
+		$sid = UUIDGen::generate();
+		
+		// Mountpoint
+		if (!($mp instanceOf Mountpoint))
+		{
+		    $mp = new Mountpoint(0, false, true);
+		    $mp->setStreamName($stream_name);
+		    $mp->setDescription($description);
+		    $mp->setUrl($url);
+		    $mp->setMediaTypeId(content_type_lookup($media_type));
+		    $mp->setBitrate($bitrate);
+		    $mp->setClusterPassword($cluster_password);
+		    
+		    $mp_id = $mp->save();
+			
+			if ($mp instanceOf Mountpoint)
+			{
+				// Add the tags
+				Tag::massTagMountpoint($mp, $genre_list);
+			}
+		}
+		if (defined('DEBUG'))
+			fwrite($fp, "Affected mountpoint ID: ".$mp_id."\n");
+		
+		// Server
+		if ($mp instanceOf Mountpoint && !$server_id)
+		{
+		    $server = new Server(0, false, true);
+		    $server->setMountpointId($mp_id);
+		    $server->setSid($sid);
+		    $server->setListenUrl($listen_url);
+		    $server->setLastTouchedFrom($ip);
+		    
+		    $server_id = $server->save();
+		}
+		if (defined('DEBUG'))
+			fwrite($fp, "Affected server ID: ".$server_id."\n");
+		
+		// Tags and stuff
+		if ($mp instanceOf Mountpoint && $server instanceOf Server)
+		{
+			// Increment the "total servers" key in memcache
+			if (!$memcache->increment('servers_total'))
+ 			{
+				$memcache->set('servers_total', 1);
+			}
+			$ct_id = content_type_lookup($media_type);
+			$ct_key = 'servers_'.intval($ct_id);
+			if (!$memcache->increment($ct_key))
+			{
+				$memcache->set($ct_key, 1);
+			}
+			
+			// Return success
+			header("YPResponse: 1");
+			header("YPMessage: Successfully added.");
+			header("SID: ".$sid);
+			header("TouchFreq: 250");
+			if (defined('DEBUG'))
+				fwrite($fp, "YPResponse: 1\nYPMessage: Successfully added.\nSID: ".$sid."\nTouchFreq: 60");
+		}
+		else
+		{
+			// Return failure
+			header("YPResponse: 0");
+			header("YPMessage: Error occured while processing your request.");
+			header("SID: -1");
+			if (defined('DEBUG'))
+				fwrite($fp, "\YPResponse: 0\nYPMessage: Error occured while processing your request.\nSID: -1");
+		}
+		
+		break;
+	case 'touch':
+		// TODO: handle the case where the index isn't defined.
+		// SID
+		$sid = $_REQUEST['sid'];
+		// Remote IP
+		$ip = $_SERVER['REMOTE_ADDR'];
+		// Song title
+		$current_song = mb_convert_encoding($_REQUEST['st'], 'UTF-8', 'UTF-8,ISO-8859-1,auto');
+		// Listeners
+		$listeners = $_REQUEST['listeners'];
+		// Max listeners
+		$max_listeners = $_REQUEST['max_listeners'];
+		
+		// MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Update the data
+		$query = 'UPDATE `server` SET `current_song` = "%s", `listeners` = %d, `last_touched_from` = INET_ATON("%s"), `last_touched_at` = NOW() WHERE `sid` = "%s";';
+		$query = sprintf($query, mysql_real_escape_string($current_song), $listeners, mysql_real_escape_string($ip), mysql_real_escape_string($sid));
+		if (defined('DEBUG'))
+		{
+		    fwrite($fp, $query."\n");
+		}
+		$res = $db->noReturnQuery($query);
+		if ($res && $db->affected_rows > 0)
+		{
+			// Return success
+			header("YPResponse: 1");
+			header("YPMessage: Updated server info.");
+			if (defined('DEBUG'))
+				fwrite($fp, "\nYPResponse: 1\nYPMessage: Updated server info.");
+		}
+		else
+		{
+			// Return failure
+			header("YPResponse: 0");
+			header("YPMessage: SID does not exist.");
+			if (defined('DEBUG'))
+				fwrite($fp, "\nYPResponse: 0\nYPMessage: SID does not exist.\n");
+		}
+		
+		break;
+	case 'remove':
+		// SID
+		$sid = $_REQUEST['sid'];
+		
+		// MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Remove the data
+		$query = 'SELECT s.`id`, s.`mountpoint_id`, m.`media_type_id` FROM `server` AS s INNER JOIN `mountpoint` AS m ON s.`mountpoint_id` = m.`id` WHERE s.`sid` = "%s";';
+		$query = sprintf($query, mysql_real_escape_string($sid));
+		try
+		{
+			$res = $db->selectQuery($query);
+			$server_id = $res->current('id');
+			$mp_id = $res->current('mountpoint_id');
+			$media_type = $res->current('media_type_id');
+			
+			// Remove server
+			$query = 'DELETE FROM `server` WHERE `id` = %d;';
+			$query = sprintf($query, $server_id);
+			$res = $db->singleQuery($query);
+			
+			if ($res)
+			{
+				// Decrement the servers keys in memcache
+				$memcache->decrement('servers_total');
+				$memcache->decrement('servers_'.intval($media_type));
+				
+				// Decrement the tag cloud values
+/*				$query = 'UPDATE `tag_cloud` SET `tag_usage` = `tag_usage` - 1 WHERE `tag_name` IN (SELECT `tag_id` FROM `mountpoints_tags` WHERE `mountpoint_id` = %d);';
+				$query = sprintf($query, $mp_id);
+				$db->noReturnQuery($query);*/
+				
+				// Return success
+				header("YPResponse: 1");
+				header("YPMessage: Deleted server info.");
+				if (defined('DEBUG'))
+					fwrite($fp, "\nYPResponse: 1\nYPMessage: Deleted server info.");
+			}
+			else
+			{
+				// Return failure
+				header("YPResponse: 0");
+				header("YPMessage: Error occured while processing your request.");
+				if (defined('DEBUG'))
+					fwrite($fp, "\YPResponse: 0\nYPMessage: Error occured while processing your request.");
+			}
+			
+/*			if ($cluster_id != null)
+			{
+				// See if cluster is empty
+				$query = 'SELECT 1 FROM `mountpoints` WHERE `cluster_id` = %d LIMIT 1;';
+				$query = sprintf($query, $cluster_id);
+				try
+				{
+					$res = $db->singleQuery($query);
+					
+					// OK, we can leave the cluster
+				}
+				catch (SQLNoResultException $e)
+				{
+					// No other server: delete the cluster
+					$query = 'DELETE FROM `clusters` WHERE `id` = %d;';
+					$query = sprintf($query, $cluster_id);
+					$res = $db->singleQuery($query);
+				}
+			}*/
+		}
+		catch (SQLNoResultException $e)
+		{
+			// Return failure
+			header("YPResponse: 0");
+			header("YPMessage: SID does not exist.");
+			if (defined('DEBUG'))
+				fwrite($fp, "\nYPResponse: 0\nYPMessage: SID does not exist.");
+		}
+		
+		break;
+	default:
+		if (defined('DEBUG'))
+			fwrite($fp, "\nUnrecognized action '".$_REQUEST['action']."'");
+}
+}
+catch (SQLException $e)
+{
+	if (defined('DEBUG'))
+		fwrite($fp, "\nMySQL Error: ".$e->getMessage());
+}
+
+if (defined('DEBUG'))
+{
+	fwrite($fp, "\n");
+	fclose($fp);
+}
+
+?>

Added: branches/dir.xiph.org/cronjobs/check_servers.php
===================================================================
--- branches/dir.xiph.org/cronjobs/check_servers.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/check_servers.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,124 @@
+<?php
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/class.db.php');
+include_once(dirname(__FILE__).'/../inc/class.mc.php');
+include_once(dirname(__FILE__).'/../inc/class.izterator.php');
+include_once(dirname(__FILE__).'/../inc/class.izteratorbuilder.php');
+include_once(dirname(__FILE__).'/../inc/inc.db.php');
+include_once(dirname(__FILE__).'/../inc/inc.mc.php');
+
+class ToDeleteException extends Exception { }
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Old stuff that "timeouted"
+$res = $db->selectQuery('SELECT * FROM `server` WHERE `checked` = 0;');
+while (!$res->endOf())
+{
+    try
+    {
+        // Get the URL
+        $url = @parse_url($res->current('listen_url'));
+        if (!$url)
+        {
+            throw new ToDeleteException();
+        }
+        
+        // Now, verify!
+        if (empty($url['scheme']) || $url['scheme'] != 'http'
+            || !array_key_exists('host', $url))
+        {
+            throw new ToDeleteException();
+        }
+        
+        // Try to open a connection to the server
+        $ok = false;
+        $count = 0;
+        while ($count < 3 && !$ok)
+        {
+            $fp = fsockopen($url['host'],
+                            array_key_exists('port', $url) ? $url['port'] : 80, // as per HTTP RFC
+                            $errno, $errstr, 5);
+            if (!$fp)
+            {
+                $count++;
+                continue;
+            }
+            
+            // Now send a request
+            $req = sprintf("GET %s HTTP/1.0\r\n\r\n", $url['path']);
+            $r = fwrite($fp, $req);
+            if (!$r || $r != strlen($req))
+            {
+                $count++;
+                continue;
+            }
+            $r = 0;
+            $headers = array();
+            do
+            {
+                $data = fgets($fp);
+                if (trim($data) != '')
+                {
+                    list($header, $value) = explode(':', $data);
+                    $headers[strtolower(trim($header))] = trim($value);
+                }
+                $r++;
+            }
+            while (trim($data) != '' && $r < 10);
+            
+            // Extremely dangerous, desactivated.
+/*            if (!array_key_exists('server', $headers)
+                || !stristr($headers['server'], 'icecast'))
+            {
+                throw new ToDeleteException();
+            }*/
+            fclose($fp);
+            
+            $count++;
+            $ok = true;
+        }
+        if (!$ok)
+        {
+            throw new ToDeleteException();
+        }
+    }
+    catch (ToDeleteException $e)
+    {
+        // TODO: remove the stream
+        echo("Delete it! ".$res->current('listen_url')."\n");
+    }
+    
+    $res->next();
+}
+
+exit();
+// Useless mountpoint
+$toDelete = $db->selectQuery('SELECT m.`id` AS `mountpoint_id`, s.`id` FROM `mountpoint` AS m LEFT OUTER JOIN `server` AS s ON m.`id` = s.`mountpoint_id` HAVING s.`id` IS NULL;');
+while (!$toDelete->endOf())
+{
+	// Deletion
+	$mp_id = $toDelete->current('mountpoint_id');
+	$sql = 'DELETE FROM `mountpoint` WHERE `id` = %d;';
+	$db->noReturnQuery(sprintf($sql, $mp_id));
+	
+	// Tag cloud update
+	$sql = 'UPDATE `tag_cloud` SET `tag_usage` = `tag_usage` - 1 WHERE `tag_id` IN (SELECT `tag_id` FROM `mountpoints_tags` WHERE `mountpoint_id` = %d);';
+	$db->noReturnQuery(sprintf($sql, $mp_id));
+	
+	// Next!
+	$toDelete->next();
+}
+
+// Now prune the tags
+$sql = 'DELETE FROM `tag` WHERE `id` IN (SELECT `tag_id` FROM `tag_cloud` WHERE `tag_usage` <= 0);';
+$db->noReturnQuery($sql);
+$sql = 'DELETE FROM `tag_cloud` WHERE `tag_usage` <= 0;';
+$db->noReturnQuery($sql);
+
+?>

Added: branches/dir.xiph.org/cronjobs/generate_tagcloud.php
===================================================================
--- branches/dir.xiph.org/cronjobs/generate_tagcloud.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/generate_tagcloud.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,60 @@
+<?php
+
+// Classes
+include_once(dirname(__FILE__).'/../inc/class.db.php');
+include_once(dirname(__FILE__).'/../inc/class.mc.php');
+include_once(dirname(__FILE__).'/../inc/class.izterator.php');
+include_once(dirname(__FILE__).'/../inc/class.izteratorbuilder.php');
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/inc.db.php');
+include_once(dirname(__FILE__).'/../inc/inc.mc.php');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Maximum number of streams in the tagcloud
+define('MAX_TAGS_IN_CLOUD', 15);
+// Maximum popularity rank defined in CSS
+define('POPULARITY_RANKS', 5);
+
+// Get data
+$query = 'SELECT t.`tag_name`, tc.`tag_usage` FROM `tag_cloud` AS tc INNER JOIN `tag` AS t ON tc.`tag_id` = t.`id` ORDER BY tc.`tag_usage` DESC LIMIT %d;';
+$query = sprintf($query, MAX_TAGS_IN_CLOUD);
+$res = $db->selectQuery($query)->array_data;
+
+// Sort alphabetically, and compute min and max
+$min = 1000000;
+$max = -1;
+function sort_by_name($s0, $s1)
+{
+	global $min, $max;
+	$min = min($min, $s0['tag_usage']);
+	$max = max($max, $s0['tag_usage']);
+	
+	// The tags can't be equal. For they are tags.
+	return ($s0['tag_name'] > $s1['tag_name']) ? +1 : -1;
+}
+usort($res, 'sort_by_name');
+
+// Add popularity info
+$delta = $max - $min;
+$delta = $delta > 0 ? $delta : 1;
+function add_popularity($s)
+{
+	global $min, $delta;
+	
+	$s['popularity'] = round(($s['tag_usage'] - $min) / ($delta / POPULARITY_RANKS));
+	
+	return $s;
+}
+$res = array_map('add_popularity', $res);
+var_dump($res);
+// Save into memcache
+$memcache->set('prod_tagcloud', $res, 0, 600); // 10 mins
+echo "OK.\n";
+
+?>

Added: branches/dir.xiph.org/cronjobs/generate_top.php
===================================================================
--- branches/dir.xiph.org/cronjobs/generate_top.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/generate_top.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,92 @@
+<?php
+
+// Classes
+include_once(dirname(__FILE__).'/../inc/class.db.php');
+include_once(dirname(__FILE__).'/../inc/class.mc.php');
+include_once(dirname(__FILE__).'/../inc/class.izterator.php');
+include_once(dirname(__FILE__).'/../inc/class.izteratorbuilder.php');
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/inc.db.php');
+include_once(dirname(__FILE__).'/../inc/inc.mc.php');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// You need at least that many listeners to make it to the homepage. SQL "optimization"
+// that allows to lower the number of rows to sort on. Should be set correctly, or the
+// CPU will burn.
+define('MIN_LISTENERS_FOR_HOMEPAGE', 10);
+// How many streams will appear on the homepage
+define('MAX_STREAMS_ON_HOMEPAGE', 20);
+
+// Clusters
+// ORDER BY `listeners` DESC LIMIT %d --> this was removed since it triggered temporary
+// + filesort total loser combo.
+/*try
+{
+	$query = 'SELECT CONCAT("c", c.`id`) AS `id`, "cluster" AS `type`, m.`stream_name`, m.`description`, m.`genre`, SUM(m.`listeners`) AS `listeners`, m.`url`, m.`current_song`, m.`media_type`, m.`bitrate`, m.`channels` FROM `mountpoints` AS m LEFT JOIN `clusters` AS c ON m.`cluster_id` = c.`id` WHERE `listeners` > %d AND m.`cluster_id` IS NOT NULL GROUP BY m.`cluster_id`;';
+	$query = sprintf($query, MIN_LISTENERS_FOR_HOMEPAGE, MAX_STREAMS_ON_HOMEPAGE);
+	$res0 = $db->selectQuery($query)->array_data;
+}
+catch (SQLNoResultException $e)
+{
+	$res0 = array();
+}
+
+// Standalone stuff
+// ORDER BY `listeners` DESC LIMIT %d --> this was removed, see above (except for
+// temporary, not triggered since everything exists in DB here)
+try
+{
+	$query = 'SELECT CONCAT("s", `id`) AS `id`, "standalone" AS `type`, `stream_name`, `description`, `genre`, `listeners`, `url`, `current_song`, `media_type`, `bitrate`, `channels` FROM `mountpoints` WHERE `listeners` > %d AND `cluster_id` IS NULL;';
+	$query = sprintf($query, MIN_LISTENERS_FOR_HOMEPAGE, MAX_STREAMS_ON_HOMEPAGE);
+	$res1 = $db->selectQuery($query)->array_data;
+}
+catch (SQLNoResultException $e)
+{
+	$res1 = array();
+}
+
+// Merging -- *not* done in DB, see above.
+function stream_compare($s0, $s1)
+{
+	return ($s0['listeners'] > $s1['listeners']) ? -1 : (($s0['listeners'] == $s1['listeners']) ? 0 : +1);
+}
+$data = array_merge($res0, $res1);
+usort($data, 'stream_compare');
+
+// Add a "rank" item
+function set_rank(&$v, $k)
+{
+	$v['rank'] = $k + 1;
+}
+array_walk($data, 'set_rank');*/
+
+try
+{
+//    $query = 'SELECT * FROM `mountpoint` ORDER BY `listeners` DESC LIMIT %d;';
+    $query = 'SELECT `id` FROM `mountpoint` ORDER BY RAND() LIMIT %d;';
+    $query = sprintf($query, MAX_STREAMS_ON_HOMEPAGE);
+    $data = $db->selectQuery($query);
+	$res = array();
+	while (!$data->endOf())
+	{
+		$res[] = intval($data->current('id'));
+		$data->next();
+	}
+	$data = $res;
+}
+catch (SQLNoResultException $e)
+{
+    $data = array();
+}
+
+// array_slice($data, 0, MAX_STREAMS_ON_HOMEPAGE)
+$memcache->set('prod_home_top', $data, false, 120) or die("Unable to save data on the Memcache server.\n");
+echo("OK.\n");
+
+?>

Added: branches/dir.xiph.org/cronjobs/generate_xml.php
===================================================================
--- branches/dir.xiph.org/cronjobs/generate_xml.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/generate_xml.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,32 @@
+<?php
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/prepend.php');
+
+define('XML_OUTPUT', dirname(__FILE__).'/../yp.xml');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Get data
+$query = "SELECT m.`stream_name`, s.`listen_url`, m.`media_type_id`, m.`bitrate`, m.`channels`, m.`samplerate`, GROUP_CONCAT(t.`tag_name` SEPARATOR ' ') AS `genre`, m.`current_song` FROM `mountpoint` AS m INNER JOIN `server` AS s ON m.`id` = s.`mountpoint_id` INNER JOIN `mountpoints_tags` AS mt ON mt.`mountpoint_id` = m.`id` INNER JOIN `tag` AS t ON mt.`tag_id` = t.`id` GROUP BY s.`id` ORDER BY NULL;";
+$res = $db->selectQuery($query)->array_data;
+$tpl->assign_by_ref('streams', $res);
+$xml = $tpl->fetch('yp.xml.tpl');
+
+// Write data
+$fp = fopen(XML_OUTPUT.'.tmp', 'w');
+$l = fwrite($fp, $xml);
+fclose($fp);
+if ($l == strlen($xml))
+{
+    if (rename(XML_OUTPUT.'.tmp', XML_OUTPUT))
+    {
+        echo "OK.\n";
+        exit();
+    }
+}
+
+echo "KO.\n";
+
+?>

Added: branches/dir.xiph.org/cronjobs/import_test_data.php
===================================================================
--- branches/dir.xiph.org/cronjobs/import_test_data.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/import_test_data.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,32 @@
+<?php
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/prepend.php');
+
+define('XML_OUTPUT', dirname(__FILE__).'/../yp.xml');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Get data
+$query = "SELECT m.`stream_name`, s.`listen_url`, m.`media_type_id`, m.`bitrate`, m.`channels`, m.`samplerate`, GROUP_CONCAT(t.`tag_name` SEPARATOR ' ') AS `genre`, m.`current_song` FROM `mountpoint` AS m INNER JOIN `server` AS s ON m.`id` = s.`mountpoint_id` INNER JOIN `mountpoints_tags` AS mt ON mt.`mountpoint_id` = m.`id` INNER JOIN `tag` AS t ON mt.`tag_id` = t.`id` GROUP BY s.`id` ORDER BY NULL;";
+$res = $db->selectQuery($query)->array_data;
+$tpl->assign_by_ref('streams', $res);
+$xml = $tpl->fetch('yp.xml.tpl');
+
+// Write data
+$fp = fopen(XML_OUTPUT.'.tmp', 'w');
+$l = fwrite($fp, $xml);
+fclose($fp);
+if ($l == strlen($xml))
+{
+    if (rename(XML_OUTPUT.'.tmp', XML_OUTPUT))
+    {
+        echo "OK.\n";
+        exit();
+    }
+}
+
+echo "KO.\n";
+
+?>

Added: branches/dir.xiph.org/cronjobs/prune_mountpoints.php
===================================================================
--- branches/dir.xiph.org/cronjobs/prune_mountpoints.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/prune_mountpoints.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,72 @@
+<?php
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/class.db.php');
+include_once(dirname(__FILE__).'/../inc/class.mc.php');
+include_once(dirname(__FILE__).'/../inc/class.izterator.php');
+include_once(dirname(__FILE__).'/../inc/class.izteratorbuilder.php');
+include_once(dirname(__FILE__).'/../inc/inc.db.php');
+include_once(dirname(__FILE__).'/../inc/inc.mc.php');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Old stuff that "timeouted"
+$db->noReturnQuery('DELETE FROM `server` WHERE `last_touched_at` <= DATE_SUB(NOW(), INTERVAL 30 MINUTE);');
+$nb = $db->affected_rows;
+$memcache->decrement('servers', $nb);
+
+// Useless mountpoint
+try
+{
+    $toDelete = $db->selectQuery('SELECT m.`id` AS `mountpoint_id`, s.`id` FROM `mountpoint` AS m LEFT OUTER JOIN `server` AS s ON m.`id` = s.`mountpoint_id` HAVING s.`id` IS NULL;');
+    while (!$toDelete->endOf())
+    {
+	    // Deletion
+	    $mp_id = $toDelete->current('mountpoint_id');
+	    $sql = 'DELETE FROM `mountpoint` WHERE `id` = %d;';
+	    $db->noReturnQuery(sprintf($sql, $mp_id));
+	    
+	    // Tag cloud update
+	    $sql = 'UPDATE `tag_cloud` SET `tag_usage` = `tag_usage` - 1 WHERE `tag_id` IN (SELECT `tag_id` FROM `mountpoints_tags` WHERE `mountpoint_id` = %d);';
+	    $db->noReturnQuery(sprintf($sql, $mp_id));
+	    
+	    // Mountpoints/tags relation table update
+	    $sql = 'DELETE FROM `mountpoints_tags` WHERE `mountpoint_id` = %d;';
+	    $db->noReturnQuery(sprintf($sql, $mp_id));
+	    
+	    // Next!
+	    $toDelete->next();
+    }
+}
+catch (SQLNoResultException $e)
+{
+}
+
+// Now prune the tags
+$sql = 'DELETE FROM `tag` WHERE `id` IN (SELECT `tag_id` FROM `tag_cloud` WHERE `tag_usage` <= 0);';
+$db->noReturnQuery($sql);
+$sql = 'DELETE FROM `tag_cloud` WHERE `tag_usage` <= 0;';
+$db->noReturnQuery($sql);
+try
+{
+    $sql = 'SELECT mt.`mountpoint_id` FROM `mountpoints_tags` AS mt LEFT OUTER JOIN `mountpoint` AS m ON mt.`mountpoint_id` = m.`id` WHERE m.`id` IS NULL GROUP BY mt.`mountpoint_id` ORDER BY NULL;';
+    $res = $db->selectQuery($sql);
+    $ids = array();
+    while (!$res->endOf())
+    {
+        $ids[] = $res->current('mountpoint_id');
+        $res->next();
+    }
+    $sql = 'DELETE FROM `mountpoints_tags` WHERE `mountpoint_id` IN (%s);';
+    $sql = sprintf($sql, implode(', ', $ids));
+    $db->noReturnQuery($sql);
+}
+catch (SQLNoResultException $e)
+{
+}
+
+?>

Added: branches/dir.xiph.org/cronjobs/update_stats.php
===================================================================
--- branches/dir.xiph.org/cronjobs/update_stats.php	                        (rev 0)
+++ branches/dir.xiph.org/cronjobs/update_stats.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,46 @@
+<?php
+
+// Classes
+include_once(dirname(__FILE__).'/../inc/class.db.php');
+include_once(dirname(__FILE__).'/../inc/class.mc.php');
+include_once(dirname(__FILE__).'/../inc/class.izterator.php');
+include_once(dirname(__FILE__).'/../inc/class.izteratorbuilder.php');
+
+// Libraries
+include_once(dirname(__FILE__).'/../inc/lib.dir.php');
+
+// Inclusions
+include_once(dirname(__FILE__).'/../inc/inc.db.php');
+include_once(dirname(__FILE__).'/../inc/inc.mc.php');
+
+// Database connection
+$db = DirXiphOrgDBC::getInstance();
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Base query
+$query_pattern = 'SELECT COUNT(id) AS `count` FROM `mountpoint` WHERE %s;';
+$where_pattern = '`media_type_id` = "%s"';
+
+// MP3
+$where = sprintf($where_pattern, CONTENT_TYPE_MP3);
+$query = sprintf($query_pattern, $where);
+$count = $db->singleQuery($query)->current('count');
+$memcache->set('servers_'.CONTENT_TYPE_MP3, $count, false, 600); // 10 minutes
+
+// Vorbis
+$where = array();
+$where = sprintf($where_pattern, CONTENT_TYPE_OGG_VORBIS);
+$query = sprintf($query_pattern, $where);
+$count = $db->singleQuery($query)->current('count');
+$memcache->set('servers_'.CONTENT_TYPE_OGG_VORBIS, $count, false, 600); // 10 minutes
+
+// Total
+$query = sprintf($query_pattern, '1');
+$count = $db->singleQuery($query)->current('count');
+$memcache->set('servers_total', $count, false, 600); // 10 minutes
+
+echo("OK.\n");
+
+?>

Added: branches/dir.xiph.org/css/style.css
===================================================================
--- branches/dir.xiph.org/css/style.css	                        (rev 0)
+++ branches/dir.xiph.org/css/style.css	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,166 @@
+/* Adaptations on the Xiph.org default CSS */
+#content {
+	padding-left: 3em;
+}
+
+#navbar h3 {
+	font-size: 0.9em;
+}
+
+h2 {
+	font-size: 1.5em;
+}
+
+#thepage {
+	width: 90%;
+}
+
+/* Top logo */
+#xiphlogo {
+	background: transparent url('/images/logo-dirxiphorg.png') no-repeat scroll top left;
+}
+
+#xiphlogo h1 a {
+	display: block;
+	text-indent: -9999px;
+	height: 75px;
+	width: 514px;
+}
+
+/* Search box */
+#sidebar-search label {
+	display: none;
+}
+
+#search {
+	width: 100px;
+}
+
+/* Tag cloud */
+.tag-cloud li {
+	display: inline;
+}
+
+.tag-cloud .context {
+	position: absolute;
+	left: -999px;
+	width: 990px;
+}
+
+.tag-cloud .popularity-0 { font-size: 0.7em !important; }
+.tag-cloud .popularity-1 { font-size: 0.9em !important; }
+.tag-cloud .popularity-2 { font-size: 1.1em !important; }
+.tag-cloud .popularity-3 { font-size: 1.3em !important; }
+.tag-cloud .popularity-4 { font-size: 1.4em !important; }
+.tag-cloud .popularity-5 { font-size: 1.5em !important; }
+.tag-cloud .popularity-3 a, .tag-cloud .popularity-4 a,
+.tag-cloud .popularity-5 a { font-weight: normal !important; }
+
+/* Servers list */
+table.servers-list {
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+	width: 100%;
+}
+
+table.servers-list tr {
+	border-bottom: 1px dashed #F33;
+	margin-bottom: 0.3em;
+	min-height: 80px;
+}
+
+table.servers-list tr p {
+	margin: 0;
+	padding: 0;
+}
+
+table.servers-list tr td {
+    border-bottom: 1px solid #9FC0D7;
+}
+
+table.servers-list td.rank {
+	font-family: Palatino, Palatino Linotype, Georgia, Times New Roman, serif;
+	font-weight: bold;
+	font-style: italic;
+	color: #999;
+	font-size: 3em;
+	padding-right: 0.3em;
+	text-align: right;
+	vertical-align: top;
+}
+
+table.servers-list td.description {
+}
+
+table.servers-list span.name {
+	font-size: 1.3em;
+	font-weight: bold;
+}
+
+table.servers-list span.listeners {
+	font-variant: small-caps;
+}
+
+table.servers-list p.stream-description {
+	margin: 0.5em 0 0.4em;
+}
+
+table.servers-list p.stream-onair {
+	font-style: italic;
+	font-size: 0.9em;
+}
+
+table.servers-list p.stream-onair strong {
+	font-weight: bold;
+	font-style: normal;
+}
+
+table.servers-list div.stream-tags {
+    font-size: 0.9em;
+}
+
+table.servers-list div.stream-tags ul {
+    display: inline;
+}
+
+table.servers-list td.tune-in {
+	width: 20%;
+	text-align: center;
+	vertical-align: top;
+}
+
+table.servers-list td.tune-in a.tune-in-button {
+	border: 1px solid #C33;
+	background-color: #F33;
+	color: #FFF;
+	font-weight: bold;
+	padding: 0.5em;
+	display: block;
+	text-align: center;
+	margin: 1em 0.5em 0 0.5em;
+}
+
+table.servers-list td.tune-in a.tune-in-button:hover {
+	background-color: #FEE;
+	color: #000;
+}
+
+table.servers-list td.tune-in a.tune-in-button img {
+	margin-bottom: -0.3em;
+}
+
+table.servers-list td.tune-in p.format {
+	font-size: 0.9em;
+}
+
+/* Tag list */
+.inline-tags {
+    list-style-type: none;
+    padding: 0;
+    margin: 0;
+}
+
+.inline-tags li {
+    display: inline;
+}

Added: branches/dir.xiph.org/images/content-bg.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/content-bg.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/default_icon.jpg
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/default_icon.jpg
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/header-bg.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/header-bg.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/header-title.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/header-title.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/logo-dirxiphorg.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/logo-dirxiphorg.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/music.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/music.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/page-bg.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/page-bg.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/images/search-box.png
===================================================================
(Binary files differ)


Property changes on: branches/dir.xiph.org/images/search-box.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/dir.xiph.org/inc/class.db.php
===================================================================
--- branches/dir.xiph.org/inc/class.db.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.db.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,301 @@
+<?php
+  
+/**
+ * Database interfaces.
+ *
+ * @version $Id$
+ * @copyright 2005-2007
+ * @author Vincent Tabard
+ */
+
+/**
+* Base database connection class.
+* 
+* Note: izterator and izteratorBuilder are classes from the Yoop framework
+* ({@link http://www.yoop.org/}). Given that izterator implements the interface
+* Iterator (from PHP5's SPL), you can easily adapt this file in order not to use
+* izterator - but these classes are free software, so it might be easier just to
+* download them ;)
+* 
+* @author Vincent Tabard
+*/
+abstract class DataBaseConnection
+{
+	/**
+	* Database server host.
+	* 
+	* @var string
+	*/
+	private $db_host;
+	
+	/**
+	* Database server username.
+	* 
+	* @var string
+	*/
+	private $db_user;
+	
+	/**
+	* Database server password.
+	* 
+	* @var string
+	*/
+	private $db_pass;
+	
+	/**
+	* Database on the database server.
+	* 
+	* @var string
+	*/
+	private $db_base;
+	
+	/**
+	* Database connection handle.
+	* 
+	* @var resource
+	*/
+	protected $db_handle;
+	
+	/**
+	 * Number of rows affected by the last query.
+	 */
+	public $affected_rows = null;
+	
+	/**
+	* Constructor.
+	* 
+	* @param string $host
+	* @param string $user
+	* @param string $pass
+	* @param string $base
+	*/
+	protected function __construct($host, $user, $pass, $base)
+	{
+		$this->db_host = $host;
+		$this->db_user = $user;
+		$this->db_pass = $pass;
+		$this->db_base = $base;
+	}
+	
+	/**
+	* Connects to the database.
+	* 
+	* @return boolean
+	*/
+	public function connect()
+	{
+		// Connexion à la BDD
+		$this->db_handle = @mysql_connect($this->db_host, $this->db_user, $this->db_pass);
+		if ($this->db_handle === false)
+		{
+		    throw new SQLConnectionException();
+		}
+		
+		// Sélection de la base
+		if (!@mysql_select_db($this->db_base))
+		{
+			throw new SQLDBSelectException();
+		}
+		
+		return true;
+	}
+	
+	/**
+	* Performs a query on the database.
+	* 
+	* @param string $sql The query to execute.
+	* @return resource
+	* @access public
+	*/
+	public function query($sql)
+	{
+		$qry = @mysql_query($sql);
+		
+		if ($qry === false)
+		{
+		    throw new SQLBadQueryException();
+		}
+		else
+		{
+			return $qry;
+		}
+	}
+	
+	/**
+	* Performs a query that should return a single (row of) result, or a
+	* boolean (DELETE, UPDATE, ...) query.
+	* 
+	* @param string $sql The query to execute.
+	* @return mixed An izterator on the result row or a boolean.
+	* @access public
+	*/
+	public function singleQuery($sql)
+	{
+		$qry = $this->query($sql);
+		
+		if ($qry === true)
+		{
+		    return true;
+		}
+		elseif (mysql_num_rows($qry) == 0)
+		{
+		    throw new SQLNoResultException();
+		}
+		else
+		{
+			return new izterator(array(mysql_fetch_array($qry)));
+		}
+	}
+	
+	/**
+	* Performs a SELECT query on the database. It does not actually mind what
+	* kind of query you're trying to do, but it tries to return an izterator
+	* over the result set.
+	* 
+	* @param string $sql The query to execute.
+	* @return izterator
+	* @access public
+	*/
+	public function selectQuery($sql)
+	{
+		$qry = $this->query($sql);
+		
+		if (@mysql_num_rows($qry) == 0)
+		{
+		    throw new SQLNoResultException();
+		}
+		else
+		{
+			// On construit un itérateur sur les résultats
+			$izb = new izteratorBuilder();
+			while($row = @mysql_fetch_array($qry))
+			{
+				$izb->push($row);
+			}
+			
+			return $izb->getIzterator();
+		}
+	}
+	
+	/**
+	* Performs an INSERT query on the database. It does not actually mind what
+	* kind of query you're trying to do, but it tries to return the last inserted
+	* ID.
+	* 
+	* @param string $sql The query to execute.
+	* @return mixed An int if the query was successful, (boolean) false else.
+	* @access public
+	*/
+	public function insertQuery($sql)
+	{
+		$qry = $this->singleQuery($sql);
+		
+		if($qry === true)
+		{
+			return @mysql_insert_id($this->db_handle);
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	/**
+	* Performs a query on the database that doesn't return anything.
+	* 
+	* @param string $sql The query to execute.
+	* @return boolean
+	* @access public
+	*/
+	public function noReturnQuery($sql)
+	{
+		$qry = $this->singleQuery($sql);
+		
+		return (boolean) $qry;
+	}
+	
+	/**
+	 * Returns the number of rows affected by the latest query.
+	 * 
+	 * @return int
+	 */
+	public function affectedRows()
+	{
+	    $this->affected_rows = @mysql_affected_rows($this->db_handle);
+	    
+	    return $this->affected_rows;
+	}
+	
+	/**
+	 * Returns an instance of the DatabaseConnection.
+	 */
+	abstract public static function getInstance($force_new = false, $auto_connect = true);
+}
+
+/**
+* Defines an SQL Exception, ie an Exception during a database operation.
+*/
+class SQLException extends Exception
+{
+	/**
+	* Constructor. Takes the message from mysql_error(), and then calls its parent's
+	* constructor (Exception::__construct()).
+	* 
+	* @access public
+	*/
+	function __construct()
+	{
+		$this->message = mysql_error();
+		
+		parent::__construct($this->message);
+	}
+}
+
+/**
+* Defines an SQL Exception <b>on connection</b>, ie that happens
+* during the connection (bad credentials, connection lost...).
+*/
+class SQLOnConnectException extends SQLException
+{
+}
+
+/**
+* Defines an SQL On Connect Exception related to the connection process
+* itself, and not to the database selection that takes place afterwards.
+*/
+class SQLConnectionException extends SQLOnConnectException
+{
+}
+
+/**
+* Defines an SQL On Connect Exception related to the database selection
+* process.
+*/
+class SQLDBSelectException extends SQLOnConnectException
+{
+}
+
+/**
+* Defines an SQL Exception <b>on a query</b>, ie that happens during
+* or after a query.
+*/
+class SQLOnQueryException extends SQLException
+{
+}
+
+/**
+* Defines an SQL On Query Exception caused by a bad SQL query (bad syntax, ...).
+*/
+class SQLBadQueryException extends SQLOnQueryException
+{
+}
+
+/**
+* Defines an SQL On Query Exception that arises from the lack of result (ie there's
+* no answer).
+*/
+class SQLNoResultException extends SQLOnQueryException
+{
+}
+  
+?>

Added: branches/dir.xiph.org/inc/class.izterator.php
===================================================================
--- branches/dir.xiph.org/inc/class.izterator.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.izterator.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,135 @@
+<?php
+
+/**
+* Izterator - implements the PHP5 interface "Iterator".
+* 
+* @author Loïc Mathaud
+* @package izterator
+*/
+class izterator implements Iterator
+{
+	var $array_data;
+	var $array_index;
+	var $nbResults;
+
+	/**
+	 * Constructeur
+	 * 
+	 * Affecte le tableau passé en paramètre à une variable de classe puis
+	 * appelle la méthode de classe {@link rewind()}
+	 * 
+	 * @param array $array tableau de résultat MySQL depuis classe dbMysql
+	 * @access public 
+	 */
+	function izterator($array)
+	{
+		if (is_array($array))
+		{
+			$this->array_data = $array;
+		} 
+		$this->nbResults = count($array);
+		$this->rewind();
+	} 
+
+	/**
+	 * Retourne la valeur du champ spécifié par $field pour l'index courant
+	 * ou retourne un tableau de tout l'enregistrement pour ce même index
+	 * 
+	 * @param string $field champ à afficher pour l'enregistrement courant
+	 * @access public 
+	 */
+	function current($field = '')
+	{
+		if (empty($field))
+		{
+			return $this->array_data[$this->array_index];
+		} 
+		else
+		{
+			//$field = strtolower($field);
+			return $this->array_data[$this->array_index][$field];
+		} 
+	} 
+
+	/**
+	 * Avance le curseur sur l'enregistrement suivant
+	 * 
+	 * @access public 
+	 */
+	function next()
+	{
+		$this->array_index++;
+		return true;
+	} 
+
+	/**
+	 * Retourne TRUE si on est à la fin de la pile, FALSE sinon
+	 * 
+	 * @access public 
+	 */
+	function endof()
+	{
+		if (($this->array_index + 1) <= $this->nbResults)
+		{
+			return false;
+		} 
+		else
+		{
+			return true;
+		} 
+	} 
+
+	/**
+	 * Retourne l'index pointé actuellement
+	 * 
+	 * @access public 
+	 */
+	function key()
+	{
+		return $this->array_index;
+	} 
+
+	/**
+	 * Fait pointer le curseur sur le premier enregistrement
+	 * 
+	 * @access public 
+	 */
+	function rewind()
+	{
+		$this->array_index = 0;
+		return true;
+	} 
+
+	/**
+	 * Fait pointer le curseur sur le dernier enregistrement
+	 * 
+	 * @access public 
+	 */
+	function end()
+	{
+		$this->array_index = $this->nbResults - 1;
+		return true;
+	} 
+
+	/**
+	 * Retourne le nombre d'enregistrements dans la pile
+	 * 
+	 * @access public 
+	 */
+	function count()
+	{
+		return $this->nbResults;
+	} 
+
+	/**
+	 * Pour la validité de l'interface.
+	 * 
+	 * @access public 
+	 */
+	function valid()
+	{
+		return !$this->endof();
+	} 
+} 
+
+?>

Added: branches/dir.xiph.org/inc/class.izteratorbuilder.php
===================================================================
--- branches/dir.xiph.org/inc/class.izteratorbuilder.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.izteratorbuilder.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,73 @@
+<?php
+
+/**
+* Builds an iterator.
+* 
+* @author Vincent Tabard
+* @package izterator
+*/
+class izteratorBuilder
+{
+	/**
+	* @var array
+	* @access private
+	*/
+	var $array;
+	
+	/**
+	* Constructor. Does not do anything (but needs to be called).
+	* 
+	* @author Vincent Tabard
+	*/
+	function izteratorBuilder()
+	{
+		$this->array = array();
+	}
+	
+	/**
+	* Adds an element at the end of the izterator.
+	* 
+	* @param mixed $element The element to push.
+	* @return void
+	* @author Vincent Tabard
+	*/
+	function push($element)
+	{
+		$this->array[] = $element;
+	}
+	
+	/**
+	* Removes the last element from the izterator.
+	* 
+	* @return void
+	* @author Vincent Tabard
+	*/
+	function pop()
+	{
+		$value = array_pop($this->array);
+		
+		return $value;
+	}
+	
+	/**
+	* Returns the izterator we've built.
+	* 
+	* @param boolean $mourirEnEnfantant Vide la pile après l'avoir renvoyée dans un izterator.
+	* @return Iterator
+	* @author Vincent Tabard
+	*/
+	function getIzterator($mourirEnEnfantant=false)
+	{
+		$izterator = new izterator($this->array);
+		
+		if ($mourirEnEnfantant)
+		{
+		    $this->array = array();
+		}
+		
+		return $izterator;
+	}
+}
+
+
+?>
\ No newline at end of file

Added: branches/dir.xiph.org/inc/class.mc.php
===================================================================
--- branches/dir.xiph.org/inc/class.mc.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.mc.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,23 @@
+<?php
+
+abstract class MemcacheConnection extends Memcache
+{
+	
+	/**
+	 * Constructor.
+	 * 
+	 * @param string $host
+	 * @param string $port
+	 */
+	protected function __construct($host, $port = 11211)
+	{
+		$this->addServer($host, $port);
+	}
+	
+	/**
+	 * Returns an instance of the MemcacheConnection.
+	 */
+	abstract public static function getInstance($force_new = false);
+}
+
+?>

Added: branches/dir.xiph.org/inc/class.mountpoint.php
===================================================================
--- branches/dir.xiph.org/inc/class.mountpoint.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.mountpoint.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,370 @@
+<?php
+
+class Mountpoint
+{
+    protected $mountpoint_id = null;
+    protected $table_name = 'mountpoint';
+    protected $cache_expiration = 60;
+    public $loaded = false;
+    
+    protected $stream_name;
+    protected $description;
+    protected $url;
+    protected $listeners;
+    protected $current_song;
+    protected $media_type;
+    protected $media_type_id;
+    protected $bitrate;
+    protected $channels;
+    protected $samplerate;
+    protected $cluster_password;
+    
+    public function __construct($mountpoint_id, $force_reload = false, $no_load = false)
+    {
+        if (!is_numeric($mountpoint_id) || $mountpoint_id < 0)
+        {
+            throw new BadIdException("Bad ID: " . (string) $mountpoint_id);
+        }
+        $this->mountpoint_id = intval($mountpoint_id);
+        
+        if (!$no_load)
+        {
+            if ($force_reload)
+            {
+                $this->loadFromDb();
+            }
+            else
+            {
+                if (!$this->loadFromCache())
+                {
+                    $this->loadFromDb();
+                    $this->saveIntoCache();
+                }
+            }
+        }
+    }
+    
+    /**
+     * Saves the mountpoint data both into the database and into the cache.
+     * 
+     * @return boolean
+     */
+    public function save()
+    {
+        $mp_id = $this->saveIntoDb();
+        $this->saveIntoCache();
+        
+        return $mp_id;
+    }
+    
+    /**
+     * Retrieves the mountpoint from either the db or the cache.
+     * 
+     * @return Mountpoint or false if an error occured.
+     */
+    public static function retrieveByPk($pk)
+    {
+        $m = new Mountpoint($pk);
+        
+        return ($m->loaded ? $m : false);
+    }
+    
+    /**
+     * Saves the mountpoint into the database.
+     * 
+     * @return integer
+     */
+    protected function saveIntoDb()
+    {
+        // MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Query
+        $query = 'INSERT INTO `%1$s` (`stream_name`, `description`, `url`, `listeners`, `current_song`, `media_type_id`, `bitrate`, `channels`, `samplerate`, `cluster_password`) '
+			    .'VALUES ("%2$s", %3$s, %4$s, %5$d, %6$s, %7$d, "%8$s", %9$s, %10$s, %11$s) '
+			    .'ON DUPLICATE KEY UPDATE `stream_name` = "%2$s", `description` = %3$s, `url` = %4$s, `listeners` = %5$d, `current_song` = %6$s, `media_type_id` = %7$d, `bitrate` = "%8$s", `channels` = %9$s, `samplerate` = %10$s, `cluster_password` = %11$s;';
+	    $query = sprintf($query, $this->table_name,
+	                             mysql_real_escape_string($this->stream_name),
+							     ($this->description != null) ? '"'.mysql_real_escape_string($this->description).'"' : 'NULL',
+							     ($this->url != null) ? '"'.mysql_real_escape_string($this->url).'"' : 'NULL',
+							     ($this->listeners != null) ? $this->listeners : 0,
+							     ($this->current_song != null) ? '"'.mysql_real_escape_string($this->current_song).'"' : 'NULL',
+							     $this->media_type_id,
+							     mysql_real_escape_string($this->bitrate),
+							     ($this->channels != null) ? intval($this->channels) : 'NULL',
+							     ($this->samplerate != null) ? intval($this->samplerate) : 'NULL',
+							     ($this->cluster_password != null) ? '"'.mysql_real_escape_string($this->cluster_password).'"' : 'NULL');
+	    $mp_id = $db->insertQuery($query);
+	    if ($mp_id !== false && $this->mountpoint_id == 0)
+	    {
+	        $this->mountpoint_id = $mp_id;
+	    }
+	    
+	    return $mp_id;
+    }
+    
+    /**
+     * Loads the data from the database.
+     * 
+     * @return boolean
+     */
+    protected function loadFromDb()
+    {
+        // MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Query
+        $query = "SELECT * FROM `%s` WHERE `id` = %d;";
+        $query = sprintf($query, $this->table_name, $this->mountpoint_id);
+        try
+        {
+            $m = $db->singleQuery($query);
+        }
+        catch (SQLNoResultException $e)
+        {
+            return false;
+        }
+        
+        if (!$m)
+        {
+            return false;
+        }
+        
+        $this->loadFromArray($m->array_data[0]);
+        
+        return true;
+    }
+    
+    /**
+     * Saves the data into the cache.
+     * 
+     * @return boolean
+     */
+    protected function saveIntoCache()
+    {
+        // Memcache connection
+        $cache = DirXiphOrgMCC::getInstance();
+        
+        $a = $this->__toArray();
+        
+        return $cache->set($this->getCacheKey(), $a, false, $this->cache_expiration);
+    }
+    
+    /**
+     * Loads the data from the cache.
+     * 
+     * @return boolean
+     */
+    protected function loadFromCache()
+    {
+        // Memcache connection
+        $cache = DirXiphOrgMCC::getInstance();
+        
+        $a = $cache->get($this->getCacheKey());
+        if ($a === false)
+        {
+            return false;
+        }
+        
+        return $this->loadFromArray($a);
+    }
+    
+    /**
+     * Builds a cache key for this mountpoint.
+     * 
+     * @return string
+     */
+    protected function getCacheKey()
+    {
+        if (!defined('ENVIRONMENT'))
+        {
+            throw new EnvironmentUndefinedException();
+        }
+        
+        return sprintf("%s_mountpoint_%d", ENVIRONMENT, $this->mountpoint_id);
+    }
+    
+    /**
+     * Serializes the data into an array
+     * 
+     * @return array
+     */
+    protected function saveIntoArray()
+    {
+        return $this->__toArray();
+    }
+    
+    /**
+     * Loads the data from an array.
+     * 
+     * @return boolean
+     */
+    protected function loadFromArray($a)
+    {
+        $this->stream_name = $a['stream_name'];
+        $this->description = $a['description'];
+        $this->url = $a['url'];
+        $this->listeners = intval($a['listeners']);
+        $this->current_song = $a['current_song'];
+        $this->media_type_id = $a['media_type_id'];
+        $this->bitrate = $a['bitrate'];
+        $this->channels = $a['channels'];
+        $this->samplerate = $a['samplerate'];
+        $this->cluster_password = $a['cluster_password'];
+        
+        $this->loaded = true;
+        
+        return true;
+    }
+    
+    /**
+     * Packs the object contents into an array.
+     * 
+     * @return array
+     */
+    protected function __toArray()
+    {
+        $a = array();
+        
+        $a['stream_name'] = $this->stream_name;
+        $a['description'] = $this->description;
+        $a['url'] = $this->url;
+        $a['listeners'] = intval($this->listeners);
+        $a['current_song'] = $this->current_song;
+        $a['media_type_id'] = $this->media_type_id;
+        $a['bitrate'] = $this->bitrate;
+        $a['channels'] = $this->channels;
+        $a['samplerate'] = $this->samplerate;
+        $a['cluster_password'] = $this->cluster_password;
+        
+        return $a;
+    }
+    
+    public function getId()
+    {
+        return $this->mountpoint_id;
+    }
+    
+    public function setStreamName($sn)
+    {
+        $this->stream_name = $sn;
+    }
+    
+    public function getStreamName()
+    {
+        return $this->stream_name;
+    }
+    
+    public function setDescription($d)
+    {
+        $this->description = $d;
+    }
+    
+    public function getDescription()
+    {
+        return $this->description;
+    }
+    
+    public function setUrl($u)
+    {
+        $this->url = $u;
+    }
+    
+    public function getUrl()
+    {
+        return $this->url;
+    }
+    
+    public function setListeners($l)
+    {
+        $this->listeners = $l;
+    }
+    
+    public function getListeners()
+    {
+        return $this->listeners;
+    }
+    
+    public function setCurrentSong($cs)
+    {
+        $this->current_song = $cs;
+    }
+    
+    public function getCurrentSong()
+    {
+        return $this->current_song;
+    }
+    
+    public function setMediaTypeId($mti)
+    {
+        $this->media_type_id = $mti;
+    }
+    
+/*    public function setMediaType(MediaType $mt)
+    {
+        $this->media_type = $mt;
+        $this->media_type_id = $mt->getId();
+    }*/
+    
+    public function getMediaTypeId()
+    {
+        return $this->media_type_id;
+    }
+    
+/*    public function getMediaType()
+    {
+        if (!isset($this->media_type))
+        {
+            $this->media_type = new MediaType($this->media_type_id);
+        }
+        
+        return $this->media_type;
+    }*/
+    
+    public function setBitrate($b)
+    {
+        $this->bitrate = $b;
+    }
+    
+    public function getBitrate()
+    {
+        return $this->bitrate;
+    }
+    
+    public function setChannels($c)
+    {
+        $this->channels = $c;
+    }
+    
+    public function getChannels()
+    {
+        return $this->channels;
+    }
+    
+    public function setSamplerate($s)
+    {
+        $this->samplerate = $s;
+    }
+    
+    public function getSamplerate()
+    {
+        return $this->samplerate;
+    }
+    
+    public function setClusterPassword($cp)
+    {
+        $this->cluster_password = $cp;
+    }
+    
+    public function getClusterPassword()
+    {
+        return $this->cluster_password;
+    }
+    
+    public function getTags()
+    {
+        return Tag::getMountpointTags($this->mountpoint_id);
+    }
+}
+
+?>

Added: branches/dir.xiph.org/inc/class.server.php
===================================================================
--- branches/dir.xiph.org/inc/class.server.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.server.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,299 @@
+<?php
+
+class Server
+{
+    protected $server_id;
+    protected $table_name = 'server';
+    protected $cache_expiration = 60;
+    public $loaded = false;
+    
+    protected $mountpoint_id;
+    protected $sid;
+    protected $current_song;
+    protected $listen_url;
+    protected $listeners;
+    protected $last_touched_at;
+    protected $last_touched_from;
+    
+    public function __construct($server_id, $force_reload = false, $no_load = false)
+    {
+        if (!is_numeric($server_id) || $server_id < 0)
+        {
+            throw new BadIdException($mountpoint_id);
+        }
+        $this->server_id = intval($server_id);
+        
+        if (!$no_load)
+        {
+            if ($force_reload)
+            {
+                $this->loadFromDb();
+            }
+            else
+            {
+                if (!$this->loadFromCache())
+                {
+                    $this->loadFromDb();
+                }
+            }
+        }
+    }
+    
+    /**
+     * Saves the server data both into the database and into the cache.
+     * 
+     * @return boolean
+     */
+    public function save()
+    {
+        $mp_id = $this->saveIntoDb();
+        $this->saveIntoCache();
+        
+        return $mp_id;
+    }
+    
+    /**
+     * Retrieves the server from either the db or the cache.
+     * 
+     * @return Server or false if an error occured.
+     */
+    public static function retrieveByPk($pk)
+    {
+        $s = new Server($pk);
+        
+        return ($s->loaded ? $s : false);
+    }
+    
+    /**
+     * Saves the server into the database.
+     * 
+     * @return integer
+     */
+    protected function saveIntoDb()
+    {
+        // MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Query
+        $query = 'INSERT INTO `%1$s` (`mountpoint_id`, `sid`, `current_song`, `listen_url`, `listeners`, `last_touched_at`, `last_touched_from`) '
+			    .'VALUES (%2$d, "%3$s", %4$s, "%5$s", %6$d, %7$s, INET_ATON("%8$s")) '
+			    .'ON DUPLICATE KEY UPDATE `mountpoint_id` = %2$d, `sid` = "%3$s", `current_song` = %4$s, `listen_url` = "%5$s", `listeners` = %6$d, `last_touched_at` = %7$s, `last_touched_from` = INET_ATON("%8$s");';
+	    $query = sprintf($query, $this->table_name,
+	                             $this->mountpoint_id,
+							     mysql_real_escape_string($this->sid),
+							     ($this->current_song != null) ? '"'.mysql_real_escape_string($this->current_song).'"' : 'NULL',
+							     mysql_real_escape_string($this->listen_url),
+							     ($this->listeners != null) ? $this->listeners : 0,
+							     ($this->last_touched_at != null) ? '"'.mysql_real_escape_string($this->last_touched_at).'"' : 'NOW()',
+							     mysql_real_escape_string($this->last_touched_from));
+	    $server_id = $db->insertQuery($query);
+	    
+	    return $server_id;
+    }
+    
+    /**
+     * Loads the data from the database.
+     * 
+     * @return boolean
+     */
+    protected function loadFromDb()
+    {
+        // MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Query
+        $query = "SELECT `mountpoint_id`, `sid`, `current_song`, `listen_url`, `listeners`, `last_touched_at`, INET_NTOA(`last_touched_from`) AS `last_touched_from` FROM `%s` WHERE `id` = %d;";
+        $query = sprintf($query, $this->table_name, $this->server_id);
+        $m = $db->singleQuery($query);
+        
+        if (!$m)
+        {
+            return false;
+        }
+        
+        $this->loadFromArray($m->array_data);
+        
+        return true;
+    }
+    
+    /**
+     * Saves the data into the cache.
+     * 
+     * @return boolean
+     */
+    protected function saveIntoCache()
+    {
+        // Memcache connection
+        $cache = DirXiphOrgMCC::getInstance();
+        
+        $a = $this->__toArray();
+        
+        return $cache->set($this->getCacheKey(), $a, false, $this->cache_expiration);
+    }
+    
+    /**
+     * Loads the data from the cache.
+     * 
+     * @return boolean
+     */
+    protected function loadFromCache()
+    {
+        // Memcache connection
+        $cache = DirXiphOrgMCC::getInstance();
+        
+        $a = $cache->get($this->getCacheKey());
+        if ($a === false)
+        {
+            return false;
+        }
+        
+        return $this->loadFromArray($a);
+    }
+    
+    /**
+     * Builds a cache key for this mountpoint.
+     * 
+     * @return string
+     */
+    protected function getCacheKey()
+    {
+        if (!defined('ENVIRONMENT'))
+        {
+            throw new EnvironmentUndefinedException();
+        }
+        
+        return sprintf("%s_server_%d", ENVIRONMENT, $this->server_id);
+    }
+    
+    /**
+     * Serializes the data into an array
+     * 
+     * @return array
+     */
+    protected function saveIntoArray()
+    {
+        return $this->__toArray();
+    }
+    
+    /**
+     * Loads the data from an array.
+     * 
+     * @return boolean
+     */
+    protected function loadFromArray($a)
+    {
+        $this->mountpoint_id = $a['mountpoint_id'];
+        $this->sid = $a['sid'];
+        $this->current_song = $a['current_song'];
+        $this->listen_url = $a['listen_url'];
+        $this->listeners = intval($a['listeners']);
+        $this->last_touched_at = $a['last_touched_at'];
+        $this->last_touched_from = $a['last_touched_from'];
+        
+        $this->loaded = true;
+        
+        return true;
+    }
+    
+    /**
+     * Packs the object contents into an array.
+     * 
+     * @return array
+     */
+    protected function __toArray()
+    {
+        $a = array();
+        
+        $a['mountpoint_id'] = $this->mountpoint_id;
+        $a['sid'] = $this->sid;
+        $a['current_song'] = $this->current_song;
+        $a['listen_url'] = $this->listen_url;
+        $a['listeners'] = intval($this->listeners);
+        $a['last_touched_at'] = $this->last_touched_at;
+        $a['last_touched_from'] = $this->last_touched_from;
+        
+        return $a;
+    }
+    
+    public function getId()
+    {
+        return $this->server_id;
+    }
+    
+    public function setMountpointId($mp_id)
+    {
+        $this->mountpoint_id = $mp_id;
+    }
+    
+    public function getMountpointId()
+    {
+        return $this->mountpoint_id;
+    }
+    
+    public function setSid($s)
+    {
+        $this->sid = $s;
+    }
+    
+    public function getSid()
+    {
+        return $this->sid;
+    }
+    
+    public function setCurrentSong($cs)
+    {
+        $this->current_song = $cs;
+    }
+    
+    public function getCurrentSong()
+    {
+        return $this->current_song;
+    }
+    
+    public function setListenUrl($u)
+    {
+        $this->listen_url = $u;
+    }
+    
+    public function getListenUrl()
+    {
+        return $this->listen_url;
+    }
+    
+    public function setListeners($l)
+    {
+        $this->listeners = $l;
+    }
+    
+    public function getListeners()
+    {
+        return $this->listeners;
+    }
+    
+    public function setLastTouchedAt($at = null)
+    {
+        if ($at === null)
+        {
+            $at = date('Y-m-d H:i:s');
+        }
+        
+        $this->last_touched_at = $at;
+    }
+    
+    public function getLastTouchedAt()
+    {
+        return $this->last_touched_at;
+    }
+    
+    public function setLastTouchedFrom($f)
+    {
+        $this->last_touched_from = $f;
+    }
+    
+    public function getLastTouchedFrom()
+    {
+        return $this->last_touched_from;
+    }
+}
+
+?>

Added: branches/dir.xiph.org/inc/class.tag.php
===================================================================
--- branches/dir.xiph.org/inc/class.tag.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.tag.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,123 @@
+<?php
+
+class Tag
+{
+    protected static $mountpoints_tags_cache_expiration = 900; // 15 min
+    
+    /**
+     * Adds many tags to a mountpoint.
+     * 
+     * @param Mountpoint $mountpoint
+     * @param array $tags
+     * @return boolean
+     */
+    public static function massTagMountpoint(Mountpoint $mountpoint, $tags)
+    {
+        // MySQL Connection
+		$db = DirXiphOrgDBC::getInstance();
+		
+		// Add the tags
+		$query = 'INSERT IGNORE INTO `tag` (`tag_name`) VALUES %s;';
+		$tags_clause = array();
+		foreach ($tags as $t)
+		{
+			$tags_clause[] = sprintf('("%s")', mysql_real_escape_string(strtolower($t)));
+		}
+		$tags_clause = implode(', ', $tags_clause);
+		$query = sprintf($query, $tags_clause);
+//		fwrite($fp, "Adding tags: ".$query."\n");
+		$db->noReturnQuery($query);
+		
+		// Get back the tags IDs
+		$query = 'SELECT `id` FROM `tag` WHERE `tag_name` IN (%s);';
+		$tags_clause = array();
+		foreach ($tags as $t)
+		{
+			$tags_clause[] = sprintf('"%s"', mysql_real_escape_string(strtolower($t)));
+		}
+		$tags_clause = implode(', ', $tags_clause);
+		$query = sprintf($query, $tags_clause);
+//		fwrite($fp, "Fetching back tags: ".$query."\n");
+		$tags = $db->selectQuery($query);
+		
+		// Link the mountpoint to the tags
+		$query = 'INSERT IGNORE INTO `mountpoints_tags` (`mountpoint_id`, `tag_id`) VALUES %s;';
+		$tags_clause = array();
+		while (!$tags->endOf())
+		{
+			$tags_clause[] = sprintf('(%d, %d)', $mountpoint->getId(), $tags->current('id'));
+			$tags->next();
+		}
+		$tags->rewind();
+		$tags_clause = implode(', ', $tags_clause);
+		$query = sprintf($query, $tags_clause);
+//		fwrite($fp, "Linking tags: ".$query."\n");
+		$db->noReturnQuery($query);
+		
+		// Increment the tag cloud values
+		$query = 'INSERT INTO `tag_cloud` (`tag_id`, `tag_usage`) VALUES %s ON DUPLICATE KEY UPDATE `tag_usage` = `tag_usage` + 1;';
+		$tags_clause = array();
+		while (!$tags->endOf())
+		{
+			$tags_clause[] = sprintf('(%d, 1)', $tags->current('id'));
+			$tags->next();
+		}
+		$tags_clause = implode(', ', $tags_clause);
+		$query = sprintf($query, $tags_clause);
+//		fwrite($fp, "Incrementing tags: ".$query."\n");
+		$db->noReturnQuery($query);
+    }
+    
+    /**
+     * Returns all of the tags linked to a particular mountpoint.
+     * 
+     * @param int $mountpoint_id
+     * @return array
+     */
+    public static function getMountpointTags($mountpoint_id)
+    {
+        // Memcache Connection
+		$mc = DirXiphOrgMCC::getInstance();
+		
+		if (!defined('ENVIRONMENT'))
+        {
+            throw new EnvironmentUndefinedException();
+        }
+        
+        $key = sprintf("%s_mountpointtags_%d", ENVIRONMENT, $mountpoint_id);
+        
+        $tags = $mc->get($key);
+        if ($tags !== false)
+        {
+            return $tags;
+        }
+        else
+        {
+            // MySQL Connection
+		    $db = DirXiphOrgDBC::getInstance();
+		    
+		    $sql = "SELECT mt.`tag_id`, t.`tag_name` FROM `mountpoints_tags` AS mt INNER JOIN `tag` AS t ON mt.`tag_id` = t.`id` WHERE mt.`mountpoint_id` = %d;";
+		    $sql = sprintf($sql, intval($mountpoint_id));
+		    try
+		    {
+    		    $data = $db->selectQuery($sql);
+    		    
+    		    $res = array();
+    		    while (!$data->endOf())
+    		    {
+    		        $res[$data->current('tag_id')] = $data->current('tag_name');
+    		        $data->next();
+    		    }
+    		    
+    		    $mc->set($key, $res, false, self::$mountpoints_tags_cache_expiration);
+    		    return $res;
+		    }
+		    catch (SQLNoResultException $e)
+		    {
+		        return array();
+	        }
+		}
+    }
+}
+
+?>

Added: branches/dir.xiph.org/inc/class.ypclient.php
===================================================================
--- branches/dir.xiph.org/inc/class.ypclient.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/class.ypclient.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,267 @@
+<?php
+
+class YPClient
+{
+	protected $url;
+	
+	public function __construct($url)
+	{
+		$this->url = $url;
+	}
+	
+	public function addServer($name, $type, $genre, $bitrate, $listen_url,
+							  $desc=null, $url=null, $stype=null,
+							  $cluster_password=null)
+	{
+		// Mandatory parameters
+		$query_string = "?action=add&sn=%s&type=%s&genre=%s&b=%s&listenurl=%s";
+		$query_string = sprintf($query_string,
+								urlencode($name),
+								urlencode($type),
+								urlencode($genre),
+								urlencode($bitrate),
+								urlencode($listen_url));
+		
+		// Optional parameters
+		if ($desc !== null)
+		{
+			$query_string .= '&desc='.urlencode($desc);
+		}
+		if ($url !== null)
+		{
+			$query_string .= '&url='.urlencode($url);
+		}
+		if ($stype !== null)
+		{
+			$query_string .= '&stype='.urlencode($stype);
+		}
+		if ($cpswd !== null)
+		{
+			$query_string .= '&cpswd='.urlencode($cpswd);
+		}
+		
+		$url = $this->url.$query_string;
+		$result = $this->makeAPICall($url);
+		
+		if (array_key_exists('YPResponse', $result)
+			&& intval($result['YPResponse']) == 1
+			&& array_key_exists('SID', $result))
+		{
+			return $result['SID'];
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	public function touch($sid, $song_title=null, $listeners=null,
+						  $max_listeners=null)
+	{
+		$url = $this->url.'?action=touch&sid='.urlencode($sid);
+		if ($song_title !== null)
+		{
+			$url .= '&st='.urlencode($song_title);
+		}
+		if ($listeners !== null)
+		{
+			$url .= '&listeners='.urlencode($listeners);
+		}
+		if ($max_listeners !== null)
+		{
+			$url .= '&max_listeners='.urlencode($max_listeners);
+		}
+		$result = $this->makeAPICall($url);
+		
+		return (array_key_exists('YPResponse', $result)
+				&& intval($result['YPResponse']) == 1);
+	}
+	
+	public function removeServer($sid)
+	{
+		$url = $this->url.'?action=remove&sid='.urlencode($sid);
+		$result = $this->makeAPICall($url);
+		
+		return (array_key_exists('YPResponse', $result)
+				&& intval($result['YPResponse']) == 1);
+	}
+	
+	protected function makeAPICall($url)
+	{
+		$url = parse_url($url);
+		$fp = fsockopen($url['host'],
+						array_key_exists('port', $url) ? $url['port'] : 80,
+						$errno,
+						$errstr,
+						2); // 2 s timeout
+		if (!$fp)
+		{
+			printf("Error %d: %s.\n", $errno, $errstr);
+			
+			return false;
+		}
+		
+		fwrite($fp, sprintf("GET %s?%s HTTP/1.0\r\n\r\n", $url['path'],
+														  $url['query']));
+		$data = array();
+		while ($n = fgets($fp))
+		{
+			if (trim($n) != '')
+			{
+				list($header, $value) = explode(':', $n);
+				$data[trim($header)] = trim($value);
+			}
+		}
+		fclose($fp);
+		
+		return $data;
+	}
+}
+
+function make_prononcable_string($length = 10, $length_variation = 0.1, $space_probability = 20)
+{
+	$vowels = array('a', 'e', 'i', 'o', 'u', 'y');
+	$consonnants = array('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n',
+				 		 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z');
+	
+	$l = rand($length - ($length * $length_variation),
+			  $length + ($length * $length_variation));
+	$acc0 = 0;
+	$acc1 = 0;
+	$str = '';
+	for ($i = 0 ; $i < $l ; $i++)
+	{
+		$r = rand(0, $acc1);
+		if ($r == 0)
+		{
+			$str .= ' ';
+			$acc1 = $space_probability;
+		}
+		else
+		{
+			if ($acc0 % 2 == 0)
+			{
+				shuffle($consonnants);
+				$str .= current($consonnants);
+			}
+			else
+			{
+				shuffle($vowels);
+				$str .= current($vowels);
+			}
+		}
+		
+		$acc0++;
+		$acc1--;
+	}
+	
+	return preg_replace('/ +/', ' ', trim($str));
+}
+
+// New client
+$c = new YPClient("http://localhost/dir.xiph.org/yp.php");
+
+// Loop
+$stream_types = array('audio/mpeg', 'application/ogg+vorbis',
+					  'audio/aac', 'audio/aacp', 'application/ogg+theora',
+					  'video/nsv');
+$stations = array();
+$creation_probability = 10;
+$deletion_probability = 100;
+while (true)
+{
+	foreach ($stations as $k=>$s)
+	{
+		if ($s['timeout'] == 0)
+		{
+			// Stream title
+			$artist = ucwords(make_prononcable_string(13, 0.4, 10));
+			$song = ucwords(make_prononcable_string(20, 0.7, 10));
+			$st = sprintf('%s -- %s', $artist, $song);
+			
+			// Listeners
+			$listeners_delta = $s['previous_listeners'] * 0.4;
+			$listeners = rand(min($s['previous_listeners'] - $listeners_delta,
+								  0),
+							  max($s['previous_listeners'] + $listeners_delta,
+								  $s['max_listeners']));
+			$listeners += rand(0, max(10, $listeners_delta)); // kickstart + random peaks
+			
+			printf("Touching SID %s\n", $s['sid']);
+			$c->touch($s['sid'], $st, $listeners, $s['max_listeners']);
+			$stations[$k]['previous_listeners'] = $listeners;
+			$stations[$k]['timeout'] = rand(25, 35); // ~30 s
+		}
+		else
+		{
+			$stations[$k]['timeout']--;
+		}
+	}
+	
+	if (rand(0, $creation_probability) == 0)
+	{
+		// Name
+		$sn = ucwords(make_prononcable_string(10, 0.1, 15));
+		
+		// Type
+		$type = $stream_types[array_rand($stream_types)];
+		
+		// Tags
+		$tags = make_prononcable_string(25, 0.4, 10);
+		
+		// Bitrate
+		$bitrate = rand(0, 384);
+		
+		// URL
+		$url = 'http://localhost:'.rand(1024, 65535).'/'
+				.preg_replace('/ +/', '-', strtolower($sn));
+		
+		// Description
+		$desc = ucfirst(make_prononcable_string(40, 0.4, 10));
+		
+		// Now register
+		$sid = $c->addServer($sn, $type, $tags, $bitrate, $url, $desc);
+		printf("Added server '%s', SID %s\n", $sn, $sid);
+		
+		if ($sid !== false)
+		{
+			// Remember, camembert.
+			$max_listeners = rand(300, 30000);
+			$timeout = rand(1, 5);
+			$s = array('name'=>$sn, 'type'=>$type, 'tags'=>$tags,
+					   'bitrate'=>$bitrate, 'url'=>$url,
+					   'previous_listeners'=>0, 'max_listeners'=>0,
+					   'timeout'=>$timeout, 'sid'=>$sid);
+			$stations[] = $s;
+		}
+		else
+		{
+			echo("Unable to register server.\n");
+		}
+	}
+	
+	if (rand(0, $deletion_probability) == 0)
+	{
+		$id = array_rand($stations);
+		if ($id !== false)
+		{
+			$sid = $stations[$id]['sid'];
+		
+			printf("Removing SID %s\n", $sid);
+			if ($c->removeServer($sid))
+			{
+				unset($stations[$id]);
+			}
+		}
+	}
+	
+	sleep(1);
+}
+
+/*
+$sid = $c->addServer('Test Server', 'audio/mpeg', 'Random', 32, 'http://localhost:2435/prout.ogg');
+var_dump($sid);
+var_dump($c->touch($sid, 'Unknown', 234, 2452));
+var_dump($c->removeServer($sid));
+*/
+?>
\ No newline at end of file

Added: branches/dir.xiph.org/inc/inc.db.php
===================================================================
--- branches/dir.xiph.org/inc/inc.db.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/inc.db.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Connection parameters for dir.xiph.org
+ **/
+
+define('CP_DB_HOST', 'localhost');
+define('CP_DB_USER', 'dir_xiph_org_t');
+define('CP_DB_PASS', '6.NvxjR7B5j3Q');
+define('CP_DB_NAME', 'dir_xiph_org_test');
+
+/**
+ * Database connection class with semi-hard-coded (?!) login and stuff.
+ */
+class DirXiphOrgDBC extends DatabaseConnection
+{
+    private static $instance;
+	public $queries = 0;
+	public $log = array();
+	
+	/**
+	* Constructor.
+	*/
+	protected function __construct()
+	{
+		parent::__construct(CP_DB_HOST, CP_DB_USER, CP_DB_PASS, CP_DB_NAME);
+	}
+	
+	public function query($sql)
+	{
+		$this->queries++;
+		$start = microtime(true);
+		
+		$res = parent::query($sql);
+		$nb = $this->affectedRows();
+		
+		$time = (microtime(true) - $start) * 1000;
+		$this->log[] = array('query'=>$sql,
+		                     'time'=>round($time, 3),
+		                     'rows'=>$nb);
+		
+		return $res;
+	}
+	
+	/**
+	 * Returns an instance of the DatabaseConnection.
+	 */
+	public static function getInstance($force_new = false, $auto_connect = true)
+	{ 
+		if (!isset(self::$instance) || $force_new)
+		{
+			self::$instance = new DirXiphOrgDBC();
+		}
+		
+		if ($auto_connect)
+		{
+		    self::$instance->connect();
+		}
+		
+		return self::$instance;
+	}
+}
+
+?>

Added: branches/dir.xiph.org/inc/inc.mc.php
===================================================================
--- branches/dir.xiph.org/inc/inc.mc.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/inc.mc.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,67 @@
+<?php
+
+define('MC_HOST', 'localhost');
+define('MC_PORT', 11211);
+
+class DirXiphOrgMCC extends MemcacheConnection
+{
+	/**
+	 * Singleton instance.
+	 */
+	protected static $instance;
+	public $gets = 0;
+	public $sets = 0;
+	public $log = array();
+	
+    protected function __construct()
+    {
+        parent::__construct(MC_HOST, MC_PORT);
+    }
+    
+    public function get($key)
+    {
+        $this->gets++;
+		$start = microtime(true);
+		
+		$res = parent::get($key);
+		
+		$time = (microtime(true) - $start) * 1000;
+		$this->log[] = array('key'=>is_string($key) ? $key : implode(', ', $key),
+		                     'hit'=>($res !== false),
+		                     'time'=>round($time, 3),
+		                     'type'=>'get');
+		
+		return $res;
+    }
+    
+    public function set($key, $var, $flag=0, $expire=0)
+    {
+        $this->sets++;
+		$start = microtime(true);
+		
+		$res = parent::set($key, $var, $flag, $expire);
+		
+		$time = (microtime(true) - $start) * 1000;
+		$this->log[] = array('key'=>$key,
+		                     'ok'=>($res != false),
+		                     'time'=>round($time, 3),
+		                     'type'=>'set');
+		
+		return $res;
+    }
+	
+	/**
+	 * Returns an instance of the MemcacheConnection.
+	 */
+	public static function getInstance($force_new = false)
+	{ 
+		if (!isset(self::$instance) || $force_new)
+		{
+			self::$instance = new DirXiphOrgMCC();
+		}
+		
+		return self::$instance;
+	}
+}
+
+?>

Added: branches/dir.xiph.org/inc/inc.templating.php
===================================================================
--- branches/dir.xiph.org/inc/inc.templating.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/inc.templating.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,18 @@
+<?php
+
+#include_once(dirname(__FILE__).'/template-lite/class.template.php');
+include_once('/usr/share/php/smarty/Smarty.class.php');
+#/usr/share/php/smarty/Smarty_Compiler.class.php
+
+// Templating
+#$tpl = new Template_Lite();
+$tpl = new Smarty();
+$tpl->compile_dir = dirname(__FILE__).'/../c_templates/';
+$tpl->template_dir = dirname(__FILE__).'/../templates/';
+$tpl->plugins_dir[] = dirname(__FILE__).'/smarty-plugins/';
+
+// Globals
+//$tpl->assign('root_url', '/dir.xiph.org');
+$tpl->assign('root_url', '');
+
+?>

Added: branches/dir.xiph.org/inc/lib.dir.php
===================================================================
--- branches/dir.xiph.org/inc/lib.dir.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/lib.dir.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,134 @@
+<?php
+
+define('CONTENT_TYPE_UNKNOWN', 100);
+define('CONTENT_TYPE_OGG_VORBIS', 101);
+define('CONTENT_TYPE_OGG_THEORA', 102);
+define('CONTENT_TYPE_MP3', 103);
+define('CONTENT_TYPE_NSV', 104);
+define('CONTENT_TYPE_AAC', 105);
+define('CONTENT_TYPE_AACPLUS', 106);
+
+/**
+ * Returns the content type ID.
+ * 
+ * @param string $content_type MIME type to lookup.
+ * @param string $server_subtype The subtype (if exists).
+ * @return int
+ */
+function content_type_lookup($content_type)
+{
+	// Ogg
+	if ($content_type == "application/x-ogg" || $content_type == "application/ogg" || $content_type == 'application/ogg+vorbis')
+	{
+		return CONTENT_TYPE_OGG_VORBIS;
+	}
+	elseif ($content_type == "application/ogg+theora")
+	{
+		return CONTENT_TYPE_OGG_THEORA;
+	}
+	
+	// MP3
+	elseif ($content_type == "audio/mpeg" || $content_type == "audio/x-mpeg")
+	{
+		return CONTENT_TYPE_MP3;
+	}
+	
+	// NSV
+	elseif ($content_type == "video/nsv")
+	{
+		return CONTENT_TYPE_NSV;
+	}
+	
+	// AAC / AAC+
+	elseif ($content_type == "audio/aac")
+	{
+		return CONTENT_TYPE_AAC;
+	}
+	elseif ($content_type == "audio/aacp")
+	{
+		return CONTENT_TYPE_AACPLUS;
+	}
+	
+	// Fallback
+	return CONTENT_TYPE_UNKNOWN;
+}
+
+/**
+ * Inverse of content_type_lookup.
+ * 
+ * @param int $type_id
+ * @return string
+ */
+function get_media_type_string($type_id)
+{
+	$type = 'Unknown';
+	switch ($type_id)
+	{
+		case CONTENT_TYPE_OGG_VORBIS:
+			$type = 'Ogg Vorbis';
+			break;
+		case CONTENT_TYPE_OGG_THEORA:
+			$type = 'Ogg Theora';
+			break;
+		case CONTENT_TYPE_MP3:
+			$type = 'MP3';
+			break;
+		case CONTENT_TYPE_NSV:
+			$type = 'NSV';
+			break;
+		case CONTENT_TYPE_AAC:
+			$type = 'AAC';
+			break;
+		case CONTENT_TYPE_AACPLUS:
+			$type = 'AAC+';
+			break;
+	}
+	
+	return $type;
+}
+
+/**
+ * Inverse of content_type_lookup.
+ * 
+ * @param int $type_id
+ * @param bool $full_type If set to true, will return application/ogg+vorbis as
+ *        MIME type for Ogg Vorbis
+ * @return string
+ */
+function get_mime_type_string($type_id, $full_type=false)
+{
+	$type = 'data';
+	switch ($type_id)
+	{
+		case CONTENT_TYPE_OGG_VORBIS:
+			$type = 'application/ogg';
+			if ($full_type)
+			{
+			    $type .= '+vorbis';
+			}
+			break;
+		case CONTENT_TYPE_OGG_THEORA:
+			$type = 'application/ogg';
+			if ($full_type)
+			{
+			    $type .= '+theora';
+			}
+			break;
+		case CONTENT_TYPE_MP3:
+			$type = 'audio/mpeg';
+			break;
+		case CONTENT_TYPE_NSV:
+			$type = 'video/nsv';
+			break;
+		case CONTENT_TYPE_AAC:
+			$type = 'audio/aac';
+			break;
+		case CONTENT_TYPE_AACPLUS:
+			$type = 'audio/aacp';
+			break;
+	}
+	
+	return $type;
+}
+
+?>

Added: branches/dir.xiph.org/inc/lib.uuidgen.php
===================================================================
--- branches/dir.xiph.org/inc/lib.uuidgen.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/lib.uuidgen.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Build a UUID or GUID via PHP.
+ * May or may not be Microsoft GUID compatible.
+ * Thanks to all the internet code examples!
+ * 
+ * Contact me with corrections and changes please,
+ * soulhuntre at soulhuntre.com
+ * 
+ * Do whatever you want with this code, it's in the public domain 
+ *
+ * @version 1.0 (10/29/2004)
+ * @copyright Public Domain
+ **/
+class UUIDGen
+{
+	function generate()
+	{
+		$rawid = strtoupper(md5(uniqid(rand(), true)));
+		$workid = $rawid;
+		
+		// hopefully conform to the spec, mark this as a "random" type
+		// lets handle the version byte as a number
+		$byte = hexdec(substr($workid, 12, 2));
+		$byte = $byte & hexdec('0f');
+		$byte = $byte | hexdec('40');
+		$workid = substr_replace($workid, strtoupper(dechex($byte)), 12, 2);
+		
+		// hopefully conform to the spec, mark this common variant
+		// lets handle the "variant"
+		$byte = hexdec(substr($workid, 16, 2));
+		$byte = $byte & hexdec('3f');
+		$byte = $byte | hexdec('80');
+		$workid = substr_replace($workid, strtoupper(dechex($byte)), 16, 2);
+		
+		// build a human readable version
+		$wid = substr($workid, 0, 8).'-'
+		    .substr($workid, 8, 4).'-'
+		    .substr($workid,12, 4).'-'
+		    .substr($workid,16, 4).'-'
+		    .substr($workid,20,12);
+		
+		return $wid;
+	}
+}
+
+?>

Added: branches/dir.xiph.org/inc/prepend.php
===================================================================
--- branches/dir.xiph.org/inc/prepend.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/prepend.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,39 @@
+<?php
+
+class BadIDException extends Exception { }
+class EnvironmentUndefinedException extends Exception { }
+
+define('ENVIRONMENT', 'preprod');
+//define('DEBUG', 'true');
+
+if (ENVIRONMENT != 'prod')
+{
+	ini_set('error_reporting', E_ALL);
+	ini_set('display_errors', 'on');
+}
+
+define('MAX_RESULTS_PER_PAGE', 20);
+define('MAX_SEARCH_RESULTS', 100);
+
+$begin_time = microtime(true);
+
+// Classes
+include_once(dirname(__FILE__).'/prepend.php');
+include_once(dirname(__FILE__).'/class.db.php');
+include_once(dirname(__FILE__).'/class.izterator.php');
+include_once(dirname(__FILE__).'/class.izteratorbuilder.php');
+include_once(dirname(__FILE__).'/class.mc.php');
+include_once(dirname(__FILE__).'/class.mountpoint.php');
+include_once(dirname(__FILE__).'/class.server.php');
+include_once(dirname(__FILE__).'/class.tag.php');
+
+// Inclusions
+include_once(dirname(__FILE__).'/inc.db.php');
+include_once(dirname(__FILE__).'/inc.mc.php');
+include_once(dirname(__FILE__).'/inc.templating.php');
+
+// Libs
+include_once(dirname(__FILE__).'/lib.uuidgen.php');
+include_once(dirname(__FILE__).'/lib.dir.php');
+
+?>

Added: branches/dir.xiph.org/inc/smarty-plugins/modifier.get_media_type.php
===================================================================
--- branches/dir.xiph.org/inc/smarty-plugins/modifier.get_media_type.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/smarty-plugins/modifier.get_media_type.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,16 @@
+<?php
+
+include_once(dirname(__FILE__).'/../lib.dir.php');
+
+/**
+ * template_lite capitalize modifier plugin
+ *
+ * Type:     modifier
+ * Name:     get_media_type
+ * Purpose:  Return the media type from the media type ID
+ */
+function smarty_modifier_get_media_type($id)
+{
+	return get_media_type_string($id);
+}
+?>

Added: branches/dir.xiph.org/inc/smarty-plugins/modifier.get_mime_type.php
===================================================================
--- branches/dir.xiph.org/inc/smarty-plugins/modifier.get_mime_type.php	                        (rev 0)
+++ branches/dir.xiph.org/inc/smarty-plugins/modifier.get_mime_type.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,16 @@
+<?php
+
+include_once(dirname(__FILE__).'/../lib.dir.php');
+
+/**
+ * template_lite capitalize modifier plugin
+ *
+ * Type:     modifier
+ * Name:     get_media_type
+ * Purpose:  Return the media type from the media type ID
+ */
+function smarty_modifier_get_mime_type($id)
+{
+	return get_mime_type_string($id);
+}
+?>

Added: branches/dir.xiph.org/index.php
===================================================================
--- branches/dir.xiph.org/index.php	                        (rev 0)
+++ branches/dir.xiph.org/index.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,32 @@
+<?php
+
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Header
+$tpl->display("head.tpl");
+
+// Get the data from the Memcache server
+$top = $memcache->get('prod_home_top');
+$top = array_map(array('Mountpoint', 'retrieveByPk'), $top);
+$tpl->assign('data', $top);
+$tpl->assign('servers_total', $memcache->get('servers_total'));
+$tpl->assign('servers_mp3', $memcache->get('servers_'.CONTENT_TYPE_MP3));
+$tpl->assign('servers_vorbis', $memcache->get('servers_'.CONTENT_TYPE_OGG_VORBIS));
+$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+$tpl->display('index.tpl');
+
+// Footer
+$tpl->assign('generation_time', (microtime(true) - $begin_time) * 1000);
+$tpl->assign('sql_queries', isset($db) ? $db->queries : 0);
+$tpl->assign('mc_gets', isset($memcache) ? $memcache->gets : 0);
+$tpl->assign('mc_sets', isset($memcache) ? $memcache->sets : 0);
+if (isset($memcache))
+{
+    $tpl->assign('mc_debug', $memcache->log);
+}
+$tpl->display('foot.tpl');
+
+?>

Added: branches/dir.xiph.org/listen.php
===================================================================
--- branches/dir.xiph.org/listen.php	                        (rev 0)
+++ branches/dir.xiph.org/listen.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,157 @@
+<?php
+
+define('FILE_TYPE_M3U', 120);
+define('FILE_TYPE_XSPF', 121);
+
+/******************************************************************************/
+/*                                CONTROLLER                                  */
+/******************************************************************************/
+
+// Get request parameters
+$p_id = 0;
+$file_type = FILE_TYPE_M3U;
+if (!empty($_GET))
+{
+    // PID
+    if (!array_key_exists('pid', $_GET))
+    {
+	    header('HTTP/1.1 400 Bad Request', true);
+	    die("Missing PID.\n");
+    }
+    if (!preg_match('/^(\d+)$/', $_GET['pid']))
+    {
+	    header('HTTP/1.1 400 Bad Request', true);
+	    die("Bad PID.\n");
+    }
+    $p_id = intval($_GET['pid']);
+    
+    // Filetype
+    if (array_key_exists('file', $_GET) && $_GET['file'] == 'listen.xspf')
+    {
+        $file_type = FILE_TYPE_XSPF;
+    }
+}
+elseif (empty($_GET) && !empty($_SERVER['PATH_INFO']))
+{
+    // PID
+    $data = explode('/', $_SERVER['PATH_INFO']);
+    if (!array_key_exists(1, $data))
+    {
+	    header('HTTP/1.1 400 Bad Request', true);
+	    die("Missing PID.\n");
+    }
+    if (!preg_match('/^(\d+)$/', $data[1]))
+    {
+	    header('HTTP/1.1 400 Bad Request', true);
+	    die("Bad PID.\n");
+    }
+    $p_id = intval($data[1]);
+    
+    // Filetype
+    if (array_key_exists(2, $data) && $data[2] == 'listen.xspf')
+    {
+        $file_type = FILE_TYPE_XSPF;
+    }
+}
+else
+{
+    header('HTTP/1.1 400 Bad Request', true);
+    die("Missing PID.\n");
+}
+
+/******************************************************************************/
+/*                                  MODEL                                     */
+/******************************************************************************/
+
+// Inclusions
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Check the memcache server
+$playlist = $memcache->get('prod_playlist_'.$p_id);
+if ($playlist === false)
+{
+	// Database connection
+	$db = DirXiphOrgDBC::getInstance();
+	
+	// Cache miss, query the database
+	$query = 'SELECT `listen_url` FROM `server` WHERE `mountpoint_id` = %d;';
+	$query = sprintf($query, $p_id);
+	
+	try
+	{
+		$result = $db->selectQuery($query);
+		$playlist = array();
+		
+		while (!$result->endOf())
+		{
+			$playlist[] = $result->current('listen_url');
+			$result->next();
+		}
+		
+		// Store into memcache
+		$memcache->set('prod_playlist_'.$p_id, $playlist, 60);
+	}
+	catch (SQLNoResultException $e)
+	{
+		header('HTTP/1.1 404 Not Found', true);
+		die("No such PID.\n");
+	}
+}
+
+/******************************************************************************/
+/*                                     VIEW                                   */
+/******************************************************************************/
+
+switch ($file_type)
+{
+    case FILE_TYPE_M3U:
+        // Header
+        header("Content-type: audio/x-mpegurl");
+        if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT']))
+        {
+	        header("Content-Disposition: filename=\"listen.m3u\"");
+        }
+        else
+        {
+	        header("Content-Disposition: inline; filename=\"listen.m3u\"");
+        }
+
+        // Data
+        echo implode("\r\n", $playlist);
+        break;
+    case FILE_TYPE_XSPF:
+        // Header
+        header("Content-type: application/xspf+xml");
+        if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT']))
+        {
+	        header("Content-Disposition: filename=\"listen.xspf\"");
+        }
+        else
+        {
+	        header("Content-Disposition: inline; filename=\"listen.xspf\"");
+        }
+        
+        // Data
+        $mountpoint = Mountpoint::retrieveByPk($p_id);
+        printf('<?xml version="1.0" encoding="UTF-8"?>
+<playlist version="1" xmlns="http://xspf.org/ns/0/">
+    <title>%s</title>
+    <info>%s</info>
+    <trackList>
+',
+        $mountpoint->getStreamName(),
+        $mountpoint->getUrl());
+        foreach ($playlist as $p)
+        {
+            printf("        <track><location>%s</location></track>\n", $p);
+        }
+        echo '    </trackList>
+</playlist>
+';
+        break;
+}
+
+?>

Added: branches/dir.xiph.org/search.php
===================================================================
--- branches/dir.xiph.org/search.php	                        (rev 0)
+++ branches/dir.xiph.org/search.php	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,137 @@
+<?php
+
+include_once(dirname(__FILE__).'/inc/prepend.php');
+
+// Memcache connection
+$memcache = DirXiphOrgMCC::getInstance();
+
+// Get the args
+$page_n = array_key_exists('page', $_GET) ? intval($_GET['page']) : 0;
+if ($page_n > (MAX_SEARCH_RESULTS / MAX_RESULTS_PER_PAGE))
+{
+    $page_n = 0;
+}
+$keyword = array_key_exists('search', $_GET) ? $_GET['search'] : false;
+if ($keyword !== false)
+{
+	// Clean the input string
+	// We can't do further cleaning on things like '-', '=', '+',
+	// because regexps are not made for matching things like
+	// '品情報、映画音楽 ゲーム金融'. Too bad.
+	$keyword = trim(strip_tags($keyword));
+	$keyword = substr($keyword, 0, 20);
+	$tpl->assign('search_keyword', $keyword);
+	$keyword = preg_replace('/\s+/', ' ', $keyword);
+	
+	// Database connection
+	$db = DirXiphOrgDBC::getInstance();
+	
+	// Now generate the search string that will be used in the LIKE
+	// clause. No more than 3 words, unless we want to screw up the
+	// MySQL server big time by a) overloading it with genuine queries
+	// b) opening a San Andreas' security breach for DOS.
+	$keywords = explode(' ', $keyword);
+	$search_string = '%';
+	$search_in = array();
+	$count = 0;
+	foreach ($keywords as $k)
+	{
+		if (strlen($k) < 3)
+		{
+			continue;
+		}
+		if (strlen($k) > 10)
+		{
+			$k = substr($k, 0, 10);
+		}
+		
+		$search_string .= $k.'%';
+		$search_in[] = '"'.mysql_real_escape_string($k).'"';
+		
+		if (++$count >= 3)
+		{
+			break;
+		}
+	}
+	if ($count > 0)
+	{
+		$search_string_hash = jenkins_hash_hex($search_string);
+		$search_in = implode(', ', $search_in);
+	    
+		// Get the data from the Memcache server
+		if (($results = $memcache->get('prod_search_'.$search_string_hash)) === false)
+		{
+			// Cache miss. Now query the database.
+			try
+			{
+//				$query = 'SELECT * FROM `mountpoints` WHERE `stream_name` LIKE "%1$s" OR `description` LIKE "%1$s" OR `genre` LIKE "%1$s" GROUP BY `stream_name`, `cluster_id` ORDER BY `listeners` DESC LIMIT 50;';
+	            $query = 'SELECT * FROM `mountpoint` WHERE `stream_name` LIKE "%1$s" OR `description` LIKE "%1$s" OR `id` IN (SELECT `mountpoint_id` FROM `mountpoints_tags` AS mt INNER JOIN `tag` AS t ON mt.`tag_id` = t.`id` WHERE `tag_name` IN (%2$s)) ORDER BY `listeners` DESC LIMIT %3$d;';
+				$query = sprintf($query,
+				                 mysql_real_escape_string($search_string),
+				                 $search_in,
+				                 MAX_SEARCH_RESULTS);
+//				var_dump($query);
+				$results = $db->selectQuery($query);
+			    $res = array();
+			    while (!$results->endOf())
+			    {
+				    $res[] = Mountpoint::retrieveByPk($results->current('id'));
+				    $results->next();
+			    }
+				$results = $res;
+			}
+			catch (SQLNoResultException $e)
+			{
+				$results = array();
+			}
+		
+			// Cache the resultset
+			$memcache->set('prod_search_'.$search_string_hash, $results,
+			               false, 60);
+		}
+	    
+	    // Now assign the results to a template var
+		if ($results !== false && $results !== array())
+		{
+		    $n_results = count($results);
+			$results_pages = $n_results / MAX_RESULTS_PER_PAGE;
+			if ($page_n > $results_pages)
+			{
+			    $page_n = 0;
+			}
+			$offset = $page_n * MAX_RESULTS_PER_PAGE;
+		    $results = array_slice($results, $offset,
+		                                     MAX_RESULTS_PER_PAGE);
+			$tpl->assign_by_ref('results', $results);
+			$tpl->assign_by_ref('results_pages', $results_pages);
+			$tpl->assign_by_ref('results_page_no', $page_n);
+		}
+	}
+}
+
+// Header
+$tpl->display("head.tpl");
+
+// Display the results
+$tpl->assign('servers_total', $memcache->get('servers_total'));
+$tpl->assign('servers_mp3', $memcache->get('servers_'.CONTENT_TYPE_MP3));
+$tpl->assign('servers_vorbis', $memcache->get('servers_'.CONTENT_TYPE_OGG_VORBIS));
+$tpl->assign('tag_cloud', $memcache->get('prod_tagcloud'));
+$tpl->display('search.tpl');
+
+// Footer
+$tpl->assign('generation_time', (microtime(true) - $begin_time) * 1000);
+$tpl->assign('sql_queries', isset($db) ? $db->queries : 0);
+if (isset($db))
+{
+	$tpl->assign('sql_debug', $db->log);	
+}
+$tpl->assign('mc_gets', isset($memcache) ? $memcache->gets : 0);
+$tpl->assign('mc_sets', isset($memcache) ? $memcache->sets : 0);
+if (isset($memcache))
+{
+    $tpl->assign('mc_debug', $memcache->log);
+}
+$tpl->display('foot.tpl');
+
+?>

Added: branches/dir.xiph.org/templates/foot.tpl
===================================================================
--- branches/dir.xiph.org/templates/foot.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/foot.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,24 @@
+			<div id="copyright">
+				<p>Generated in {$generation_time} ms.</p>
+				<p>Executed {$sql_queries} SQL queries.</p>
+{if !empty($sql_debug)}
+				<h4>Queries:</h4>
+				<ul>
+{foreach item=query from=$sql_debug}
+					<li>[{$query.time} ms] {$query.query|truncate}</li>
+{/foreach}
+				</ul>
+{/if}
+				<p>Executed {$mc_gets} Memcache gets and {$mc_sets} Memcache sets.</p>
+{if !empty($mc_debug)}
+				<h4>Queries:</h4>
+				<ul>
+{foreach item=query from=$mc_debug}
+					<li>[{$query.time} ms] [{$query.type|upper}] {if $query.type|upper == 'GET'}[{if $query.hit}HIT{else}MISS{/if}]{/if} {$query.key}</li>
+{/foreach}
+				</ul>
+{/if}
+			</div>
+		</div>
+	</body>
+</html>

Added: branches/dir.xiph.org/templates/head.tpl
===================================================================
--- branches/dir.xiph.org/templates/head.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/head.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+	<head>
+		<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+		<title>Streaming directory</title>
+		<link rel="icon" href="http://www.icecast.org/favicon.ico" type="image/x-icon"/>
+		<link rel="shortcut icon" href="http://www.icecast.org/favicon.ico" type="image/x-icon"/>
+		<link rel="stylesheet" href="http://www.xiph.org/css/screen.css" type="text/css"/>
+		<link rel="stylesheet" href="/css/style.css" type="text/css" />
+	</head>
+	<body>
+		<div id="xiphbar_outer" style="@import url(/common/xiphbar.css);">
+		<table id="xiphbar" border="0" cellpadding="0" cellspacing="0">
+		<tr>
+		  <td>
+		    <img id="xiphbanner" src="http://xiph.org/images/logos/xiphbar.png" 
+		         alt="Xiph logo" width="231" height="17"/>
+		  </td>
+		  <td id="xiphlinks" align="right">
+		    <a href="http://www.xiph.org/">Xiph.org</a>
+		    <a href="http://www.vorbis.com/">Vorbis</a>
+		    <a href="http://www.theora.org/">Theora</a>
+		    <a href="http://www.icecast.org/">Icecast</a>
+		    <a href="http://www.speex.org/">Speex</a>
+		    <a href="http://flac.sourceforge.net/">FLAC</a>
+		    <a href="http://www.xspf.org/">XSPF</a>
+		  </td>
+		</tr>
+		</table>
+		</div>
+		<div id="thepage">
+			<div id="xiphlogo">
+				<h1><a href="{$root_url}/">Streaming directory</a></h1>
+			</div>

Added: branches/dir.xiph.org/templates/index.tpl
===================================================================
--- branches/dir.xiph.org/templates/index.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/index.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,6 @@
+{include file="menu_right.tpl"}
+				<div id="content">
+					<h2>Random selection</h2>
+{include file="streams_list.tpl" stream_list=$data display_counter=true}
+				</div>
+				<div id="content-footer"></div>
\ No newline at end of file

Added: branches/dir.xiph.org/templates/menu_right.tpl
===================================================================
--- branches/dir.xiph.org/templates/menu_right.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/menu_right.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,67 @@
+				<div id="navbar">
+					<div id="sidebar-statistics">
+						<h3>Statistics</h3>
+						<ul>
+{if !empty($servers_total)}
+							<li>Total streams: <strong>{$servers_total|intval}</strong></li>
+{/if}
+{if !empty($servers_vorbis)}
+							<li>Ogg Vorbis: <strong>{$servers_vorbis|intval}</strong></li>
+{/if}
+{if !empty($servers_mp3)}
+							<li>MP3: <strong>{$servers_mp3|intval}</strong></li>
+{/if}
+						</ul>
+					</div>
+					<div id="sidebar-search">
+						<h3>Search</h3>
+						<ul>
+							<li><form method="get" action="{$root_url}/search" id="search-box">
+								<div><label for="search">Search:</label> <input type="text" name="search" id="search" maxlength="20"{if !empty($search_keyword)} value="{$search_keyword}"{/if} />
+								<input type="submit" value="OK" class="button" /></div>
+							</form></li>
+						</ul>
+						<h3>By genre</h3>
+						<div id="search-genre">
+{if !empty($tag_cloud)}
+							<ul class="tag-cloud">
+{foreach item=tag from=$tag_cloud}
+								<li class="popularity-{$tag.popularity}"><span class="context">{$tag.tag_usage} streams tagged with </span><a href="{$root_url}/by_genre/{$tag.tag_name|capitalize|escape}" class="tag">{$tag.tag_name|capitalize|escape}</a></li>
+{/foreach}
+							</ul>
+{else}
+							<ul class="tag-cloud">
+								<li><a href="{$root_url}/by_genre/Alternative" class="tag">Alternative</a></li>
+								<li><a href="{$root_url}/by_genre/Classical" class="tag">Classical</a></li>
+								<li><a href="{$root_url}/by_genre/Comedy" class="tag">Comedy</a></li>
+								<li><a href="{$root_url}/by_genre/Country" class="tag">Country</a></li>
+								<li><a href="{$root_url}/by_genre/Dance" class="tag">Dance</a></li>
+								<li><a href="{$root_url}/by_genre/Funk" class="tag">Funk</a></li>
+								<li><a href="{$root_url}/by_genre/Jazz" class="tag">Jazz</a></li>
+								<li><a href="{$root_url}/by_genre/Metal" class="tag">Metal</a></li>
+								<li><a href="{$root_url}/by_genre/Mixed" class="tag">Mixed</a></li>
+								<li><a href="{$root_url}/by_genre/Pop" class="tag">Pop</a></li>
+								<li><a href="{$root_url}/by_genre/Rap" class="tag">Rap</a></li>
+								<li><a href="{$root_url}/by_genre/RnB" class="tag">RnB</a></li>
+								<li><a href="{$root_url}/by_genre/Rock" class="tag">Rock</a></li>
+								<li><a href="{$root_url}/by_genre/Talk" class="tag">Talk</a></li>
+								<li><a href="{$root_url}/by_genre/Techno" class="tag">Techno</a></li>
+								<li><a href="{$root_url}/by_genre/80s" class="tag">80s</a></li>
+								<li><a href="{$root_url}/by_genre/70s" class="tag">70s</a></li>
+								<li class="plus"><a href="{$root_url}/by_genre">more genres &raquo;</a></li>
+							</ul>
+{/if}
+						</div>
+						<h3>By format</h3>
+						<div id="search-format">
+							<ul>
+								<li><a href="{$root_url}/by_format/Ogg_Vorbis">Ogg Vorbis</a></li>
+								<li><a href="{$root_url}/by_format/MP3">MP3</a></li>
+								<li><a href="{$root_url}/by_format/AAC">AAC</a></li>
+								<li><a href="{$root_url}/by_format/AAC+">AAC+</a></li>
+								<li><a href="{$root_url}/by_format/Ogg_Theora">Ogg Theora</a></li>
+								<li><a href="{$root_url}/by_format/NSV">NSV</a></li>
+							</ul>
+						</div>
+					</div>
+				</div>

Added: branches/dir.xiph.org/templates/search.tpl
===================================================================
--- branches/dir.xiph.org/templates/search.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/search.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,15 @@
+{include file="menu_right.tpl"}
+				<div id="content">
+					<h2>Search results</h2>
+{if !empty($results)}
+{include file="streams_list.tpl" stream_list=$results display_counter=false}
+{elseif $display_tag_cloud && !empty($tag_cloud)}
+					<ul class="tag-cloud">
+{foreach item=tag from=$tag_cloud}
+						<li class="popularity-{$tag.popularity}"><span class="context">{$tag.tag_usage} streams tagged with </span><a href="{$root_url}/by_genre/{$tag.tag_name|capitalize}" class="tag">{$tag.tag_name|capitalize}</a></li>
+{/foreach}
+					</ul>
+{else}
+					<p class="warning">Sorry, no result for your search of '{$search_keyword}'.</p>
+{/if}
+				</div>

Added: branches/dir.xiph.org/templates/streams_list.tpl
===================================================================
--- branches/dir.xiph.org/templates/streams_list.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/streams_list.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,46 @@
+{if $stream_list !== false}
+					<table class="servers-list">
+{foreach item=stream from=$stream_list key=rowCounter}
+						<tr class="row{cycle values="0,1"}">
+{if ($display_counter)}
+							<td class="rank">#{counter}</td>
+{/if}
+							<td class="description">
+								<p class="stream-name">
+									<span class="name">{if $stream->getUrl() != ''}<a href="{$stream->getUrl()|escape}">{/if}{$stream->getStreamName()|escape}{if $stream->getUrl() != ''}</a>{/if}</span>
+									<span class="listeners">[{$stream->getListeners()|intval}&nbsp;listener{if (($stream->getListeners()|intval) != 1)}s{/if}]</span>
+								</p>
+{*if $stream->getIcon()}
+								<div class="img"><img src="{$root_url}/images/default_icon.jpg" alt="Purely illustrative image." /></div>
+{/if*}
+{if $stream->getDescription() != ''}
+								<p class="stream-description">{$stream->getDescription()|escape}</p>
+{/if}
+{if $stream->getCurrentSong() != ''}
+								<p class="stream-onair"><strong>On Air:</strong> {$stream->getCurrentSong()|escape}</p>
+{/if}
+{assign var=tags value=$stream->getTags()}
+{if $tags != false}
+                                <div class="stream-tags"><strong>Tags:</strong>
+                                    <ul class="inline-tags">
+{foreach item=tag_name key=tag_id from=$tags}
+                                        <li><a href="{$root_url}/by_genre/{$tag_name|capitalize|escape}">{$tag_name|capitalize|escape}</a></li>
+{/foreach}
+                                    </ul>
+                                </div>
+{/if}
+							</td>
+							<td class="tune-in">
+							    <p class="format">Tune in:</p>
+								<p>[ <a href="{$root_url}/listen/{$stream->getId()}/listen.m3u" title="Listen to '{$stream->getStreamName()|truncate:20:"...":true|escape}'"{* class="tune-in-button"*}>M3U</a> | <a href="{$root_url}/listen/{$stream->getId()}/listen.xspf" title="Listen to '{$stream->getStreamName()|truncate:20:"...":true|escape}'"{* class="tune-in-button"*}>XSPF</a> ]</p>
+{if $stream->getMediaTypeId() != ''}
+								<p class="format"{if $stream->getBitrate() != ''} title="{if is_numeric($stream->getBitrate())}{$stream->getBitrate()|intval} kbps{else}{$stream->getBitrate()}{/if}"{/if}>
+									{$stream->getMediaTypeId()|get_media_type}
+									{*if (!empty($stream.channels) && is_numeric($stream.channels))}{if ($stream.channels == 1)}mono{elseif $stream.channels == 2}stereo{else}{$stream.channels} ch{/if}{/if*}
+								</p>
+{/if}
+							</td>
+						</tr>
+{/foreach}
+					</table>
+{/if}

Added: branches/dir.xiph.org/templates/yp.xml.tpl
===================================================================
--- branches/dir.xiph.org/templates/yp.xml.tpl	                        (rev 0)
+++ branches/dir.xiph.org/templates/yp.xml.tpl	2008-04-07 18:48:08 UTC (rev 14664)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<directory>
+{foreach item=stream from=$streams}
+    <entry>
+        <server_name>{$stream.stream_name|escape}</server_name>
+        <listen_url>{$stream.listen_url|escape}</listen_url>
+        <server_type>{$stream.media_type_id|get_mime_type_string}</server_type>
+        <bitrate>{$stream.bitrate|escape}</bitrate>
+        <channels>{$stream.channels|intval}</channels>
+        <samplerate>{$stream.samplerate|intval}</samplerate>
+        <genre>{$stream.genre|escape}</genre>
+        <current_song>{$stream.current_song|escape}</current_song>
+    </entry>
+{/foreach}
+</directory>



More information about the commits mailing list