#include "FileList.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "swis.h"

#include "WimpLib:DCS.h"
#include "DocEvents.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:OrderedSet.h"
#include "WimpLib:PtrArray.h"
#include "WimpLib:SaveAs.h"
#include "WimpLib:StrCol.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"
#include "WimpLib:XFer.h"

#include "DigitalCD.h"
#include "FileTypes.h"
#include "FLTreeView.h"
#include "Options.h"
#include "Player.h"
#include "PlayList.h"
#include "PListFiles.h"
#include "PListRadio.h"
#include "Setup.h"

typedef struct
{
	StrCol* pFiles;
	int     startindex;
	int     index;
	int     flags;
} AddList;

struct FileList
{
	Document      m_Document;
	PtrArray*     m_pTracks;
	const char*   m_pBaseDir;
	const char*   m_pTitle;
	const char*   m_pAuthor;
	const char*   m_pInfo;
	FLTrack*      m_pOwner;
	bool          m_bAutoModified;
	bool          m_bUpdateStarted;
	FileList_Type m_Type;
	AddList*      m_pAddList;
};

static void throw_FileList_AddView(FileList* This)
{
//	throw_New_FLView(This);
	throw_New_FLTreeView(This);
	FileList_Show(This, -2);
}

/*-------------------------------------------------------------------------*
 *--- Document management -------------------------------------------------*
 *-------------------------------------------------------------------------*/

// Procedure to mark document with non-dangerous modifications
// which can be saved with user confirmation

void FileList_SetAutoModifiedFlag(FileList* This)
{
	if (!Document_IsNew(&This->m_Document)
	&&  !Document_IsModified(&This->m_Document)
	&&  This->m_pOwner
	&&  (This->m_Type < FileList_TypeTemporary))
		This->m_bAutoModified = true;
}

void FileList_SetModifiedFlag(FileList* This, bool bModified)
{
	if (bModified && (This->m_Type < FileList_TypeTemporary))
		Document_SetModifiedFlag(&This->m_Document, bModified);
	else
		Document_SetModifiedFlag(&This->m_Document, false);
}

// Procedure to tell all views that updates will follow

