// ------------------------------------------------
// File : channel.cpp
// Date: 4-apr-2002
// Author: giles
// Desc: 
//		Channel streaming classes. These do the actual 
//		streaming of media between clients. 
//
// (c) 2002 peercast.org
// 
// ------------------------------------------------
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// ------------------------------------------------

#include <string.h>
#include <stdlib.h>
#include "common.h"
#include "socket.h"
#include "channel.h"
#include "gnutella.h"
#include "servent.h"
#include "servmgr.h"
#include "sys.h"
#include "xml.h"
#include "http.h"
#include "peercast.h"
#include "asf.h"

// -----------------------------------
char *Channel::srcTypes[]=
{
	"NONE",
	"PEERCAST",
	"SHOUTCAST",
	"ICECAST",
	"URL"
};
// -----------------------------------
char *Channel::statusMsgs[]=
{
	"NONE",
	"WAIT",
	"CONNECT",
	"REQUEST",
	"CLOSE",
	"RECEIVE",
	"BROADCAST",
	"ABORT",
	"SEARCH",
	"NOHOSTS",
	"IDLE",
	"ERROR"
};


// -----------------------------------
void readXMLString(String &str, XML::Node *n, const char *arg)
{
	char *p;
	p = n->findAttr(arg);
	if (p)
	{
		str.set(p,String::T_HTML);
		str.convertTo(String::T_ASCII);
	}
}


// -----------------------------------------------------------------------------
// Initialise the channel to its default settings of unallocated and reset.
// -----------------------------------------------------------------------------
void Channel::init()
{
	reset();
}
// -----------------------------------------------------------------------------
// Close this channel and stop thread
// -----------------------------------------------------------------------------
void Channel::close()
{
	thread.active = false;
	setStatus(S_CLOSING);
}
// -----------------------------------------------------------------------------
void Channel::endThread()
{
	close();
	thread.unlock();
	init();
}
// -----------------------------------------------------------------------------
void Channel::resetPlayTime()
{
	info.lastPlay = sys->getTime();
}
// -----------------------------------------------------------------------------
void Channel::setStatus(STATUS s)
{
	status = s;
	if (isPlaying())
		info.status = ChanInfo::S_PLAY;
	else
		info.status = ChanInfo::S_UNKNOWN;
}
	
// -----------------------------------------------------------------------------
// Reset channel and make it available 
// -----------------------------------------------------------------------------
void Channel::reset()
{
	currSource.init();

	srcType = SRC_NONE;

	lastIdleTime = 0;
	prefetchCnt=0;		

	numRelays = 0;
	numListeners = 0;

	info.init();
	index = 0;

	mount.clear();
	bump = false;
	stayConnected = false;

	icyMetaInterval = 0;
	syncPos = 0;

	headMeta.init();
	insertMeta.init();

	chanData.init();

	setStatus(S_NONE);
	type = T_NONE;

	readDelay = false;
	sock = NULL;

	sourceURL.clear();
}

// -----------------------------------
bool	Channel::checkIdle()
{
	if ((numListeners > 0) || (stayConnected || (status == S_BROADCASTING)))
	{
		prefetchCnt=0;
		return false;
	}

	if (prefetchCnt) 
		prefetchCnt--;

	if (prefetchCnt)
		LOG_CHANNEL("prefetch %d",prefetchCnt);
	return prefetchCnt==0;
}

// -----------------------------------
bool	Channel::isFull()
{
	return chanMgr->maxStreamsPerChannel ? numRelays >= chanMgr->maxStreamsPerChannel : false;
}

// -----------------------------------
void	Channel::startMP3File(char *fn)
{
	type = T_BROADCAST;
	FileStream *fs = new FileStream();

	fs->openReadOnly(fn);
	input = fs;

	thread.data = this;
	thread.func = streamMP3File;
	if (!sys->startThread(&thread))
		init();
}
// -----------------------------------
int	Channel::streamMP3File(ThreadInfo *thread)
{
	thread->lock();

	Channel *ch = (Channel *)thread->data;

	LOG_CHANNEL("Channel started: %s",ch->getName());

	try 
	{
		while (thread->active)
		{
			ch->input->read(&ch->mp3Head,sizeof(MP3Header));

			ch->readMP3();

			ch->input->rewind();
			LOG_CHANNEL("%s end",ch->getName());
		}
	}catch(StreamException &e)
	{
		LOG_ERROR("Unable to read file: %s",e.msg);
	}
	ch->input->close();

	delete ch->input;

	LOG_CHANNEL("Channel stopped: %s",ch->getName());
	ch->endThread();
	return 0;
}
// -----------------------------------
void	Channel::startGet()
{
	srcType = SRC_PEERCAST;
	type = T_RELAY;
	input = NULL;
	info.srcProtocol = ChanInfo::SP_PEERCAST;


	thread.data = this;
	thread.func = streamGet;
	if (!sys->startThread(&thread))
		init();
}
// -----------------------------------
void	Channel::startURL(const char *u)
{
	sourceURL.set(u);

	srcType = SRC_URL;
	type = T_BROADCAST;
	stayConnected = true;

	// source type should be set before here.
	//info.srcType = ChanInfo::T_UNKNOWN;	

	resetPlayTime();

	thread.data = this;
	thread.func = streamURL;
	if (!sys->startThread(&thread))
		init();
}

// -----------------------------------
int	Channel::findProc(ThreadInfo *thread)
{
	thread->lock();

	Channel *ch = (Channel *)thread->data;

	ch->setStatus(S_SEARCHING);

	int findCnt=0;
	while (thread->active)
	{
		ChanHitList *chl = chanMgr->findHitListByID(ch->info.id);
		if (chl && chl->numHits())
		{
			// update chaninfo with latest 
			ch->info = chl->info;

			ch->setStatus(S_IDLE);
			thread->unlock();

			ch->startGet();
			return 0;
		}else
		{
			if ((findCnt%60) == 0)
				servMgr->findChannel(ch->info);

			if (findCnt++ > 300)		// give up eventually
				break;
		}

		sys->sleep(1000);
	}

	ch->endThread();
	return 0;
}
// -----------------------------------
String Channel::streamURL(const char *url)
{
	String nextURL;


	String urlTmp;
	urlTmp.set(url);

	char *fileName = urlTmp.cstr();

	Stream *file = NULL;
	PlayList *pls=NULL;

	LOG_CHANNEL("Fetch URL=%s",fileName);

	try
	{

		// get the source protocol
		if (strnicmp(fileName,"http://",7)==0) 	
		{
			info.srcProtocol = ChanInfo::SP_HTTP;
			fileName += 7;
		}
		else if (strnicmp(fileName,"mms://",6)==0) 	
		{
			info.srcProtocol = ChanInfo::SP_MMS;
			fileName += 6;
		}
		else if (strnicmp(fileName,"file://",7)==0) 	
		{
			info.srcProtocol = ChanInfo::SP_FILE;
			fileName += 7;
		}
		else 
		{
			info.srcProtocol = ChanInfo::SP_FILE;
		}

		setStatus(S_CONNECTING);

		if ((info.srcProtocol == ChanInfo::SP_HTTP) || (info.srcProtocol == ChanInfo::SP_MMS))
		{

			if ((info.contentType == ChanInfo::T_WMA) || (info.contentType == ChanInfo::T_WMV))
				info.srcProtocol = ChanInfo::SP_MMS;

			
			LOG_CHANNEL("Channel source is HTTP");

			ClientSocket *sock = sys->createSocket();
			if (!sock)
				throw StreamException("Ch.%d cannot create socket",index);

			file = sock;

			char *dir = strstr(fileName,"/");
			if (dir)
				*dir++=0;


			LOG_CHANNEL("Fetch Host=%s",fileName);
			if (dir)
				LOG_CHANNEL("Fetch Dir=%s",dir);


			Host host;
			host.fromStrName(fileName,80);

			sock->open(host);
			sock->connect();

			HTTP http(*sock);
			http.writeLine("GET /%s HTTP/1.1",dir?dir:"");

			http.writeLine("%s %s",HTTP_HS_HOST,fileName);
			http.writeLine("%s %s",HTTP_HS_CONNECTION,"close");
			http.writeLine("%s %s",HTTP_HS_ACCEPT,"*/*");

			if (info.srcProtocol == ChanInfo::SP_MMS)
			{
				http.writeLine("%s %s",HTTP_HS_AGENT,"NSPlayer/4.1.0.3856");
				http.writeLine("Pragma: no-cache,rate=1.000000,request-context=2");
				http.writeLine("Pragma: xPlayStrm=1");
				//http.writeLine("Pragma: stream-switch-count=1");
				//http.writeLine("Pragma: stream-switch-entry=ffff:1:0");
			}else
			{
				http.writeLine("%s %s",HTTP_HS_AGENT,PCX_AGENT);  
				http.writeLine("icy-metadata:1");
			}

			http.writeLine("");

			int res = http.readResponse();

			if ((res!=200) && (res!=302))
			{
				LOG_ERROR("HTTP response: %s",http.cmdLine);
				throw StreamException("Bad HTTP connect");
			}

			String name = info.name;

			while (http.nextHeader())
			{
				LOG_CHANNEL("Fetch HTTP: %s",http.cmdLine);

				ChanInfo tmpInfo = info;
				Servent::readICYHeader(http,info,NULL);

				if (!tmpInfo.name.isEmpty())
					info.name = tmpInfo.name;
				if (!tmpInfo.genre.isEmpty())
					info.genre = tmpInfo.genre;
				if (!tmpInfo.url.isEmpty())
					info.url = tmpInfo.url;

				if (http.isHeader("icy-metaint"))
					icyMetaInterval = http.getArgInt();
				else if (http.isHeader("Location:"))
					nextURL.set(http.getArgStr());

				char *arg = http.getArgStr();
				if (arg)
				{
					if (http.isHeader("content-type"))
					{
						if (stristr(arg,MIME_XSCPLS))
							pls = new PlayList(PlayList::T_SCPLS, 1000);
						else if (stristr(arg,MIME_PLS))
							pls = new PlayList(PlayList::T_PLS, 1000);
						else if (stristr(arg,MIME_XPLS))
							pls = new PlayList(PlayList::T_PLS, 1000);
						else if (stristr(arg,MIME_M3U))
							pls = new PlayList(PlayList::T_PLS, 1000);
						else if (stristr(arg,MIME_TEXT))
							pls = new PlayList(PlayList::T_PLS, 1000);
						else if (stristr(arg,MIME_ASX))
							pls = new PlayList(PlayList::T_ASX, 1000);
						else if (stristr(arg,MIME_MMS))
							info.srcProtocol = ChanInfo::SP_MMS;
					}
				}

			}



			if ((!nextURL.isEmpty()) && (res==302))
			{
				LOG_CHANNEL("Ch.%d redirect: %s",index,nextURL.cstr());
				sock->close();
				delete sock;
				sock = NULL;
				return nextURL;

			}

		}else if (info.srcProtocol == ChanInfo::SP_FILE)
		{

			LOG_CHANNEL("Channel source is FILE");

			FileStream *fs = new FileStream();
			fs->openReadOnly(fileName);
			file = fs;

			ChanInfo::TYPE fileType = ChanInfo::T_UNKNOWN;
			// if filetype is unknown, try and figure it out from file extension.
			//if ((info.srcType == ChanInfo::T_UNKNOWN) || (info.srcType == ChanInfo::T_PLAYLIST))
			{
				const char *ext = fileName+strlen(fileName);
				while (*--ext)
					if (*ext=='.')
					{
						ext++;
						break;
					}

				fileType = ChanInfo::getTypeFromStr(ext);
			}


			if (info.bitrate)
				readDelay = true;


			if (fileType == ChanInfo::T_PLS) 
				pls = new PlayList(PlayList::T_PLS, 1000);
			else if (fileType == ChanInfo::T_ASX) 
				pls = new PlayList(PlayList::T_ASX, 1000);
			else
				info.contentType = fileType;

		}else
		{
			throw StreamException("Unsupported URL");
		}

		
		if (pls)
		{

			LOG_CHANNEL("Ch.%d is Playlist",index);

			pls->read(*file);

			file->close();
			delete file;
			file = NULL;

			int urlNum=0;
			String url;

			LOG_CHANNEL("Playlist: %d URLs",pls->numURLs);
			while ((thread.active) && (pls->numURLs))
			{
				if (url.isEmpty())
				{
					url = pls->urls[urlNum%pls->numURLs];
					urlNum++;
				}
				try
				{
					url = streamURL(url.cstr());
				}catch(StreamException &)
				{}
			}

			delete pls;
			
		}else
		{

			// if we didn`t get a channel id from the source, then create our own (its an original broadcast)
			if (!info.id.isSet())
			{
				info.id = chanMgr->broadcastID;
				info.id.encode(&servMgr->serverHost,info.name.cstr(),NULL,info.bitrate);
			}

			input = file;

			setStatus(S_BROADCASTING);

			readStream();

			file->close();
		}

	}catch(StreamException &e)
	{
		setStatus(S_ERROR);
		LOG_ERROR("Ch.%d error: %s",index,e.msg);
		sys->sleep(1000);
	}
			
	setStatus(S_CLOSING);
	if (file)
	{
		file->close();
		delete file;
	}

	return nextURL;
}

