/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

#include "stdafx.h"
#include "DsLavSplit.h"
#include "DsLavDoc.h"

#include "DsLavSplitterView.h"	// For GetView()
#include "DsLavListView.h"		// For OpenFile
#include "DsLavVideoView.h"
#include "OpenUrlDlg.h"			// For OpenUrl
#include "Curlinet.h"			// CURL
#include "ExportDlg.h"			// Export
#include "ImportDlg.h"			// Import
#include "EditMediaItemDlg.h"	// Edit Media Item
#include "NewTopicDlg.h"			// New Topic Dlg
#include "StdioFileEx.h"			// Unicode txt file i/o
#include "LavSetupDlg.h"			// LAV settings
#include "DvdNavDlg.h"			// DVD navigation
#include "ImportPlaylistDlg.h"	// Import - List View context
#include "FoldersDlg.h"			// Setup Folders

#include "RadioBrowseDb.h"			// test radio browser db
#include "RadioBrowseUpdateDlg.h"	//radio browse update dlg
#include "RadioBrowseSearchDlg.h"	//radio browse search dlg


//#include "CdaRead.h"			// CD Audio disc testing
//#include <string>				// test wstring in OnTest()

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CDsLavDoc

IMPLEMENT_DYNCREATE(CDsLavDoc, CDocument)

BEGIN_MESSAGE_MAP(CDsLavDoc, CDocument)
	//{{AFX_MSG_MAP(CDsLavDoc)
	ON_COMMAND(ID_FILE_OPEN, OnOpenMediaFile)
	ON_COMMAND(ID_FILE_OPEN_URL, OnOpenMediaUrl)
	ON_UPDATE_COMMAND_UI(ID_FILE_OPEN, OnUpdateFileOpen)
	ON_COMMAND(ID_TEST_TEST, OnTestTest)
	ON_COMMAND(ID_TOOLS_EXPORT, OnToolsExport)
	ON_COMMAND(ID_TOOLS_IMPORT, OnToolsImport)
	ON_COMMAND(ID_FILE_SAVE_CONTENTS, OnFileSaveContents)
	ON_COMMAND(ID_SETUP_LAV_SETUP, OnSetupLavFilters)
	ON_COMMAND(ID_SETUP_LIST_FONT, OnSetupListFont)
	ON_COMMAND(ID_SETUP_CONTENTS_FONT, OnSetupContentsFont)
	ON_COMMAND(ID_DVD_NAVIGATION, OnDvdNavigation)
	ON_COMMAND(ID_DVD_PLAY_DVD, OnDvdPlayDvd)
	ON_COMMAND(ID_HELP_DSLAVHELP, OnHelpDsLavHelp)
	ON_COMMAND(ID_TOOLS_UPDATE_RB, OnToolsUpdateRb)
	ON_COMMAND(ID_TOOLS_SEARCH_RB, OnToolsSearchRb)
	ON_COMMAND(ID_SETUP_CHECK_UPDATES, OnCheckForUpdates)
	ON_COMMAND(ID_SETUP_FOLDER_LOCATIONS, OnSetupFolderLocations)
	ON_UPDATE_COMMAND_UI(ID_DVD_NAVIGATION, OnUpdateDvdNavigation)
	ON_UPDATE_COMMAND_UI(ID_DVD_PLAY_DVD, OnUpdateDvdPlayDvd)
	ON_COMMAND(ID_DVD_PLAY_CD, OnDvdPlayCd)
	ON_UPDATE_COMMAND_UI(ID_DVD_PLAY_CD, OnUpdateDvdPlayCd)
	//}}AFX_MSG_MAP

	/* Facilitate message routing from CControlsBar Dialog Bar. out 8/22-6
	ON_BN_CLICKED(IDC_BUTTON_PLAY_PAUSE, OnPlayPause)
	ON_BN_CLICKED(IDC_BUTTON_MUTE, OnMute)
	ON_BN_CLICKED(IDC_SLIDER_VOL, OnVolume)
	ON_BN_CLICKED(IDC_STATIC_VOL, OnVolumeStatic)
	ON_BN_CLICKED(IDC_BUTTON_STOP, OnStop)
	ON_BN_CLICKED(IDC_STATIC_TIME, OnTimeStatic)
	ON_BN_CLICKED(IDC_SLIDER_VID, OnSliderVid)
	ON_BN_CLICKED(IDC_BUTTON_LEFT_PANE, OnHidePane)*/

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDsLavDoc construction/destruction

CDsLavDoc::CDsLavDoc()
{
	TRACE(_T("In CDsLavDoc Constructor constructing CDoc\n"));

	// Serialize TopicTree flag.
	m_bSaveTopicTree = FALSE;

	// Get a default font height from SPI_GETNONCLIENTMETRICS.
	// Note this is a Tahoma font with -18 height. RC file uses MS Sans Serif.
	// Height reported as 10 by CFontDialog in OnSetupList/TreeFont(). 
	NONCLIENTMETRICS ncm;
	ncm.cbSize = sizeof(NONCLIENTMETRICS);
	SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);

	// Set List and Tree fonts.
	m_listLf = ncm.lfMenuFont;
	m_treeLf = ncm.lfMenuFont;
	// Set file paths.			UserData Version 2 - 9/27/2021
	CString m_pathVideoFolder = _T("");
	CString m_pathContentsFiles = _T("");

	// Current Topic flags.
	m_bCurTopicDrive = FALSE;
	m_bCurTopicFavs = FALSE;

	// User App Data Folder path - set in OnNewDocument().
	m_userAppDataPath = _T("");

	// RadioBrowser Update,
	m_rbThreadParam.Zero();
	m_hRbThread = NULL;
	m_tempPath = _T("");
	m_rbUrl = _T("");
	m_bRbUpdateInstalled = FALSE;
	m_hwndDlg = FALSE;				// used during testing - see CancelUpdate().
}

CDsLavDoc::~CDsLavDoc()
{
	TRACE(_T("Destructing CDsLavDoc\n"));

	// See CMySplitterWnd::ReplaceView re AutoDelete.
	// See also MSDN DeleteContents re SDI apps.
}

// Starting up.
BOOL CDsLavDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;

	// Set CDsLavApp ptr used by CMySplitterWnd
	((CDsLavApp*)AfxGetApp())->m_pDocument = this;

	// Load m_aliasAr aliases - used by Read() Write().
	m_aliasAr.InitializeTopicAlias();

	//
	// For UAC, the following files:
	//   DsLavContents.dat
	//   DsLavUser.dat
	//	 RadioBrowse.dat
	//   results.txt
	// must have write access for standard users, requiring
	// the files be placed in the users CSIDL_APPDATA folder
	// or similar.
	//
	// The Setup Installer may or may not have created the 
	// folder and copied the files there, so we do it here.
	//
	// As a fallback, we use the files located in the current
	// directory - but a non administrator user will not be 
	// able to save there and will be denied access.
	//

	CString appDataPath;
	CString msg, pathSrc, pathDest;
	BOOL haveUserAppDataFolder = TRUE;

	// Get current directory.
	CString curDirPath = ((CDsLavApp*)AfxGetApp())->m_curDirectory;

	// Get User App Data folder path.
	//  Should create in: 
	//    C:\Documents and Settings\Administrator\Application Data\DsLav
	// And if folder not available, use current directory.
	if(((CDsLavApp*)AfxGetApp())->GetUserSettingsFolder(appDataPath))
	{
		CFileStatus status;
		int ret = 0;

		// Check for files - copy as needed.
		//
		// DsLavContents.dat
		pathDest = appDataPath;
		pathDest += _T("\\");
		pathDest += _T("DsLavContents.dat");
		if(!CFile::GetStatus(pathDest, status)) // static version for test if exists.
		{
			pathSrc = curDirPath;
			pathSrc += _T("\\");
			pathSrc += _T("DsLavContents.dat");
			if(!CopyFile(pathSrc, pathDest, TRUE)) // 1
				haveUserAppDataFolder = FALSE;
		}
		// DsLavUser.dat
		pathDest = appDataPath;
		pathDest += _T("\\");
		pathDest += _T("DsLavUser.dat");
		if(!CFile::GetStatus(pathDest, status)) // static version for test if exists.
		{
			pathSrc = curDirPath;
			pathSrc += _T("\\");
			pathSrc += _T("DsLavUser.dat");
			if(!CopyFile(pathSrc, pathDest, TRUE)) // 1
				haveUserAppDataFolder = FALSE;
		}
		// RadioBrowse.dat
		pathDest = appDataPath;
		pathDest += _T("\\");
		pathDest += _T("RadioBrowse.dat");
		if(!CFile::GetStatus(pathDest, status)) // static version for test if exists.
		{
			pathSrc = curDirPath;
			pathSrc += _T("\\");
			pathSrc += _T("RadioBrowse.dat");
			if(!CopyFile(pathSrc, pathDest, TRUE)) // 1
				haveUserAppDataFolder = FALSE;
		}
		// results.txt
		pathDest = appDataPath;
		pathDest += _T("\\");
		pathDest += _T("results.txt");
		if(!CFile::GetStatus(pathDest, status)) // static version for test if exists.
		{
			pathSrc = curDirPath;
			pathSrc += _T("\\");
			pathSrc += _T("results.txt");
			if(!CopyFile(pathSrc, pathDest, TRUE)) // 1
				haveUserAppDataFolder = FALSE;
		}
	}
	// Set member with folder path.
	if(haveUserAppDataFolder)
		m_userAppDataPath = appDataPath;
	else
	{
		m_userAppDataPath = curDirPath;
		AfxMessageBox(_T("User AppData folder not available. Changes cannot be saved."));
	}

	// Read TopicTree serialized file.  
	pathSrc = m_userAppDataPath;
	pathSrc += _T("\\DsLavContents.dat");
	ReadTopicTree(pathSrc);

	// Read User Settings serialized file.
	pathSrc = m_userAppDataPath;
	pathSrc += _T("\\DsLavUser.dat");
	ReadUserData(pathSrc);
	
	// Read Radio Browser serialized file.
	pathSrc = m_userAppDataPath;
	pathSrc += _T("\\RadioBrowse.dat");
	ReadRadioBrowserData(pathSrc);

	return TRUE;
}

void CDsLavDoc::OnCloseDocument() 
{
	// Cleanup moved to SaveModified().
	TRACE(_T("In CDsLavDoc OnCloseDocument\n"));	
	CDocument::OnCloseDocument();
}

