[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 »</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} 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