// -----------------------------------
void Channel::checkReadDelay(unsigned int len)
{
	if (readDelay)
	{
		unsigned int time = (len*1000)/((info.bitrate*1024)/8);
		//LOG_CHANNEL("sleeping for %d\n",time);
		sys->sleep(time);
	}
}

// -----------------------------------
int	Channel::streamURL(ThreadInfo *thread)
{
	thread->lock();

	Channel *ch = (Channel *)thread->data;
	ClientSocket *sock = NULL;

	LOG_CHANNEL("Ch.%d started: %s",ch->index,ch->sourceURL.cstr());


	String url;
	while (thread->active)
	{
		if (url.isEmpty())
			url = ch->sourceURL;

		url = ch->streamURL(url.cstr());
	}

	ch->endThread();

	return 0;
}	

// -----------------------------------
int	Channel::streamGet(ThreadInfo *thread)
{
	thread->lock();
	GnuPacket pack;
	Channel *ch = (Channel *)thread->data;

	chanMgr->lockHitList(ch->info.id,true);
	LOG_CHANNEL("Ch.%d started: %s",ch->index,ch->getName());


	while (thread->active)
	{

		ch->lastIdleTime = sys->getTime();
		ch->setStatus(S_IDLE);
		while ((ch->checkIdle()) && (thread->active))
			sys->sleepIdle();

		if (!thread->active)
			break;

		//ch->info.title.set("Please wait.",String::T_ASCII);

		bool doneSearch=false;
		do 
		{
			Host sh = servMgr->serverHost;

			ChanHitList *chl = chanMgr->findHitListByID(ch->info.id);
			if (chl)
			{
				if (servMgr->getFirewall() == ServMgr::FW_OFF)
				{
					// we are non-firewalled so try non push hosts, then push hosts
					ch->currSource = chl->getHit(false);
					if (!ch->currSource.host.ip)
						ch->currSource = chl->getHit(true);
				}else{
					// we are firewalled so try non push hosts only
					ch->currSource = chl->getHit(false);
				}
			}

			if (sh.isSame(ch->currSource.host))
				ch->currSource.host.ip = 0;


			if (!ch->currSource.host.ip)
			{
				ch->setStatus(S_SEARCHING);
				if (!doneSearch)
				{
					LOG_CHANNEL("Ch.%d search..",ch->index);
					if (servMgr->findChannel(ch->info))
						doneSearch = true;
				}
				sys->sleepIdle();
			}

		}while((ch->currSource.host.ip==0) && (thread->active));


		// totally give up
		if (!ch->currSource.host.ip)
			break;

		{

			try 
			{

				char hostName[64];
				ch->currSource.host.IPtoStr(hostName);


				if (!ch->currSource.firewalled || (servMgr->serverHost.localIP() && ch->currSource.host.localIP()))
				{

					ClientSocket *s = sys->createSocket();
					if (!s)
						throw StreamException("Ch.%d cannot create socket",ch->index);

					ch->sock = s;
					ch->setStatus(S_CONNECTING);
					ch->sock->open(ch->currSource.host);
					ch->sock->timeout = 10000;
					ch->sock->connect();

					LOG_CHANNEL("Ch.%d connect to %s",ch->index,hostName);

				}else{

					Host sh = servMgr->serverHost;


					if (!sh.isValid() || sh.loopbackIP())
						throw StreamException("No Server, unable to ask for push.");

					ch->setStatus(S_REQUESTING);

					sys->sleep(500);	// wait a bit for the previous find to go

					int timeout;

					LOG_CHANNEL("Ch.%d Push request",ch->index);

					
					ch->pushSock = NULL;
					ch->pushIndex = ch->currSource.index;

					for(int i=0; i<chanMgr->pushTries; i++)
					{
						LOG_NETWORK("Push-request try %d",i+1);
						pack.initPush(ch->currSource,sh);
						servMgr->route(pack,ch->currSource.packetID,NULL);
						timeout = chanMgr->pushTimeout;		
						while ((!ch->pushSock) && (thread->active))
						{
							if (ch->checkBump())
								throw StreamException("Bumped");

							if (timeout-- <= 0)
								break;
							sys->sleep(1000);
						}

						if (ch->pushSock || (!thread->active))
							break;
					}

					if (!ch->pushSock)
						throw StreamException("Push timeout");

					ch->setStatus(S_CONNECTING);
					ch->sock = ch->pushSock;
				
				}

				char idStr[64];
				ch->info.id.toStr(idStr);
				
				if (ch->info.srcProtocol != ChanInfo::SP_PEERCAST)
				{
					// raw data stream

					ch->sock->writeLine("GET /%s/%s HTTP/1.0",ch->info.getTypeExt(ch->info.contentType),idStr);

					ch->sock->writeLine("%s %s",HTTP_HS_AGENT,PCX_AGENT);
					// request metadata for mp3
					if (ch->info.contentType == ChanInfo::T_MP3)
						ch->sock->writeLine("icy-metadata:1");

					ch->sock->writeLine("");

					ch->icyMetaInterval = 0;
					HTTP http(*ch->sock);

					http.checkResponse(200);

					while (http.nextHeader())
					{
						if (http.isHeader("icy-metaint"))
							ch->icyMetaInterval = http.getArgInt();

						LOG_CHANNEL("Ch.%d Raw GET: %s",ch->index,http.cmdLine);
					}
				}else{

					ch->sock->writeLine("GET /channel/%s HTTP/1.0",idStr);
					ch->sock->writeLine("%s %s",HTTP_HS_AGENT,PCX_AGENT);
					ch->sock->writeLine("");
					HTTP http(*ch->sock);

					http.checkResponse(200);

					while (http.nextHeader())
						LOG_CHANNEL("Ch.%d GET: %s",ch->index,http.cmdLine);
				}

				ch->setStatus(S_RECEIVING);
				ch->resetPlayTime();

				LOG_CHANNEL("Ch.%d Ready",ch->index);

				ch->input = ch->sock;

				ch->readStream();

				ch->setStatus(S_CLOSING);

				LOG_CHANNEL("Ch.%d Closed",ch->index);

			}catch(StreamException &e)
			{
				ChanHitList *chl = chanMgr->findHitListByID(ch->info.id);
				if (chl)
					chl->deadHit(ch->currSource);
				ch->setStatus(S_ABORT);
				LOG_ERROR("Ch.%d aborted: %s",ch->index,e.msg);
			}

			if (ch->sock)
			{
				ch->sock->close();
				delete ch->sock;
				ch->sock = NULL;
			}

			//peercastApp->updateChannelInfo(NULL);

		}

		sys->sleepIdle();
	}

	LOG_CHANNEL("Ch.%d closed",ch->index);
	chanMgr->lockHitList(ch->info.id,false);
	ch->endThread();
	return 0;
}
// -----------------------------------
void	Channel::startICY(ClientSocket *cs, SRC_TYPE st)
{
	srcType = st;
	type = T_BROADCAST;
	cs->timeout = 30000;
	sock = cs;
	input = cs;
	info.srcProtocol = ChanInfo::SP_HTTP;

	thread.data = this;
	thread.func = streamICY;
	if (!sys->startThread(&thread))
		init();
}
// -----------------------------------
void	Channel::startFind()
{
	type = T_RELAY;
	thread.data = this;
	thread.func = findProc;
	if (!sys->startThread(&thread))
		init();
}