// Check if latest version
void CDsLavDoc::OnCheckForUpdates() 
{
	// Get version_info.html from Codeberg pages.
	//  It's a html file because that's the only way to stash 
	//  an updateable file with static URL at codeburg. The 
	//  extension makes no difference.
//	CString url = _T("https://dslav.codeberg.page/version_info.html");
//	CCurlinet inet;

	// 8/8/2021 - move to tux
	// Get version.dat from Tux:
	CString url = _T("http://download.tuxfamily.org/dslav/SourceCode/version.dat");
	CCurlinet inet;


	// Temp file path
	CString tempFilePath = m_userAppDataPath;
	tempFilePath += _T("\\results.txt");
	CString strErr;
	CString msg;

	// Fetch.
	{
		CWaitCursor wait;
		if(!inet.GetInternetDataUnicode(url, tempFilePath, strErr))
		{
			CString msg;
			msg.Format(_T("Unable to check for updates. %s"), strErr);
			AfxMessageBox(msg);
			return;
		}
	}
	// Open temp file.
	CStdioFile file;
	CFileException e;
	if(!file.Open(tempFilePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		msg.Format(_T("Can not open file %s, error = %u\n"), tempFilePath, e.m_cause );
		AfxMessageBox(msg);
		return;
	}
	// Verify file size for CString below.
	ULONGLONG fileLen = file.GetLength();
	if(fileLen > 20)
	{
		file.Close();
		msg.Format(_T("Check for updates - file %s is too long.\n"), tempFilePath);
		AfxMessageBox(msg);
		return;
	}

	// Parse version from temp file.
	CString fileStr, verOld, verNew;
	int index = 0;
	BOOL bError = FALSE, isOlder = FALSE, isNewer = FALSE;
	msg = _T("Check for updates failed");

	// Get contents in one string.
	while(file.ReadString(fileStr))
	{
		// File is always: dslav 0.0.0.0 - 13 chars.
		//
		// Get new version.
		int nChars = fileStr.GetLength();
		if(nChars != 13)
		{
			bError = TRUE;
			continue;
		}
		verNew = fileStr.Right(7);

		// Get exist version.
		GetVersion(verOld);
		if(verOld.GetLength() != 7)
		{
			bError = TRUE;
			continue;
		}
		// Test.
		TCHAR ch1, ch2;
		for(int i = 0; i < 7; i++)
		{
			// Validate
			ch1 = verOld[i];
			ch2 = verNew[i];
			// If a digit or a dot.
			if(ch1 == _T('.'))
				continue;
			if(!_istdigit(ch1) || !_istdigit(ch2))
			{
				bError = TRUE;
				break;
			}
			// Test.
			//
			// v1 is older.
			if(verOld[i] < verNew[i])
			{
				isOlder = TRUE;
				break;
			}
			// v1 is newer
			else if(verOld[i] > verNew[i])
			{
				isNewer = TRUE;
				break;
			}				
		}
	}
	// Error - info user.
	if(bError)
		AfxMessageBox(msg);

	// If older.
	else if(isOlder)
	{
	//	msg.Format(_T("A newer version, %s, is available at: https://dslav.codeberg.page"), verNew);

		// 8/8/2021 - move to tux
		msg.Format(_T("A newer version, %s, is available at: https://www.dslav.tuxfamily.org"), verNew);
		AfxMessageBox(msg, MB_OK | MB_ICONINFORMATION);
	}
	// If newer or the same.
	else
		AfxMessageBox(_T("You have the latest version."),  MB_OK | MB_ICONINFORMATION);

	// Done with file.
	file.Close();
}


// Get application Version, e.g. 0.4.0.0, into strVersion.
//  Need to link with version.lib - see header.
BOOL CDsLavDoc::GetVersion(CString & strVersion)
{
	// From: MSDN Apr98CQAcode.exe - Paul DiLascia

	// Get module handle
	TCHAR filename[_MAX_PATH];
	HMODULE hModule = ::GetModuleHandle(NULL);
	if(!hModule)
		return FALSE;

	// Get module file name
	DWORD len = GetModuleFileName(hModule, filename,
								  sizeof(filename) / sizeof(filename[0]));
	if(len <= 0)
		return FALSE;

	// Read file version info. Handle will always be set to zero.
	DWORD dwDummyHandle;					
	len = GetFileVersionInfoSize(filename, &dwDummyHandle);
	if(len <= 0)
		return FALSE;

	// Allocate version info	
	BYTE * pVersionInfo = new BYTE[len];			
	if (!::GetFileVersionInfo(filename, 0, len, pVersionInfo))
	{
		if(pVersionInfo)
			delete [] pVersionInfo;
		return FALSE;
	}

	// Get VS_FIXEDFILEINFO.
	VS_FIXEDFILEINFO * fixedInfo = NULL;
	UINT iLen;
	if(!VerQueryValue(pVersionInfo, _T("\\"), (void **) &fixedInfo, &iLen))
	{
		if(pVersionInfo)
			delete [] pVersionInfo;
		return FALSE;
	}

	// Set version string with VS_FIXEDFILEINFO data.
	strVersion.Format(_T("%d.%d.%d.%d"), HIWORD(fixedInfo->dwFileVersionMS), 
										 LOWORD(fixedInfo->dwFileVersionMS),
										 HIWORD(fixedInfo->dwFileVersionLS), 
										 LOWORD(fixedInfo->dwFileVersionLS) ); 
	// Clean up.
	if(pVersionInfo)
		delete [] pVersionInfo;


	// dbg test
//	CString v2 = _T("0.6.0.0\n");
//	int ret = CompareVersions(strVersion, v2);

	return TRUE;
}

// Open the help file.
void CDsLavDoc::OnHelpDsLavHelp() 
{
	// Call chm in current directory.
	HtmlHelp(0,
			 _T("DsLavHelp.chm"),
			 HH_DISPLAY_TOC,
			 NULL);

	// Test open page - works
/*	HtmlHelp(0,
			 _T("DsLavHelp.chm::/Player.htm"),
			 HH_DISPLAY_TOPIC,
			 NULL);
See MSDN: About the HTML Help API function
HWND hwnd = HtmlHelp(GetDesktopWindow(),
					 "c:\\Help.chm::/Intro.htm>Mainwin",
					 HH_DISPLAY_TOPIC,
					 NULL);*/

}

// Shutting down. Turn off player and serialize Contents if changed,
BOOL CDsLavDoc::SaveModified() 
{
	TRACE(_T("In CDsLavDoc SaveModified\n"));

	// Stop any video playback,
	TRACE(_T("In CDsLavDoc SaveModified - calling StopVideoPlayback\n"));
	GetMainFrame()->StopVideoPlayback();

	// We can bypass using the MFC SetModifiedFlag - IsModified framework
	// and implement our own.
	if(m_bSaveTopicTree)
	{
		if(AfxMessageBox( _T("Save Changes to Contents?"), MB_YESNO | MB_ICONINFORMATION) == IDYES)
		{
			CString path;

			// Serialize TopicTree.
			path = m_userAppDataPath;
			path += _T("\\DsLavContents.dat");
			WriteTopicTree(path);

			// Serialize User Settings.
			path = m_userAppDataPath;
			path += _T("\\DsLavUser.dat");
			WriteUserData(path);
		}
	}
	// Must return framework or app cannot close.	
	return CDocument::SaveModified(); // calls OnCloseDocument()
}

// File-Save-Contents pressed by user.
void CDsLavDoc::OnFileSaveContents() 
{
	CString path;

	// Serialize TopicTree.
	path = m_userAppDataPath;
	path += _T("\\DsLavContents.dat");
	WriteTopicTree(path);

	// Serialize User Settings.
	path = m_userAppDataPath;
	path += _T("\\DsLavUser.dat");
	WriteUserData(path);

	// Clear flag.
	m_bSaveTopicTree = FALSE;
}

// Load TopicTree data from application directory.
BOOL CDsLavDoc::ReadTopicTree()
{
	TRACE(_T("In CDsLavDoc ReadTopicTree - from application dir\n"));

	int ret = TRUE;

	// Get current directory.
	CString curDir;
	curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	curDir += _T("\\DsLavContents.dat");

	// Call overload.
	return ReadTopicTree(curDir);
}

// Read TopicTree from serialized path param.
BOOL CDsLavDoc::ReadTopicTree(CString path)
{
	TRACE(_T("In CDsLavDoc ReadTopicTree\n"));

	int ret = TRUE;

	TRACE(_T("CDsLavDoc::ReadTopicTree Path = %s\n"), path);

	// Open the file for reading.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeRead | CFile::shareDenyWrite, &ex))
	{
		ex.ReportError();
		return FALSE;
	}
	if(file.GetLength() == 0)
	{
		TRACE(_T("ERROR CDsLavDoc::ReadTopicTree GetLength() = 0\n"));
		return FALSE;
	}

	CArchive ar(&file, CArchive::load);
	try
	{	
		// Get schema
		int schema; // note GetObjectSchema() is for serializable classes
		ar >> schema;
		if(schema != 1)
		{
			MessageBox(NULL,
					   _T("User data file could not be read."), 
					   _T("DsLav Application"), 
					   MB_OK | MB_ICONINFORMATION);
			
			// Close Archive
			ar.Close();
			// Close the file and ignore any assertions	
			file.Abort();
			return FALSE;
		}
		// Read the TopicTree. Note m_aliasAr initialized 
		// in OnNewDocument().
		m_topicTree.Zero();
		m_topicTree.Read(ar, m_aliasAr, schema);

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	catch(CException* pException)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort();
		pException->ReportError();
		pException->Delete();
		ret = FALSE;
	}

	// If no root topics read.
	int nRootItems = m_topicTree.m_topicTreeAr.GetSize();
	if(!nRootItems)
	{
		TRACE(_T("ERROR CDsLavDoc::ReadTopicTree no root items read\n"));
		return FALSE;
	}

	// Update Current Topic, Lists, Favorites and Views.
	// Sets current topic to Favorites.
	UpdateCurrentTopic(TRUE, TRUE, NULL, TRUE, TRUE);

	/* Dbg print to output.
	#ifdef _DEBUG
		m_topicTree.PrintContentsMediaItems();
	#endif*/

	// OK
	TRACE(_T("CDsLavDoc::ReadTopicTree - %d Root Topics added to Tree.\n"), nRootItems);
	return ret;
}

// Write TopicTree to program folder.
BOOL CDsLavDoc::WriteTopicTree()
{
	TRACE(_T("In CDsLavDoc WriteTopicTree for curDir\n"));

	int ret = TRUE;

	// Get current directory.
	CString curDir;
	curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	curDir += _T("\\DsLavContents.dat"); 

	TRACE(_T("CDsLavDoc::WriteTopicTree calling WriteTopicTree for curDir\n"));
	return WriteTopicTree(curDir);
}

// Write TopicTree to path param.
BOOL CDsLavDoc::WriteTopicTree(CString path)
{
	TRACE(_T("CDsLavDoc::WriteTopicTree Path = %s\n"), path);
	int ret = TRUE;
	
	// Open the file for writing.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeCreate | CFile::modeWrite , &ex))
	{
		ex.ReportError();
		return FALSE;
	}

	CArchive ar(&file, CArchive::store);
	try
	{
		// Set schema
		int schema = 1; // note SetObjectSchema() is for serializable classes
		ar << 1;

		// Write TopicTree.
		m_topicTree.Write(ar, m_aliasAr, schema);

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	catch(CException* pException)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort(); 
		pException->ReportError();
		pException->Delete();
		ret = FALSE;
	}
	return ret;
}

// Read user data. 
// Data will be set to constructor defaults first in case this fails.
// Called from CDsLavDoc::OnNewDocument().
// Reads from current directory.
BOOL CDsLavDoc::ReadUserData()
{
	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;

	// Add User file name to path.
	curDir += _T("\\DsLavUser.dat");

	// Read.
	return ReadUserData(curDir);
}


// Read user data. 
// Data will be set to constructor defaults first in case this fails.
// Called from CDsLavDoc::OnNewDocument().
// Path should be to current directory or User Data folder.
BOOL CDsLavDoc::ReadUserData(CString path)
{
	int ret = TRUE;

	// Open the file for reading.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeRead | CFile::shareDenyWrite, &ex))
	{
		//ex.ReportError();

		// Will not exist on first start - less installer created it.
		// Set modified to ensure it gets written - or just wait
		// for user to make a change.
		//m_bSaveTopicTree = TRUE;
		return FALSE;
	}
	if(file.GetLength() == 0)
	{
		TRACE(_T("ERROR CDoc ReadUserData GetLength() = 0\n"));
		return FALSE;
	}

	CArchive ar(&file, CArchive::load);
	TRY
	{	
		// Get schema
		int schema; // note GetObjectSchema() is for serializable classes
		ar >> schema;
	//	if(schema != 1)

		// Read V1 & V2.
		//
		// Error.
		if(schema > 2)		// Version 2, 9/27/2021
		{
		/*	MessageBox(NULL,
					   _T("User data file could not be read."), 
					   _T("DsLav Application"), 
					   MB_OK | MB_ICONINFORMATION);*/
			
			// Close Archive
			ar.Close();
			// Close the file and ignore any assertions	
			file.Abort();
			return FALSE;
		}
		// OK
		//
		// Read TreeView and ListView LOGFONTS.
		ar.Read(&m_listLf, sizeof(LOGFONT));
		ar.Read(&m_treeLf, sizeof(LOGFONT));

		// Read LAV Settings.
		m_lavSetup.Read(ar, schema);

		// 9/27/2021
		// New for Version 2:
		// Read folder paths.
		if(schema >= 2)
		{
			ar >> m_pathVideoFolder;
			ar >> m_pathContentsFiles;
		}

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	CATCH_ALL(e)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort(); 
		TRY
		{
			// CDoc member
			//ReportSaveLoadException(filePath, e, FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
		}
		END_TRY
		e->Delete();
		ret = FALSE;
	}
	END_CATCH_ALL

	return ret;
}

// Write User Data - writes to current directory.
BOOL CDsLavDoc::WriteUserData()
{
	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	curDir += _T("\\DsLavUser.dat");

	// Write. fix typo 9/27/2021
	return WriteUserData(curDir);
}

// Write user data.
// Called from:
//  CDsLavDoc::SaveModified()
//  CDsLavDoc::OnFileSaveContents()
BOOL CDsLavDoc::WriteUserData(CString path)
{
	int ret = TRUE;

	// Open the file for writing.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeCreate | CFile::modeWrite , &ex))
	{
		ex.ReportError();
		return FALSE;
	}

	CArchive ar(&file, CArchive::store);
	TRY
	{
		// Set schema
		//int schema = 1; // note SetObjectSchema() is for serializable classes
		int schema = 2; // Version 2, 9/27/2021
		ar << schema;

		// Write List and Tree LOGFONTS.
		ar.Write(&m_listLf, sizeof(LOGFONT));
		ar.Write(&m_treeLf, sizeof(LOGFONT));

		// Write LAV Settings.
		m_lavSetup.Write(ar, schema);

		// 9/27/2021
		// New for Version 2:
		// Write folder paths.
		if(schema >= 2)
		{
			ar << m_pathVideoFolder;
			ar << m_pathContentsFiles;
		}

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	CATCH_ALL(e)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort(); 
		TRY
		{
			// CDoc member
			//ReportSaveLoadException(curDir, e, TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
			// THROW_LAST();
		}
		END_TRY
		e->Delete();
		ret = FALSE;
	}
	END_CATCH_ALL

	return ret;
}

// Write RadioBrowseDB.
// Called only from CDsLavDoc::UpdateFinished().
BOOL CDsLavDoc::WriteRadioBrowserData()
{
	// Write to current dir for now.
	//CString path = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	//path += _T("\\RadioBrowse.dat");

	// Write to AppData folder.
	CString path = m_userAppDataPath;
	path += _T("\\RadioBrowse.dat");


	// Open the file for writing.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeCreate | CFile::modeWrite , &ex))
	{
		ex.ReportError();
		return FALSE;
	}

	CArchive ar(&file, CArchive::store);
	try
	{
		// Set schema
		int schema = 1; // note SetObjectSchema() is for serializable classes
		ar << schema;

		// Write.
		m_radioBrowse.Write(ar, schema);	// OK - RadioBrowse.dat 6.65 MB

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	catch(CException* pException)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort(); 
		pException->ReportError();
		pException->Delete();
		return FALSE;
	}
	return TRUE;
}
BOOL CDsLavDoc::ReadRadioBrowserData(CString path)
{
/*	// Read from current directory for now.
	CString path = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	path += _T("\\RadioBrowse.dat");*/

	// Open the file for reading.
	CFile file;
	CFileException ex;
	if(!file.Open(path, CFile::modeRead | CFile::shareDenyWrite, &ex))
	{
		ex.ReportError();
		return FALSE;
	}
	if(file.GetLength() == 0)
	{
		TRACE(_T("ERROR CDoc ReadRadioBrowserData GetLength() = 0\n"));
		return FALSE;
	}

	CArchive ar(&file, CArchive::load);
	try	
	{
		// Get schema
		int schema; 
		ar >> schema;
		if(schema != 1)
		{
			MessageBox(NULL,
					   _T("RB data wrong schema."), 
					   _T("DsLav Application"), 
					   MB_OK | MB_ICONINFORMATION);
			
			// Close Archive
			ar.Close();
			// Close the file and ignore any assertions	
			file.Abort();
			return FALSE;
		}

		// Read.
		m_radioBrowse.Read(ar, schema);

		// Close the archive.	
		ar.Close();
		// Close the file	
		file.Close();
	}
	catch(CException* pException)
	{
		// Close the archive.	
		ar.Close();
		// Close file safely and quietly
		file.Abort();
		pException->ReportError();
		pException->Delete();
		return FALSE;
	}
	return TRUE;
}

// From Wanderer - Prosise - ch. 11, p. 698
// Called from CMainFrame::OnCmdMsg().
BOOL CDsLavDoc::RouteCmdToAllViews(CView *pView, UINT nID, int nCode, void *pExtra,
									AFX_CMDHANDLERINFO *pHandlerInfo)
{
    POSITION pos = GetFirstViewPosition();

    while (pos != NULL)
	{
        CView * pNextView = GetNextView(pos);
        if(pNextView != pView)
		{
            if(pNextView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
                return TRUE;
        }
    }
    return FALSE;
}

CMainFrame * CDsLavDoc::GetMainFrame()
{
	CWnd * pFrame = AfxGetMainWnd();
	if(!pFrame->IsKindOf( RUNTIME_CLASS(CMainFrame)))
	{
		return NULL;
	}
	return (CMainFrame *)AfxGetMainWnd();
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavDoc commands

// Open/Play a media file - from Menu File / Open Media File. 
void CDsLavDoc::OnOpenMediaFile() 
{
	TRACE(_T("In CDsLavDoc OnOpenFile\n"));

	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;

	TCHAR filters[] = _T("Media Files (*.avi*.mp4*.mp3*.flv*.wmv)|*.avi;*.mp4;*.mp3;*.flv;*.wmv|All Files (*.*)|*.*||");   
  
	CFileDialog dlg(TRUE, 
					NULL, 
					NULL,				//OFN_NOCHANGEDIR   |
					OFN_FILEMUSTEXIST |
					OFN_PATHMUSTEXIST, 
					filters);

	// Set initial directory if available. 9/27/2021
	if(m_pathVideoFolder != _T(""))
		dlg.m_ofn.lpstrInitialDir = m_pathVideoFolder;

	// Proccess selection
	if(dlg.DoModal() == IDOK)
	{
		// Zero path and name used by CDsLavListView::OnLButtonDblClk 
		// when opening a media file. Needed if starting without
		// stopping first. StartVideoPlayback will get name and path 
		// from filePath. 
		GetMainFrame()->SetMediaPath(_T(""));

		CString filePath = dlg.GetPathName();
		TRACE(_T("In CDsLavDoc OnOpenFile calling StartVideoPlayback\n"));

		// If opening a DVD file.
		if(filePath == _T("VIDEO_TS.VOB") || 
		   filePath == _T("VIDEO_TS.BUP") || 
		   filePath == _T("VIDEO_TS.IFO") )
		{
			// Play with DVD Explorer.
			if(!pFrame->StartDvdPlayback(filePath))
			{
				TRACE(_T("In CDsLavDoc OnOpenFile StartDvdPlayback failed\n"));
				return;
			}
		}
		// Play a CD Audio file.
		else if(filePath.Find(_T(".cda")) != -1)
		{
			// Play with AsyncCD Filter.
			if(!pFrame->StartCdPlayback(filePath))
			{
				TRACE(_T("In CDsLavDoc OnOpenFile StartCdPlayback failed\n"));
				return;
			}
		}
		// Play all others.
		else if(!pFrame->StartVideoPlayback(filePath))
		{
			TRACE(_T("In CDsLavDoc OnOpenFile StartVideoPlayback failed\n"));
			return;
		}
	}	
}

void CDsLavDoc::OnUpdateFileOpen(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here	
}

void CDsLavDoc::OnOpenMediaUrl() 
{
	TRACE(_T("In CDsLavDoc OnOpenUrl \n"));

	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;

	COpenUrlDlg dlg;

	// Set demo URL - Khan Oakland.
	dlg.m_strUrl = _T("http://s3.amazonaws.com/KA-youtube-converted/2tYdClfLSCE.mp4/2tYdClfLSCE.mp4");

	// Proccess selection
	if(dlg.DoModal() == IDOK)
	{
		CString filePath = dlg.m_strUrl;

		if(filePath.IsEmpty())
			return;

		int len = filePath.GetLength();

		// Ignore case.
		CString temp = filePath;
		temp.MakeLower();

		// rtsp - todo: 
		//	rtspt://rtsp-v3-spbtv.msk.spbtv.com/spbtv_v3_1/30_110.sdp
		//  see: https://forum.doom9.org/showthread.php?p=1809299
		//  Live555

		// Samples - some work:
		// https://cctvview.info/Cameras
		//	http://www.courierpress.com/libraryghost/cams/researchroom/
		//	
		// abs
		//  http://s3.amazonaws.com/KA-youtube-converted/frBJEYvyd-8.mp4/frBJEYvyd-8.mp4 
		// oakland
		//  http://s3.amazonaws.com/KA-youtube-converted/2tYdClfLSCE.mp4/2tYdClfLSCE.mp4 
		// cnn
		//  http://www.lehman.edu/faculty/hoffmann/itc/techteach/video/Video1.WMV
		// pls
		//  http://radiojackie.com:12614/listen.pls
		// m3u
		//	http://www.anetstation.com/MP3/ANETSTATIONDOTCOM.m3u
		// asx
		//	https://www.stream24.net/tune-in/r3080.asx

		// Validate prefix. 
		if(temp.Find(_T("http://"), 0)  == -1 &&
		   temp.Find(_T("https://"), 0) == -1 && 
		   temp.Find(_T("rtsp://"), 0)  == -1 &&
		   temp.Find(_T("rtspt://"), 0) == -1 )
		{
			AfxMessageBox(_T("Invalid URL - no http://, https://, rtsp:// or rtspt://."));
			return;
		}
		// .pls file?
		if(temp.Find(_T(".pls"), 0)  != -1)
		{
			// Proccess .pls file.
			AfxMessageBox(_T("Use Tools-Import for a .pls file."));
			return;
		}
		// .m3u file?
		if(temp.Find(_T(".m3u"), 0)  != -1)
		{
			// Proccess .m3u file.
			AfxMessageBox(_T("Use Tools-Import for a .m3u file."));
			return;
		}
		// .asx file?
		if(temp.Find(_T(".asx"), 0)  != -1)
		{
			// Proccess .asx file.
			AfxMessageBox(_T("Use Tools-Import for a .asx file."));
			return;
		}
		/* na - Check for 3 char extension.
		int ret = filePath.ReverseFind(_T('.'));
		if(len - ret != 4)
		{ }*/

		TRACE(_T("In CDsLavDoc OnOpenUrl calling StartVideoPlayback\n"));
		// Play.
		if(!pFrame->StartVideoPlayback(filePath))
		{
			TRACE(_T("In CDsLavDoc OnOpenUrl StartVideoPlayback failed\n"));
			return;
		}
	}
}

// Called from CDsLavListView::OnContextMenu().
void CDsLavDoc::ImportMediaItemPlaylist(const CNodeArray & nodesAr, CMediaItem & mediaItem)
{
	CImportPlaylistDlg dlg;
	// Prep dlg.
	dlg.m_bDeleteMediaItem = FALSE;
	dlg.m_folderStr = _T("Imported Media");

	// Run.
	if(dlg.DoModal() == IDOK)
	{
		BOOL bDelete = dlg.m_bDeleteMediaItem;
		CString folder = dlg.m_folderStr;
		CString url = mediaItem.m_mediaUrl;
		BOOL isPls = url.Find(_T("pls"), 0) != -1;
		CString errStr;

		// Import.
		int ret = 0, nRead = 0;
		if(isPls)
			ret = ReadPls(url, folder, nRead);
		else
			ret = ReadM3u(url, folder, nRead);
		if(!ret)
		{
			if(!nRead)
				AfxMessageBox(_T("No radio stations in Playlist."), MB_OK | MB_ICONINFORMATION);
			else
				AfxMessageBox(_T("Playlist import failed."));
			// Done.
			return;
		}
		// Delete as needed.
		if(bDelete)
		{
			if(!DeleteMediaItem(nodesAr, mediaItem))
				AfxMessageBox(_T("Playlist delete failed."));
		}
	}
}

// Launch Export Dlg.
void CDsLavDoc::OnToolsExport() 
{
	CExportDlg dlg;

	// Set initial directory.
	dlg.m_pathContentsFiles = m_pathContentsFiles;

	// Run dlg.
	int nRet = -1;
	nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;

	// Proccess selection:
	CString resultsMsg;
	//
	// Get path.
	CString path = dlg.m_strPath;
	// Exclude disc files.
	BOOL bExcludeDisc = dlg.m_bCheckExclude;
	// Write unicode.
	BOOL bWriteUnicode = dlg.m_bCheckUnicode;
	// Write aliases.
	BOOL bWriteAlias = dlg.m_bCheckAlias;

	// Save as DsLav dat.
	if(dlg.m_bCheckDat)
	{
		if(WriteTopicTree(path))
		{
			// Get count.
			int nTopics = 0, nMediaItems = 0;
			m_topicTree.CountTopicsMediaItems(nTopics, nMediaItems);
			TRACE(_T("CUpdateDlg::OnOK WriteTopicTree returned TRUE\n"));
			resultsMsg.Format(_T("%d Topics and %d Media items saved to %s."), 
								nTopics, nMediaItems, path);
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsExport WriteTopicTree returned FALSE\n"));
			resultsMsg.Format(_T("Export to %s failed."), path);
			return;
		}
	}
	// Save as Media Record Format txt.
	else if(dlg.m_bCheckMediaListFmt)
	{
		TRACE(_T("CDsLavDoc::OnToolsExport calling WriteMediaRecordFormat\n"));
		int nWritten = 0;
		resultsMsg = _T("");
		if(WriteMediaRecordFormat(path, bExcludeDisc, bWriteUnicode, bWriteAlias, nWritten))
		{
			TRACE(_T("CUpdateDlg::OnOK WriteMediaRecordFormat returned TRUE\n"));
			resultsMsg.Format(_T("%d Media records saved to %s."), nWritten, path);
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsExport WriteMediaRecordFormat returned FALSE\n"));
			resultsMsg.Format(_T("Export to %s failed."), path);
			return;
		}		
	}
	// Save as M3u file.
	else if(dlg.m_bCheckM3u)
	{
		TRACE(_T("CDsLavDoc::OnToolsExport calling WriteM3uFormat\n"));
		int nWritten = 0;
		resultsMsg = _T("");
		if(WriteM3uFormat(path, bWriteUnicode, nWritten))
		{
			TRACE(_T("CUpdateDlg::OnOK WriteM3uFormat returned TRUE\n"));
			resultsMsg.Format(_T("%d Media records saved to %s."), nWritten, path);
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsExport WriteM3uFormat returned FALSE\n"));
			resultsMsg.Format(_T("Export to %s failed."), path);
			return;
		}		
	}
	// Error
	else
		return;

	// Show message.
	if(!resultsMsg.IsEmpty())
	{
		AfxMessageBox(resultsMsg);
	}	
}

// Launch Import Dlg.
void CDsLavDoc::OnToolsImport() 
{
	CImportDlg dlg;
	dlg.m_pathContentsFiles = m_pathContentsFiles;

	// Run dlg.
	int nRet = -1;
	nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;

	// Proccess selection:
	CString resultsMsg;
	BOOL	bCheckDat		= dlg.m_bCheckDat;
	BOOL	bCheckM3u		= dlg.m_bCheckM3u;
	BOOL	bCheckMrf		= dlg.m_bCheckMrf;
	BOOL	bCheckPls		= dlg.m_bCheckPls;
	BOOL	bCheckTopic		= dlg.m_bCheckTopic;
	BOOL	bCheckOverwrite = dlg.m_bCheckOverwrite;
	CString pathStr			= dlg.m_editPathStr;
	CString topicStr		= dlg.m_editTopicStr;

	// Importing serialized data - .dat File.
	// TopicTree is always overwritten by serialized dat file.
	if(bCheckDat)
	{
		// This is the same as OnNewDocument(), except
		// we pass a path param.  
		ReadTopicTree(pathStr);
		// Save changes.
		m_bSaveTopicTree = TRUE;
		return;
	}

	// Importing DsLav Media Record Format text file.
	// TopicTree overwritten per bCheckOverwrite, or MediaItems place 
	// under new/existing Root topic topicStr per bCheckTopic.
	else if(bCheckMrf)
	{
		TRACE(_T("CDsLavDoc::OnToolsImport calling ReadMediaRecordFormat\n"));
		int nItemsRead = 0;
		resultsMsg = _T("");
		if(ReadMediaRecordFormat(pathStr, topicStr, nItemsRead))
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadMediaRecordFormat returned TRUE\n"));
			if(bCheckOverwrite)
				resultsMsg.Format(_T("Contents overwritten with %d Media Items."), 
										nItemsRead);
			else if(bCheckTopic)
				resultsMsg.Format(_T("%d Media Items placed under %s Topic."), 
											nItemsRead, topicStr);
			// Save changes.
			m_bSaveTopicTree = TRUE;
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadMediaRecordFormat returned FALSE\n"));
			resultsMsg.Format(_T("Import from %s failed."), pathStr);
		//	return;
		}
	}
	// Importing PLS file or url. -Url Test: http://www.1.fm/jazz.pls
	// MediaItems place under new/existing Root topic topicStr.
	else if(bCheckPls)
	{
		TRACE(_T("CDsLavDoc::OnToolsImport calling ReadPls\n"));
		int nItemsRead = 0;
		resultsMsg = _T("");
		if(ReadPls(pathStr, topicStr, nItemsRead))
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadPls returned TRUE\n"));
			resultsMsg.Format(_T("%d Media Items placed under %s Topic."), 
											nItemsRead, topicStr);
			// Save changes.
			m_bSaveTopicTree = TRUE;
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadPls returned FALSE\n"));
			resultsMsg.Format(_T("Import from %s failed."), pathStr);
		//	return;
		}
	}
	// Importing m3u file or url.
	// MediaItems place under new/existing Root topic topicStr.
	else if(bCheckM3u)
	{
		TRACE(_T("CDsLavDoc::OnToolsImport calling ReadM3u\n"));
		int nItemsRead = 0;
		resultsMsg = _T("");
		if(ReadM3u(pathStr, topicStr, nItemsRead))
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadM3u returned TRUE\n"));
			resultsMsg.Format(_T("%d Media Items placed under %s Topic."), 
											nItemsRead, topicStr);
			// Save changes.
			m_bSaveTopicTree = TRUE;
		}
		else
		{
			TRACE(_T("CDsLavDoc::OnToolsImport ReadPls returned FALSE\n"));
			resultsMsg.Format(_T("Import from %s failed."), pathStr);
		//	return;
		}		
	}
	// Problem
	else
		return;

	// Show message.
	if(!resultsMsg.IsEmpty())
	{
		AfxMessageBox(resultsMsg);
	}
}