void FileList_StartUpdate(FileList* This)
{
	DocEvent e = {EDocEvent_StartUpdate, NULL, This};

	try
	{
		DocEvents_NotifyListeners(NULL, &e);
		This->m_bUpdateStarted = true;
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

// Procedure to tell all views that updates end
// also sets if the document is modified

void FileList_EndUpdate(FileList* This, bool bSetAutoModified, bool bSetModified)
{
	DocEvent e = {EDocEvent_EndUpdate, NULL, This};

	try
	{
		DocEvents_NotifyListeners(NULL, &e);

		if (bSetAutoModified) FileList_SetAutoModifiedFlag(This);
		if (bSetModified)
		{
			FileList_SetModifiedFlag(This, true);
			if (This->m_Type != FileList_TypeSearch)
			{
				PListFiles_RefreshTrackList(PListFiles_Get(), This);
				if (This->m_Type != FileList_TypeQueue)
					PListRadio_RefreshTrackList(PListRadio_Get());
			}
		}
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	This->m_bUpdateStarted = false;
}

// Procedure to insert a track in the list

int throw_FileList_InsertTrackAt(FileList* This, int InsertPos, const FLTrack* pTrack)
{
	DocEvent e = {EDocEvent_InsertElement, pTrack, This};

	if (InsertPos ==-1) InsertPos = PtrArray_Count(This->m_pTracks);

	PtrArray_Insert(This->m_pTracks, InsertPos, (void*) pTrack);

	try
	{
		DocEvents_NotifyListeners(This, &e);
	}
	catch
	{
		try
		{
			DocEvent e = {EDocEvent_RemoveElement, pTrack, This};
			DocEvents_NotifyListeners(This, &e);
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		PtrArray_Remove(This->m_pTracks, InsertPos);

		throw_current();
	}
	catch_end

	return InsertPos;
}

FLTrack* FileList_GetOwner(const FileList* This)
{
	return This->m_pOwner;
}

const char* FileList_GetBaseDir(const FileList* This)
{
	return This->m_pBaseDir;
}

const char* FileList_GetTitle(const FileList* This)
{
	return This->m_pTitle;
}

const char* FileList_GetAuthor(const FileList* This)
{
	return This->m_pAuthor;
}

const char* FileList_GetInfo(const FileList* This)
{
	return This->m_pInfo;
}

FileList_Type FileList_GetType(const FileList* This)
{
	return This->m_Type;
}

bool FileList_IsPlayable(const FileList* This)
{
	if (This->m_pOwner) return FLTrack_IsPlayable(This->m_pOwner);

	return true;
}

bool FileList_IsBusy(const FileList* This)
{
	if (This->m_bUpdateStarted
	||  JobList_HasJobs(Task_GetJobList(), This))
		return true;

	return false;
}

static MetaScan* FileList_GetScanner(const FileList* This)
{
	if (This->m_pOwner)
		return FLTrack_GetScanner(This->m_pOwner);

	return NULL;
}

void FileList_SetOwner(FileList* This, FLTrack* pTrack)
{
	This->m_pOwner = pTrack;
}

bool FileList_SetBaseDir(FileList* This, const char* basedir)
{
	bool bret = false;
	int i;

	if (!basedir) basedir = &nil;

	if (*basedir)
	{
		// Exchange existing basedir with new one
		bret = mem_setstring(&This->m_pBaseDir, basedir);

		if (!bret) return bret;

		for (i = PtrArray_Count(This->m_pTracks) - 1; i >= 0; i--)
		{
			FLTrack* pTrack = PtrArray_Get(This->m_pTracks, i);

			FLTrack_SetFilename(pTrack, FLTrack_GetFilename(pTrack), true);
		}
	}
	else
	{
		// Move basedir info back to filename
		const char* prefbasedir = This->m_pBaseDir;

		for (i = PtrArray_Count(This->m_pTracks) - 1; i >= 0; i--)
		{
			FLTrack* pTrack = PtrArray_Get(This->m_pTracks, i);
			const char* pfilename = FLTrack_GetFilename(pTrack);

			This->m_pBaseDir = (char*) &nil;
			FLTrack_SetFilename(pTrack, pfilename, true);
			This->m_pBaseDir = prefbasedir;
		}

		bret = mem_setstring(&This->m_pBaseDir, basedir);
	}

	return bret;
}

bool FileList_SetTitle(FileList* This, const char* title)
{
	return mem_setstring(&This->m_pTitle, title);
}

bool FileList_SetAuthor(FileList* This, const char* author)
{
	return mem_setstring(&This->m_pAuthor, author);
}

bool FileList_SetInfo(FileList* This, const char* info)
{
	return mem_setstring(&This->m_pInfo, info);
}

static FLTrack* throw_FileList_NewTrack_Any(FileList* This, int InsertPos, const char* filename, char* params)
{
	FLTrack* pTrack = throw_New_FLTrack(This, FLTrack_DecodeFlags(params), filename);

	try
	{
		// Decoding of parameters
		if (!(FLTrack_GetFlags(pTrack) & FLTrack_IsUrl))
		{
			MetaScan* pScanner = FileList_GetScanner(This);
			if (pScanner) FLTrack_ScanFilename(pTrack, pScanner);
		}
		FLTrack_DecodeParameters(pTrack, params);

		throw_FileList_InsertTrackAt(This, InsertPos, pTrack);
	}
	catch
	{
		Delete_FLTrack(pTrack);
		throw_current();
	}
	catch_end

	return pTrack;
}

static FLTrack* throw_FileList_NewTrack_Url(FileList* This, const char* url, int InsertPos)
{
	FLTrack* pTrack = throw_New_FLTrack(This, FLTrack_IsUrl, url);

	try
	{
		throw_FileList_InsertTrackAt(This, InsertPos, pTrack);
	}
	catch
	{
		Delete_FLTrack(pTrack);
		throw_current();
	}
	catch_end

	return pTrack;
}

static FLTrack* throw_FileList_NewTrack_List(FileList* This, int InsertPos, FileList* pList)
{
	FLTrack* pTrack = throw_New_FLTrack(This, FLTrack_IsPlaylist, Document_GetPathName(&pList->m_Document));

	try
	{
		pList->m_pOwner = pTrack;
		FLTrack_AttachSubList(pTrack, pList);

		throw_FileList_InsertTrackAt(This, InsertPos, pTrack);
	}
	catch
	{
		pList->m_pOwner = NULL;
		FLTrack_AttachSubList(pTrack, NULL);
		Delete_FLTrack(pTrack);
		throw_current();
	}
	catch_end

	return pTrack;
}

static EJobAction Job_New_FileList_FromTrack(void* pOwner, EJobAction action, void* pParams)
{
	FLTrack* pTrack = pParams;
	IGNORE(pOwner);

	try
	{
		if (action == EJobAction_Continue)
		{
			throw_New_FileList_FromTrack(pTrack);
		}

		action = EJobAction_Stop;
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	return action;
}

#define DiskSample_Version			0x52ec0
#define DiskSample_StreamClose		0x52ec3
#define DiskSample_StreamCreate		0x52ec4
#define DiskSample_StreamSource		0x52ec5
#define DiskSample_StreamReceiver	0x52ec6
#define DiskSample_StreamPlay		0x52ec8
#define DiskSample_StreamStatus		0x52ece
#define DiskSample_StreamParam		0x52ed2

static EJobAction Job_AddFilesAndDirectories(void* pOwner, EJobAction action, void* pParams)
{
	FileList* This = pOwner;
	AddList* pAL = This->m_pAddList;
	IGNORE(pParams);

	try
	{
		if (action == EJobAction_Continue)
		{
			clock_t timeout = clock() + (CLK_TCK / 50);

			// Insert a limited number of files/dir at a time
			while((OrderedSet_Count(pAL->pFiles) > 0) && (clock() < timeout))
			{
				OrderedNode* pNode = OrderedSet_GetNode(pAL->pFiles, 0);
				const char* filename = OrderedSet_GetNodeData(pAL->pFiles, pNode);
				file_type Type = File_GetFileType(filename);

				switch(Type)
				{
					case 0x1000:
					case 0x2000:
					{
						// Dir, add contents to list to insert
						int entry = 0;
						int pathlen = strlen(filename) + 1;
						char* pnewfile = throw_mem_alloc(pathlen + 256);
						char* pbuffer = pnewfile + pathlen;

						strcpy(pnewfile, filename);
						pbuffer[-1] = FILE_FOLDER_CHAR;

						while(entry != -1)
						{
							entry = Dir_ReadEntry(filename, entry, pbuffer);
							if (pbuffer[0])
							{
								Type = File_GetFileType(pnewfile);

								switch(Type)
								{
									case 0x1000:
									case 0x2000:
									{
										throw_StrCol_Add(pAL->pFiles, pnewfile);
									}
									break;
									default:
									{
										// File, insert if supported
										if (LoadTypes_FindNext(Type, NULL) != NULL)
										{
											throw_FileList_NewTrack_Any(This, pAL->index, pnewfile, NULL);
											pAL->index++;
										}
									}
								}
							}
						}
						mem_free(pnewfile);
					}
					break;
					default:
					{
						// File, insert if supported
						if (LoadTypes_FindNext(Type, NULL) != NULL)
						{
							throw_FileList_NewTrack_Any(This, pAL->index, filename, NULL);
							pAL->index++;
						}
					}
				}

				// Remove treated file/dir from list
				OrderedSet_RemoveNode(pAL->pFiles, pNode);
			}

			if (OrderedSet_Count(pAL->pFiles) == 0)
				action = EJobAction_Stop;
		}
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	if (action == EJobAction_Stop)
	{
		FileList_EndUpdate(This, false, (pAL->index != pAL->startindex));

		if (pAL->index != pAL->startindex)
		{
			// Show container?
			if (pAL->flags & FileList_Add_Show)
				FileList_Show(This, pAL->startindex);

			// Play inserted track(s)?
			if (pAL->flags & FileList_Add_Play)
				FileList_Play(This, pAL->startindex);
		}

		Delete_StrCol(pAL->pFiles);
		mem_free(pAL);
		This->m_pAddList = NULL;
	}

	return action;
}

void FileList_AddFile(FileList* This, int InsertPos, const char* filename, int flags)
{
	AddList* pAL = This->m_pAddList;

	try
	{
		if (!pAL)
		{
			pAL = This->m_pAddList = throw_mem_alloc(sizeof(*This->m_pAddList));
			pAL->pFiles = NULL;
			pAL->flags = 0;

			if ((InsertPos < 0) || InsertPos > FileList_TrackCount(This))
				InsertPos = FileList_TrackCount(This);
			pAL->index = pAL->startindex = InsertPos;

			pAL->pFiles = throw_New_StrCol();

		}

		pAL->flags |= flags;
		throw_StrCol_Add(pAL->pFiles, filename);

		JobList_Add(Task_GetJobList(), true, This, NULL, Job_AddFilesAndDirectories);
		FileList_StartUpdate(This);
	}
	catch
	{
		if (pAL)
		{
			Delete_StrCol(pAL->pFiles);
			mem_free(pAL);
			This->m_pAddList = NULL;
		}

		App_ReportException();
	}
	catch_end
}

typedef struct
{
	char* filename; // Name of temporary file
	int handle; // Handle of DiskSample download stream
	int state;
	clock_t timeout;
} DownloadYP;

static EJobAction Job_DownloadYellowPages(void* pOwner, EJobAction action, void* pParams)
{
	FileList* This = pOwner;
	DownloadYP* pYP = pParams;
	const _kernel_oserror* e = NULL;

	try
	{
		if (action == EJobAction_Continue)
		{
			switch(pYP->state)
			{
				case 0:
				{
					if (pYP->handle == -1)
					{
						pYP->filename = throw_mem_allocprint("<Wimp$ScrapDir>.DigitalCD.%08x", rand());
						// Ensure modules are loaded
						e = RMEnsure("DiskSample", 40, "System:Modules.Audio.DiskSample");
						if (!e) e = File_EnsurePath(pYP->filename);

						if (!e) e = _swix(DiskSample_StreamCreate,_INR(0,1)|_OUT(0), 0, 0, &pYP->handle);
						if (!e) e = _swix(DiskSample_StreamReceiver,_INR(0,3), pYP->handle, 1, pYP->filename, 0);
						if (!e) e = _swix(DiskSample_StreamSource,_INR(0,3), pYP->handle, 2
									, FLTrack_GetFilename(FileList_GetOwner(This))
									, (Options()->Internet.bUseProxy && *Options()->Internet.ProxyUrl)
										? Options()->Internet.ProxyUrl : NULL
								    );
						if (e) throw_os(e);
						pYP->state = 1;
						pYP->timeout = clock() + 60*CLK_TCK;
					}
				}
				break;
				case 1:
				{
					int status;

					e = _swix(DiskSample_StreamStatus,_INR(0,2)|_OUT(1), pYP->handle, 0, 0, &status);

					// Stream is ready to start recording?
					if (!e && (status & 1))
					{
						int filetype;

						e = _swix(DiskSample_StreamParam,_INR(0,1)|_OUT(2), pYP->handle, 13, &filetype);
						if (e) throw_os(e);

						switch(filetype)
						{
							case FileType_M3u:
							case FileType_Pls:
							case FileType_Xml:
							{
								e = _swix(DiskSample_StreamPlay,_IN(0), pYP->handle);
								if (e) throw_os(e);
								pYP->state = 2;
								pYP->timeout = clock() + 300*CLK_TCK;
							}
							break;
							default:
							{
								const char* mimetype = &nil;

								_swix(DiskSample_StreamParam,_INR(0,1)|_OUT(2), pYP->handle, 14, &mimetype);
								throw_string
									( "YPErr0:%s %s %3x"
									, FLTrack_GetFilename(FileList_GetOwner(This))
									, mimetype
									, filetype);
							}
						}
					}
					else if (clock() > pYP->timeout)
					{
						throw_string("YPErr1:%s", FLTrack_GetFilename(FileList_GetOwner(This)));
					}
				}
				break;
				case 2:
				{
					int status;

					e = _swix(DiskSample_StreamStatus,_INR(0,2)|_OUT(1), pYP->handle, 0, 0, &status);

					// Finished recording?
					if (!e && !(status & 2))
					{
						e = _swix(DiskSample_StreamClose,_IN(0), pYP->handle);
						pYP->handle = -1;
						pYP->state = 3;
					}
					else if (!e && (clock() > pYP->timeout))
					{
						throw_string("YPErr1:%s", FLTrack_GetFilename(FileList_GetOwner(This)));
					}
					if (e) throw_os(e);
				}
				break;
			}
		}
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	if (action == EJobAction_Stop)
	{
		if (pYP->handle != -1)
			_swix(DiskSample_StreamClose,_IN(0), pYP->handle);

		mem_free(pYP->filename);
		mem_free(pYP);
	}
	else if (pYP->state == 3)
	{
		// The OnOpenDocument call kills all jobs including this one so we must copy parameters first
		char* volatile filename = NULL;

		try
		{
			filename = throw_mem_allocstring(pYP->filename);
			Document_OnOpenDocument(&This->m_Document, filename);
			// Restore document name
			throw_Document_SetPathName(&This->m_Document, FLTrack_GetFilename(FileList_GetOwner(This)));
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		if (filename)
		{
			remove(filename);
			mem_free(filename);
		}

		action = EJobAction_Stop;
	}

	return action;
}

static bool FileList_IsPath(const char* string)
{
	int rc;

	if (!*string)
		return 1;


	rc = FLTrack_IsValidUrl(string);
	if (rc > 0)
	{
		// for compatibility
		const char c = string[strlen(string) - 1];
		if ((c == FILE_FOLDER_CHAR)
		||  (c == ':'))
			return 1;

		if (rc == 2)
			return 1; // protcol://domain/

		return 0; // protcol://domain or protcol://domain/subpath
	}

	// Must accept things like var:
	return ('\0' == *File_GetLeafName(string));
}

bool FileList_AllowTrackType(const FileList* This, unsigned int flags)
{
	switch(FileList_GetType(This))
	{
		case FileList_TypeNormal:
			return (flags & (FLTrack_IsPlaylist | FLTrack_IsDir)) == 0;
		case FileList_TypeMain:
			return true;
		case FileList_TypeExtern:
			return (flags & (FLTrack_IsPlaylist | FLTrack_IsDir | FLTrack_IsUrl)) == FLTrack_IsUrl;
		case FileList_TypeQueue:
			return (flags & (FLTrack_IsPlaylist | FLTrack_IsDir | FLTrack_IsUrl)) == 0;
	}

	return false;
}

static void FileList_SetXFerPerc(const XFer* xfer)
{
	int total = XFer_GetEstimatedTotalSize(xfer);
	int pos = XFer_CountReceived(xfer);

	if (pos > total) pos = total;
	if (total > 0) WLib_Hourglass_Percentage(100*pos/total);
}

static const _kernel_oserror* FileList_LoadPlaylist(FileList* This, XFer* xfer, int ins, bool bSetBase)
{
	const _kernel_oserror* err = NULL;
	bool  bAdded = false;
	bool  bFirst = true;
	char* subname;
	char* pch;
	char* params;
	char  string[4096];
	char  pathname[1024];
	char  basedir[1024];

	FileList_StartUpdate(This);

	try
	{
		pathname[0] = 0;
		basedir[0] = 0;
		subname = pathname;

		// Scan file until end of file
		while(XFer_GetErrorStatus(xfer) == NULL)
		{
			pch = string;
			*pch = 0;

			FileList_SetXFerPerc(xfer);

			// Try to read a line
			while (XFer_ReceiveRead(xfer, pch, 1) == 1)
			{
				if ('\n' == *pch) break;
				pch++;
			}

			*pch = 0;
			// Was it an error, end of line or end of file
			if ((XFer_GetErrorStatus(xfer) != NULL)
			&&  (XFer_GetErrorStatus(xfer) != &XFer_Error_EOF))
				throw_os(XFer_GetErrorStatus(xfer));

			String_StripBlanks(string);

			params = strchr(string, '*');
			if (params)
			{
				*params = '\0';
				params++;
			}

			if (!params && FileList_IsPath(string))
			{
				// Is only a path
				int len = strlen(string);

				bFirst = false;

				if (len == 0)
				{
					pathname[0] = 0;
					subname = pathname;
				}
				else
				{
					// if is '.path' prefix with basedir
					if (string[0] == FILE_FOLDER_CHAR)
					{
						strcpy(pathname, basedir);
						subname = pathname + strlen(pathname);
					}
					else subname = pathname;
					strcpy(subname, string);
					subname += len;
				}
			}
			else
			{
				unsigned int flags = FLTrack_DecodeFlags(params);

				// Is a file/URL
				if ((bFirst == true) && !(flags & FLTrack_IsUrl))
				{
					// on first line it's a basedir
					if (bSetBase)
					{
						FileList_SetBaseDir(This, string);

						if (This->m_pOwner && params)
						{
							FLTrack_DecodeParameters(This->m_pOwner, params);
							FLTrack_RefreshViews(This->m_pOwner, This, true, false);
						}
					}
					strcpy(basedir, string);
					strcpy(pathname, string);
					subname = pathname + strlen(pathname);
					subname[0] = FILE_FOLDER_CHAR;
					subname++;
				}
				else if (FileList_AllowTrackType(This, flags))
				{
					FLTrack* pTrack;

					strcpy(subname, string);

					pTrack = throw_FileList_NewTrack_Any(This, ins, pathname, params);

					if ((FLTrack_GetFlags(pTrack) & FLTrack_IsPlaylist)
					&&  !(FLTrack_GetFlags(pTrack) & FLTrack_DoNotAutoload))
						JobList_Add(Task_GetJobList(), false, This, pTrack, Job_New_FileList_FromTrack);

					ins++;
					bAdded = true;
				}
				bFirst = false;
			}
		}
	}
	catch
	{
		const exception* e = exception_current();

		err = &e->error;
	}
	catch_end

	FileList_EndUpdate(This, false, bAdded);

	if (bAdded)
	{
		// Partial success
		App_ReportOSError(err);
		err = NULL;
	}

	return err;
}

static const _kernel_oserror* FileList_LoadM3u(FileList* This, XFer* xfer, int ins)
{
	const _kernel_oserror* err = NULL;
	bool  bAdded = false;
	char* pch;
	char  string[4096];

	FileList_StartUpdate(This);

	try
	{
		// Scan file until end of file
		while(XFer_GetErrorStatus(xfer) == NULL)
		{
			pch = string;
			*pch = 0;

			FileList_SetXFerPerc(xfer);

			// Try to read a line
			while (XFer_ReceiveRead(xfer, pch, 1) == 1)
			{
				if ('\n' == *pch) break;
				pch++;
			}

			*pch = 0;
			// Was it an error, end of line or end of file
			if ((XFer_GetErrorStatus(xfer) != NULL)
			&&  (XFer_GetErrorStatus(xfer) != &XFer_Error_EOF))
				throw_os(XFer_GetErrorStatus(xfer));

			String_StripBlanks(string);

			if(!strncmp(string, "http://", 7))
			{
				throw_FileList_NewTrack_Url(This, string, ins);
				ins++;
				bAdded = true;
			}
		}
	}
	catch
	{
		const exception* e = exception_current();

		err = &e->error;
	}
	catch_end

	FileList_EndUpdate(This, false, bAdded);

	if (bAdded)
	{
		// Partial success
		App_ReportOSError(err);
		err = NULL;
	}

	return err;
}

static const _kernel_oserror* FileList_LoadPls(FileList* This, XFer* xfer, int ins)
{
	const _kernel_oserror* err = NULL;
	bool  bAdded = false;
	char* pch;
	char  string[4096];
	FLTrack* pTrack = NULL;

	FileList_StartUpdate(This);

	try
	{
		// Scan file until end of file
		while(XFer_GetErrorStatus(xfer) == NULL)
		{
			pch = string;
			*pch = 0;

			FileList_SetXFerPerc(xfer);

			// Try to read a line
			while (XFer_ReceiveRead(xfer, pch, 1) == 1)
			{
				if ('\n' == *pch) break;
				pch++;
			}

			*pch = 0;
			// Was it an error, end of line or end of file
			if ((XFer_GetErrorStatus(xfer) != NULL)
			&&  (XFer_GetErrorStatus(xfer) != &XFer_Error_EOF))
				throw_os(XFer_GetErrorStatus(xfer));

			String_StripBlanks(string);
			if (!strncmp(string, "File", 4))
			{
				pTrack = NULL;
				pch = strstr(string, "=");
				if (!pch)
					continue;
				String_StripBlanks(++pch);

				if(!strncmp(pch, "http://", 7))
				{
					pTrack = throw_FileList_NewTrack_Url(This, pch, ins);
					ins++;
					bAdded = true;
				}
			}
			else if (pTrack && !strncmp(string, "Title", 5))
			{
				pch = strstr(string, "=");
				if (!pch)
					continue;
				String_StripBlanks(++pch);
				FLTrack_SetMetaText
					( pTrack
					, EMetaId_StreamStation
					, EMetaOrigin_User
					, pch
					);
			}
		}
	}
	catch
	{
		const exception* e = exception_current();

		err = &e->error;
	}
	catch_end

	FileList_EndUpdate(This, false, bAdded);

	if (bAdded)
	{
		// Partial success
		App_ReportOSError(err);
		err = NULL;
	}

	return err;
}

static char* String_toXML(const char* pstring)
{
	const char* f = pstring;
	char* t = SPrintf("");

	while (*f)
	{
		if (*f == '<')
			t += sprintf(t, "&lt;");
		else if (*f == '>')
			t += sprintf(t, "&gt;");
		else if (*f == '&')
			t += sprintf(t, "&amp;");
		else *t++ = *f++;
	}

	return t;
}

static char* String_fromXML(char* pstring)
{
	char* f = pstring;
	char* t = pstring;

	while (*f)
	{
		if (*f == '&')
		{
			if (!strncmp(f, "&lt;", 4))
			{
				*t++ = '<';
				f += 4;
			}
			else if (!strncmp(f, "&gt;", 4))
			{
				*t++ = '>';
				f += 4;
			}
			else if (!strncmp(f, "&amp;", 5))
			{
				*t++ = '&';
				f += 5;
			}
			else if (!strncmp(f, "&#039;", 6))
			{
				*t++ = '\'';
				f += 6;
			}
			else if (!strncmp(f, "&quot;", 6))
			{
				*t++ = '\'';
				f += 6;
			}
			else *t++ = *f++;
		}
		else *t++ = *f++;
	}
	*t = '\0';

	return pstring;
}

#define BUF_SIZE 64*1024
static const char* TAGS[] =
{ "directory"
, "entry"
, "server_name"
, "listen_url"
, "bitrate"
, "server_type"
, "genre"
};

static const _kernel_oserror* FileList_LoadYellowPages(FileList* This, XFer* xfer, int ins)
{
	const _kernel_oserror* err = NULL;
	bool  bAdded = false;
	bool  bFill = true;
	FLTrack* pTrack = NULL;
	char* volatile pbuffer = NULL;
	char* volatile picbuf = NULL;
	char* pread;
	char* pdata;
	char* pwrite;
	char* picwrite = NULL;
	int   state;
	int   level;
	int   size;
	int   tag;
	int   offset;
	int   hiconv = 0;

	FileList_StartUpdate(This);

	// Iconv_Open
	_swix(0x57540, _INR(0, 1) | _OUT(0), "Latin1//TRANSLIT", "UTF-8", &hiconv);

	try
	{
		pbuffer = throw_mem_alloc(BUF_SIZE + 1);
		pdata = pread = pwrite = pbuffer;
		state = 0;
		level = -1;
		tag = 0;
		offset = 0;

		if (hiconv)
		{
			picbuf = throw_mem_alloc(BUF_SIZE + 1);
			picwrite = picbuf;
		}

		// Scan file until end of file
		while((XFer_GetErrorStatus(xfer) == NULL) || !bFill)
		{
			FileList_SetXFerPerc(xfer);

			if (bFill)
			{
				size = pdata - pbuffer;
				memmove(pbuffer, pdata, pwrite - pdata);
				pwrite -= size;
				pread -= size;
				pdata = pbuffer;

				size = BUF_SIZE - (pwrite - pbuffer);

				if (hiconv)
				{
					int picsize = BUF_SIZE - (picwrite - picbuf);

					if (picsize > 0)
					{
						int read = XFer_ReceiveRead(xfer, picwrite, picsize);
						picwrite += read;
						offset += read;
					}
					else
						throw_string("YPErr2");
					bFill = false;

					// Iconv_Convert
					err = _swix(0x57543, _INR(0, 4) | _OUT(2) | _OUT(3)
								, hiconv, picbuf, picwrite - picbuf, pwrite, size
								, &picsize, &pwrite
								);
					if (err)
					{
						// Iconv sets only an error number
						throw_string("IConv failed with code &%x at offset &%x\n", err->errnum, offset);
					}

					memmove(picbuf, picwrite - picsize, picsize);
					picwrite = picbuf + picsize;
				}
				else
				{
					if (size > 0)
					{
						int read = XFer_ReceiveRead(xfer, pwrite, size);
						pwrite += read;
						offset += read;
					}
					else
						throw_string("YPErr2");
					bFill = false;
				}

				// Was it an error, end of line or end of file
				if ((XFer_GetErrorStatus(xfer) != NULL)
				&&  (XFer_GetErrorStatus(xfer) != &XFer_Error_EOF))
					throw_os(XFer_GetErrorStatus(xfer));
			}

			switch(state)
			{
				case 0: // is xml?
				{
					size = 11;
					if ((pwrite - pread) < size)
					{
						bFill = true;
						continue;
					}

					if (!strncmp(pread, "<?xml", 5))
					{
						pread += size;
						pdata = pread;
					}
					state = 1;
				}
				break;
				case 1:
				case 5: // skip till start of tag
				{
					while (pread < pwrite)
					{
						if (pread[0] == '<')
							break;

						pread++;
					}

					if (pread < pwrite)
						state++;
					else
					{
						if (state == 1)
							pdata = pread;
						bFill = true;
					}
				}
				break;
				case 2: // in between two tags
				{
					pread++;
					pdata = pread;
					state++;
				}
				break;
				case 6: // between tag and end tag
				{
					// skip blanks and control characters
					while (pdata < pread)
					{
						if (!isspace(*pdata) && !iscntrl(*pdata))
							break;

						pdata++;
					}
					*pread = 0;

					switch(tag)
					{
						case 2: // server_name
						{
							String_StripBlanks(pdata);
							FLTrack_SetMetaText
								( pTrack
								, EMetaId_StreamStation
								, EMetaOrigin_User
								, String_fromXML(pdata)
								);
						}
						break;
						case 3: // listen_url
						{
							String_StripBlanks(pdata);
							FLTrack_SetFilename(pTrack, String_fromXML(pdata), false);
						}
						break;
						case 4: // bitrate
						{
							int rate;

							String_StripBlanks(pdata);
							rate = atoi(pdata);
							if (rate > 0) FLTrack_SetRate(pTrack, rate);
						}
						break;
						case 5: // server_type
						{
							String_StripBlanks(pdata);
							FLTrack_SetMetaText
								( pTrack
								, EMetaId_StreamMimeType
								, EMetaOrigin_User
								, String_fromXML(pdata)
								);
						}
						break;
						case 6: // genre
						{
							String_StripBlanks(pdata);
							FLTrack_SetInfo(pTrack, String_fromXML(pdata));
						}
						break;
					}

					pread++;
					pdata = pread;
					state++;
				}
				break;
				case 3:
				case 7: // skip till end of tag
				{
					while (pread < pwrite)
					{
						if (*pread == '>')
							break;

						pread++;
					}

					if (pread < pwrite)
						state++;
					else
						bFill = true;
				}
				break;
				case 4:
				case 8: // tag, endtag
				{
					while (pdata < pread)
					{
						if (!isspace(*pdata) && !iscntrl(*pdata))
							break;

						pdata++;
					}

					char* pch = pdata;
					while (pch < pread)
					{
						if (isspace(*pch) || iscntrl(*pch))
							break;

						pch++;
					}
					size = pch - pdata;
					*pch = 0;

					if (pdata[0] == '/')
					{
						// end tag
						if ((tag >= 0) && strcmp(pdata + 1, TAGS[tag]))
							throw_string("YPErr3:<%s> instead of expected </%s> at offset %d", pdata, TAGS[tag], offset - (pwrite - pdata));

						switch(level)
						{
							case 0: // about to close directory
							{
								tag = -1;
							}
							break;
							case 1: // about to close entry
							{
								if (strcmp(FLTrack_GetFilename(pTrack), "http://"))
								{
									pTrack = NULL;
									ins++;
									bAdded = true;
								}
								else
								{
									FileList_DelTrack(This, pTrack);
									pTrack = NULL;
								}

								tag = 0;
							}
							break;
							default:
								tag = 1;
						}

						level--;
						state = 1;
					}
					else
					{
						// start tag
						switch(level)
						{
							case -1:
							{
								if (strcmp(pdata, TAGS[0]))
									throw_string("YPErr4: unexpected <%s> at offset %d", pdata, offset - (pwrite - pdata));

								tag = 0;
								state = 1;
							}
							break;
							case 0:
							{
								if (strcmp(pdata, TAGS[1]))
									throw_string("YPErr4: unexpected <%s> at offset %d", pdata, offset - (pwrite - pdata));

								tag = 1;
								state = 1;

								pTrack = throw_FileList_NewTrack_Url(This, "http://", ins);
							}
							break;
							case 1:
							{
								if (!strcmp(pdata, TAGS[2]))
								{
									tag = 2;
									state = 5;
								}
								else if (!strcmp(pdata, TAGS[3]))
								{
									tag = 3;
									state = 5;
								}
								else if (!strcmp(pdata, TAGS[4]))
								{
									tag = 4;
									state = 5;
								}
								else if (!strcmp(pdata, TAGS[5]))
								{
									tag = 5;
									state = 5;
								}
								else if (!strcmp(pdata, TAGS[6]))
								{
									tag = 6;
									state = 5;
								}
								else
								{
									tag = -1;
									state = 1;
								}
							}
							break;
							default:
								throw_string("YPErr4: unexpected <%s> at offset %d", pdata, offset - (pwrite - pdata));
						}

						level++;
					}

					pread++;
					pdata = pread;
				}
				break;
			}
		}
	}
	catch
	{
		const exception* e = exception_current();

		err = &e->error;
	}
	catch_end

	FileList_EndUpdate(This, false, bAdded);

	if (pTrack)
	{
		FileList_DelTrack(This, pTrack);
	}
	mem_free(pbuffer);
	mem_free(picbuf);

	if (bAdded)
	{
		// Partial success
		App_ReportOSError(err);
		err = NULL;
	}

	if (hiconv)
	{
		// Iconv_Close
		_swix(0x57542, _IN(0), hiconv);
	}

	return err;
}

void FileList_OpenBaseDir(FileList* This)
{
	char* pch;

	if (This->m_pBaseDir[0])
	{
		pch = SPrintf("Filer_OpenDir %s", This->m_pBaseDir);

		_swix(OS_CLI, _IN(0), pch);
	}
}

void FileList_Show(FileList* This, int Index) throws(index)
{
	DocEvent e;

	e.pContainer = This;

	switch (Index)
	{
		case -2:
		{
			e.event = EDocEvent_ShowView;
			e.pElement = NULL;
		}
		break;
		case -1:
		{
			e.event = EDocEvent_ShowElement;
			e.pElement = NULL;
		}
		break;
		default:
		{
			e.event = EDocEvent_ShowElement;
			e.pElement = PtrArray_Get(This->m_pTracks, Index);
		}
	}

	try
	{
		DocEvents_NotifyListeners(This, &e);
	}
	catch
	{
		App_ReportException();
	}
	catch_end

}

int FileList_TrackCount(const FileList* This)
{
	return PtrArray_Count(This->m_pTracks);
}

int FileList_CountTracksToPlay(const FileList* This, FLTrack_ObjectType type)
{
	int count = 0;
	int max = PtrArray_Count(This->m_pTracks);
	int i;
	const FLTrack* pTrack;

	if (This->m_Type == FileList_TypeQueue)
	{
		for (i = 0; i < max; i++)
		{
			pTrack = PtrArray_Get(This->m_pTracks, i);

			if (FLTrack_GetObjectType(pTrack) == type)
				count++;
		}
	}
	else
	{
		for (i = 0; i < max; i++)
		{
			pTrack = PtrArray_Get(This->m_pTracks, i);

			if (!(FLTrack_GetFlags(pTrack) & (FLTrack_MustNotPlay | FLTrack_NeverPlay)))
			{
				if (FLTrack_IsContainer(pTrack))
				{
					FileList* pSubList = FLTrack_GetSubList(pTrack);
					if (pSubList)
						count += FileList_CountTracksToPlay(pSubList, type);
				}
				else if (FLTrack_GetObjectType(pTrack) == type)
					count++;
			}
		}
	}

	return count;
}

const void** FileList_ListTracksToPlay
		( const FileList* This
		, const void** ptracks
		, FLTrack_ObjectType type
		)
{
	int max = PtrArray_Count(This->m_pTracks);
	int i;
	const FLTrack* pTrack;

	if (This->m_Type == FileList_TypeQueue)
	{
		for (i = 0; i < max; i++)
		{
			pTrack = PtrArray_Get(This->m_pTracks, i);

			if (FLTrack_GetObjectType(pTrack) == type)
				*ptracks++ = pTrack;
		}
	}
	else
	{
		for (i = 0; i < max; i++)
		{
			pTrack = PtrArray_Get(This->m_pTracks, i);

			if (!(FLTrack_GetFlags(pTrack) & (FLTrack_MustNotPlay | FLTrack_NeverPlay)))
			{
				if (FLTrack_IsContainer(pTrack))
				{
					FileList* pSubList = FLTrack_GetSubList(pTrack);
					if (pSubList)
						ptracks = FileList_ListTracksToPlay(pSubList, ptracks, type);
				}
				else if (FLTrack_GetObjectType(pTrack) == type)
					*ptracks++ = pTrack;
			}
		}
	}

	return ptracks;
}

/*-------------------------------------------------------------------------*
 *--- Rescan folder job ---------------------------------------------------*
 *-------------------------------------------------------------------------*/

static int RescanSet_Compare(const void* pa, const void* pb)
{
	const FLTrack* pTrackA = pa;
	const FLTrack* pTrackB = pb;

	int val = strcmp(FLTrack_GetPathName(pTrackA), FLTrack_GetPathName(pTrackB));
	if (!val) val = strcmp(FLTrack_GetLeafName(pTrackA), FLTrack_GetLeafName(pTrackB));

	return val;
}

static const OrderedSetVPtr RescanSet_VPtr =
{ NULL
, NULL
, RescanSet_Compare
};

typedef struct
{
	OrderedSet* pOSet; // List of files in list ordered by path
	StrCol* pFiles;
	int     count;
	int     flags;
} ScanList;

static void FileList_CompleteWithTrack
		(FileList* This
		, ScanList* pSL
		, MetaScan* pScanner
		, const char* filename
		)
{
	// First define new track
	FLTrack* pTrack = throw_New_FLTrack(This, 0, filename);

	try
	{
		// Insert into ordered set which removes duplicates
		OrderedNode* pNode = OrderedSet_Add(pSL->pOSet, pTrack);
		FLTrack* pRefTrack = (FLTrack*) OrderedSet_GetNodeData(pSL->pOSet, pNode);

		if (pRefTrack == pTrack)
		{
			// Is a new one, insert it in filelist before track with next path
			pNode = OrderedSet_GetSuccessor(pSL->pOSet, pNode);

			int pos = pNode
			        ? FileList_FindTrack(This, OrderedSet_GetNodeData(pSL->pOSet, pNode))
					: 0;

			// Decoding of parameters
			if (pScanner) FLTrack_ScanFilename(pTrack, pScanner);
			throw_FileList_InsertTrackAt(This, pos, pTrack);

			pSL->count++;
		}
		else
		{
			// Is duplicate, mark old track as valid and delete new copy
			FLTrack_SetFlags(pRefTrack, 0, FLTrack_Invalidated);
			Delete_FLTrack(pTrack);
		}
	}
	catch
	{
		Delete_FLTrack(pTrack);
		throw_current();
	}
	catch_end
}

static EJobAction Job_RescanFilesAndDirectories(void* pOwner, EJobAction action, void* pParams)
{
	FileList* This = pOwner;
	ScanList* pSL = pParams;
	MetaScan* pScanner = FileList_GetScanner(This);

	try
	{
		if (action == EJobAction_Continue)
		{
			clock_t timeout = clock() + (CLK_TCK / 50);

			// Scan a limited number of files/dirs at a time
			while((OrderedSet_Count(pSL->pFiles) > 0) && (clock() < timeout))
			{
				OrderedNode* pNode = OrderedSet_GetNode(pSL->pFiles, 0);
				const char* filename = OrderedSet_GetNodeData(pSL->pFiles, pNode);
				file_type Type = File_GetFileType(filename);

				switch(Type)
				{
					case 0x1000:
					case 0x2000:
					{
						// Dir, add contents to list to scan
						int entry = 0;
						int pathlen = strlen(filename) + 1;
						char* pnewfile = throw_mem_alloc(pathlen + 256);
						char* pbuffer = pnewfile + pathlen;

						strcpy(pnewfile, filename);
						pbuffer[-1] = FILE_FOLDER_CHAR;

						while(entry != -1)
						{
							entry = Dir_ReadEntry(filename, entry, pbuffer);
							if (pbuffer[0])
							{
								Type = File_GetFileType(pnewfile);

								switch(Type)
								{
									case 0x1000:
									case 0x2000:
									{
										throw_StrCol_Add(pSL->pFiles, pnewfile);
									}
									break;
									default:
									{
										// File, insert if supported and not present or validate if present
										if (LoadTypes_FindNext(Type, NULL) != NULL)
										{
											FileList_CompleteWithTrack(This, pSL, pScanner, pnewfile);
										}
									}
								}
							}
						}
						mem_free(pnewfile);
					}
					break;
					default:
					{
						// File, insert if supported and not present or validate if present
						if (LoadTypes_FindNext(Type, NULL) != NULL)
						{
							FileList_CompleteWithTrack(This, pSL, pScanner, filename);
						}
					}
				}

				// Remove treated file/dir from list
				OrderedSet_RemoveNode(pSL->pFiles, pNode);
			}

			if (OrderedSet_Count(pSL->pFiles) == 0)
				action = EJobAction_Stop;
		}
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	if (action == EJobAction_Stop)
	{
		FileList_EndUpdate(This, false, (pSL->count > 0));

		Delete_StrCol(pSL->pFiles);
		Delete_OrderedSet(pSL->pOSet);
		mem_free(pSL);
	}

	return action;
}

void FileList_Rescan(FileList* This)
{
	const char* basedir = FileList_GetBaseDir(This);
	ScanList* volatile pSL = NULL;

	if (!*basedir) return;

	try
	{
		pSL = throw_mem_calloc(sizeof(*pSL), 1);

		pSL->pOSet = New_OrderedSet(NULL, &RescanSet_VPtr);

		pSL->pFiles = throw_New_StrCol();

		// Build set of files using the basedir, ordered by path, mark them as invalid
		for (int i = FileList_TrackCount(This) - 1; i >= 0; i--)
		{
			FLTrack* pTrack = FileList_GetTrack(This, i);

			if (!(FLTrack_GetFlags(pTrack) & (FLTrack_IsDir | FLTrack_IsUrl | FLTrack_IsPlaylist))
			&&  (FLTrack_GetPathName(pTrack)[0] == FILE_FOLDER_CHAR))
			{
				FLTrack_SetFlags(pTrack, FLTrack_Invalidated, FLTrack_Invalidated);

				OrderedSet_Add(pSL->pOSet, pTrack);
			}
		}

		throw_StrCol_Add(pSL->pFiles, basedir);

		JobList_Add(Task_GetJobList(), true, This, pSL, Job_RescanFilesAndDirectories);
		FileList_StartUpdate(This);
	}
	catch
	{
		if (pSL)
		{
			Delete_OrderedSet(pSL->pOSet);
			Delete_StrCol(pSL->pFiles);
			mem_free(pSL);
		}

		App_ReportException();
	}
	catch_end
}

//-------------------------------------------------------------------------

FLTrack* FileList_GetTrack(const FileList* This, int i) throws(index)
{
	return (FLTrack*) PtrArray_Get(This->m_pTracks, i);
}

int FileList_FindTrack(const FileList* This, const FLTrack* pTrack)
{
	return PtrArray_Find(This->m_pTracks, 0, pTrack);
}

bool FileList_DelTrack(FileList* This, FLTrack* pTrack)
{
	int Pos = PtrArray_Find(This->m_pTracks, 0, pTrack);

	if (Pos == -1) return false;

	try
	{
		DocEvent e = {EDocEvent_RemoveElement, pTrack, This};
		DocEvents_NotifyListeners(This, &e);
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	PListFiles_RemoveTrack(PListFiles_Get(), pTrack);
	PListRadio_RemoveTrack(PListRadio_Get(), pTrack);

	PtrArray_Remove(This->m_pTracks, Pos);
	Delete_FLTrack(pTrack);

	return true;
}

FLTrack* FileList_GetPlayTrack(const FileList* This, int item) throws(index)
{
	FLTrack* ptrack = PtrArray_Get(This->m_pTracks, item);
	const FileList* pList;

	if (!ptrack) return NULL;

	if ((This->m_Type != FileList_TypeQueue) && !FLTrack_IsPlayable(ptrack))
		return NULL;

	pList = FLTrack_GetSubList(ptrack);

	if (pList)
	{
		for (item = 0; item < PtrArray_Count(pList->m_pTracks); item++)
		{
			ptrack = FileList_GetPlayTrack(pList, item);

			if (ptrack) break;
		}
	}
	else
	{
		if (FLTrack_IsContainer(ptrack))
			ptrack = NULL;
	}

	return ptrack;
}

static EJobAction Job_FileList_Play(void* pOwner, EJobAction action, void* pParams)
{
	FileList* This = pOwner;
	FLTrack* pTrack = pParams;

	try
	{
		if (action == EJobAction_Continue)
		{
			Player* pPlayer;
			PlayList* pPlayList;
			int index;

			if (FileList_GetType(This) == FileList_TypeSearch)
			{
				pTrack = (FLTrack*) FLTrack_ResolveLink(pTrack);
				This = FLTrack_GetOwner(pTrack);
			}

			if (FLTrack_GetObjectType(pTrack) == FLTrack_Type_File)
				pPlayList = (PlayList*) PListFiles_Get();
			else
				pPlayList = (PlayList*) PListRadio_Get();
			// Delay action if necessary
			if (JobList_HasJobs(Task_GetJobList(), pPlayList))
				JobList_Add(Task_GetJobList(), false, pOwner, pParams, Job_FileList_Play);
			else
			{
				pPlayer = throw_PlayList_EnsurePlayer(pPlayList, true);

				if (FLTrack_GetObjectType(pTrack) == FLTrack_Type_File)
					Player_SetListMode(pPlayer, (FileList_GetType(This) == FileList_TypeQueue));

				index = Player_FindTrack(pPlayer, pTrack);
				if (index >= 0) Player_PlayTrack(pPlayer, index);
			}
		}

		action = EJobAction_Stop;
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	return action;
}

void FileList_Play(FileList* This, int item)
{
	FLTrack* pTrack = FileList_GetPlayTrack(This, item);

	if (!pTrack) return;

	// Player could have waiting jobs, so performed in the background
	JobList_Add(Task_GetJobList(), false, This, pTrack, Job_FileList_Play);
}

/*-------------------------------------------------------------------------*
 *--- Helpers for data transfers ------------------------------------------*
 *-------------------------------------------------------------------------*/

const file_type FileList_Load_FileTypes[] =
{
	  FileType_PlayList
	, FileType_M3u
	, FileType_Pls
	, FileType_Xml
	, -1
};

const file_type FileList_Save_FileTypes[] =
{
	  FileType_PlayList
	, FileType_Text
	, -1
};

static const file_type FileList_SaveM3u_FileTypes[] =
{
	  FileType_M3u
	, FileType_PlayList
	, FileType_Pls
	, FileType_Xml
	, FileType_Text
	, -1
};

static const file_type FileList_SavePls_FileTypes[] =
{
	  FileType_Pls
	, FileType_PlayList
	, FileType_M3u
	, FileType_Xml
	, FileType_Text
	, -1
};

static const file_type FileList_SaveXml_FileTypes[] =
{
	  FileType_Xml
	, FileType_PlayList
	, FileType_M3u
	, FileType_Pls
	, FileType_Text
	, -1
};

bool FileList_AcceptSourceFileType(file_type type)
{
	int i;

	for (i = 0; FileList_Load_FileTypes[i] != -1; i++)
	{
		if (FileList_Load_FileTypes[i] == type)
			return true;
	}

	return false;
}

static char sendbuf[4096];

// Escape string to avoid confusion with special delimiters

static char* FileList_EncodeString(char* pout, const char* p)
{
	for (; *p;)
	{
		if ((*p == ',')
		||  (*p == '\\'))
			*pout++ = '\\';
		*pout++ = *p++;
	}

	return pout;
}

// Send file header to output destination

static void FileList_SendHeaderPlayList(const FileList* This, sXFer_Chunk* pchunk)
{
	char* pstring;

	pchunk->pdata = pstring = sendbuf;
	if (*This->m_pBaseDir)
	{
		// save basedir
		pstring += sprintf(pstring, "%s", This->m_pBaseDir);
	}

	// save track parameters
	pstring[0] = '*';
	pstring++;

	if (This->m_pTitle && This->m_pTitle[0])
	{
		*pstring++ = 't';
		pstring = FileList_EncodeString(pstring, This->m_pTitle);
		*pstring++ = ',';
	}
	if (This->m_pAuthor && This->m_pAuthor[0])
	{
		*pstring++ = 'a';
		pstring = FileList_EncodeString(pstring, This->m_pAuthor);
		*pstring++ = ',';
	}
	if (This->m_pInfo && This->m_pInfo[0])
	{
		*pstring++ = 'c';
		pstring = FileList_EncodeString(pstring, This->m_pInfo);
		*pstring++ = ',';
	}
	if (This->m_pOwner)
	{
		const char* scanner = FLTrack_GetScannerName(This->m_pOwner);
		if (scanner && *scanner)
		{
			pstring += sprintf(pstring, "z%s,", scanner);
		}
	}

	// Overwrite '*' or the last ','
	pstring[-1] = '\n';
	pstring[0] = 0;

	pchunk->size = strlen((const char*) pchunk->pdata);
	// don't send anything if only '\n'
	if (pchunk->size == 1) pchunk->size = 0;
}

static void FileList_SendHeaderPls(const FileList* This, uint32_t count, sXFer_Chunk* pchunk)
{
	IGNORE(This);
	char* pstring;

	pchunk->pdata = pstring = sendbuf;
	sprintf(pstring, "[playlist]\nNumberOfEntries=%d\n", count);

	pchunk->size = strlen((const char*) pchunk->pdata);
}

static void FileList_SendHeaderYellowPage(const FileList* This, sXFer_Chunk* pchunk)
{
	IGNORE(This);
	char* pstring;

	pchunk->pdata = pstring = sendbuf;
	sprintf(pstring, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<directory>\n");

	pchunk->size = strlen((const char*) pchunk->pdata);
}

// Send file trailer to output destination

static void FileList_SendTrailerYellowPage(const FileList* This, sXFer_Chunk* pchunk)
{
	IGNORE(This);
	char* pstring;

	pchunk->pdata = pstring = sendbuf;
	sprintf(pstring, "</directory>\n");

	pchunk->size = strlen((const char*) pchunk->pdata);
}

// Send a track to output destination

static void FileList_SendTrackPlayList(const FileList* This, const FLTrack* pTrack, bool bExport, sXFer_Chunk* pchunk)
{
	FileList* pSubList = FLTrack_GetSubList(pTrack);
	static const char* pathname; // static as we keep compare with previous path
	char* pstring;

	IGNORE(This);

	if (pchunk->ref == 0) pathname = &nil;

	pchunk->pdata = pstring = sendbuf;

	if ((FLTrack_GetFlags(pTrack) & FLTrack_IsPlaylist)
	&&  (bExport || (pSubList && Document_IsNew(&pSubList->m_Document))))
	{
		pchunk->pdata = NULL;
		pchunk->size = 0;
		return;
	}

	if (pTrack != FLTrack_ResolveLink(pTrack))
	{
		if (strcmp(FLTrack_GetFullPathName(pTrack), pathname))
		{
			// if pathname changes save pathname, then leafname
			pathname = FLTrack_GetFullPathName(pTrack);
			pstring += sprintf(pstring, "%s\n%s", pathname, FLTrack_GetLeafName(pTrack));
		}
		else
		{
			// save leafname
			pstring += sprintf(pstring, "%s", FLTrack_GetLeafName(pTrack));
		}
	}
	else
	{
		if (strcmp(FLTrack_GetPathName(pTrack), pathname))
		{
			// if pathname changes save pathname, then leafname
			pathname = FLTrack_GetPathName(pTrack);
			pstring += sprintf(pstring, "%s\n%s", pathname, FLTrack_GetLeafName(pTrack));
		}
		else
		{
			// save leafname
			pstring += sprintf(pstring, "%s", FLTrack_GetLeafName(pTrack));
		}
	}

	// save track parameters
	pstring[0] = '*';
	pstring++;
	FLTrack_SaveParameters(pTrack, pstring, bExport);
	if (pstring[0])
	{
		pstring += strlen(pstring);
		pstring[0] = '\n';
		pstring[1] = '\0';
	}
	else
		pstring[-1] = '\n';

	pchunk->size = strlen((const char*) pchunk->pdata);
}

static void FileList_SendTrackM3u(const FileList* This, const FLTrack* pTrack, bool bExport, sXFer_Chunk* pchunk)
{
	FileList* pSubList = FLTrack_GetSubList(pTrack);
	const char* pathname;
	char* pstring;

	IGNORE(This);

	pchunk->pdata = pstring = sendbuf;

	if ((FLTrack_GetFlags(pTrack) & FLTrack_IsPlaylist)
	&&  (bExport || (pSubList && Document_IsNew(&pSubList->m_Document))))
	{
		pchunk->pdata = NULL;
		pchunk->size = 0;
		return;
	}

	pathname = FLTrack_GetFullPathName(pTrack);

	// save pathname, then leafname
	pstring += sprintf(pstring, "%s%s\n", pathname, FLTrack_GetLeafName(pTrack));

	pchunk->size = strlen((const char*) pchunk->pdata);
}

static void FileList_SendTrackPls(const FileList* This, const FLTrack* pTrack, bool bExport, sXFer_Chunk* pchunk)
{
	FileList* pSubList = FLTrack_GetSubList(pTrack);
	const char* pathname;
	char* pstring;

	IGNORE(This);

	pchunk->pdata = pstring = sendbuf;

	if ((FLTrack_GetFlags(pTrack) & FLTrack_IsPlaylist)
	&&  (bExport || (pSubList && Document_IsNew(&pSubList->m_Document))))
	{
		pchunk->pdata = NULL;
		pchunk->size = 0;
		return;
	}

	pathname = FLTrack_GetFullPathName(pTrack);

	// save pathname, then leafname
	pstring += sprintf(pstring, "File%d=%s%s\n", pchunk->ref + 1, pathname, FLTrack_GetLeafName(pTrack));
	pstring += sprintf(pstring, "Title%d=%s\n", pchunk->ref + 1, FLTrack_GetMetaString(pTrack, EMetaId_StreamStation));

	pchunk->size = strlen((const char*) pchunk->pdata);
}

static void FileList_SendTrackYellowPage(const FileList* This, const FLTrack* pTrack, bool bExport, sXFer_Chunk* pchunk)
{
	FileList* pSubList = FLTrack_GetSubList(pTrack);
	const char* pathname;
	char* pstring;
	const char* pparam;

	IGNORE(This);

	pchunk->pdata = pstring = sendbuf;

	if ((FLTrack_GetFlags(pTrack) & FLTrack_IsPlaylist)
	&&  (bExport || (pSubList && Document_IsNew(&pSubList->m_Document))))
	{
		pchunk->pdata = NULL;
		pchunk->size = 0;
		return;
	}

	pathname = FLTrack_GetFullPathName(pTrack);

	// save pathname, then leafname
	pstring += sprintf(pstring, "\t<entry>\n");
	pparam = FLTrack_GetMetaString(pTrack, EMetaId_StreamStation);
	pstring += sprintf(pstring, "\t\t<server_name>%s</server_name>\n", String_toXML(pparam));
	pstring += sprintf(pstring, "\t\t<listen_url>%s%s</listen_url>\n"
				, String_toXML(pathname), String_toXML(FLTrack_GetLeafName(pTrack)));
	pparam = FLTrack_GetMetaString(pTrack, EMetaId_StreamMimeType);
	pstring += sprintf(pstring, "\t\t<server_type>%s</server_type>\n", String_toXML(pparam));
	pstring += sprintf(pstring, "\t\t<bitrate>%d</bitrate>\n", FLTrack_GetRate(pTrack));
	pstring += sprintf(pstring, "\t</entry>\n");

	pchunk->size = strlen((const char*) pchunk->pdata);
}

/*-------------------------------------------------------------------------*
 *--- Viewer Clipboard handling -------------------------------------------*
 *-------------------------------------------------------------------------*/

// Callback to remove what we have put into the clipboard

static void FileList_Clipboard_FreeContents(void* pContents)
{
	if (pContents)
	{
		List* pList = pContents;

		while(List_Count(pList))
		{
			FLTrack* pTrack = List_Get(pList, 0);
			Delete_FLTrack(pTrack);
			List_Remove(pList, 0);
		}

		Delete_List(pList);
	}
}

// Callback to put the current selection into the clipboard

static void* FileList_Clipboard_FillContents(void* pHandlerData)
{
	const List* pSelection = pHandlerData;
	FLTrack* volatile pNewNode = NULL;
	List* volatile pList = NULL;
	int i;

	try
	{
		pList = New_List();

		for (i = 0; i < List_Count(pSelection); i++)
		{
			pNewNode = throw_New_FLTrack_Copy(NULL, List_Get(pSelection, i));

			List_InsertBefore(pList, NULL, pNewNode);
		}
	}
	catch
	{
		App_ReportException();
		Delete_FLTrack(pNewNode);
		FileList_Clipboard_FreeContents(pList);
		return NULL;
	}
	catch_end

	return pList;
}

// Callback for clipboard Paste: sets the file type of the data for transfer

static file_type FileList_Clipboard_SetSaveType(void* pContents, const file_type* types)
{
	IGNORE(pContents);

	return DragDrop_SetFileType(types, FileList_Save_FileTypes);
}

// Callback for clipboard Paste: performs the transfer of the data we have put into the clipboard.
// The transfer procedure ask for successive chunks of data which it splits as required to fit in
// the fixed size buffer provided by the other application or stores into a file.

static bool FileList_Clipboard_SendProc(void* pContents, file_type type, sXFer_Chunk* pchunk)
{
	List*    pList = pContents;
	FLTrack* pTrack;

	IGNORE(type);

	if ((pchunk->ref < 0) || (pchunk->ref >= List_Count(pList)))
		return false;

	pTrack = List_Get(pList, pchunk->ref);
	FileList_SendTrackPlayList(NULL, pTrack, true, pchunk);
	pchunk->ref++;

	return true;
}

// The clipboard handler for our view

ClipboardHandler FileList_Clipboard =
{
	  FileList_Clipboard_FillContents
	, FileList_Clipboard_FreeContents
	, FileList_Clipboard_SetSaveType
	, FileList_Clipboard_SendProc
	, false
};

/*-------------------------------------------------------------------------*
 *--- FileList Document callbacks -----------------------------------------*
 *-------------------------------------------------------------------------*/

// Virtual destructor

static void FileList_NotFileList(Document* pDoc)
{
	FileList* This = (FileList*) pDoc;

	if (This->m_pOwner) FLTrack_AttachSubList(This->m_pOwner, NULL);
	Delete_PtrArray(This->m_pTracks);
	mem_free(This->m_pBaseDir);
	mem_free(This->m_pTitle);
	mem_free(This->m_pAuthor);
	mem_free(This->m_pInfo);
	if (This->m_pAddList != NULL)
	{
		Delete_StrCol(This->m_pAddList->pFiles);
		mem_free(This->m_pAddList);
	}

	Document_NotDocument(pDoc);
}

// Ask DCS

static DCS_Enum FileList_AskDiscardCancelSave(const Document* pDoc)
{
	IGNORE(pDoc);

	return DCS_Ask(Msg_Lookup("DCS"));
}

// Clear the content

static void FileList_ClearContents(Document* pDoc)
{
	FileList* This = (FileList*) pDoc;
	int i;

	// Remove background jobs
	JobList_RemoveAll(Task_GetJobList(), This);

	if (This->m_bAutoModified)
		Document_OnSaveDocument(pDoc);

	// May affect windows with links to our tracks
	FileList_StartUpdate(This);

	for (i = PtrArray_Count(This->m_pTracks) - 1; i >= 0; i--)
	{
		FLTrack* pTrack = PtrArray_Get(This->m_pTracks, i);

		try
		{
			DocEvent e = {EDocEvent_RemoveElement, pTrack, This};
			DocEvents_NotifyListeners(This, &e);
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		PListFiles_StopTrack(PListFiles_Get(), pTrack);
		PListRadio_StopTrack(PListRadio_Get(), pTrack);
		Delete_FLTrack(pTrack);
	}

	PtrArray_Clear(This->m_pTracks);

	FileList_EndUpdate(This, false, true);
	FileList_SetModifiedFlag(This, false);

	mem_setstring(&This->m_pBaseDir, NULL);
	mem_setstring(&This->m_pTitle, NULL);
	mem_setstring(&This->m_pAuthor, NULL);
	mem_setstring(&This->m_pInfo, NULL);
}

// Load the content of an input source

const _kernel_oserror* FileList_LoadDocument
		( Document* pDoc
		, XFer* xfer
		, file_type type
		, const ScreenPos* pInfo
		)
{
	const _kernel_oserror* err = NULL;
	FileList* This = (FileList*) pDoc;
	bool       bSetBase = false;
	int        ins = -1;

	if (FileList_GetType(This) == FileList_TypeSearch)
		return NULL;

	if (!FileList_AcceptSourceFileType(type))
		return NULL;

	if ((FileList_GetType(This) == FileList_TypeQueue)
	&&  (type != FileType_PlayList))
		return NULL;

	if (pInfo) ins = pInfo->i;

	if ((ins < 0) || (ins > PtrArray_Count(This->m_pTracks)))
	{
		ins = PtrArray_Count(This->m_pTracks);
		bSetBase = true;
	}

	bool off = !WLib_Hourglass_IsOn();
	if (off) WLib_Hourglass_On();

	switch (type)
	{
		case FileType_PlayList:
			err = FileList_LoadPlaylist(This, xfer, ins, bSetBase);
		break;
		case FileType_M3u:
			err = FileList_LoadM3u(This, xfer, ins);
		break;
		case FileType_Pls:
			err = FileList_LoadPls(This, xfer, ins);
		break;
		case FileType_Xml:
			err = FileList_LoadYellowPages(This, xfer, ins);
		break;
	}

	if (off) WLib_Hourglass_Off();

	return err;
}

// Send content to an output destination, one chunk at a time

void FileList_SendHeader(const FileList* This, file_type type, sXFer_Chunk* pchunk, uint32_t count)
{
	pchunk->size = 0;

	switch (type)
	{
		case FileType_PlayList:
		case FileType_Text:
			FileList_SendHeaderPlayList(This, pchunk);
		break;
		case FileType_Pls:
			FileList_SendHeaderPls(This, count, pchunk);
		break;
		case FileType_Xml:
			FileList_SendHeaderYellowPage(This, pchunk);
		break;
	}
}

void FileList_SendTrack(const FileList* This, file_type type, sXFer_Chunk* pchunk, const FLTrack* pTrack, bool bExport)
{
	pchunk->size = 0;

	switch (type)
	{
		case FileType_PlayList:
		case FileType_Text:
			FileList_SendTrackPlayList(This, pTrack, bExport, pchunk);
		break;
		case FileType_M3u:
			FileList_SendTrackM3u(This, pTrack, bExport, pchunk);
		break;
		case FileType_Pls:
			FileList_SendTrackPls(This, pTrack, bExport, pchunk);
		break;
		case FileType_Xml:
			FileList_SendTrackYellowPage(This, pTrack, bExport, pchunk);
		break;
	}
}

void FileList_SendTrailer(const FileList* This, file_type type, sXFer_Chunk* pchunk, uint32_t count)
{
	pchunk->size = 0;

	IGNORE(count);

	switch (type)
	{
		case FileType_Xml:
			FileList_SendTrailerYellowPage(This, pchunk);
		break;
	}
}

static bool FileList_SendDocument(Document* pDoc, file_type type, sXFer_Chunk* pchunk)
{
	FileList* This = (FileList*) pDoc;
	FLTrack*  pTrack;
	uint32_t count = PtrArray_Count(This->m_pTracks);

	if (!pchunk->ref)
	{
		// Very first call, send header, set next ref to -1
		FileList_SendHeader(This, type, pchunk, count);
		pchunk->ref = -1;
		return true;
	}
	else if (pchunk->ref == -1)
	{
		// first track to save, reset ref to 0, will be incremented in turn by the code below
		pchunk->ref = 0;
	}
	else if (pchunk->ref == -2)
	{
		// Very last call, trailer was saved, return false

		// There is no other way to do it within the framework
		This->m_bAutoModified = false;
		return false;
	}

	if (pchunk->ref >= count)
	{
		// all tracks send, send trailer, set next ref to -2
		FileList_SendTrailer(This, type, pchunk, count);
		pchunk->ref = -2;
		return true;
	}

	pTrack = PtrArray_Get(This->m_pTracks, pchunk->ref);
	FileList_SendTrack(This, type, pchunk, pTrack, false);
	pchunk->ref++;

	return true;
}

static const DocumentVPtr FileList_VPtr =
{
	FileList_NotFileList,
	FileList_AskDiscardCancelSave,
	SaveAs_AskSaveAs,
	FileList_ClearContents,
	FileList_LoadDocument,
	FileList_SendDocument
};

bool FileList_IsFileList(const Document* pDoc)
{
	return Document_IsKindOf(pDoc, &FileList_VPtr);
}

/*-------------------------------------------------------------------------*
 *--- FileList creation/destruction ---------------------------------------*
 *-------------------------------------------------------------------------*/

// Creation of new basic FileList

static FileList* throw_New_FileList(FileList_Type type, const char* name, file_type fileType)
{
	FileList* This = throw_mem_calloc(sizeof(*This), 1);
	const file_type* pExportTypes = FileList_Save_FileTypes;

	switch(fileType)
	{
		case FileType_M3u: pExportTypes = FileList_SaveM3u_FileTypes; break;
		case FileType_Pls: pExportTypes = FileList_SavePls_FileTypes; break;
		case FileType_Xml: pExportTypes = FileList_SaveXml_FileTypes; break;
	}

	This->m_pTracks = New_PtrArray(256);
	This->m_Type = type;

	throw_Document_Document
		( &This->m_Document
		, pExportTypes
		, &FileList_VPtr);

	try
	{
		throw_Document_SetPathName(&This->m_Document, name);
		Document_SetFileType(&This->m_Document, fileType);

		This->m_pBaseDir = throw_mem_allocstring(NULL);
		This->m_pTitle = throw_mem_allocstring(NULL);
		This->m_pAuthor = throw_mem_allocstring(NULL);
		This->m_pInfo = throw_mem_allocstring(NULL);
	}
	catch
	{
		Delete_Document(&This->m_Document);
		throw_current();
	}
	catch_end

	return This;
}

// Get the main FileList, if necessary create it and load its content

static const char DefListName[] = "<Choices$Write>.DigitalCD.PlayList";
static FileList* MainList = NULL;

FileList* throw_New_FileList_Queue(void)
{
	FileList* This = throw_New_FileList(FileList_TypeQueue, Msg_Lookup("FLQueue"), FileType_PlayList);
	Document_SetNonFileFlag(&This->m_Document, true);
	Document_SetAutoDeleteFlag(&This->m_Document, false);
	throw_FileList_AddView(This);

	return This;
}

FileList* throw_New_FileList_Search(void)
{
	FileList* This = throw_New_FileList(FileList_TypeSearch, Msg_Lookup("FLSearch"), FileType_PlayList);
	Document_SetNonFileFlag(&This->m_Document, true);
	Document_SetAutoDeleteFlag(&This->m_Document, true);
	throw_FileList_AddView(This);

	return This;
}

FileList* throw_FileList_GetMain(void)
{
	if (!MainList)
	{
		FileList* This = throw_New_FileList(FileList_TypeMain, DefListName, FileType_PlayList);

		if (File_GetFileType(DefListName) == FileType_PlayList)
			Document_OnOpenDocument(&This->m_Document, DefListName);

		throw_FileList_AddView(This);

		MainList = This;
	}

	return MainList;
}

// Create a new empty FileList and create an associated track in the parent list

FileList* throw_New_FileList_Sublist(FileList* pParent, FileList_Type type, const char* name, file_type fileType)
{
	FileList* This = throw_New_FileList(type, name, fileType);

	try
	{
		throw_FileList_NewTrack_List(pParent, -1, This);
		if (name) throw_FileList_AddView(This);
	}
	catch
	{
		Delete_FileList(This);
		throw_current();
	}
	catch_end

	return This;
}

// Create the FileList associated to a given track and load its content

FileList* throw_New_FileList_FromTrack(FLTrack* pTrack)
{
	static const char seps[] = {FILE_FILESYSTEM_CHAR, FILE_FOLDER_CHAR};
	FileList* volatile This = NULL;

	try
	{
		switch (FLTrack_GetObjectType(pTrack))
		{
			case FLTrack_Type_YellowPage:
			{
				DownloadYP* pYP = throw_mem_calloc(sizeof(*pYP), 1);

				pYP->handle = -1;

				This = throw_New_FileList(FileList_TypeYP, FLTrack_GetFilename(pTrack), FileType_Xml);
				Document_SetNonFileFlag(&This->m_Document, true);

				try
				{
					FLTrack_AttachSubList(pTrack, This);
					This->m_pOwner = pTrack;
					FileList_SetModifiedFlag(This, false);
					throw_FileList_AddView(This);
				}
				catch
				{
					FLTrack_AttachSubList(pTrack, NULL);
					Delete_FileList(This);
					throw_current();
				}
				catch_end

				JobList_Add(Task_GetJobList(), false, This, pYP, Job_DownloadYellowPages);
			}
			break;
			case FLTrack_Type_Directory:
			case FLTrack_Type_FileList:
			{
				const char* pathname = throw_mem_allocstring(FLTrack_GetFilename(pTrack));
				file_type type;
				bool bIsList;
				bool bIsNew = (strpbrk(pathname, seps) == NULL);

				if (bIsNew)
				{
					bIsList = true;
					type = FileType_PlayList;
				}
				else
				{
					type = File_GetFileType(pathname);
					bIsList = FileList_AcceptSourceFileType(type);

					if (bIsList)
						This = (FileList*) DocList_Find(throw_DocList_Get(), pathname);
				}

				if (bIsList
				||  (type == 0x1000)
				||  (type == 0x2000))
				{
					if (This == NULL)
					{
						This = throw_New_FileList
								( bIsList
								  ? (type == FileType_PlayList)
								    ? FileList_TypeNormal
								    : FileList_TypeExtern
								  : FileList_TypeDir
								, pathname, type);

						try
						{
							FLTrack_AttachSubList(pTrack, This);
							This->m_pOwner = pTrack;

							if (bIsList)
							{
								FLTrack_SetFlags(pTrack, 0, FLTrack_IsDir);
								if (bIsNew)
									FileList_SetModifiedFlag(This, true);
								else
									Document_OnOpenDocument(&This->m_Document, pathname);

								throw_FileList_AddView(This);
							}
							else
							{
								Document_SetNonFileFlag(&This->m_Document, true);
								FLTrack_SetFlags(pTrack, FLTrack_IsDir, FLTrack_IsDir);
								FileList_SetBaseDir(This, pathname);
								throw_FileList_AddView(This);
								FileList_AddFile(This, -1, pathname, 0);
								FileList_SetModifiedFlag(This, false);
							}
						}
						catch
						{
							FLTrack_AttachSubList(pTrack, NULL);
							Delete_FileList(This);
							throw_current();
						}
						catch_end
					}
				}
				else if (type != -2)
					FLTrack_SetFlags(pTrack, 0, FLTrack_IsPlaylist|FLTrack_IsDir);
				else App_ReportError("DSErr3:%s", pathname);

				mem_free(pathname);
			}
			break;
		}
	}
	catch
	{
		This = NULL;
		App_ReportException();
	}
	catch_end

	if (This != NULL)
		FLTrack_SetFlags
			( pTrack
			, FLTrack_Played | FLTrack_PlayedToday
		    , FLTrack_Played | FLTrack_PlayedToday | FLTrack_Invalidated | FLTrack_FailedToPlay
		    );
	else
		FLTrack_SetFlags(pTrack, FLTrack_FailedToPlay, FLTrack_FailedToPlay);

	FLTrack_RefreshViews(pTrack, This, false, false);

	PListFiles_RefreshTrackList(PListFiles_Get(), This);
	PListRadio_RefreshTrackList(PListRadio_Get());

	return This;
}

// Create an new FileList, load its content from the given file
// and create an associated track in the parent list

FileList* throw_New_FileList_File(FileList* pParent, const char* filename)
{
	FileList* This = (FileList*) DocList_Find(throw_DocList_Get(), filename);

	if (This) return This;

	try
	{
		file_type fileType = File_GetFileType(filename);
		This = throw_New_FileList_Sublist
					( pParent
					, (fileType == FileType_PlayList)
					  ? FileList_TypeNormal : FileList_TypeExtern
					, NULL
					, fileType
					);

		if (!Document_OnOpenDocument(&This->m_Document, filename))
		{
			Delete_FileList(This);
			This = NULL;
		}
		else throw_FileList_AddView(This);
	}
	catch
	{
		App_ReportException();
		return NULL;
	}
	catch_end

	return This;
}

// Create an new FileList, load its content according to the given Wimp message
// and create an associated track in the parent list

FileList* throw_New_FileList_Receive(FileList* pParent, const Msg_FileData* rcv)
{
	FileList* volatile This = NULL;

	// Receive a file
	if ((rcv->hdr.action == EMsg_DataLoad) || (rcv->hdr.action == EMsg_DataOpen))
	{
		This = (FileList*) DocList_Find(throw_DocList_Get(), rcv->name);

		if (This)
		{
			// Send a DataLoadAck message to the sender task
			Msg msg = *(Msg*) rcv;
			msg.hdr.ref = rcv->hdr.id;
			msg.hdr.action = EMsg_DataLoadAck;

			Task_SendMsg(&msg, rcv->hdr.task);

			return This;
		}

		try
		{
			This = throw_New_FileList_Sublist
						( pParent
						, (rcv->type == FileType_PlayList)
						  ? FileList_TypeNormal : FileList_TypeExtern
						, rcv->name
						, rcv->type
						);

			Document_OnReceiveDocument(&This->m_Document, rcv);
		}
		catch
		{
			Delete_FileList(This);
			throw_current();
		}
		catch_end

		return This;
	}

	// Receive RAM data
	try
	{
		This = throw_New_FileList_Sublist(pParent, FileList_TypeNormal, rcv->name, rcv->type);

		Document_OnReceiveDocument(&This->m_Document, rcv);
	}
	catch
	{
		Delete_FileList(This);
		throw_current();
	}
	catch_end

	return This;
}

// Delete a FileList

void Delete_FileList(FileList* This)
{
	if (This)
	{
		Delete_Document(&This->m_Document);
	}
}