// -----------------------------------
int	Channel::streamICY(ThreadInfo *thread)
{
	thread->lock();

	Channel *ch = (Channel *)thread->data;
	chanMgr->lockHitList(ch->info.id,true);


	LOG_CHANNEL("Channel started: %s",ch->getName());


	try 
	{
		ch->resetPlayTime();

		while ((thread->active) && (!ch->input->eof()))
		{

			ch->setStatus(S_BROADCASTING);
			ch->readStream();
		}
		LOG_CHANNEL("Ch.%d ended",ch->index);
	}catch(StreamException &e)
	{
		ch->setStatus(S_ABORT);
		LOG_ERROR("Ch.%d aborted: %s",ch->index,e.msg);
		sys->sleep(1000);
		ch->input->close();
	}


	ch->setStatus(S_CLOSING);
	LOG_CHANNEL("Ch.%d stopped",ch->index);

	if (ch->input)
	{
		ch->input->close();
		delete ch->input;
	}

	chanMgr->lockHitList(ch->info.id,false);
	ch->endThread();
	return 0;
}
// -----------------------------------
static char *nextMetaPart(char *str,char delim)
{
	while (*str)
	{
		if (*str == delim)
		{
			*str++ = 0;
			return str;
		}
		str++;
	}
	return NULL;
}
// -----------------------------------
static void copyStr(char *to,char *from,int max)
{
	char c;
	while ((c=*from++) && (--max))
		if (c != '\'')
			*to++ = c;

	*to = 0;
}

// -----------------------------------
void Channel::processMetadata(char *str)
{
	char *cmd=str;
	while (cmd)
	{
		char *arg = nextMetaPart(cmd,'=');
		if (!arg)
			break;

		char *next = nextMetaPart(arg,';');

		if (strcmp(cmd,"StreamTitle")==0)
			info.track.title.setUnquote(arg,String::T_ASCII);
		else if (strcmp(cmd,"StreamUrl")==0)
			info.track.contact.setUnquote(arg,String::T_ASCII);


		cmd = next;
	}

	updateMeta();
}

// -----------------------------------
XML::Node *ChanHit::createXML()
{
	// IP
	char ipStr[64];
	host.toStr(ipStr);
	
	return new XML::Node("host ip=\"%s\" hops=\"%d\" listeners=\"%d\" uptime=\"%d\" skips=\"%d\" push=\"%d\" busy=\"%d\" stable=\"%d\" agent=\"%s\" update=\"%d\" ",
		ipStr,
		hops,
		numListeners,
		upTime,
		numSkips,
		firewalled?1:0,
		busy?1:0,
		stable?1:0,
		agentStr,
		sys->getTime()-time
		);

}

// -----------------------------------
XML::Node *ChanHitList::createHitsXML()
{
	XML::Node *hn = new XML::Node("hits listeners=\"%d\" hosts=\"%d\" busy=\"%d\" stable=\"%d\" firewalled=\"%d\" closest=\"%d\" furthest=\"%d\" newest=\"%d\"",
		numListeners(),
		numHits(),
		numBusy(),
		numStable(),
		numFirewalled(),
		closestHit(),
		furthestHit(),
		sys->getTime()-newestHit()
		);		

	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			hn->add(hits[i].createXML());

	return hn;
}

// -----------------------------------
XML::Node *Channel::createRelayXML(bool showStat)
{
	const char *ststr;
	ststr = getStatusStr();
	if (!showStat)
		if ((status == S_RECEIVING) || (status == S_BROADCASTING))
			ststr = "OK";

	ChanHitList *chl = chanMgr->findHitListByID(info.id);

	return new XML::Node("relay listeners=\"%d\" relays=\"%d\" hosts=\"%d\" status=\"%s\"",
		numListeners,
		numRelays,
		(chl!=NULL)?chl->numHits():0,
		ststr
		);	
}

// -----------------------------------
void ChanMeta::fromXML(XML &xml)
{
	MemoryStream tout(data,MAX_DATALEN);
	xml.write(tout);

	len = tout.pos;
}
// -----------------------------------
void ChanMeta::fromMem(void *p, int l)
{
	len = l;
	memcpy(data,p,len);
}
// -----------------------------------
void ChanMeta::addMem(void *p, int l)
{
	if ((len+l) <= MAX_DATALEN)
	{
		memcpy(data+len,p,l);
		len += l;
	}
}
// -----------------------------------
void Channel::updateMeta()
{
	XML xml;

	XML::Node *n = info.createChannelXML();
	n->add(info.createTrackXML());
//	n->add(info.createServentXML());

	xml.setRoot(n);

	insertMeta.fromXML(xml);

	ChanPacket pack;

	pack.init('META',insertMeta.data,insertMeta.len);
	chanData.writePacket(pack);

}
// -----------------------------------
void Channel::readStream()
{
	if (servMgr->relayBroadcast)
		chanMgr->broadcastRelays(NULL,chanMgr->minBroadcastTTL,chanMgr->maxBroadcastTTL);


	if (info.srcProtocol == ChanInfo::SP_PEERCAST)
	{
		LOG_CHANNEL("Ch.%d is Peercast",index);
		readPeercast();
	}
	else if (info.srcProtocol == ChanInfo::SP_MMS)
	{
		LOG_CHANNEL("Ch.%d is MMS",index);
		readMMS();
	}else
	{
		switch(info.contentType)
		{
			case ChanInfo::T_MP3:
				LOG_CHANNEL("Ch.%d is MP3 - meta: %d",index,icyMetaInterval);
				readMP3();
				break;
			case ChanInfo::T_NSV:
				LOG_CHANNEL("Ch.%d is NSV",index);
				readRaw();
				break;
			case ChanInfo::T_WMA:
			case ChanInfo::T_WMV:
				throw StreamException("Ch.%d is WMA/WMV - but not MMS",index);
				break;
			case ChanInfo::T_OGG:
				LOG_CHANNEL("Ch.%d is OGG",index);
				readOGG();
				break;
			case ChanInfo::T_MOV:
				LOG_CHANNEL("Ch.%d is MOV",index);
				readMOV();
				break;
			case ChanInfo::T_MPG:
				LOG_CHANNEL("Ch.%d is MPG",index);
				readMPG();
				break;
			default:
				LOG_CHANNEL("Ch.%d is Raw",index);
				readRaw();
		}
	}
}
// -----------------------------------
void Channel::readPeercast()
{
	ChanPacket pack;

	if (input->readTag() != 'PCST')
		throw StreamException("Not PeerCast stream");

	syncPos = 0;
	info.numSkips = 0;

	peercastApp->channelStart(&info);

	while ((!input->eof() && (thread.active)))
	{

		pack.read(*input);

		chanData.writePacket(pack);
		MemoryStream mem(pack.data,pack.len);


		switch(pack.type)
		{
			case 'HEAD':
				//LOG_CHANNEL("Ch.%d HEAD: %d",index,pack.len);
				if (pack.len > ChanMeta::MAX_DATALEN)
					throw StreamException("Bad HEAD");
				headMeta.fromMem(pack.data,pack.len);
				break;
			case 'META':
				//LOG_CHANNEL("Ch.%d META: %d",index,pack.len);
				insertMeta.fromMem(pack.data,pack.len);
				{
					if (pack.len)
					{
						XML xml;
						xml.read(mem);
						XML::Node *n = xml.findNode("channel");					
						if (n)
						{
							info.updateFromXML(n);
							LOG_CHANNEL("Ch.%d track update: %s - %s",index,info.track.artist.cstr(),info.track.title.cstr());

							peercastApp->channelUpdate(&info);

							ChanHitList *chl = chanMgr->findHitListByID(info.id);
							if (chl)
								chl->info.updateFromXML(n);
;
						}
					}
				}
				break;
			case 'DATA':
				//LOG_CHANNEL("DATA: %d",pack.len);
				if (info.numSkips)
					info.numSkips--;
				break;
			case 'SYNC':
				{
					unsigned int s = mem.readLong();
					if ((s-syncPos) != 1)
					{
						LOG_CHANNEL("Ch.%d SKIP: %d to %d (%d)",index,syncPos,s,info.numSkips);
						if (syncPos)
						{
							info.numSkips++;
							if (info.numSkips>50)
								throw StreamException("Bumped - Too many skips");
						}
					}

					syncPos = s;
				}
				break;
			default:
				LOG_CHANNEL("Bad channel packet: %x",pack.type);

		}

		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;

	}
	peercastApp->channelStop(&info);


}
// -----------------------------------
void Channel::readRaw()
{
	ChanPacket pack;


	while ((!input->eof() && (thread.active)))
	{
		syncPos++;
		pack.init('SYNC',&syncPos,sizeof(syncPos));
		chanData.writePacket(pack);

		pack.len = sizeof(pack.data);
		input->read(pack.data,pack.len);
		pack.type = 'DATA';
		chanData.writePacket(pack);
		checkReadDelay(pack.len);

		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;


	}
}