// Lav setup dlg.
void CDsLavDoc::OnSetupLavFilters() 
{
	CLavSetupDlg dlg;
	// Init dlg.
	dlg.m_bCheckWMA2   = m_lavSetup.GetAudioWMA2();
	dlg.m_bCheckWMAALL = m_lavSetup.GetAudioWMALL();
	dlg.m_bCheckWMAPRO = m_lavSetup.GetAudioWMAPRO();
	dlg.m_bCheckStereo = m_lavSetup.GetAudioMixing();

	// Run dlg.
	int nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;	

	// Init m_lavSetup.
	m_lavSetup.SetAudioWMA2(dlg.m_bCheckWMA2);
	m_lavSetup.SetAudioWMALL(dlg.m_bCheckWMAALL);
	m_lavSetup.SetAudioWMAPRO(dlg.m_bCheckWMAPRO);
	m_lavSetup.SetAudioMixing(dlg.m_bCheckStereo);

	// Save changes - user settings.
	if(dlg.m_bChanged)
		m_bSaveTopicTree = TRUE;
}

// ListView font.
void CDsLavDoc::OnSetupListFont() 
{
	CFontDialog dlg(&m_listLf);
	// Run dlg.
	int nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;
	dlg.GetCurrentFont(&m_listLf);
	UpdateAllViews(NULL, LIST_FONT_UPDATE, NULL);
	// Save changes - user settings.
	m_bSaveTopicTree = TRUE;	
}
// TreeView font.
void CDsLavDoc::OnSetupContentsFont() 
{
	CFontDialog dlg(&m_treeLf);
	// Run dlg.
	int nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;
	dlg.GetCurrentFont(&m_treeLf);
	UpdateAllViews(NULL, TREE_FONT_UPDATE, NULL);
	// Save changes - user settings.
	m_bSaveTopicTree = TRUE;
	
}

// Set initial folder locations for video files and contents files.
// Paths are used with Tools-Export/Import dialogs and File-Open Media File
// dialog to set initial directory.
void CDsLavDoc::OnSetupFolderLocations() 
{
	CFoldersDlg dlg;
	dlg.m_pathContents = m_pathContentsFiles;
	dlg.m_pathVideos = m_pathVideoFolder;

	int nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;

	// Get dialog paths.
	m_pathContentsFiles = dlg.m_pathContents;
	m_pathVideoFolder = dlg.m_pathVideos;

	// Save changes - user settings.
	m_bSaveTopicTree = TRUE;
}

// DVD Navigation dlg
void CDsLavDoc::OnDvdNavigation() 
{
	// If DVD is not currently playing, bail.
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;
	if(!pFrame->m_player.IsRunningDvd())
		return;

	CDvdNavDlg dlg;
	dlg.m_pPlayer = &pFrame->m_player;
	// Run dlg.
	int nRet = dlg.DoModal();
	if(nRet != IDOK)
		return;	
}
// Update DVD Nav Menu. ID_DVD_NAVIGATION				8/22-3
//  Called by framework when DVD menu item drops down.
void CDsLavDoc::OnUpdateDvdNavigation(CCmdUI* pCmdUI) 
{
	// If DVD is currently playing.
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	if(pFrame->m_player.IsRunningDvd())
	{
		pCmdUI->Enable();
	}
	else
		pCmdUI->Enable(FALSE);
}