// -----------------------------------
void Channel::readMPG()
{
	ChanPacket pack;

//	for(int i=0; i<10; i++)
//	{
//		unsigned int v = in.readLong();
//		LOG_CHANNEL("raw %d: %08x",i,SWAP4(v));
//	}

	while ((!numListeners) && (thread.active))
		sys->sleep(1000);
	sys->sleep(2000);



//	in.read(headMeta.data,1024);
//	headMeta.len = 1024;

	while ((!input->eof() && (thread.active)))
	{
		syncPos++;
		pack.init('SYNC',&syncPos,sizeof(syncPos));
		chanData.writePacket(pack);

		int rlen = 4000;

		rlen = input->read(pack.data,rlen);

		//LOG_CHANNEL("raw read %d - %d",syncPos,rlen);

		pack.len = rlen;
		pack.type = 'DATA';
		chanData.writePacket(pack);
		checkReadDelay(pack.len);

		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;

	}
}

// -----------------------------------
void Channel::readMP3()
{
	ChanPacket pack;

	while ((!input->eof() && (thread.active)))
	{

		if (icyMetaInterval)
		{

			int rlen = icyMetaInterval;

			while (rlen)
			{
				int rl = rlen;
				if (rl > sizeof(pack.data))
					rl = sizeof(pack.data);

				syncPos++;
				pack.init('SYNC',&syncPos,sizeof(syncPos));
				chanData.writePacket(pack);

				pack.len = rl;
				input->read(pack.data,pack.len);
				pack.type = 'DATA';
				chanData.writePacket(pack);
				checkReadDelay(pack.len);

				rlen-=rl;
			}

			unsigned char len;
			input->read(&len,1);
			if (len)
			{
				if (len*16 > 1024) len = 1024/16;
				char buf[1024];
				input->read(buf,len*16);
				processMetadata(buf);
			}

		}else{
			syncPos++;

			pack.init('SYNC',&syncPos,sizeof(syncPos));
			chanData.writePacket(pack);

			pack.len = sizeof(pack.data);
			input->read(pack.data,pack.len);

			pack.type = 'DATA';
			chanData.writePacket(pack);
			checkReadDelay(pack.len);

		}

		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;

	}
}
// -----------------------------------
void Channel::readMOV()
{
	ChanPacket pack;

	bool done=false;
	headMeta.len = 0;
	while ((!input->eof() && (thread.active)) && (!done))
	{


		unsigned int olen = input->readLong();
		unsigned int otag = input->readLong();

        // don't ask.
		unsigned int len = SWAP4(olen);
		unsigned int tag = SWAP4(otag);

		len -= 8;


		if ((tag == 'mdat') && (len))
		{
			LOG_CHANNEL("Ch.%d mdat: %d",index,len);
			{
				LOG_CHANNEL("mov wait: %d",headMeta.len);

				headMeta.addMem(&olen,4);
				headMeta.addMem(&otag,4);

				while ((!numListeners) && (thread.active))
					sys->sleep(1000);
				sys->sleep(1000);
				LOG_CHANNEL("mov go");

				while ((len) && (thread.active))
				{
					syncPos++;
					pack.init('SYNC',&syncPos,sizeof(syncPos));
					chanData.writePacket(pack);

					int rlen = 4000;
					rlen = input->read(pack.data,rlen);
					pack.type = 'DATA';
					pack.len = rlen;

					chanData.writePacket(pack);
					checkReadDelay(pack.len);

					len -= rlen;

					if (checkBump())
						done=true;
					if (checkIdle())
						done=true;


				}
				LOG_CHANNEL("Mov data end");
			}
		}else{
			LOG_CHANNEL("Ch.%d %c%c%c%c: %d",index,tag>>24&0xff,tag>>16&0xff,tag>>8&0xff,tag&0xff,len);

			if (headMeta.len+len > ChanMeta::MAX_DATALEN)
				throw StreamException("MOV section too big");

			headMeta.addMem(&olen,4);
			headMeta.addMem(&otag,4);
			if (len)
			{
				input->read(headMeta.data+headMeta.len,len);
				headMeta.len += len;
			}
		}
	}


}
// -----------------------------------
bool OggPacket::isBOS()
{
	return (data[5] & 0x02) != 0;
}
// -----------------------------------
bool OggPacket::isNewPacket()
{
	return (data[5] & 0x01) == 0;
}
// -----------------------------------
void OggPacket::read(Stream &in)
{
	// skip until we get OGG capture pattern
	bool gotOgg=false;
	while (!gotOgg)
	{
		if (in.readChar() == 'O')
			if (in.readChar() == 'g')
				if (in.readChar() == 'g')
					if (in.readChar() == 'S')
						gotOgg = true;
		if (!gotOgg)
			LOG_CHANNEL("Skipping OGG packet");
	}



	memcpy(&data[0],"OggS",4);

	in.read(&data[4],27-4);

	int numSegs = data[26];
	bodyLen = 0;

	// read segment table
	in.read(&data[27],numSegs);
	for(int i=0; i<numSegs; i++)
		bodyLen += data[27+i];

	if (bodyLen >= MAX_BODYLEN)		
		throw StreamException("OGG body too big");

	headLen = 27+numSegs;

	if (headLen > MAX_HEADERLEN)
		throw StreamException("OGG header too big");

	in.read(&data[headLen],bodyLen);



	#if 0
		LOG_DEBUG("OGG Packet - page %d, id = %x - %s %s %s - %d segs, %d bytes",
			*(unsigned int *)&data[18],
			*(unsigned int *)&data[14],
			data[5]&0x1?"cont":"new",
			data[5]&0x2?"bos":"",
			data[5]&0x4?"eos":"",
			numSegs,
			headLen+bodyLen);
	#endif

}
// -----------------------------------
void ChanPacket::init(unsigned int t, const void *p, unsigned int l)
{
	type = t;
	if (l > MAX_DATALEN)
		l = MAX_DATALEN;
	len = l;
	memcpy(data,p,len);
}
// -----------------------------------
void ChanPacket::write(Stream &out)
{
	out.writeTag(type);
	out.writeShort(len);
	out.writeShort(0);
	out.write(data,len);
}
// -----------------------------------
void ChanPacket::read(Stream &in)
{
	type = in.readTag();
	len = in.readShort();
	in.readShort();
	if (len > MAX_DATALEN)
		throw StreamException("Bad ChanPacket");
	in.read(data,len);
}

#if 0
// -----------------------------------
unsigned int ChanBuffer::read(unsigned int gpos,void *p,unsigned int len)
{
	if (gpos == 0)
		gpos = pos;

	while (len)
	{
		// skip forward if too far behind
		//if ((pos > MAX_DATALEN) && (gpos < (pos-MAX_DATALEN)))
		//	gpos = pos-MAX_DATALEN;

		// sleep if no data waiting
		unsigned int tim = sys->getTime();
		while (gpos >= pos)
		{
			sys->sleepIdle();
			if ((sys->getTime() - tim) > 30)
				throw TimeoutException();
		}



		int spos = gpos % MAX_DATALEN; // start
		int epos = pos % MAX_DATALEN;	// end

		unsigned int rlen;

		if (pos >= MAX_DATALEN)
		{
			if (spos < epos)	
				rlen = epos-spos;
			else
				rlen = MAX_DATALEN-spos;
		}else{
			rlen = epos-spos;
		}

		// read as many bytes as we can
		if (rlen > len)
			rlen = len;

		if (rlen)
		{

			//LOG("BUF READ gpos=%d, pos=%d, spos=%d, epos=%d, avail=%d",gpos,pos,spos,epos,avail);

			if (rlen > MAX_DATALEN)	
			{
				// something went horribly wrong
				throw StreamException("Chan buffer read fail");
			}

			memcpy(p,&data[spos],rlen);

			p = (char *)p+rlen;
			len -= rlen;
			gpos += rlen;
		}


	}
	return gpos;
}
// -----------------------------------
void	ChanBuffer::write(const void *p,int len)
{
	while (len)
	{
		unsigned int bpos = pos % MAX_DATALEN;
		int wlen = len;
		if ((bpos+wlen) > MAX_DATALEN)
			wlen = MAX_DATALEN-bpos;
		memcpy(&data[bpos],p,wlen);

		p = (char *)p+wlen;
		len -= wlen;
		pos += wlen;
	}
}
// -----------------------------------
void	ChanBuffer::writePacket(ChanPacket &pack)
{
	lock.on();

	unsigned int t = sys->gtohl( SWAP4(pack.type) );
	
	//LOG("write packet %x,%d",t,pack.len);

	lastPacket = pos;
	
	if ((lastPacket-firstPacket) > MAX_DATALEN)
		firstPacket = lastPacket;

	write(&t,4);
	write(&pack.len,sizeof(int));
	write(pack.data,pack.len);

	lock.off();
}
#endif

// -----------------------------------
unsigned int ChanPacketBuffer::writePacket(ChanPacket &pack)
{
	lock.on();

	packets[currPacket%MAX_PACKETS] = pack;
	lastPacket = currPacket;
	currPacket++;

	if (currPacket <= MAX_PACKETS)
		firstPacket = 0;
	else
		firstPacket = currPacket-MAX_PACKETS;

	lock.off();

	return currPacket-1;
}
// -----------------------------------
unsigned int	ChanPacketBuffer::readPacket(unsigned int pos,ChanPacket &pack)
{
	unsigned int tim = sys->getTime();

	if (pos > currPacket)
		pos = currPacket;

	while (pos == currPacket)
	{
		sys->sleepIdle();
		if ((sys->getTime() - tim) > 30)
			throw TimeoutException();
	}

	lock.on();

	if (pos == 0)			// start of stream
		pos = firstPacket;	// or lastPacket
	else if (pos < firstPacket)	// too far behind
		pos = firstPacket;		// so skip forward


	pack = 	packets[pos%MAX_PACKETS];
	pos++;

	lock.off();
	return pos;
}

#if 0
// -----------------------------------
unsigned int	ChanBuffer::readPacket(unsigned int gpos, ChanPacket &pack)
{
	while (gpos >= pos)
		sys->sleepIdle();

	lock.on();

	try
	{

		gpos = read(gpos,&pack.type,sizeof(pack.type));
		pack.type = sys->gtohl( SWAP4(pack.type) );

		gpos = read(gpos,&pack.len,sizeof(pack.len));

		if (pack.len > ChanPacket::MAX_DATALEN)
			pack.len = ChanPacket::MAX_DATALEN;

		gpos = read(gpos,pack.data,pack.len);
	}catch (StreamException &)
	{
	}
	lock.off();
	return gpos;
}

#endif

// -----------------------------------
void Channel::readVorbisIdent(Stream &in)
{
	int ver = in.readLong();
	int chans = in.readChar();
	int rate = in.readLong();
	int brMax = in.readLong();
	int brNom = in.readLong();
	int brLow = in.readLong();

	in.readChar();	// skip blocksize 0+1

	LOG_CHANNEL("OGG Ident: ver=%d, chans=%d, rate=%d, brMax=%d, brNom=%d, brLow=%d",
		ver,chans,rate,brMax,brNom,brLow);

	if (info.bitrate == 0)
	{
		if (brNom > 0)
			info.bitrate = brNom/1000;
		//else
		//	info.bitrate = 0;
	}

	char frame = in.readChar();		// framing bit
	if (!frame)
		throw StreamException("Bad Indent frame");

}

 
// -----------------------------------
void Channel::readVorbisSetup(Stream &in)
{
	// skip everything in packet
	int cnt=0;
	while (!in.eof())
	{
		cnt++;
		in.readChar();
	}

	LOG_CHANNEL("Read %d bytes of Vorbis Setup",cnt);
}
// -----------------------------------
void Channel::readVorbisComment(Stream &in)
{
	int vLen = in.readLong();	// vendor len

	in.skip(vLen);

	char argBuf[8192];

	info.track.clear();

	int cLen = in.readLong();	// comment len
	for(int i=0; i<cLen; i++)
	{
		int l = in.readLong();
		if (l > sizeof(argBuf))
			throw StreamException("Comment string too long");
		in.read(argBuf,l);
		argBuf[l] = 0;
		LOG_CHANNEL("OGG Comment: %s",argBuf);

		char *arg;
		if ((arg=stristr(argBuf,"ARTIST=")))
			info.track.artist.set(arg+7,String::T_ASCII);
		else if ((arg=stristr(argBuf,"TITLE=")))
			info.track.title.set(arg+6,String::T_ASCII);
		else if ((arg=stristr(argBuf,"GENRE=")))
			info.track.genre.set(arg+6,String::T_ASCII);
		else if ((arg=stristr(argBuf,"CONTACT=")))
			info.track.contact.set(arg+8,String::T_ASCII);
		else if ((arg=stristr(argBuf,"ALBUM=")))
			info.track.album.set(arg+6,String::T_ASCII);
	}

	char frame = in.readChar();		// framing bit
	if (!frame)
		throw StreamException("Bad Comment frame");

	updateMeta();

}

// -----------------------------------
void Channel::getStreamPath(char *str)
{
	char idStr[64];

	getIDStr(idStr);

	sprintf(str,"/stream/%s.%s",idStr,info.getTypeExt(info.contentType));
}
// -----------------------------------
void Channel::readVorbisHeaders(Stream &in, OggPacket &ogg)
{
	LOG_CHANNEL("Reading OGG Vorbis headers");
		
//	if (!ogg.isVorbisPacket())
//		throw StreamException("Not Vorbis stream");

	headMeta.len = 0;

	VorbisPacket vorbis;
	

	int numHeaders = 0;
	
	while (numHeaders < 3)	// ogg vorbis always starts with 3 headers
	{

		// read until we have complete ogg page
		vorbis.bodyLen = 0;
		while (!in.eof())
		{
			if (!thread.active)
				throw StreamException("Thread ended waiting for OGG packet");

			if ((vorbis.bodyLen + ogg.bodyLen) >= VorbisPacket::MAX_BODYLEN)
				throw StreamException("Vorbis body too big");

			if (headMeta.len+(ogg.bodyLen+ogg.headLen) >= ChanMeta::MAX_DATALEN)
				throw StreamException("OGG packet too big for headMeta");

			// copy complete ogg packet into headMeta
			memcpy(&headMeta.data[headMeta.len],ogg.data,ogg.headLen+ogg.bodyLen);
			headMeta.len += ogg.headLen+ogg.bodyLen;

			// add body to vorbis packet
			memcpy(&vorbis.body[vorbis.bodyLen],&ogg.data[ogg.headLen],ogg.bodyLen);
			vorbis.bodyLen += ogg.bodyLen;
		
			// read new ogg packet and check for end of last packet.
			ogg.read(in);
			if (ogg.isNewPacket())
				break;
		}

		MemoryStream vin(vorbis.body,vorbis.bodyLen);

		while (!vin.eof())
		{
			char id[7];

			vin.read(id,7);
			if (memcmp(&id[1],"vorbis",6)!=0)
				throw StreamException("Expected Vorbis packet");

			switch (id[0])
			{
				case 1:	// ident
					LOG_CHANNEL("Vorbis Header: Ident");
					readVorbisIdent(vin);
					break;
				case 3: // comment
					LOG_CHANNEL("Vorbis Header: Comment");
					readVorbisComment(vin);
					break;
				case 5: // setup
					LOG_CHANNEL("Vorbis Header: Setup");
					readVorbisSetup(vin);
					break;
				default:
					throw StreamException("Unknown Vorbis packet while reading headers");
					break;
			}

			numHeaders++;
		}

	}


	ChanPacket pack;
	pack.init('HEAD',headMeta.data,headMeta.len);
	headMeta.startPos = chanData.writePacket(pack);

	LOG_CHANNEL("Got Vorbis headers at %d: total %d bytes",headMeta.startPos,headMeta.len);

}
// -----------------------------------
void Channel::readOGG()
{

	OggPacket ogg;
	ChanPacket pack;

	while ((!input->eof() && (thread.active)))
	{

		ogg.read(*input);

		if (ogg.isBOS())
			readVorbisHeaders(*input,ogg);

		syncPos++;
		pack.init('SYNC',&syncPos,sizeof(syncPos));
		chanData.writePacket(pack);

		pack.init('DATA',ogg.data,ogg.headLen+ogg.bodyLen);
		chanData.writePacket(pack);
		checkReadDelay(pack.len);

		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;


	}
}


// -----------------------------------
ASFStream parseASFHeader(Stream &in)
{
	ASFStream asf;

	try
	{
		int numHeaders = in.readLong();

		in.readChar();
		in.readChar();

		LOG_CHANNEL("ASF Headers: %d",numHeaders);
		for(int i=0; i<numHeaders; i++)
		{

			ASFObject obj;

			unsigned int l = obj.readHead(in);
			obj.readData(in,l);


			MemoryStream data(obj.data,obj.lenLo);

			if (obj.type == ASFObject::T_FILE_PROP)
			{
				data.skip(32);

				unsigned int dpLo = data.readLong();
				unsigned int dpHi = data.readLong();

				data.skip(24);

				data.readLong();
				//data.writeLong(1);	// flags = broadcast, not seekable

				int min = data.readLong();
				int max = data.readLong();
				int br = data.readLong();

				if (min != max)
					throw StreamException("ASF packetsizes (min/max) must match");

				asf.packetSize = max;
				asf.bitrate = br;
				asf.numPackets = dpLo;

			}

		}
	}catch(StreamException &e)
	{
		LOG_ERROR("ASF: %s",e.msg);
	}

	return asf;
}
// -----------------------------------
class ASFChunk
{
public:

	void read(Stream &in)
	{
		type = in.readShort();
		len = in.readShort();
		seq = in.readLong();
		v1 = in.readShort();
		v2 = in.readShort();

		dataLen = len-8;
		if (dataLen > sizeof(data)) 
			throw StreamException("ASF chunk too big");
		in.read(data,dataLen);
	}

	void write(Stream &out)
	{
		out.writeShort(type);
		out.writeShort(len);
		out.writeLong(seq);
		out.writeShort(v1);
		out.writeShort(v2);
		out.write(data,dataLen);
	}

	unsigned int seq,dataLen;
	unsigned short type,len,v1,v2;
	unsigned char data[8192];
};

// -----------------------------------
void Channel::readMMS()
{	
	ChanPacket pack;


	while ((!input->eof() && (thread.active)))
	{

		ASFChunk chunk;

		chunk.read(*input);

		switch (chunk.type)
		{
			case 0x4824:		// asf header
			{
				MemoryStream mem(headMeta.data,sizeof(headMeta.data));

				chunk.write(mem);

				headMeta.len = mem.pos;


				MemoryStream asfm(chunk.data,chunk.dataLen);
				ASFObject asfHead;
				asfHead.readHead(asfm);

				ASFStream asf = parseASFHeader(asfm);
				LOG_DEBUG("ASF file prop: pnum=%d, psize=%d, br=%d",asf.numPackets,asf.packetSize,asf.bitrate);
				info.bitrate = asf.bitrate/1000;


				break;
			}
			case 0x4424:		// asf data
			{

				syncPos++;
				pack.init('SYNC',&syncPos,sizeof(syncPos));
				chanData.writePacket(pack);

				MemoryStream mem(pack.data,sizeof(pack.data));

				chunk.write(mem);

				pack.len = mem.pos;
				pack.type = 'DATA';
				chanData.writePacket(pack);

				break;
			}
			default:
				throw StreamException("Unknown ASF chunk");

		}


		if (checkBump())
			throw StreamException("Bumped");
		if (checkIdle())
			break;
	}
}