// 8/22-3
// User clicked DVD menu "Play DVD" item.
void CDsLavDoc::OnDvdPlayDvd() 
{
	// Ensure a DVD root path - set in OnUpdateDvdPlayDvd().
	if(m_playDvdRootPath == _T(""))
		return;

	// From OnOpenMediaFile()
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;

	// Play with DVD Explorer.
	if(!pFrame->StartDvdPlayback(m_playDvdRootPath))
	{
		TRACE(_T("In CDsLavDoc OnDvdPlayDvd StartDvdPlayback failed\n"));
	}	
}
// 8/22-3
// Update DVD menu "Play DVD" item.
//  Called by framework when DVD menu item drops down.
void CDsLavDoc::OnUpdateDvdPlayDvd(CCmdUI* pCmdUI) 
{
	// If DVD is currently playing, disable.
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	if(pFrame->m_player.IsRunningDvd())
		pCmdUI->Enable(FALSE);

	// Else if DVD available.
	else
	{
		// Zero current root path.
		m_playDvdRootPath = _T("");

		// If DVD in drive.
		if(DiskInDrive(TRUE) && m_playDvdRootPath != _T(""))
			pCmdUI->Enable(TRUE);
		else
			pCmdUI->Enable(FALSE);
	}
}

// 8/22-3
// User clicked DVD menu "Play CD" item.
void CDsLavDoc::OnDvdPlayCd() 
{
	// Ensure a CD path - set in OnUpdateDvdPlayDvd().
	if(m_playCdDrive == _T(""))
		return;

	// Get MainFrame ptr.
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;

	// CMainFrame plays all tracks.
	if(!pFrame->StartCdPlaybackMenu(m_playCdDrive))
	{
		TRACE(_T("ERROR CDsLavDoc::OnDvdPlayCd StartCdPlaybackMenu failed\n"));
	}
	return;

	// Build list of .cda files. good
	//
	// File Storage API.
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	CString mediaPath;
	CString strComp, strDir;
	CString strCdaFile;

	// Prep path.
	CString strPath = m_playCdDrive;
	if(strPath.Right(1) != _T ("\\"))
		strPath += _T ("\\");
	strPath += _T ("*.*");

	// If a Drive file or Directory.
	if((hFind = ::FindFirstFile(strPath, &fd)) != INVALID_HANDLE_VALUE) 
	{
		// Get directories.
		if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
		{
			TRACE(_T("WARNING CDsLavDoc::OnDvdPlayCd child directory in cdrom drive\n"));
		}
		// Else get the .cda file.
		else
		{
			strCdaFile = (LPCTSTR)&fd.cFileName;

			// Add to array
			int g = 0;
		}
		while(::FindNextFile(hFind, &fd)) 
		{
			// Get directories.
			if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			{
				TRACE(_T("WARNING CDsLavDoc::OnDvdPlayCd child directory in cdrom drive 2\n"));
			}
			// Else get files.
			else
			{
				strCdaFile = (LPCTSTR)&fd.cFileName;

				// Add to array
				int g = 0;
			}
		}
		::FindClose (hFind);
	}
}
// 8/22-3
// Update DVD menu "Play CD" item.
//  Called by framework when DVD menu item drops down.
void CDsLavDoc::OnUpdateDvdPlayCd(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(FALSE);	

	// If CD is currently playing, disable.
	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	if(pFrame->m_player.IsRunningCd())
		pCmdUI->Enable(FALSE);

	// Else if CD available.
	else
	{
		// Zero current root path.
		m_playCdDrive = _T("");

		// If CD in drive.
		if(DiskInDrive(FALSE) && m_playCdDrive != _T(""))
			pCmdUI->Enable(TRUE);
		else
			pCmdUI->Enable(FALSE);
	}
}

// 8/22-3 
// Check if a CDROM or DVD is in tray.
BOOL CDsLavDoc::DiskInDrive(BOOL isDVD)
{
	// From Prossie DriveTree
	int nPos = 0;
	int nDrivesAdded = 0;
	CString diskPath;
	CString drivePath = _T("?:\\");

	DWORD dwDriveList = ::GetLogicalDrives();

	while(dwDriveList)	// if we have a bit
	{
		if(dwDriveList & 1)
		{
			// Set a drive path with letter - "E:\"
			drivePath.SetAt(0, _T('A') + nPos);

			// Get drive type
			UINT nType = ::GetDriveType(drivePath);
			int g = 0;

			switch(nType) 
			{
				case DRIVE_UNKNOWN:		// 0
				case DRIVE_NO_ROOT_DIR:	// 1
				case DRIVE_REMOVABLE:	// 2, Floppy, USB
				case DRIVE_FIXED:		// 3, Hard disk
				case DRIVE_REMOTE:		// 4, Net drive
				case DRIVE_RAMDISK:		// 6, Ram disk
					break;
				case DRIVE_CDROM:		// 5, CD, DVD
				{
					if(GetDiskPath(drivePath, diskPath, isDVD))
					{
						// Set DVD path.
						if(isDVD)
						{
							m_playDvdRootPath = diskPath;
							return TRUE;
						}
						// Set CD path.
						else
						{
							m_playCdDrive = diskPath;
							return TRUE;
						}
					}
					break;
				}
				default:
					break;
			}
		}
		// move to next bit with right-shift assignment operator
		dwDriveList >>= 1; 
		nPos++;
	}
	return FALSE;
}
// 8/22-3
BOOL CDsLavDoc::GetDiskPath(const CString & drivePath, CString & diskPath, BOOL isDVD)
{
 	// From Prossie DriveTree
	HANDLE hFind;
	WIN32_FIND_DATA fd;

	// Build temp path for FindFirstFile().
	CString strTemp = drivePath;
	if(strTemp.Right(1) != _T ("\\"))
		strTemp += _T ("\\");
	strTemp += _T ("*.*");

	if((hFind = ::FindFirstFile(strTemp, &fd)) == INVALID_HANDLE_VALUE)
		return FALSE;
	do 
	{
		// If a directory in drivePath.
		if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
		{
			CString strDir = (LPCTSTR)&fd.cFileName;
			if((strDir != _T(".")) && (strDir != _T(".."))) 
			{
				// If at the DVD VIDEO_TS root directory.
				if(strDir == _T("VIDEO_TS") && isDVD)
				{
					// Build path to DVD root dir, e.g. "E:\VIDEO_TS\"
					//  Note CPlayer::PlayDvdLav uses files 
					//  (.IFO etc) within root dir to play.
					CString strDvdRootPath = drivePath + strDir + _T("\\");

					// Init param with new root path.
					diskPath = strDvdRootPath;

					// Clean search.
					::FindClose(hFind);

					// Done.
					return TRUE;
				}
			}
		}
		// Else if a CDROM file in the drive.
		else if(fd.dwFileAttributes & FILE_SHARE_READ && !isDVD)
		{
			CString strFile = (LPCTSTR)&fd.cFileName;

			// If cFileName is the first cd track.
			if(strFile == _T("Track01.cda"))
			{
				// Init param with drive - "E:\".
				diskPath = drivePath;

				// Clean search.
				::FindClose(hFind);

				// Done.
				return TRUE;
			}
		}
    }
	// Get next directory in drivePath.
	while(::FindNextFile(hFind, &fd));

    ::FindClose (hFind);

	return FALSE;
}

// Edit a MediaItem.
// Called from:
//	-CDsLavListView::OnContextMenu
void CDsLavDoc::EditMediaItem(CNodeArray nodesArSrc, CMediaItem mediaItem, BOOL isNewMedia) 
{
	// Note: nodesArSrc param is the TopicTree 
	// parent Topic of the mediaItem param.

	CEditMediaItemDlg dlg;
	dlg.m_pDoc = this;
	dlg.m_mediaItem = mediaItem;
	CString msg;

	// Set dlg Title can use for "new item dlg" maybe.
	if(isNewMedia)
		dlg.m_dlgTitle = MEDIA_NEW_STR;
	else
		dlg.m_dlgTitle = MEDIA_EDIT_STR;

	// Proccess selection
	int nRet = -1;
	nRet = dlg.DoModal();

	// Canceled.
	if(nRet != IDOK)
		return;

	// Get changed item.
	CMediaItem newMediaItem = dlg.m_mediaItem;

	// If a new media item.
	if(isNewMedia)
	{
		if(newMediaItem.IsValidItem() && 
		   m_topicTree.AddMediaItemToTopic(nodesArSrc, newMediaItem, TRUE) )
		{
			// Update current Topic location.
			UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
			msg = _T("New Media Item creation successfull.");
		}
		else
		{
			TRACE(_T("CDsLavDoc::EditMediaItem - new item - AddMediaItemToTopic failed.\n"));
			msg = _T("New Media Item failed!");
			AfxMessageBox(msg);
			return;
		}		
	}
	// Else editing media item.
	//
	// Favs changed.
	else if(dlg.m_bFavsChange)
	{
		// Call local member - does all updating.
		BOOL fav = newMediaItem.m_isFavorite;
		if(!SetMediaItemFavorites(nodesArSrc, mediaItem, fav))
		{
			TRACE(_T("CDsLavDoc::EditMediaItem - SetMediaItemFavorites failed.\n"));
			msg = _T("EditMediaItem failed!");
			AfxMessageBox(msg);
			return;
		}
	}
	// Title, Type, or url changed.
	else if(dlg.m_bTitleUrlTypeChange)
	{
		// Update.
		if(m_topicTree.UpdateMediaItem(nodesArSrc, mediaItem, newMediaItem, TRUE))
		{
			UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		}
		else
		{
			msg = _T("EditMediaItem failed!");
			AfxMessageBox(msg);
			return;
		}
	}

	// Ok - Save changes.
	m_bSaveTopicTree = TRUE;
	// Success msg.
	//AfxMessageBox(msg); no
	return;
}