// -----------------------------------
void ChanMgr::startSearch(ChanInfo &info)
{
	searchInfo = info;
	clearHitLists();
	numFinds = 0;
	lastHit = 0;
//	lastSearch = 0;
	searchActive = true;
}
// -----------------------------------
void ChanMgr::lockHitList(GnuID &id, bool on)
{
	ChanHitList *chl = findHitListByID(id);
	if (chl)
		chl->locked = on;
}

// -----------------------------------
Channel *ChanMgr::findChannel(ChanInfo &info)
{
	Channel *c=NULL;
	findChannels(info,&c,1);
	return c;
}

// -----------------------------------
Channel *ChanMgr::findChannelByName(const char *n)
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (stricmp(channels[i].info.name,n)==0)
				return &channels[i];

	return NULL;
}
// -----------------------------------
Channel *ChanMgr::findListenerChannel()
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].numListeners)
				return &channels[i];

	return NULL;
}
// -----------------------------------
Channel *ChanMgr::findChannelByIndex(int index)
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].index == index)
				return &channels[i];

	return NULL;
}	
// -----------------------------------
Channel *ChanMgr::findChannelByMount(const char *str)
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (strcmp(channels[i].mount,str)==0)
				return &channels[i];

	return NULL;
}	
// -----------------------------------
Channel *ChanMgr::findChannelByID(GnuID &id)
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].info.id.isSame(id))
				return &channels[i];

	return NULL;
}	
// -----------------------------------
Channel *ChanMgr::findPushChannel(int index)
{
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if ((channels[i].pushIndex == index) && (!channels[i].pushSock))
				return &channels[i];

	return NULL;
}	
// -----------------------------------
int ChanMgr::findChannels(ChanInfo &info, Channel **ch, int max)
{
	int cnt=0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].info.match(info))
			{
				ch[cnt++] = &channels[i];
				if (cnt >= max)
					break;
			}
	return cnt;
}
// -----------------------------------
int ChanMgr::findChannelsByStatus(Channel **ch, int max, Channel::STATUS status)
{
	int cnt=0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].status == status)
			{
				ch[cnt++] = &channels[i];
				if (cnt >= max)
					break;
			}
	return cnt;
}
// -----------------------------------
Channel *ChanMgr::createRelay(ChanInfo &info, bool stayConnected)
{
	Channel *c = chanMgr->createChannel(info,NULL);
	if (c)
	{
		c->stayConnected = stayConnected;
		c->startFind();
		return c;
	}
	return NULL;
}
// -----------------------------------
int ChanMgr::findAndRelay(ChanInfo &info, Channel **ch, int max)
{
	int cnt=0;

	char idStr[64];
	info.id.toStr(idStr);
	LOG_CHANNEL("Searching for: %s %s",info.name.cstr(),idStr);

	for(int i=0; i<180; i++)	// search for 3 minutes.
	{

		ChanHitList *chl = findHitList(info);

		if (chl)
		{
			Channel *c;

			c = findChannelByID(chl->info.id);

			if (!c)
			{
				c = chanMgr->createChannel(chl->info,NULL);
				if (c)
					c->startGet();
			}

			if (!c)
				break;
			
			ch[0] = c;
			cnt = 1;
			break;
		}else
		{
			if ((i%60) == 0)
				servMgr->findChannel(info);
		}

		sys->sleep(1000);
	}

	LOG_CHANNEL("Search results: %d",cnt);

	return cnt;
}
// -----------------------------------
ChanMgr::ChanMgr()
{
	int i;

	for(i=0; i<MAX_CHANNELS; i++)
		channels[i].init();


	for(i=0; i<MAX_HITLISTS; i++)
	{
		hitlists[i].index = i;
		hitlists[i].init();
	}

	broadcastMsg.clear();
	broadcastMsgInterval=10;

	broadcastID.generate();

	deadHitAge = 600;

	icyMetaInterval = 8192;	
	maxStreamsPerChannel = 0;

	searchInfo.init();

	minBroadcastTTL = 1;
	maxBroadcastTTL = 7;

	pushTimeout = 60;	// 1 minute
	pushTries = 5;		// 5 times
	maxPushHops = 8;	// max 8 hops away
	maxUptime = 0;		// 0 = none

	autoQuery = 0;	
	lastQuery = 0;
}
// -----------------------------------
void ChanMgr::broadcastRelays(Servent *serv, int minTTL, int maxTTL)
{
	if (!servMgr->relayBroadcast)
		return;

	//if ((servMgr->getFirewall() == ServMgr::FW_OFF) || servMgr->serverHost.localIP())
	{

		Host sh = servMgr->serverHost;
		bool push = (servMgr->getFirewall()!=ServMgr::FW_OFF);
		bool busy = (servMgr->inFull() && servMgr->outFull()) || servMgr->streamFull();
		bool stable = servMgr->totalStreams>0;


		GnuPacket hit;

		int numChans=0;


		for(int i=0; i<MAX_CHANNELS; i++)
		{
			Channel *c = &channels[i];

			if (c->isPlaying())
			{
				int ttl = (c->info.getUptime() / servMgr->relayBroadcast);	// 1 hop per N seconds

				if (ttl < minTTL)
					ttl = minTTL;

				if (ttl > maxTTL)
					ttl = maxTTL;

				if (hit.initHit(sh,1,&c,NULL,push,busy,stable,ttl))
				{
					int numOut=0;
					numChans++;
					if (serv)
					{
						serv->outputPacket(hit,false);
						numOut++;
					}
					else
						numOut+=servMgr->broadcast(hit,NULL);

					LOG_NETWORK("Sent ch.%d to %d servents, TTL %d",c->index,numOut,ttl);		

				}
			}
		}
		//if (numChans)
		//	LOG_NETWORK("Sent %d channels to %d servents",numChans,numOut);		
	}
}
// -----------------------------------
void ChanMgr::setBroadcastMsg(String &msg)
{
	//if (!msg.isSame(broadcastMsg))
	{
		broadcastMsg = msg;

		for(int i=0; i<MAX_CHANNELS; i++)
		{
			Channel *c = &channels[i];
			if (c->isActive())
				if (c->status == Channel::S_BROADCASTING)
				{
					c->info.comment = broadcastMsg;
					c->updateMeta();
				}
		}

	}
}
// -----------------------------------
void ChanMgr::clearHitLists()
{
	for(int i=0; i<MAX_HITLISTS; i++)
		if (!hitlists[i].locked)
		{
			peercastApp->delChannel(&hitlists[i].info);
			hitlists[i].init();
		}
}

// -----------------------------------
Channel *ChanMgr::createChannel(ChanInfo &info, const char *mount)
{
	lock.on();
	for(int i=0; i<MAX_CHANNELS; i++)
	{
		Channel *c = &channels[i];
		if (!c->isActive())
		{
			c->info = info;
			c->info.lastPlay = 0;
			c->info.status = ChanInfo::S_UNKNOWN;
			if (mount)
				c->mount.set(mount);
			c->index = i+1;
			c->setStatus(Channel::S_WAIT);
			c->type = Channel::T_ALLOCATED;
			lock.off();
			return c;
		}
	}
	lock.off();
	return NULL;
}
// -----------------------------------
ChanHitList *ChanMgr::findHitList(ChanInfo &info)
{
	for(int i=0; i<MAX_HITLISTS; i++)
		if (hitlists[i].isUsed())
			if (hitlists[i].info.match(info))
				return &hitlists[i];
	return NULL;
}
// -----------------------------------
ChanHitList *ChanMgr::findHitListByID(GnuID &id)
{
	for(int i=0; i<MAX_HITLISTS; i++)
		if (hitlists[i].isUsed())
			if (hitlists[i].info.id.isSame(id))
				return &hitlists[i];
	return NULL;
}
// -----------------------------------
int ChanMgr::numHitLists()
{
	int num=0;
	for(int i=0; i<MAX_HITLISTS; i++)
		if (hitlists[i].isUsed())
			num++;
	return num;
}
// -----------------------------------
ChanHitList *ChanMgr::addHitList(ChanInfo &info)
{
	ChanHitList *hl = NULL;


	for(int i=0; i<MAX_HITLISTS; i++)
		if (!hitlists[i].isUsed())
		{
			hl = &hitlists[i];
			break;
		}

	if (hl)
	{
		hl->info = info;
		peercastApp->addChannel(&hl->info);
	}

	return hl;
}
// -----------------------------------
void ChanMgr::clearDeadHits()
{
	for(int i=0; i<MAX_HITLISTS; i++)
		if (hitlists[i].isUsed())
			if (hitlists[i].clearDeadHits(deadHitAge) == 0)
			{
				peercastApp->delChannel(&hitlists[i].info);
				hitlists[i].init();
			}
}

// -----------------------------------
int ChanMgr::numConnected()
{
	int tot = 0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			tot++;
	return tot;
}
// -----------------------------------
int ChanMgr::numRelayed()
{
	int tot = 0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].isPlaying())
				if (channels[i].numRelays>0)
					tot++;
	return tot;
}
// -----------------------------------
int ChanMgr::numListeners()
{
	int tot = 0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].isPlaying())
				if (channels[i].numListeners>0)
					tot++;
	return tot;
}
// -----------------------------------
int ChanMgr::numIdle()
{
	int tot = 0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].status == Channel::S_IDLE)
				tot++;
	return tot;
}

// -----------------------------------
unsigned int ChanMgr::totalInput()
{
	unsigned int tot = 0;
	for(int i=0; i<MAX_CHANNELS; i++)
		if (channels[i].isActive())
			if (channels[i].isPlaying())
				if (channels[i].input)
					tot+=channels[i].input->bytesInPerSec;

	return tot;
}
// -----------------------------------
ChanHit	*ChanMgr::addHit(ChanInfo &info, ChanHit &h)
{
	if (searchActive)
		lastHit = sys->getTime();

	// if channel is waiting for update then give it
	Channel *ch = findChannelByID(info.id);
	if (ch)
	{
		if (!ch->isBroadcasting())
			ch->info.update(info);
	}


	// otherwise add to list of channels
	ChanHitList *hl = findHitListByID(info.id);
	if (!hl)
		hl = addHitList(info);

	if (hl)
	{
		hl->info.update(info);
		return hl->addHit(h);
	}else
		return NULL;
}

// -----------------------------------
void ChanMgr::playChannels(Channel **cl, int num)
{
	if (num)
	{

		Channel *c1 = cl[0];

		char str[128],fname[128],idStr[128];

		sprintf(str,"http://localhost:%d",servMgr->serverHost.port);
		c1->info.id.toStr(idStr);

		PlayList::TYPE type;


		if ((c1->info.contentType == ChanInfo::T_WMA) || (c1->info.contentType == ChanInfo::T_WMV))
		{
			type = PlayList::T_ASX;
			// WMP seems to have a bug where it doesn`t re-read asx files if they have the same name
			// so we prepend the channel id to make it unique - NOTE: should be deleted afterwards.
			sprintf(fname,"%s.asx",idStr);	
		}else
		{
			type = PlayList::T_SCPLS;
			sprintf(fname,"play.pls");
		}


		PlayList *pls = new PlayList(type,num);
		pls->addChannels(str,cl,num);


		FileStream file;
		file.openWriteReplace(fname);
		pls->write(file);
		file.close();

		sys->executeFile(fname);
		delete pls;

	}

}
// -----------------------------------
class ChanFindInfo : public ThreadInfo
{
public:
	GnuID	id;
	bool	keep;
};
// -----------------------------------
THREAD_PROC playChannelProc(ThreadInfo *th)
{
	ChanFindInfo *cfi = (ChanFindInfo *)th;

	ChanInfo info;
	info.id = cfi->id;

	peercastApp->notifyMessage(ServMgr::NT_PEERCAST,"Connecting to channel, please wait...");

	Channel *ch;
	int num = chanMgr->findChannels(info,&ch,1);

	if (!num)
		num = chanMgr->findAndRelay(info,&ch,1);

	if (num)
	{
		ch->prefetchCnt = 10;	// prefetch for 10 packets before giving up and closing channel
		chanMgr->playChannels(&ch,1);
		if (cfi->keep)
			ch->stayConnected = cfi->keep;
	}

	delete cfi;
	return 0;
}
// -----------------------------------
void ChanMgr::playChannel(GnuID &id, bool keep)
{
	ChanFindInfo *cfi = new ChanFindInfo;
	cfi->id = id;
	cfi->keep = keep;
	cfi->func = playChannelProc;

	sys->startThread(cfi);
}
// -----------------------------------
void ChanHitList::init()
{
	info.init();
	memset(hits,0,sizeof(ChanHit)*MAX_HITS);
	lastHitTime = 0;
	locked = false;
}
// -----------------------------------
bool	ChanHitList::isAvailable() 
{
	return (numHits()-numBusy())>0;
}

// -----------------------------------
ChanHit *ChanHitList::addHit(ChanHit &h)
{
	int i;

	lastHitTime = sys->getTime();
	h.time = lastHitTime;

	for(i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip == h.host.ip)
			if (hits[i].host.port == h.host.port)
			{
				hits[i] = h;
				return &hits[i];
			}


	for(i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip == 0)
		{
			hits[i] = h;
			return &hits[i];
		}

	return NULL;
}


// -----------------------------------
int	ChanHitList::clearDeadHits(unsigned int timeout)
{
	int cnt=0;
	unsigned int ctime = sys->getTime();
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
		{
			if ((ctime-hits[i].time) > timeout)
				hits[i].init();
			else
				cnt++;
		}
	return cnt;
}


// -----------------------------------
void	ChanHitList::deadHit(ChanHit &h)
{
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip == h.host.ip)
			if (hits[i].host.port == h.host.port)
				hits[i].init();
}
// -----------------------------------
int	ChanHitList::numHits()
{
	int cnt=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			cnt++;

	return cnt;
}
// -----------------------------------
int	ChanHitList::numListeners()
{
	int cnt=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			cnt += hits[i].numListeners;

	return cnt;
}
// -----------------------------------
int	ChanHitList::numBusy()
{
	int cnt=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			cnt += hits[i].busy?1:0;

	return cnt;
}
// -----------------------------------
int	ChanHitList::numStable()
{
	int cnt=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			cnt += hits[i].stable?1:0;

	return cnt;
}
// -----------------------------------
int	ChanHitList::numFirewalled()
{
	int cnt=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			cnt += hits[i].firewalled?1:0;

	return cnt;
}
// -----------------------------------
int	ChanHitList::closestHit()
{
	int hop=10000;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			if (hits[i].hops < hop)
				hop = hits[i].hops;

	return hop;
}
// -----------------------------------
int	ChanHitList::furthestHit()
{
	int hop=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			if (hits[i].hops > hop)
				hop = hits[i].hops;

	return hop;
}
// -----------------------------------
unsigned int	ChanHitList::newestHit()
{
	unsigned int time=0;
	for(int i=0; i<MAX_HITS; i++)
		if (hits[i].host.ip)
			if (hits[i].time > time)
				time = hits[i].time;

	return time;
}
// -----------------------------------
ChanHit	ChanHitList::getHit(bool allowPush)
{
	int minHops = 100;
	unsigned int maxUpTime = 0;

	ChanHit ch;
	ch.init();

	for(int i=0; i<MAX_HITS; i++)	
	{
		ChanHit *c = &hits[i];
		if (c->host.ip)
			if (!c->busy)
				if (c->upTime >= maxUpTime)
				{
					bool firewallOK;

					firewallOK = (allowPush && c->firewalled) || (!c->firewalled) || (c->host.localIP());

					if (c->firewalled)
						if (((c->hops > chanMgr->maxPushHops) || (c->hops > minHops)))
							firewallOK = false;
					
					if (firewallOK)
					{
						ch = *c;
						minHops = c->hops;
						maxUpTime = c->upTime;
					}
				}
	}
	return ch;
}

// -----------------------------------
void ChanHit::init()
{
	host.ip = 0;
	host.port = 0;
	numListeners = 0;
	firewalled = busy = stable = false;
	hops = 0;
	index = 0;
	time = upTime = 0;
	agentStr[0]=0;
	numSkips=0;
	packetID.clear();
	maxPreviewTime=0;
}
// -----------------------------------
const char *ChanInfo::getTypeStr(TYPE t)
{
	switch (t)
	{
		case T_OGG: return "OGG";
		case T_RAW: return "RAW";
		case T_MP3: return "MP3";
		case T_MOV: return "MOV";
		case T_MPG: return "MPG";
		case T_NSV: return "NSV";
		case T_WMA: return "WMA";
		case T_WMV: return "WMV";
		default: return "UNKNOWN";
	}
}
// -----------------------------------
const char *ChanInfo::getProtocolStr(PROTOCOL t)
{
	switch (t)
	{
		case SP_PEERCAST: return "PEERCAST";
		case SP_HTTP: return "HTTP";
		case SP_FILE: return "FILE";
		case SP_MMS: return "MMS";
		default: return "UNKNOWN";
	}
}
// -----------------------------------
ChanInfo::PROTOCOL ChanInfo::getProtocolFromStr(const char *str)
{
	if (stricmp(str,"PEERCAST")==0)
		return SP_PEERCAST;
	else if (stricmp(str,"HTTP")==0)
		return SP_HTTP;
	else if (stricmp(str,"FILE")==0)
		return SP_FILE;
	else if (stricmp(str,"MMS")==0)
		return SP_MMS;
	else 
		return SP_UNKNOWN;
}

// -----------------------------------
const char *ChanInfo::getTypeExt(TYPE t)
{
	switch(t)
	{
		case ChanInfo::T_OGG:
			return "ogg";
		case ChanInfo::T_MP3:
			return "mp3";
		case ChanInfo::T_MOV:
			return "mov";
		case ChanInfo::T_NSV:
			return "nsv";
		case ChanInfo::T_WMV:
			return "wmv";
		case ChanInfo::T_WMA:
			return "wma";
		default:
			return "stream";
	}
}
// -----------------------------------
ChanInfo::TYPE ChanInfo::getTypeFromStr(const char *str)
{
	if (stricmp(str,"MP3")==0)
		return T_MP3;
	else if (stricmp(str,"OGG")==0)
		return T_OGG;
	else if (stricmp(str,"RAW")==0)
		return T_RAW;
	else if (stricmp(str,"NSV")==0)
		return T_NSV;
	else if (stricmp(str,"WMA")==0)
		return T_WMA;
	else if (stricmp(str,"WMV")==0)
		return T_WMV;
	else if (stricmp(str,"PLS")==0)
		return T_PLS;
	else if (stricmp(str,"M3U")==0)
		return T_PLS;
	else if (stricmp(str,"ASX")==0)
		return T_ASX;
	else 
		return T_UNKNOWN;
}
// -----------------------------------
bool	ChanInfo::match(ChanInfo &inf)
{
	bool matchAny=true;

	if (inf.status != S_UNKNOWN)
	{
		if (status != inf.status)
			return false;
	}

	if (inf.bitrate != 0)
	{
		if (bitrate == inf.bitrate)
			return true;
		matchAny = false;
	}

	if (inf.id.isSet())
	{
		if (id.isSame(inf.id))
			return true;
		matchAny = false;
	}

	if (inf.contentType != T_UNKNOWN)
	{
		if (contentType == inf.contentType)
			return true;
		matchAny = false;
	}

	if (!inf.name.isEmpty())
	{
		if (name.contains(inf.name))
			return true;
		matchAny = false;
	}

	if (!inf.genre.isEmpty())
	{
		if (genre.contains(inf.genre))
			return true;
		matchAny = false;
	}

	return matchAny;
}

// -----------------------------------
void ChanInfo::update(ChanInfo &info)
{
	// only update from chaninfo that has full name etc..
	if (info.name.isEmpty())
		return;


	if (contentType == T_UNKNOWN)
	{
		bitrate = info.bitrate;
		contentType = info.contentType;
		name = info.name;
	}

	track = info.track;
	comment = info.comment;
	genre = info.genre;
	url = info.url;
}
// -----------------------------------
void ChanInfo::initNameID(const char *n)
{
	init();
	id.fromStr(n);
	if (!id.isSet())
		name.set(n);
}

// -----------------------------------
void ChanInfo::init()
{
	status = S_UNKNOWN;
	name.clear();
	bitrate = 0;
	contentType = T_UNKNOWN;
	srcProtocol = SP_UNKNOWN;
	id.clear();
	url.clear();
	genre.clear();
	comment.clear();
	track.clear();
	lastPlay = 0;
	numSkips = 0;

}
// -----------------------------------
void ChanInfo::readTrackXML(XML::Node *n)
{
	track.clear();
	readXMLString(track.title,n,"title");
	readXMLString(track.contact,n,"contact");
	readXMLString(track.artist,n,"artist");
	readXMLString(track.album,n,"album");
	readXMLString(track.genre,n,"genre");
}
// -----------------------------------
unsigned int ChanInfo::getUptime()
{
	// calculate uptime and cap if requested by settings.
	unsigned int upt;
	upt = lastPlay?(sys->getTime()-lastPlay):0;
	if (chanMgr->maxUptime)
		if (upt > chanMgr->maxUptime)
			upt = chanMgr->maxUptime;
	return upt;
}
// -----------------------------------
XML::Node *ChanInfo::createChannelXML()
{
	char idStr[64];

	String nameHTML = name;
	nameHTML.convertTo(String::T_HTML);
	String urlHTML = url;
	urlHTML.convertTo(String::T_HTML);
	String genreHTML = genre;
	genreHTML.convertTo(String::T_HTML);
	String descHTML = desc;
	descHTML.convertTo(String::T_HTML);
	String commentHTML;
	commentHTML = comment;
	commentHTML.convertTo(String::T_HTML);


	id.toStr(idStr);


	return new XML::Node("channel name=\"%s\" id=\"%s\" bitrate=\"%d\" type=\"%s\" genre=\"%s\" desc=\"%s\" url=\"%s\" uptime=\"%d\" comment=\"%s\" skips=\"%d\"",
		nameHTML.cstr(),
		idStr,
		bitrate,
		getTypeStr(contentType),
		genreHTML.cstr(),
		descHTML.cstr(),
		urlHTML.cstr(),
		getUptime(),
		commentHTML.cstr(),
		numSkips
		);	
}

// -----------------------------------
XML::Node *ChanInfo::createQueryXML()
{
	char buf[512];
	char idStr[64];


	String nameHTML = name;
	nameHTML.convertTo(String::T_HTML);
	String genreHTML = genre;
	genreHTML.convertTo(String::T_HTML);

	buf[0]=0;
	if (!nameHTML.isEmpty())
	{
		strcat(buf," name=\"");
		strcat(buf,nameHTML.cstr());
		strcat(buf,"\"");
	}

	if (!genreHTML.isEmpty())
	{
		strcat(buf," genre=\"");
		strcat(buf,genreHTML.cstr());
		strcat(buf,"\"");
	}

	if (id.isSet())
	{
		id.toStr(idStr);
		strcat(buf," id=\"");
		strcat(buf,idStr);
		strcat(buf,"\"");
	}
		

	return new XML::Node("channel %s",buf);
}

// -----------------------------------
XML::Node *ChanInfo::createRelayChannelXML()
{
	char idStr[64];

	id.toStr(idStr);


	return new XML::Node("channel id=\"%s\" uptime=\"%d\" skips=\"%d\"",
		idStr,
		getUptime(),
		numSkips
		);	
}// -----------------------------------
XML::Node *ChanInfo::createTrackXML()
{
	String titleHTML,contactHTML,albumHTML,genreHTML,artistHTML;
	titleHTML = track.title;
	titleHTML.convertTo(String::T_HTML);
	artistHTML = track.artist;
	artistHTML.convertTo(String::T_HTML);
	albumHTML = track.album;
	albumHTML.convertTo(String::T_HTML);
	genreHTML = track.genre;
	genreHTML.convertTo(String::T_HTML);
	contactHTML = track.contact;
	contactHTML.convertTo(String::T_HTML);


	return new XML::Node("track title=\"%s\" artist=\"%s\" album=\"%s\" genre=\"%s\" contact=\"%s\"",
		titleHTML.cstr(),
		artistHTML.cstr(),
		albumHTML.cstr(),
		genreHTML.cstr(),
		contactHTML.cstr()
		);
}

// -----------------------------------
void ChanInfo::init(XML::Node *n)
{
	init();

	updateFromXML(n);
}
// -----------------------------------
void ChanInfo::updateFromXML(XML::Node *n)
{
	String typeStr,idStr;

	readXMLString(name,n,"name");
	readXMLString(genre,n,"genre");
	readXMLString(url,n,"url");
	readXMLString(desc,n,"desc");


	int br = n->findAttrInt("bitrate");
	if (br)
		bitrate = br;

	readXMLString(typeStr,n,"type");
	if (!typeStr.isEmpty())
		contentType = getTypeFromStr(typeStr.cstr());


	readXMLString(idStr,n,"id");
	if (!idStr.isEmpty())
		id.fromStr(idStr.cstr());

	readXMLString(comment,n,"comment");

	XML::Node *tn = n->findNode("track");
	if (tn)
		readTrackXML(tn);

}

// -----------------------------------
void ChanInfo::init(const char *n, GnuID &cid, TYPE tp, int br)
{
	init();

	name.set(n);
	bitrate = br;
	contentType = tp;
	id = cid;
}

// -----------------------------------
void ChanInfo::init(const char *fn)
{
	init();

	if (fn)
		name.set(fn);
}
// -----------------------------------
void PlayList::readASX(Stream &in)
{
	LOG_DEBUG("Reading ASX");
	XML xml;

	try
	{
		xml.read(in);
	}catch(StreamException &) {} // TODO: eof is NOT handled properly in sockets - always get error at end

	if (xml.root)
	{
		XML::Node *n = xml.root->child;
		while (n)
		{
			if (stricmp("entry",n->getName())==0)
			{
				XML::Node *rf = n->findNode("ref");
				if (rf)
				{
					char *hr = rf->findAttr("href");
					if (hr)
					{
						addURL(hr,"");
						//LOG("asx url %s",hr);
					}

				}
			}
			n=n->sibling;
		}
	}
}
// -----------------------------------
void PlayList::readSCPLS(Stream &in)
{
	char tmp[256];
	while (in.readLine(tmp,sizeof(tmp)))
	{
		if (strnicmp(tmp,"file",4)==0)
		{
			char *p = strstr(tmp,"=");
			if (p)
				addURL(p+1,"");
		}
	}
}
// -----------------------------------
void PlayList::readPLS(Stream &in)
{
	char tmp[256];
	while (in.readLine(tmp,sizeof(tmp)))
	{
		if (tmp[0] != '#')
			addURL(tmp,"");
	}
}
// -----------------------------------
void PlayList::writeSCPLS(Stream &out)
{
	out.writeLine("[playlist]");
	out.writeLine("");
	out.writeLine("NumberOfEntries=%d",numURLs);

	for(int i=0; i<numURLs; i++)
	{
		out.writeLine("File%d=%s",i+1,urls[i].cstr());
		out.writeLine("Title%d=%s",i+1,titles[i].cstr());
		out.writeLine("Length%d=-1",i+1);
	}
	out.writeLine("Version=2");
}
// -----------------------------------
void PlayList::writePLS(Stream &out)
{
	for(int i=0; i<numURLs; i++)
		out.writeLine("%s",urls[i].cstr());
}

// -----------------------------------
void PlayList::writeASX(Stream &out)
{
	out.writeLine("<ASX Version=\"3.0\">");
	for(int i=0; i<numURLs; i++)
	{
		out.writeLine("<ENTRY>");
		out.writeLine("<REF href = \"%s\" />",urls[i].cstr());
		out.writeLine("</ENTRY>");
	}
	out.writeLine("</ASX>");
}


// -----------------------------------
void PlayList::addChannels(const char *path, Channel **cl, int num)
{
	String url;

	for(int i=0; i<num; i++)
	{
		char idStr[64];

		cl[i]->getIDStr(idStr);

		sprintf(url.cstr(),"%s/stream/%s.%s",path,idStr,ChanInfo::getTypeExt(cl[i]->info.contentType));
		addURL(url.cstr(),cl[i]->info.name);

	}	
}
// -----------------------------------
void PlayList::addChannel(const char *path, ChanInfo &info)
{
	String url;

	char idStr[64];

	info.id.toStr(idStr);

	sprintf(url.cstr(),"%s/stream/%s.%s",path,idStr,ChanInfo::getTypeExt(info.contentType));
	addURL(url.cstr(),info.name);
}