// Add a new empty topic to TopicTree or edit existing.
void CDsLavDoc::EditTopic(const CNodeArray & nodesAr, MEDIA_TYPE type, BOOL isNewTopic)
{
	CNewTopicDlg dlg;

	// Get num nodes.
	int nNodes = nodesAr.GetSize();

	// Flag.
	BOOL isRoot = FALSE;

	// If a New Topic.
	if(isNewTopic)
	{
		// If nodes but no type - error.
		if(nNodes && type == NONE)
		{
			return;
		}
		// If no nodes, a new ROOT topic.
		else if(!nNodes)
		{
			dlg.m_type = NONE;	// User must set.
			isRoot = TRUE;
		}
		// Else a new Child topic
		else
			dlg.m_type = type;	// Dlg sets fixed.

		dlg.m_pDoc = this;

		// Set Dlg Title.
		dlg.m_dlgTitle = TOPIC_NEW_STR;		

		////////////////////////////////////
		// Proccess selection
		int nRet = -1;
		nRet = dlg.DoModal();

		// Canceled.
		if(nRet != IDOK)
			return;

		MEDIA_TYPE newType = dlg.m_type;
		CString newTopicName = dlg.m_topicName;

		// If adding a new root topic.
		if(isRoot)
		{
			// Set a CStringArray with the new Topic Name.
			CStringArray strAr;
			strAr.Add(newTopicName);

			// Insert the new root topic.
			if(!m_topicTree.InsertTopic(strAr, newType, TRUE))
			{
				TRACE(_T("CDsLavDoc::EditTopic InsertTopic strAr failed.\n"));
				return;
			}
			// Set current lists to new topic.
			UpdateCurrentTopic(FALSE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		}
		// Else adding a new child topic to exist nodesAr topic.
		else
		{
			TRACE(_T("CDsLavDoc::EditTopic adding new child topic.\n"));
			// Create a new topic. 
			CTopic newTopic;

			// Set type.
			newTopic.m_topicMediaType = newType; // or type param

			// Add a nodeItem with the new topic name. InsertTopic
			// will update the topic's nodesAr.
			CNodeItem node;
			node.m_topicName = newTopicName;
			newTopic.m_nodesAr.Add(node);

			// Insert new topic.
			if(!m_topicTree.InsertTopic(nodesAr, newTopic, TRUE))
			{
				TRACE(_T("CDsLavDoc::EditTopic InsertTopic failed.\n"));
				return;
			}
			else
			{
				TRACE(_T("CDsLavDoc::EditTopic InsertTopic succeded for child topic %s.\n"), newTopicName);
			}
			// Set current lists to new topic.
			UpdateCurrentTopic(FALSE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		}
		// Save changes.
		m_bSaveTopicTree = TRUE;
	}
	// Else editing existing topic.
	else
	{
		// Must have Nodes.
		if(!nNodes)
		{
			return;
		}
		// Get copy of Topic - needed for type.
		CTopic topic;
		if(!m_topicTree.GetTopic(nodesAr, topic))
		{
			return;
		}
		// Set CDoc ptr.
		dlg.m_pDoc = this;

		// Set Dlg Title.
		dlg.m_dlgTitle = TOPIC_EDIT_STR;		

		// Set Type
		dlg.m_type = topic.m_topicMediaType;

		// Set Topic Name.
		dlg.m_topicName = topic.GetTopicName();

		////////////////////////////////////
		// Proccess selection
		int nRet = -1;
		nRet = dlg.DoModal();

		// Canceled.
		if(nRet != IDOK)
			return;

		// If Topic name changed.
		BOOL bNameChanged = FALSE;
		if(dlg.m_topicName != topic.GetTopicName())
			bNameChanged = TRUE;
		// If Topic type changed.
		BOOL bTypeChanged = FALSE;
		if(dlg.m_type != topic.m_topicMediaType)
			bTypeChanged = TRUE;

		// Update Topic.
		if(bNameChanged || bTypeChanged)
		{
			if(!m_topicTree.UpdateTopic(nodesAr, dlg.m_topicName, dlg.m_type, TRUE))
			{
				TRACE(_T("CDsLavDoc::EditTopic UpdateTopic failed.\n"));
				return;
			}
			// Set current lists to new topic.
			UpdateCurrentTopic(FALSE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		}
		// Save changes.
		m_bSaveTopicTree = TRUE;
	}
}
// Delete a Topic at nodesAr.
void CDsLavDoc::DeleteTopic(const CNodeArray & nodesAr)
{
	if(!m_topicTree.DeleteTopic(nodesAr, TRUE))
	{
		TRACE(_T("CDsLavDoc::DeleteTopic DeleteTopic failed.\n"));
		return;
	}
	// Set current lists to new topic.
	// The topic set at m_resultTopic will be the parent topic if a 
	// child topic deleted, or the previous Root topic if a Root
	// deleted.
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
}

// Add media files to topic. 
void CDsLavDoc::AddMediaFilesToTopic(const CNodeArray & nodesAr, MEDIA_TYPE type) 
{
	// search m_ofn.lpstrFile for code
	TRACE(_T("In CDsLavDoc AddMediaFilesToTopic\n"));

	CMainFrame * pFrame = GetMainFrame();
	if(!pFrame)
		return;

	// Flag for extensions to show.
	BOOL isVid = FALSE;
	if(type == VIDF)
		isVid = TRUE;

	// Video
	TCHAR videoExt[] =	_T("Video Files (*.avi*.mp4*.flv*.wmv*.mov*.webm*.ogg)|")
						_T("*.avi;*.mp4;*.flv;*.wmv;*.mov;*.webm;*.ogg|")
						_T("All Files (*.*)|")
						_T("*.*||");     
	// Audio
	TCHAR audioExt[] =	_T("Audio Files (*.aac*.flac*.m4a*.mp3*.ogg*.wav*.wma)|")
						_T("*.aac;*.flac;*.m4a;*.mp3;*.oog;*.wav;*.wma|")
						_T("All Files (*.*)|")
						 _T("*.*||"); 
	
	CFileDialog dlg(TRUE, NULL, NULL, //OFN_NOCHANGEDIR |
									  OFN_ALLOWMULTISELECT	|
									  OFN_FILEMUSTEXIST		| 
									  OFN_PATHMUSTEXIST, 
									  isVid ? videoExt : audioExt);

	// Set initial directory if available. 6/22
	if(m_pathVideoFolder != _T(""))
		dlg.m_ofn.lpstrInitialDir = m_pathVideoFolder;

	// Set buff for file names
	const DWORD nChars = 10248;
	dlg.m_ofn.nMaxFile = nChars - 1;
	TCHAR * buf = new TCHAR[nChars];
	memset(buf, 0, sizeof(TCHAR) * nChars);
	dlg.m_ofn.lpstrFile = buf;

	// Set title.
	if(isVid)
		dlg.m_ofn.lpstrTitle = _T("Add Video Files");
	else
		dlg.m_ofn.lpstrTitle = _T("Add Audio Files");

	// Array for temp storage.
	CMediaArray mediaAr;

	// Proccess selection
	if(dlg.DoModal() == IDOK)
	{
		// Get nodes a string array.
		CStringArray strAr;
		nodesAr.GetTopicNameStrings(strAr);

		// MediaItem.
		CMediaItem item;	

		// Set constant MediaItem members.
		item.m_mediaType = type;
		item.m_nodesList.Copy(strAr);

		// Get paths. 
		CString path;
		CString mediaName;
		POSITION pos = dlg.GetStartPosition();
		while(pos != NULL)
		{
			// Get user path,
			path = dlg.GetNextPathName(pos);

			// Get name from path.
			mediaName = path;
			int len = mediaName.ReverseFind('\\');
			if(len)
				mediaName.Delete(0, len + 1);

			// Set other MediaItem members.
			item.m_mediaName = mediaName;
			item.m_mediaUrl = path;

			// Save to ar,
			mediaAr.Add(item);
		}
	}
	// Clean up.
	delete[]buf; 

	// If MediaItems gotten.
	int nItems = mediaAr.GetSize();
	if(!nItems)
		return;

	// Add items to topic at nodesAr;
	if(!m_topicTree.AddMediaItemsToTopic(nodesAr, mediaAr, TRUE))
	{
		TRACE(_T("CDsLavDoc::AddMediaFilesToTopic AddMediaItemsToTopic failed.\n"));
		return;
	}
	// Set current lists to new topic.
	UpdateCurrentTopic(FALSE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// Media handlers
// not used - delete after serialize code finished
BOOL CDsLavDoc::LoadTopicTree()
{
	return FALSE;
	// Load m_aliasAr aliases.
	m_aliasAr.InitializeTopicAlias();

	// Prepare.
	m_topicTree.m_topicTreeAr.RemoveAll();
	m_topicTree.m_topicTreeAr.SetSize(0, 32);

	// Vars.
	CMediaItem item;

	// Note: 
	// The first Topic in the TreeCtrl is always "Favorites" - STR_FAVS.
	// Favorites is not a topic in m_topicTree - it is a list created
	// below in m_favsMediaAr and displayed in CTreeView.

	////////////////////////////////////////////////////////
	// Load from file - todo
	//
	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KCRW 89.9 FM, Santa Monica"),
					  _T("http://kcrw.streamguys1.com/kcrw_192k_mp3_on_air_internet_radio"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	//File1=https://17793.live.streamtheworld.com:443/KPCC_FM_SC - dead?
	//File2=https://17963.live.streamtheworld.com:443/KPCC_FM_SC
	//File3=https://18683.live.streamtheworld.com:443/KPCC_FM_SC
	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPCC 89.3 FM, Pasadena"),
					  _T("https://17793.live.streamtheworld.com:443/KPCC_FM_SC"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPCC-2 89.3 FM, Pasadena"),
					  _T("https://17963.live.streamtheworld.com:443/KPCC_FM_SC"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPCC-3 89.3 FM, Pasadena"),
					  _T("https://18683.live.streamtheworld.com:443/KPCC_FM_SC"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KJAZ 88.1 FM, Long Beach"),
					  _T("http://1.ice1.firststreaming.com/kkjz_fm.mp3"),
					  TRUE,
					  _T("RS,RSP,RSNPR,RSJ"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KCSN 88.5 FM, Northridge"),
					  _T("http://130.166.82.184:8000"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(VIDU, 
					  _T("KA: Oakland Unity H.S."),
					  _T("http://s3.amazonaws.com/KA-youtube-converted/2tYdClfLSCE.mp4/2tYdClfLSCE.mp4"),
					  TRUE,
					  _T("VS, Khan Academy"),
					  &m_aliasAr);	
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Aural Moon: 56k MP3"),
					  _T("http://live.str3am.com:2010/"),
					  FALSE,
					  _T("RS,RSPR"),
					  &m_aliasAr);	
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Bay Smooth Jazz Radio"), // http://www.1.fm/jazz.pls
					  _T("http://sc3b-sjc.1.fm:9010"),
					  FALSE,
					  _T("RS,RSJ"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("WUIS, Chicago"),
					  _T("http://war.str3am.com:7780/"),
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("BBC World Service"),
					  _T("http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-eieuk"),
					  TRUE,
					  _T("RS,RSP,RSBBC"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	// see http://www.kpft.org:8000/status.xsl
	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPFK Pacifica, Los Angeles"),
					  _T("http://www.kpft.org:8000/kpfk_128"),
					  TRUE,
					  _T("RS,RSP,RSNPR,RSPCA"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPFT Pacifica, Houston"),
					  _T("http://www.kpft.org:8000/HD3_128"),
					  FALSE,
					  _T("RS,RSP,RSNPR,RSPCA"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("KPFA Pacifica, Berkeley"),
					  _T("http://www.kpft.org:8000/kpfa"),
					  FALSE,
					  _T("RS,RSP,RSNPR,RSPCA"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("WBAI Pacifica, New York"),
					  _T("http://www.kpft.org:8000/wbai_128"),
					  TRUE,
					  _T("RS,RSP,RSNPR,RSPCA"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("WPFW Pacifica, Washington DC"),
					  _T("http://www.kpft.org:8000/wpfw_128"),
					  FALSE,
					  _T("RS,RSP,RSNPR,RSPCA"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	// from: https://www.hiresaudio.online/cd-quality-internet-radio/
	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Radio Paradise, Main Mix"),
					  _T("http://stream.radioparadise.com/flac"),
					  FALSE,
					  _T("RS,Paradise"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Radio Paradise, Mellow Mix"),
					  _T("http://stream.radioparadise.com/mellow-flac"),
					  FALSE,
					  _T("RS,Paradise"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Radio Paradise, Rock Mix"),
					  _T("http://stream.radioparadise.com/rock-flac"),
					  FALSE,
					  _T("RS,Paradise"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("Radio Paradise, World/Eclectic Mix"),
					  _T("http://stream.radioparadise.com/eclectic-flac"),
					  FALSE,
					  _T("RS,Paradise"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	// Vermont
	// https://www.vpr.org/online-streams
	// https://www.vpr.net/apps/stream-playlists/vpraac.pls
	// https://www.vpr.net/apps/stream-playlists/vprclassicalaac.pls
	// https://www.vpr.net/apps/stream-playlists/vprmixmid.pls
	item.Zero();
	item.SetMediaItem(AUDU, 
					  _T("VPR Vermont Public Radio"),
					//  _T("https://vpr.streamguys1.com:443/vpr64.aac"),	// no go
					  _T("https://vpr.streamguys1.com/vpr96.mp3"),			// no go
					  TRUE,
					  _T("RS,RSP,RSNPR"),
					  &m_aliasAr);
	m_topicTree.AddMediaItemToTree(item);

	/////////////////////
	// Add Empty topics
	CStringArray nodesList;
	MEDIA_TYPE type;

	nodesList.RemoveAll();
	nodesList.Add(_T("Video Files")); //VF
	type = VIDF;
	m_topicTree.InsertTopic(nodesList, type);

	nodesList.RemoveAll();
	nodesList.Add(_T("Audio Files")); //AF
	type = AUDF;
	m_topicTree.InsertTopic(nodesList, type);

	// Get count.
	int nRootItems = m_topicTree.m_topicTreeAr.GetSize();

	// If no root topics.
	if(!nRootItems)
		return FALSE;

	///////////////////////////////////////////////////////////
	// Update Current Topic/Lists/Favorites + Nodes, and Views.
	UpdateCurrentTopic(TRUE, TRUE, NULL, FALSE, FALSE);

	////////////////////////
	// Dbg print to output.
	#ifdef _DEBUG
	//	m_topicTree.PrintContentsMediaItems();
	#endif

	///////////////////////////////////////////////////////////
	// OK
	TRACE(_T("CDsLavDoc LoadTopicTree - %d Root Topics added to Tree.\n"), nRootItems);
	return TRUE;
}

// Called from:
// 
// Rebuild favorites per TopicTree media item.
// Caller must call  UpdateMediaListToFavorites() 
// to set current list to favorites. 
int CDsLavDoc::RebuildFavoritesList()
{
	int nFavsGotten = 0;

	// Rebuild m_favsMediaAr.
	m_favsMediaAr.RemoveAll();
	m_favsMediaAr.SetSize(10);
	m_favsNodesAr.RemoveAll();
	m_favsNodesAr.SetSize(10);
	m_topicTree.GetFavorites(m_favsMediaAr, m_favsNodesAr, nFavsGotten);
	return nFavsGotten;
}

// Called from:
//  CDsLavListView::::OnLButtonDblClk()
void CDsLavDoc::ListViewSelectedTopic(CNodeArray nodesAr)
{
	CTopic topic;
	// Get the CTopic. Both TreeView and ListView need updating.
	if(m_topicTree.GetTopic(nodesAr, topic))
		UpdateCurrentTopic(FALSE, FALSE, &topic, TRUE, TRUE);
	else
		TRACE(_T("ERROR CDsLavDoc::ListViewSelectedTopic - GetTopic failed\n"));
}

// Called from:
//  CDsLavListView::::OnColumnclick()
void CDsLavDoc::ListViewSortMediaItems(CNodeArray nodesAr, BOOL ascend)
{
	// Sort.
	if(m_topicTree.SortMediaArray(nodesAr, ascend, TRUE))
	{
		// Must update the list view.
		UpdateCurrentTopic(FALSE, FALSE, &m_topicTree.m_resultTopic, FALSE, TRUE);

		// The contents have changed.
		m_bSaveTopicTree = TRUE;
	}
	else
	{
		TRACE(_T("ERROR CDsLavDoc::ListViewSortMediaItems - GetTopic failed\n"));
	}
}


// Called from:
//  CDsLavTreeView::OnSelchanged()
void CDsLavDoc::TreeViewSelectedTopic(CNodeArray nodesAr)
{
	// Get the CTopic. Note: TreeView already displaying topic.
	CTopic topic;
	if(m_topicTree.GetTopic(nodesAr, topic))
		UpdateCurrentTopic(FALSE, FALSE, &topic, FALSE, TRUE);
	else
		TRACE(_T("ERROR CDsLavDoc::TreeViewSelectedTopic - GetTopic failed\n"));
}

// Called from:
//  CDsLavTreeView::OnSelchanged()
void CDsLavDoc::TreeViewSelectedFavorites()
{
	// Note: TreeView already displating favs.
	UpdateCurrentTopic(FALSE, TRUE, NULL, FALSE, TRUE);
}

//////////////////////////////////////////////////////////////
// Drive handlers - CD, USB
//
// Called from:
//	CMainFrame::OnDeviceChange()
void CDsLavDoc::DriveDeviceChanged(CString drive, BOOL inserted)
{
	if(inserted)
		UpdateAllViews(NULL, TREE_DISC_INSERT, (CObject*)(LPCTSTR)drive);
	else
	{
		// If DVD or CD is currently playing, stop.
		CMainFrame * pFrame = GetMainFrame();
		if(!pFrame)
			return;
		// todo - some focus bug?
		if(pFrame->m_player.IsRunningDvd() ||
		   pFrame->m_player.IsRunningCd()  )
		{
			pFrame->StopVideoPlayback();
		}
		// Remove drive from TreeView.
		UpdateAllViews(NULL, TREE_DISC_REMOVE, (CObject*)(LPCTSTR)drive);
	}
}

// Called from:
//  CDsLavTreeView::OnSelchanged() for a CD/DVD drive selection.
//  Example drivePath param: "E:" 
void CDsLavDoc::TreeViewSelectedDrive(CString drivePath)
{
	// Fudge Drive file/dir data into CTopics and CMediaItems
	// for use by TreeView and ListView.
	//
	CMediaArray mediaAr;
	CTopicArray topicAr;	

	// File Storage API.
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	CString mediaPath;
	CString strComp, strDir;

	// Prep path.
	CString strPath = drivePath;
	if(strPath.Right(1) != _T ("\\"))
		strPath += _T ("\\");
	strPath += _T ("*.*");

	// If a Drive file or Directory.
	if((hFind = ::FindFirstFile(strPath, &fd)) != INVALID_HANDLE_VALUE) 
	{
		// Get directories.
		if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
		{
			strComp = (LPCTSTR)&fd.cFileName;
			// If not a current or parent dir.
			//  Note every directory contains a . and a .. entry;
			//   . means the directory itself - the current directory.
			//  .. means the directory's parent directory.
			if((strComp != _T (".")) && (strComp != _T (".."))) 
			{
				strDir = drivePath;
				if(strDir.Right(1) != _T ("\\"))
					strDir += _T ("\\");
				strDir += (LPCTSTR)&fd.cFileName;
				AddDriveDirectory(strDir, topicAr);
			}
		}
		// Else get files.
		else
		{
			AddDriveMediaItem(drivePath, (LPCTSTR)&fd.cFileName, mediaAr);
		}
		while(::FindNextFile(hFind, &fd)) 
		{
			// Get directories.
			if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			{
				strComp = (LPCTSTR)&fd.cFileName;
				// If not a current or parent dir.
				if((strComp != _T (".")) && (strComp != _T (".."))) 
				{
					strDir = drivePath;
					if(strDir.Right(1) != _T ("\\"))
						strDir += _T ("\\");
					strDir += (LPCTSTR)&fd.cFileName;
					AddDriveDirectory(strDir, topicAr);
				}
			}
			// Else get files.
			else
			{
				AddDriveMediaItem(drivePath, (LPCTSTR)&fd.cFileName, mediaAr);
			}
		}
		::FindClose (hFind);
	}
	// Else no files or directories.
	// Set current topic anyways to show empty directory in ListView.
	else
	{
		int g = 0;
	}
	//dbg check results.
	int nMediaItems = mediaAr.GetSize();
	int nTopics = topicAr.GetSize();

	// Prepare a CTopic.
	CTopic topic;
	topic.m_nodesAr.SetNodesFromFilePath(drivePath);
	topic.m_topicMediaAr = mediaAr;
	topic.m_childTopicAr.Copy(topicAr);
	topic.m_topicMediaType = VIDF; // AUDF
	//dbg	 
	//CString s = topic.m_nodesAr.GetFilePathFromNodes();

	// Set CDsLavDoc Current Topic,
	m_bCurTopicFavs = FALSE;
	m_bCurTopicDrive = TRUE;
	m_curTopic = topic;

	// Update the ListView to display Disc data.
	UpdateAllViews(NULL, LIST_UPDATE, NULL);
}

// Add Drive Media Item.
BOOL CDsLavDoc::AddDriveMediaItem(CString drivePath, CString mediaName, CMediaArray & mediaAr)
{
	// Ensure a valid media file extension.
	if(!ExtensionValid(mediaName))
		return FALSE;

	// Get full path for mediaUrl.
	CString path = drivePath;
	path += _T('\\');
	path += mediaName;

	// Build a MediaItem.
	CMediaItem item;
	item.m_isFavorite = FALSE;
	item.m_mediaName  = mediaName;
	if(mediaName.Find(_T(".cda"), 0) != -1)	// CD Audio
		item.m_mediaType  = AUDF;
	else
		item.m_mediaType  = VIDF;
	item.m_mediaUrl   = path;
	item.SetNodesFromFilePath(drivePath);
	//dbg
	//CString s = item.GetMediaItemTopicCsNodes(); 

	// Add to Array param.
	mediaAr.Add(item);

	// OK
	return TRUE;
}
// Add Drive directory (folder).
BOOL CDsLavDoc::AddDriveDirectory(CString drivePath, CTopicArray & topicAr)
{
	CTopic topic;
	topic.m_nodesAr.SetNodesFromFilePath(drivePath);
	topic.m_topicMediaType = VIDF; // AUDF
	//dbg
	CString s = topic.m_nodesAr.GetFilePathFromNodes();

	// Add to Array param.
	topicAr.Add(topic);

	// OK
	return TRUE;
}

// Check if a valid extension for media file. See BuildExtensionsArray().
BOOL CDsLavDoc::ExtensionValid(CString filePath)
{
	// Always allow - not all provided always.
	// Any "VIDEO_TS" can be used to play with
	// DvdNavigator. 
	// See CDsLavListView::OnLButtonDblClk().
	if(filePath == _T("VIDEO_TS.VOB") || 
	   filePath == _T("VIDEO_TS.BUP") || 
	   filePath == _T("VIDEO_TS.IFO") )
		return TRUE;

	// Get extension.
	int len = filePath.GetLength();
	int pos = filePath.ReverseFind(_T('.'));
	CString extension = filePath.Right(len - pos - 1);

	// Check list.
	int sz = m_extensionsAr.GetSize();
	if(!sz)
	{
		BuildExtensionsArray();
		sz = m_extensionsAr.GetSize();
	}
	for(int i = 0; i < sz; i++)
	{
		if(extension.CompareNoCase(m_extensionsAr[i]) == 0)
			return TRUE;
	}
	return FALSE;
}
// Extensions supported by LAV and FFMPEG.
// Future work for DVD .ifo handling.
void CDsLavDoc::BuildExtensionsArray()
{
	m_extensionsAr.RemoveAll();
	m_extensionsAr.Add(_T("3g2"));
	m_extensionsAr.Add(_T("3gp"));
	m_extensionsAr.Add(_T("3gp2"));
	m_extensionsAr.Add(_T("asf"));
	m_extensionsAr.Add(_T("avi"));
	m_extensionsAr.Add(_T("exr"));
	m_extensionsAr.Add(_T("flv"));
	m_extensionsAr.Add(_T("gxf"));
	m_extensionsAr.Add(_T("ifv"));
//	m_extensionsAr.Add(_T("ifo"));  // DVD - handled above
	m_extensionsAr.Add(_T("kux"));
	m_extensionsAr.Add(_T("m2t"));
	m_extensionsAr.Add(_T("m2ts"));
	m_extensionsAr.Add(_T("m4v"));
	m_extensionsAr.Add(_T("mkv"));
	m_extensionsAr.Add(_T("mod"));
	m_extensionsAr.Add(_T("mov"));
	m_extensionsAr.Add(_T("mp2"));	// Need a test file, FFMPEG does run
	m_extensionsAr.Add(_T("mp4"));
	m_extensionsAr.Add(_T("mpg"));
	m_extensionsAr.Add(_T("mxf"));
	m_extensionsAr.Add(_T("png"));
	m_extensionsAr.Add(_T("tod"));
	m_extensionsAr.Add(_T("vob"));
	m_extensionsAr.Add(_T("webm"));
	m_extensionsAr.Add(_T("wmv"));
	m_extensionsAr.Add(_T("xmv"));
	m_extensionsAr.Add(_T("cda"));	// CD Audio
	m_extensionsAr.Add(_T("bdmv"));	// Blu-Ray - todo
	m_extensionsAr.Add(_T("mpls"));	// Blu-Ray - todo
}

/////////////////////////////////////////////////////////////
// TopicTree handlers
//
// The TopicTree has been set/modified requiring one or more: 
//	- Rebuild Favs list
//	- Set current list to Favs
//	- Set current list to Topic with pSetToTopic param
//	- Rebuild the TreeView and set its sel
//	- Rebuild the ListView MediaItem list
void CDsLavDoc::UpdateCurrentTopic(BOOL rebuildFavs, BOOL setListFavs,
								   CTopic * pSetToTopic, BOOL rebuildTreeView, 
								   BOOL rebuildListView)
{
	//test drive
	// Set flags.
	m_bCurTopicFavs = FALSE;
	m_bCurTopicDrive = FALSE; // only set in TreeViewSelectedDrive()

	// Rebuild the m_favsMediaAr ar with TopicTree
	// items flagged favorite.
	if(rebuildFavs)
		RebuildFavoritesList();

	// Set the curListTopic and curListMedia to Favs.
	if(setListFavs)
	{
		m_curTopic.Zero();
		m_curTopic.SetTopicName(STR_FAVS);
		m_curTopic.m_topicMediaAr = m_favsMediaAr;
		m_bCurTopicFavs = TRUE;
	}
	// Or - set the curListTopic with pSetToTopic param.
	else if(pSetToTopic)
	{
		m_curTopic = *pSetToTopic;
	}
	// Rebuild CTreeView setting new indices and selection m_curList.
	if(rebuildTreeView)
		UpdateAllViews(NULL, TREE_UPDATE, NULL);

	// Rebuild CListView setting media item list per m_curList.
	if(rebuildListView)
		UpdateAllViews(NULL, LIST_UPDATE, NULL);
}

// Set a MediaItem's favorite flag.
// Called from: 
//	CDsLavListView::OnContextMenu().
//	CDsLavTreeView::ListViewDropMediaItem
BOOL CDsLavDoc::SetMediaItemFavorites(const CNodeArray & nodesAr, const CMediaItem & mediaItem, 
									  BOOL isFavorite, BOOL dropOnFavs)
{
	// Set the MediaItem.
	if(m_topicTree.SetMediaItemFavorites(nodesAr, mediaItem, isFavorite, TRUE))
	{
		// If dropOnFavs, a Media item was dropped from the ListView 
		// onto the TreeView Favorites folder. Show the Favs.

		// Set current media list to revised topic media items.
		//
		// If Favsorites.
		if(dropOnFavs || m_curTopic.GetTopicName() == STR_FAVS)
			UpdateCurrentTopic(TRUE, TRUE, NULL, TRUE, TRUE);

		// Else Topic - user picked context menu to set favs on/off.
		else
		{
			UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		}
		// Save changes.
		m_bSaveTopicTree = TRUE;
		return TRUE;
	}
	return FALSE;
}

// Delete a MediaItem.
// Called from: 
//	-CDsLavListView::OnContextMenu()
BOOL CDsLavDoc::DeleteMediaItem(const CNodeArray & nodesAr, CMediaItem & mediaItem)
{
	// Delete the MediaItem.
	if(m_topicTree.DeleteMediaItem(nodesAr, mediaItem, TRUE))
	{
		// Display parent topic in TreeView and ListView.
		UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		// Save changes.
		m_bSaveTopicTree = TRUE;
		return TRUE;
	}
	return FALSE;
}

// Delete multiple MediaItems.
// Called from:
//	-CDsLavListView::OnContextMenuMulti()
BOOL CDsLavDoc::DeleteMediaItemMulti(const CNodeArray & nodesAr, CMediaArray & mediaAr)
{
	// Copy array.
	CMediaArray array;
	array.Copy(mediaAr);

	// Delete the MediaItem.
	if(m_topicTree.DeleteMediaItemMulti(nodesAr, array, TRUE))
	{
		// Display parent topic in TreeView and ListView.
		UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
		// Save changes.
		m_bSaveTopicTree = TRUE;
		return TRUE;
	}
	return FALSE;
}
// Move a media item to a new location.
// Called from: CDsLavTreeView::ListViewDropMediaItem
BOOL CDsLavDoc::MoveMediaItem(const CMediaItem & mediaItem, 
							  const CNodeArray & nodesArSrc, const CNodeArray & nodesArDest)
{
	// Move the media item;
	if(!m_topicTree.MoveMediaItem(mediaItem, nodesArSrc, nodesArDest))
	{
		TRACE(_T("CDsLavDoc::MoveMediaItem - topicTree.MoveMediaItem failed.\n"));
		return FALSE;
	}
	// Get a copy of the topic item moved to.
	// Note func could implement "setResultsTopic" param instead.
	CTopic destTopic;
	if(!m_topicTree.GetTopic(nodesArDest, destTopic))
	{
		TRACE(_T("CDsLavDoc::MoveMediaItem GetTopic failed.\n"));
		return FALSE;;
	}
	// Set current media list to revised Topic and media items.
	UpdateCurrentTopic(TRUE, FALSE, &destTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

// Move multiple media items to a new location.
// Called from: CDsLavTreeView::ListViewDropMediaItem
BOOL CDsLavDoc::MoveMediaItemMulti(const CMediaArray & mediaAr, const CNodeArray & nodesArSrc, 
								   const CNodeArray & nodesArDest)
{
	// Move the media item;
	if(!m_topicTree.MoveMediaItemMulti(mediaAr, nodesArSrc, nodesArDest, TRUE))
	{
		TRACE(_T("CDsLavDoc::MoveMediaItemMulti - topicTree.MoveMediaItemMulti failed.\n"));
		return FALSE;
	}

	// Set current media list to revised Topic and media items.
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

// Move a topic among sibling topics within a single array.
// Called from CDsLavTreeView::OnLButtonDown().
BOOL CDsLavDoc::MoveTopicSibling(const CNodeArray & nodesArSrc, const CNodeArray & nodesArDest,
								 BOOL setAfter)
{
	if(!m_topicTree.MoveTopicSibling(nodesArSrc, nodesArDest, setAfter, TRUE))
	{
		TRACE(_T("CDsLavDoc::MoveTopicSibling MoveTopicSibling failed.\n"));
		return FALSE;;
	}
	// Set current media list to new Topic location and media items.
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

// 9/22-1 rework for tree drag and drop
// Move src topic to dest location.
//  Called from: CDsLavTreeView::MoveTreeItems().
BOOL CDsLavDoc::MoveTopic(const CNodeArray & nodesArSrc, const CNodeArray & nodesArDest,
						  MOVE_TYPE moveType)
{
	// Move the topic at nodesSrc to nodesDest.
	//  Note setResultsTopic param set to save copy of inserted 
	//  topic in m_topicTree's m_resultTopic member. 
	if(!m_topicTree.MoveTopic(nodesArSrc, nodesArDest, moveType, TRUE))
	{
		TRACE(_T("CDsLavDoc::MoveTopic topicTree.MoveTopic failed.\n"));
		return FALSE;;
	}
	// Set current media list to revised topic and media items.
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// File I/0

// Write media items to txt file per MediaRecordFormat.txt.
// Called from: OnToolsExport().
// Options:
//	excludeDisc  - exclude disc files
//	writeUnicode - write file as unicode 
//	writeAlias	 - write topics as aliases
// Uses CStdioFileEx.
BOOL CDsLavDoc::WriteMediaRecordFormat(CString filePath, BOOL excludeDisc, BOOL writeUnicode,
									   BOOL writeAlias, int & nWritten)
{
	// CFile flags.
	UINT nWriteFlags = CFile::modeCreate | CFile::modeWrite | CFile::typeText;

	// If unicode.
	if(writeUnicode)
		nWriteFlags |= CStdioFileEx::modeWriteUnicode;

	// Open.
	CStdioFileEx file;
	if(!file.Open(filePath, nWriteFlags) ) 
	{
		TRACE(_T("CDsLavDoc WriteMediaRecordFormat open file failed for %s.\n"), filePath);
		return FALSE;
	}

	// Write the tree's media items to file.
	if(!m_topicTree.WriteMediaRecordFormatEx(file, m_aliasAr, writeAlias, 
											  excludeDisc, nWritten))
	{
		TRACE(_T("CDsLavDoc WriteMediaRecordFormat WriteMediaRecordFormatEx failed for %s.\n"), 
					filePath);
		return FALSE;
	}
	// Done.
	file.Close();
	return TRUE;
}

// Write media items to M3u format file.
// Called from: OnToolsExport().
// Options:
//	writeUnicode - write file as unicode 
// Uses CStdioFileEx.
BOOL CDsLavDoc::WriteM3uFormat(CString filePath, BOOL writeUnicode, int & nWritten)
{
	// CFile flags.
	UINT nWriteFlags = CFile::modeCreate | CFile::modeWrite | CFile::typeText;

	// If unicode.
	if(writeUnicode)
		nWriteFlags |= CStdioFileEx::modeWriteUnicode;

	// Open.
	CStdioFileEx file;
	if(!file.Open(filePath, nWriteFlags) ) 
	{
		TRACE(_T("CDsLavDoc WriteM3uFormat open file failed for %s.\n"), filePath);
		return FALSE;
	}

	// Write the tree's media items to M3u file.
	if(!m_topicTree.WriteM3uStreamsEx(file, nWritten))
	{
		TRACE(_T("CDsLavDoc WriteM3uFormat WriteM3uStreamsEx failed for %s.\n"), 
					filePath);
		return FALSE;
	}
	// Done.
	file.Close();
	return TRUE;
}

// Read media items from txt file per MediaRecordFormat.txt.
// Called from: OnToolsImport().
// Uses CStdioFileEx.
BOOL CDsLavDoc::ReadMediaRecordFormat(CString filePath, CString topic, int & nItemsRead)
{
	// Get current locale code page: 1252 = Windows 3.1 US (ANSI)
	UINT  nCurrentCodePage = CStdioFileEx::GetCurrentLocaleCodePage();

	// Set count.
	nItemsRead = 0;

	// Open txt disc file. 
	CStdioFileEx file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CDsLavDoc::ReadMediaTopicTree - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}
	/*dbg
	BOOL isUnicode = file.IsFileUnicodeText();
	int  nCharCount = file.GetCharCount();*/

	CMediaArray mediaAr;
	CMediaItem item;
	CString fileStr, codeStr, valueStr;
	int iCode = -1;
	BOOL haveCode = FALSE;
	BOOL haveValue = FALSE;
	int nChars = 0;

	// Read the disk file.
	while(file.ReadString(fileStr))
	{
		nChars = fileStr.GetLength();

		// Skip comments.
		if(fileStr.Find(_T("//")) == 0) // "/" is first char 
			continue;
		// Skip empty lines.
		if(nChars == 0)
			continue;

		// Get the code - 0, 6, 7, 10, 11
		if(!haveCode)
		{
			iCode = -1;

			// Code zero is the start of a new media record.
			if(fileStr == _T("0"))
			{
				// If we have an item built.
				if(item.IsValidItem())
				{
					// Add to array.
					mediaAr.Add(item);
					nItemsRead ++;
				}
				// Prepare for new item - Media Type should be next.
				item.Zero();
				iCode = 0;
			}
			else if(fileStr == _T("6"))
				iCode = 6;
			else if(fileStr == _T("7"))
				iCode = 7;
			else if(fileStr == _T("10"))
				iCode = 10;
			else if(fileStr == _T("11"))
				iCode = 11;
			if(iCode != -1)
				haveCode = TRUE;
			continue;
		}
		// Get the value.
		else if(haveCode)
		{
			switch(iCode)
			{
				case 0: // Media Type - String - mandatory
				{
					MEDIA_TYPE type = item.GetMediaTypeFromString(fileStr);
					// Must have a type.
					if(type == NONE)
					{
						// Error
						continue;
					}
					item.m_mediaType = type;
					haveCode = FALSE;
					haveValue = TRUE;
					break;
				}
				case 6: // Topic Hierarchy - Strings - optional
				{
					// If no topic nodes, use default per type.
					if(fileStr.IsEmpty())
					{
						CString strNode = item.GetDefaultMediaTopicName();
						item.m_nodesList.Add(strNode);
					}
					else
					{
						item.SetMediaItemTopicNodes(fileStr, &m_aliasAr);
					}
					haveCode = FALSE;
					haveValue = TRUE;
					break;
				}
				case 7: // Is Favorite - String - optional
				{
					if(!fileStr.IsEmpty())
					{
						CString s = fileStr;
						s.MakeLower();
						if(s == _T("yes") || s == _T("1")) 
							item.m_isFavorite = TRUE;
					}
					haveCode = FALSE;
					haveValue = TRUE;
					break;
				}
				case 10: // Media Name - String - mandatory
				{
					if(fileStr.IsEmpty())
					{
						// Error
						continue;
					}
					item.m_mediaName = fileStr;
					haveCode = FALSE;
					haveValue = TRUE;
					break;
				}
				case 11: // Media URL - String - Mandatory for AUDU, VIDU types
				{
					if(fileStr.IsEmpty() && 
					   (item.m_mediaType == AUDU || item.m_mediaType == VIDU) )
					{
						// ERROR
						continue;
					}
					item.m_mediaUrl = fileStr;
					haveCode = FALSE;
					haveValue = TRUE;
					break;
				}
				default:
					continue;
			}
		}
	}
	// If all done reading, an item should be ready for saving.
	if(item.IsValidItem())
	{
		// Add to array.
		mediaAr.Add(item);
		nItemsRead ++;
	}
	// Done with file.
	file.Close();

	// If no items gotten, bail.
	int nMediaItems = mediaAr.GetSize();
	if(!nMediaItems)
		return FALSE;

	/////////////////////////////////////////////////////
	// If a topic string passed in, items will go there.
	if(!topic.IsEmpty())
	{
		CString node;
		int nodeListSz = 0;
		for(int i = 0; i < nMediaItems; i++)
		{
			// Get a ref.
			CMediaItem & item = mediaAr[i];
			nodeListSz = item.m_nodesList.GetSize();

			// If nodes exist, add topic to front.
			// Since this is a Media Record Format import,
			// there should already be nodes.
			if(nodeListSz)
			{
				node = item.m_nodesList.GetAt(0);
				if(node != topic)
					item.m_nodesList.InsertAt(0, topic);
			}
			// Else add single topic.
			else
				item.m_nodesList.Add(topic);

			// Add item to TopicTree.
			m_topicTree.AddMediaItemToTree(item);
		}
	}
	// Else overwrite TopicTree with new items.
	else
	{
		// Empty TopicTree.
		m_topicTree.Zero();

		// Add all items
		for(int i = 0; i < nMediaItems; i++)
		{
			// Get a ref.
			CMediaItem & item = mediaAr[i];
			// Add item to TopicTree.
			m_topicTree.AddMediaItemToTree(item);
		}
	}
	// Update current topic/lists and Views.
	UpdateCurrentTopic(TRUE, TRUE, NULL, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

// Read media items from a pls file or url.
// Called from: OnToolsImport().
BOOL CDsLavDoc::ReadPls(CString filePathUrl, CString topic, int & nItemsRead)
{
	// Radio staion diretories:
	//  https://live365.com/blog/online-radio-directories/
	//  https://en.wikipedia.org/wiki/List_of_Internet_radio_stations
	//  http://www.radiosurvivor.com/2015/03/04/digital-watch-in-search-of-high-fidelity-internet-radio/
	//  https://www.radio-browser.info/#!/
	//		docs - https://nl1.api.radio-browser.info/
	//
	// test urls: 
	// http://www.1.fm/jazz.pls
	// https://kjzz.streamguys1.com/kjzz_mp3_128.pls  - provides a m3u - public jazz??? 
	// http://playerservices.streamtheworld.com/pls/KABCAMAAC.pls
	// http://116.202.241.212:8010/listen.pls?sid=1&t=.pls

	// 9/25/2021
	// Limit the download size. See ReadM3u() for info.
	UINT maxBytes = 1024 * 40;

	// Currently only adding items to a topic.
	if(topic.IsEmpty())
	{
		TRACE(_T("ERROR CDsLavDoc ReadPls - no topic to ad.\n"));
		return FALSE;
	}
	// Set count.
	nItemsRead = 0;

	CCurlinet inet;
	CString strErr;
	CString urlPath, filePath;
	BOOL isPlsUrl = FALSE;

	// Set WaitCursor - resets when out of scope.
	CWaitCursor wait;

	// If fetching pls from a url.
	int nChar = filePathUrl.Find(_T("http"));
	if(nChar == 0)
	{
		isPlsUrl = TRUE;
		urlPath = filePathUrl;

		// We use the results.txt file to read the url data into.
		// This is located in the users app data folder as a 
		// user cannot write to the program files folder. Note
		// the path is generated in OnOpenDocument() at start up.
		filePath = m_userAppDataPath;
		// Add file name.
		filePath += _T("\\results.txt");
	}
	// Else fetching pls from the disc file param.
	else
	{
		isPlsUrl = FALSE;
		filePath = filePathUrl;
	}

	// Fetch pls url data into filePath if a url.
	if(isPlsUrl)
	{
//		if(!inet.GetInternetDataUnicode(urlPath, filePath, strErr))
		if(!inet.GetInternetDataUnicode(urlPath, filePath, strErr, maxBytes))
		{
			CString s;
			s.Format(_T("CDsLavDoc ReadPls - GetInternetData failed: %s\n"), strErr);
			TRACE(s);
			AfxMessageBox(s);
			return FALSE;
		}
	}

	// Open pls filePath - either the file param or the 
	// local file with downloaded pls data.
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CDsLavDoc ReadPls - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}

	// Parse pls file.
	// Pls appears to contain "file" items - which will be numbered 
	// if multiples, and "title" items - which will also be numbered
	// if multiples.
	// We assume they are encountered sequentially i.e:
	//	file1
	//	file2
	//	title1
	//	title2
	// Or:
	//	file1
	//	title1
	//	file2
	//	title2
	// A file item should be a URL.
	// See: https://en.wikipedia.org/wiki/PLS_(file_format)

	CStringArray urlAr;
	CStringArray titleAr;
	CString strUrl, strTitle;

	// URL's are case sensitive - save original to MediaItem.
	CString fileStr;
	// Find() is case sensitive - make everything lower for searching.
	CString fileStrLow;

	// Read the disk file.
	int index = 0;
	while(file.ReadString(fileStr))
	{
		// Make lower.
		fileStrLow = fileStr;
		fileStrLow.MakeLower();

		// Playlist - should exist.
		if(fileStrLow == _T("[playlist]"))
			continue;

		// NumberOfEntries - sometimes.
		index = fileStrLow.Find(_T("numberofentries"), 0 );
		if(index != -1)
		{
			// fetch number of entries
			continue;
		}

		// File.
		index = fileStrLow.Find(_T("file"), 0 );
		if(index != -1)
		{
			// Example: File2=https://17963.live.streamtheworld.com:443/KPCC_FM_SC

			// Get file.
			int nChars = fileStr.GetLength();
			int nPos = fileStr.Find(_T("=")); // lower not needed
			if(nPos == -1)
				continue;
			strUrl = fileStr.Right(nChars - nPos - 1);
			urlAr.Add(strUrl);
			continue;
		}

		// Title - not always.
		index = fileStrLow.Find(_T("title"), 0 );
		if(index != -1)
		{
			// Example: Title1=NewsRadio

			// Get title.
			int nChars = fileStr.GetLength();
			int nPos = fileStr.Find(_T("="));
			if(nPos == -1)
				continue;
			strTitle = fileStr.Right(nChars - nPos - 1);
			titleAr.Add(strTitle);
			continue;
		}
	}
	// Done with file.
	file.Close();

	// Check results.
	int nUrls = urlAr.GetSize();
	int nTitles = titleAr.GetSize();
	// Must have gotten urls.
	if(!nUrls)
	{
		TRACE(_T("ERROR CDsLavDoc ReadPls - no media item data read.\n"));
		return FALSE;
	}

	// Build MediaItems
	CMediaArray mediaAr;
	CMediaItem mediaItem;
	for(int i = 0; i < nUrls; i++)
	{
		mediaItem.Zero();
		// Type - will assume it's a audio url. Could be anything.
		//  -should work on this
		mediaItem.m_mediaType = AUDU;
		// URL
		mediaItem.m_mediaUrl = urlAr[i];
		// Title
		if(i < nTitles)
		{
			mediaItem.m_mediaName = titleAr[i];
		}
		else
		{
			mediaItem.m_mediaName = _T("Unknown");
		}
		// Add to array.
		mediaAr.Add(mediaItem);
	}

	// If no items gotten, bail.
	int nMediaItems = mediaAr.GetSize();
	if(!nMediaItems)
	{
		TRACE(_T("ERROR CDsLavDoc ReadPls - no media items created.\n"));
		return FALSE;
	}

	// Add media items to to TopicTree under topic param.
	CString node;
	int nodeListSz = 0;
	for(int i = 0; i < nMediaItems; i++)
	{
		// Get a ref.
		CMediaItem & item = mediaAr[i];
		nodeListSz = item.m_nodesList.GetSize();

		// If nodes exist, add topic to front.
		// Since this is a Media Record Format import,
		// there should already be nodes.
		if(nodeListSz)
		{
			node = item.m_nodesList.GetAt(0);
			if(node != topic)
				item.m_nodesList.InsertAt(0, topic);
		}
		// Else add single topic.
		else
			item.m_nodesList.Add(topic);

		// Add item to TopicTree.
		// Add the new Topic and MediaItem to the TopicTree.
		m_topicTree.AddMediaItemToTree(item, TRUE);

		// Increment param
		nItemsRead++;
	}

	// Update Current Topic/Lists and Views - new Topic gets selected..
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

// Read media items from a m3u file or url.
// Called from: OnToolsImport().
BOOL CDsLavDoc::ReadM3u(CString filePathUrl, CString topic, int & nItemsRead)
{
	// test url: http://listen.radionomy.com/abcd-eurodance.m3u - 5 urls
	//			 http://africaninternetradio.com/SLR-high.m3u

	// 9/25/2021 - Fix hang:
	// One .m3u playlist returned by Radio Browser is actually a radio stream URL,
	// not a playlist (http://stream.kjzz.org/kjzzhd2_mp3_128.m3u).
	// Curl is able to download, but it's an infinite stream and so DsLav just 
	// hangs and writes bytes to results.txt - there's no canceling as it's using
	// the UI thread.
	// The fix is to limit the download size to say 40 KB. Most m3u and pls files
	// are 1 to 6 KB.
	UINT maxBytes = 1024 * 40;

	// Currently only adding items to the topic param.
	if(topic.IsEmpty())
	{
		TRACE(_T("ERROR CDsLavDoc ReadM3u - not topic to ad.\n"));
		return FALSE;
	}
	// Set count.
	nItemsRead = 0;

	CCurlinet inet;
	CString strErr;
	CString urlPath, filePath;
	BOOL isM3uUrl = FALSE;

	// Set WaitCursor - resets when out of scope.
	CWaitCursor wait;

	// If fetching m3u from a url.
	int nChar = filePathUrl.Find(_T("http"));
	if(nChar == 0)
	{
		isM3uUrl = TRUE;
		urlPath = filePathUrl;

		// We use the results.txt file to read the url data into.
		// This is located in the users app data folder as a 
		// user cannot write to the program files folder. Note
		// the path is generated in OnOpenDocument() at start up.
		filePath = m_userAppDataPath;
		// Add file name.
		filePath += _T("\\results.txt");
	}
	// Else fetching m3u from the disc file param.
	else
	{
		isM3uUrl = FALSE;
		filePath = filePathUrl;
	}

	// Fetch m3u url data into filePath if a url.
	if(isM3uUrl)
	{
	//	if(!inet.GetInternetData(urlPath, filePath, strErr))
		if(!inet.GetInternetDataUnicode(urlPath, filePath, strErr, maxBytes))
		{
			CString s;
			s.Format(_T("CDsLavDoc ReadM3u - GetInternetData failed: %s\n"), strErr);
			TRACE(s);
			AfxMessageBox(s);
			return FALSE;
		}
	}

	// Open m3u filePath - either the file param or the 
	// local file with downloaded m3u data.
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CDsLavDoc ReadM3u - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}

	// Parse m3u file.
	CStringArray urlAr;
	CStringArray titleAr;
	CString strUrl, strTitle;

	// URL's are case sensitive - save original to MediaItem.
	CString fileStr;
	// Find() is case sensitive - make everything lower for searching.
	CString fileStrLow;

	//
	// Assume titles and url's are sequential, same
	//  as PlsToMediaItem(). See notes there.
	//

	// Read the disk file.
	int index = 0;
	while(file.ReadString(fileStr))
	{
		// Make lower.
		fileStrLow = fileStr;
		fileStrLow.MakeLower();

		// #EXTINF:-1, - posssible title.
		index = fileStrLow.Find(_T("#extinf:-1,"), 0 );
		if(index != -1)
		{
			// Example: #EXTINF:-1,Classical QED 89.3, Pittsburgh
			int nChars = fileStr.GetLength();

			// Find the comma in #extinf:-1,
			int nPos = fileStr.Find(_T(','));
			if(nPos == -1)
				continue;
			// Get chars after comma.
			strTitle = fileStr.Right(nChars - nPos - 1);
			titleAr.Add(strTitle);
			continue;
		}

		// http - https, a URL.
		//  Assume always a single line.
		index = fileStrLow.Find(_T("http"), 0 );
		if(index == 0)
		{
			// Example: https://17963.live.streamtheworld.com:443/KPCC_FM_SC

			// Save the single line.
			urlAr.Add(fileStr);
			continue;
		}
	}
	// Done with file.
	file.Close();

	/////////////////////////////////////////////////////////////
	// Process results.
	//
	int nUrls = urlAr.GetSize();
	int nTitles = titleAr.GetSize();
	// Must have gotten urls.
	if(!nUrls)
		return FALSE;

	// Build MediaItems
	CMediaArray mediaAr;
	CMediaItem mediaItem;
	for(int i = 0; i < nUrls; i++)
	{
		mediaItem.Zero();
		// Type - will assume it's a radio url. Could be anything.
		//  todo - should work on this
		mediaItem.m_mediaType = AUDU;
		// URL
		mediaItem.m_mediaUrl = urlAr[i];
		// Title
		if(i < nTitles)
		{
			mediaItem.m_mediaName = titleAr[i];
		}
		else
		{
			mediaItem.m_mediaName = _T("Unknown");
		}
		// Add to array.
		mediaAr.Add(mediaItem);
	}
	// If no items gotten, bail.
	int nMediaItems = mediaAr.GetSize();
	if(!nMediaItems)
	{
		TRACE(_T("ERROR CDsLavDoc ReadM3u - no media items created.\n"));
		return FALSE;
	}
	// Add media items to to TopicTree under topic param.
	CString node;
	int nodeListSz = 0;
	for(int i = 0; i < nMediaItems; i++)
	{
		// Get a ref.
		CMediaItem & item = mediaAr[i];
		nodeListSz = item.m_nodesList.GetSize();

		// If nodes exist, add topic to front.
		// Since this is a Media Record Format import,
		// there should already be nodes.
		if(nodeListSz)
		{
			node = item.m_nodesList.GetAt(0);
			if(node != topic)
				item.m_nodesList.InsertAt(0, topic);
		}
		// Else add single topic.
		else
			item.m_nodesList.Add(topic);

		// Add new Topic and MediaItem to TopicTree.
		m_topicTree.AddMediaItemToTree(item, TRUE);

		// Increment param
		nItemsRead++;
	}
	// Update Current Topic/Lists and Views - select new topic.
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);
	// Save changes.
	m_bSaveTopicTree = TRUE;
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// Radio Browser Search handlers

// Import RB search results to topic param. 
// Limit number of results with MaxResults param
int CDsLavDoc::ProccessRbSearchResults(CString topic, int maxResults)
{
	int sz = m_radioBrowse.GetResultsArSize();
	if(!sz)
		return 0;

	// Only adding items to a topic.
	if(topic.IsEmpty())
	{
		TRACE(_T("ERROR CDsLavDoc::ProccessRbSearchResults - no topic to ad.\n"));
		return 0;
	}

	// Build MediaItems from RB search results.
	CMediaArray mediaAr;
	CMediaItem mediaItem;

	for(int i = 0; i < sz; i++)
	{
		// Limit imported results.
		if(maxResults && i >= maxResults) // ones based
			break;

		// Get station ref.
		CStation & station = m_radioBrowse.GetSearchResultsAt(i);

		mediaItem.Zero();

		// Type - audio url.
		mediaItem.m_mediaType = AUDU;

		// URL
		mediaItem.m_mediaUrl = station.m_url;

		// Title
		mediaItem.m_mediaName = station.m_name;

		// Add to array.
		mediaAr.Add(mediaItem);
	}

	// Check results.
	int nMediaItems = mediaAr.GetSize();
	if(!nMediaItems)
	{
		TRACE(_T("ERROR CDsLavDoc::ProccessRbSearchResults - no media items created.\n"));
		return 0;
	}

	// Add media items to to TopicTree under topic param.
	CString node;
	int nodeListSz = 0;
	for(int i = 0; i < nMediaItems; i++)
	{
		// Get a ref.
		CMediaItem & item = mediaAr[i];
		nodeListSz = item.m_nodesList.GetSize();

		// Topic is a Root topic for now. Would need
		// a topic nodes list otherwise.
		item.m_nodesList.Add(topic);

		// Add media item to TopicTree - creates topic as needed.
		m_topicTree.AddMediaItemToTree(item, TRUE);
	}

	// Update Current Topic/Lists and Views - new Topic gets selected..
	UpdateCurrentTopic(TRUE, FALSE, &m_topicTree.m_resultTopic, TRUE, TRUE);

	// Remove all results from the RB results array.
	// The search action is complete.
	m_radioBrowse.ZeroSearchResultsAndCritera();

	// Save changes.
	m_bSaveTopicTree = TRUE;

	// Return num imported to dialog.
	return nMediaItems;
}


/////////////////////////////////////////////////////////////////////////////
// Radio Browser Update Database handlers

// Search Radio Browser database.
void CDsLavDoc::OnToolsSearchRb() 
{
	CRbSearchDlg dlg;
	dlg.m_pDoc = this;

	// Run Update Dlg.
	dlg.DoModal();	
}


// Update Radio Browser database.
void CDsLavDoc::OnToolsUpdateRb() 
{
	//-----------------------------------------------------
	// debugging - bypass getting servers.
	// must if not downloading file in CRadioBrowseDb::BuildStationArrayThread
	// as this will overwrite purposely copied RB xml data in results.txt
//	m_rbServer = _T("http://nl1.api.radio-browser.info");
	//-----------------------------------------------------

	// Get random server from RB server list.
	if(m_rbServer.IsEmpty())
	{
		// Set WaitCursor - resets when out of scope.
		CWaitCursor wait;

		// Temp file for writing xml.
		CString tempPath = m_userAppDataPath + _T("\\results.txt");
		CString errStr;
		CStringArray ar;

		// Returns num servers - zero is error.
		int nServers = m_radioBrowse.GetServers(tempPath, errStr, ar);
		if(!nServers)
		{
			AfxMessageBox(errStr);
			return;
		}
		// Get random RB url from list .
		srand((unsigned)time(NULL));
		int index = rand() % nServers; // zero based
		// Verify.
		if(index < 0)
			index = 0;
		else if(index >= nServers)
			index = nServers - 1;
		m_rbServer = ar[index];
		TRACE(_T("CDsLavDoc::OnToolsUpdateRb %s RB server chosen.\n"), m_rbServer);
	}

	// Prepare RB Update Dialog.
	CRadioBrowseUpdateDlg dlg;
	dlg.m_pDoc = this;
	dlg.m_strSever = m_rbServer;

	// Run Update Dlg - calls UpdateRadioBrowse() to
	// launch worker thread.
	dlg.DoModal();
}

// Called from CRadioBrowseUpdateDlg::OnButtonCancel().
// Will cancel the Radio Browser Update.
void CDsLavDoc::CancelUpdate()
{
	TRACE(_T("CDsLavDoc::CancelUpdate - setting m_rbThreadEvent Event.\n"));

	// Set event - thread will test for event
	// while downloading and proccessing and 
	// return 0 if set,
	if(m_hRbThread)
	{
		m_rbThreadEvent.SetEvent();
	}
	// When debugging/testing the dialog with update thread not running, 
	// pressing the Update Rb button disables the ok/done button.
	// The dlg handle is saved in UpdateRadioBrowse() for this purpose.
	else
	{
		// Enable the Close button when testing.
		if(m_hwndDlg)
			PostMessage(m_hwndDlg, WM_USER_THREAD_DONE, 0, 0);
	}
}

// Called from CMainFrame::OnUpdateFinished() - WM_USER_THREAD_FINISHED
// See RadioBrowseUpdateThread().
void CDsLavDoc::UpdateFinished()
{
	// Shut down the Update worker thread.
	if(m_hRbThread)
	{
		TRACE(_T("Entering CDsLavDoc::UpdateFinished.\n"));
		::WaitForSingleObject(m_hRbThread, INFINITE);
		::CloseHandle(m_hRbThread);

		// Zero progress ctrl.
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_PROGRESS_MAX, 0, (LPARAM)30);
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)0);

		// Info user.
		CString msg = _T("Copying Radio Browser data.");
		((CDsLavApp*)AfxGetApp())->SetThreadMessage(msg);
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_STATUS_STRING, 0, 0);

		// Copy m_tempRB array data to CDsLavDoc m_radioBrowse member.
		m_radioBrowse.CopyStorage(m_tempRB);

		// Serialize.
		WriteRadioBrowserData();

		// Info user.
		msg = _T("Radio Station Database update successfull.");
		((CDsLavApp*)AfxGetApp())->SetThreadMessage(msg);
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_STATUS_STRING, 0, 0);


		// Inform Update Dialog of update completion.
		PostMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_DONE, 0, 0);

		// Clean thread vars.
		m_tempRB.ZeroStorage();
		m_rbThreadParam.Zero();
		m_rbThreadEvent.ResetEvent();
		m_hRbThread = NULL;
		m_tempPath = _T("");
		m_rbUrl = _T("");
		m_bRbUpdateInstalled = FALSE;
		m_hwndDlg = NULL;
	}
	/* Dialog display in release:
	Press the Update button to begin.
	Updates are obtained from:
	  https://de1.api.radio-browser.info.
	Getting Radio Browser station data from RB server.
	Please wait.
	Connecting to Radio Browser server.
	Downloading Radio Browser data.
	Please wait.
	Download complete.
	Proccessing Radio Browser station data.
	Please wait.
	28188 stations proccessed.
	Loading countries.
	Setting country codes.
	Building tag list.
	Building states list.
	Loading languages.
	Copying Radio Browser data.
	Radio Station Database update successfull.	*/
}

// Called from CMainFrame::OnUpdateAborted() - WM_USER_THREAD_ABORTED.
// See RadioBrowseUpdateThread().
void CDsLavDoc::UpdateAborted()
{
	TRACE(_T("Entering CDsLavDoc::UpdateAborted.\n"));
	// Shut down the Update worker thread.
	if(m_hRbThread)
	{
		// Clean thread.
		::WaitForSingleObject(m_hRbThread, INFINITE);
		::CloseHandle (m_hRbThread);

		TRACE(_T("CDsLavDoc::UpdateAborted - thread canceled.\n"));

		// Zero progress ctrl.
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_PROGRESS_MAX, 0, (LPARAM)30);
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)0);

		// Info user.
		CString msg = _T("Radio Database Update failed.");
		((CDsLavApp*)AfxGetApp())->SetThreadMessage(msg);
		SendMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_STATUS_STRING, 0, 0);

		// Inform Dialog update done.
		PostMessage(m_rbThreadParam.hwndDlg, WM_USER_THREAD_DONE, 0, 0);

		// Clean thread vars.
		m_tempRB.ZeroStorage();
		m_rbThreadParam.Zero();
		m_rbThreadEvent.ResetEvent();
		m_hRbThread = NULL;
		m_tempPath = _T("");
		m_rbUrl = _T("");
		m_bRbUpdateInstalled = FALSE;
		m_hwndDlg = NULL;
	}
	/* Dialog display in release:
	Press the Update button to begin.
	Updates are obtained from:
	  https://nl1.api.radio-browser.info.
	Getting Radio Browser station data from RB server.
	Please wait.
	Connecting to Radio Browser server.
	Downloading Radio Browser data.
	Please wait.
	Attempting to cancel update.
	Canceling download.
	Download canceled by user.
	Building Stations failed.
	Radio Database Update failed.*/
}

// Called from CRadioBrowseUpdateDlg's Update button.
void CDsLavDoc::UpdateRadioBrowse(HWND hwndDlg)
{
	TRACE(_T("Entering CDsLavDoc::UpdateRadioBrowse.\n"));
	if(!hwndDlg)
		return;

	// Save hwnd for debugging purposes. See OnCancel().
	m_hwndDlg = hwndDlg;

	// Ensure not in use.
	if(m_hRbThread != NULL)
		return;

	// Prepare.
	m_rbThreadEvent.ResetEvent();	// set canceling to nonsignaled

	// Temp file for writing member.
	m_tempPath = m_userAppDataPath + _T("\\results.txt");

	// RB Url member. Note OnToolsUpdateRB() initializes the m_rbServer member.
	m_rbUrl = m_rbServer;
	m_rbUrl += _T("/xml/stations");
	//m_rbUrl = _T("http://nl1.api.radio-browser.info/xml/stations");// 28,996,710 /1024 /1024 = 27.653 MB

	// Set param member.
	CMainFrame * pFrame = (CMainFrame*)(AfxGetMainWnd());
	if(!pFrame)
		return;
	m_rbThreadParam.hwndMain	= pFrame->GetSafeHwnd();// CMainFrame HWND
	m_rbThreadParam.hwndDlg		= hwndDlg;				// CRadioBrowseUpdateDlg HWND
	m_rbThreadParam.pEvent		= &m_rbThreadEvent;		// CEvent
	m_rbThreadParam.pTempRB		= &m_tempRB;			// Temp CRadioBrowseDb
	m_rbThreadParam.pTempPath	= &m_tempPath;			// Temp file for writing - results.txt
	m_rbThreadParam.pRbUrl		= &m_rbUrl;				// RB Stations XML

	// Start worker thread in suspended state.
	CWinThread * pThread = AfxBeginThread(RadioBrowseUpdateThread, 
										  &m_rbThreadParam,
										  THREAD_PRIORITY_NORMAL, 
										  0,						// 0 = 1 MB stack
										  CREATE_SUSPENDED);
	// Copy handle to m_hRbThread member.
	::DuplicateHandle(GetCurrentProcess(),		// handle to source process
					  pThread->m_hThread,		// handle to duplicate
					  GetCurrentProcess(),		// handle to target process
					  &m_hRbThread,				// duplicate handle
					  0,						// requested access
					  FALSE,					// handle inheritance option
					  DUPLICATE_SAME_ACCESS);	// optional actions
	// Start thread.
	pThread->ResumeThread();

}

// Radio Browse Update Worker Thread function.
UINT RadioBrowseUpdateThread(LPVOID pParam)
{
	TRACE(_T("Entering RadioBrowseUpdateThread global.\n"));

	// Copy param data.
	THREADPARAM param = *(THREADPARAM *) pParam;

	// Copy path to temp file - results.txt.
	CString tempPath = *(CString *)param.pTempPath;

	// Copy RB url - http://nl1.api.radio-browser.info/xml/stations.
	CString rbUrl = *(CString *)param.pRbUrl;

	// Update the temp CRadioBrowseDb located in CDsLavDoc and
	// pointed to by param member.
	CString errStr;
	int ret = param.pTempRB->BuildRadioBrowserThread(rbUrl, tempPath, errStr, param.hwndMain,
													 param.hwndDlg, param.pEvent);
	// Return is 0 if failed or canceled.
	if(!ret)
	{
		// Post thread abort message to CDsLavDoc::UpdateAborted()
		// via CMainFrame.
		PostMessage(param.hwndMain, WM_USER_THREAD_ABORTED, 0, 0);
		// Return non 0 for fail. 
		return 1;
	}
	// Return is 1 if success.
	else
	{
		// Post thread success message to CDsLavDoc::UpdateFinished()
		// via CMainFrame to process RB data.
		PostMessage(param.hwndMain, WM_USER_THREAD_FINISHED, 0, 0);
		// Return 0 for success. 
		return 0;
	}
}

/////////////////////////////////////////////////////////////////////////////
// Radio Browser Search handlers.




/////////////////////////////////////////////////////////////////////////////
// CDsLavDoc serialization

void CDsLavDoc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO: add storing code here
	}
	else
	{
		// TODO: add loading code here
	}
}

////////////////////////////////////////////////////////////////
// Test stuff

void CDsLavDoc::OnTestTest() 
{

}

/////////////////////////////////////////////////////////////////////////////
// CDsLavDoc diagnostics

#ifdef _DEBUG
void CDsLavDoc::AssertValid() const
{
	CDocument::AssertValid();
}

void CDsLavDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG







