// FlowSshCpp_SftpGui
#include "FlowSshCpp_SftpGui.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


namespace FlowSshCpp_SftpGui
{


// ConcatPathXX

CStringW ConcatPathLocal(CStringW const& strPathLeft, CStringW const& strPathRight)
{
	CStringW str = strPathLeft;
	if (str.ReverseFind(L'\\') != str.GetLength() - 1) 
		str += L"\\";
	str += strPathRight;
	return str;
}

CStringW ConcatPathRemote(CStringW const& strPathLeft, CStringW const& strPathRight)
{
	CStringW str = strPathLeft;
	if (str.ReverseFind('/') != str.GetLength() - 1) 
		str += L"/";
	str += strPathRight;
	return str;
}

CStringW FormatFileSize(DWORD64 nSizeInBytes)
{
	NUMBERFMTW format;
	format.NumDigits = 0;
	format.LeadingZero = 0;
	format.Grouping = 3;
	format.lpDecimalSep = L"";
	format.lpThousandSep = L".";
	format.NegativeOrder = 0;

	CStringW strSizeInBytes;
	strSizeInBytes.Format(L"%d", nSizeInBytes);

	CStringW strFormattedSize(L'\0', 100);
	int nResult = GetNumberFormatW(LOCALE_USER_DEFAULT, 0, strSizeInBytes, &format, strFormattedSize.GetBuffer(), (int)strFormattedSize.GetLength());
	strFormattedSize.ReleaseBuffer();

	if (nResult > 0) return strFormattedSize;
	else			 return strSizeInBytes;
}


// File

CStringW File::GetExtension() const
{
	if (m_strExtension[0] == L'\0')
	{
		CStringW const strFile = GetName();
		int pos = strFile.ReverseFind(L'.');
		if (pos == -1)
			m_strExtension = L"";
		else
			m_strExtension = strFile.Right(strFile.GetLength() - pos);
	}
	return m_strExtension;
}

CStringW File::FormatTime(FILETIME const& fileTime, bool bLongFormat) const
{
	SYSTEMTIME systemTime;
	FileTimeToSystemTime(&fileTime, &systemTime);

	int const nBufSize = 1024;
	wchar_t awzBuf[nBufSize + 1];

	CStringW wsTime;

	if (!GetDateFormatW(LOCALE_USER_DEFAULT, DWORD(bLongFormat ? DATE_LONGDATE : DATE_SHORTDATE), &systemTime, NULL, awzBuf, nBufSize))
		return bLongFormat ? L"Invalid Date/Time" : L"invalid";

	wsTime += awzBuf;
	wsTime += bLongFormat ? L", " : L" ";

	if (!GetTimeFormatW(LOCALE_USER_DEFAULT, DWORD(bLongFormat ? 0 : TIME_NOSECONDS), &systemTime, NULL, awzBuf, nBufSize))
		return bLongFormat ? L"Invalid Date/Time" : L"invalid";

	return wsTime + awzBuf;
}


// FileTypeImpl

UINT FileTypeImpl::GetFileTypeIdx(CStringW const& str2FileNameOrExtension, DWORD dwFileAttributes)
{
	TypeKey key;
	key.strName = str2FileNameOrExtension;
	key.dwFileAttributes = dwFileAttributes;

	map_type::const_iterator itType = m_mapType.find((TypeKey) key);
	if (itType != m_mapType.end())
		return itType->second.nIndex;
	else
	{
		UINT nFlags = SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME;

		SHFILEINFOW si;
		if (::SHGetFileInfoW(str2FileNameOrExtension, dwFileAttributes, &si, sizeof(si), nFlags) == 0)
			return IdxErr;
		else
		{
			EnsureThrow(m_vecStr2Type.size() <= UINT_MAX);

			Data dataType;
			dataType.nIndex = (UINT)m_vecStr2Type.size();
			m_vecStr2Type.push_back(si.szTypeName);
			m_mapType.insert(pair<TypeKey, Data>(key, dataType));

			return dataType.nIndex;
		}
	}
}


// Folder

void Folder::SortFiles()
{
	m_vecSortedFileInfoIndex.clear();

	UINT nIdx = 0;
	bool const bAscending = (m_nPrimarySortType & FolderSort::Descending) == 0;

	if (m_nPrimarySortType == FolderSort::Acsending)
	{
		// Unsorted (original order)
		for (nIdx = 0; nIdx < GetFileCount(); ++nIdx)
			m_vecSortedFileInfoIndex.push_back(nIdx);
	}
	else if (m_nPrimarySortType == FolderSort::Descending)
	{
		// Unsorted (reversed order)
		for (nIdx = 0; nIdx < GetFileCount(); ++nIdx)
			m_vecSortedFileInfoIndex.push_back((GetFileCount() - 1) - nIdx);
	}
	else if (
		m_nPrimarySortType & FolderSort::Name|| 
		m_nPrimarySortType & FolderSort::Ext || 
		m_nPrimarySortType & FolderSort::Type || 
		m_nPrimarySortType & FolderSort::Attrs)
	{
		// String sorted
		typedef multimap<StrStrKey, UINT, KeyLessThan> sorting_multimap;

		sorting_multimap smm;

		for (nIdx = 0; nIdx < GetFileCount(); ++nIdx)
		{
			File* pFileInfo = GetFileInfo(nIdx);
			StrStrKey ssk(L"", pFileInfo->GetName(), pFileInfo->IsNavigable());

			if (m_nPrimarySortType & FolderSort::Ext)
				ssk.m_primary = pFileInfo->GetExtension();
			else if (m_nPrimarySortType & FolderSort::Type)
				ssk.m_primary = pFileInfo->GetType();
			else if (m_nPrimarySortType & FolderSort::Attrs)
				ssk.m_primary = pFileInfo->GetAttributes();
			else
				ssk.m_primary = pFileInfo->GetName();
			
			smm.insert(pair<StrStrKey, UINT>(ssk, nIdx));	
		}

		if (bAscending)
			for (sorting_multimap::iterator it = smm.begin(); it != smm.end(); ++it)
				m_vecSortedFileInfoIndex.push_back(it->second);
		else
			for (sorting_multimap::reverse_iterator it = smm.rbegin(); it != smm.rend(); ++it)
				m_vecSortedFileInfoIndex.push_back(it->second);
	}
	else if (
		m_nPrimarySortType & FolderSort::Size || 
		m_nPrimarySortType & FolderSort::CTime || 
		m_nPrimarySortType & FolderSort::Atime || 
		m_nPrimarySortType & FolderSort::MTime)
	{
		// Number sorted
		typedef multimap<Word64StrKey, UINT, KeyLessThan> sorting_multimap;

		sorting_multimap smm;

		for (nIdx = 0; nIdx < GetFileCount(); ++nIdx)
		{
			File* pFileInfo = GetFileInfo(nIdx);
			Word64StrKey wsk(0, pFileInfo->GetName(), pFileInfo->IsNavigable());

			if (m_nPrimarySortType & FolderSort::CTime)
				wsk.m_primary = pFileInfo->GetCreateTime();
			else if (m_nPrimarySortType & FolderSort::Atime)
				wsk.m_primary = pFileInfo->GetAccessTime();
			else if (m_nPrimarySortType & FolderSort::MTime)
				wsk.m_primary = pFileInfo->GetModifyTime();
			else
				wsk.m_primary = pFileInfo->GetSize();

			smm.insert(pair<Word64StrKey, UINT>(wsk, nIdx));	
		}

		if (bAscending)
			for (sorting_multimap::iterator it = smm.begin(); it != smm.end(); ++it)
				m_vecSortedFileInfoIndex.push_back(it->second);
		else
			for (sorting_multimap::reverse_iterator it = smm.rbegin(); it != smm.rend(); ++it)
				m_vecSortedFileInfoIndex.push_back(it->second);
	}
	else
	{
		// Invalid sort type
		m_nPrimarySortType = FolderSort::Acsending;
		SortFiles();
	}
}

void Folder::SortFiles(UINT nNewSortType)
{
	if (m_nPrimarySortType != nNewSortType)
	{
		m_nPrimarySortType = nNewSortType;
		SortFiles();
	}
}


// LocalFile

CStringW LocalFile::GetFullName() const
{
	if (m_pFolderInfo)
		return ConcatPathLocal(m_pFolderInfo->GetName(), GetName());
	return ConcatPathLocal(m_strFolder, GetName());
}

CStringW LocalFile::GetType() const
{
	UINT nIdx = 0;

	if (IsFolder())
		nIdx = m_pFolderInfo->GetFileTypeIdx(L"Folder", FILE_ATTRIBUTE_DIRECTORY);
	else
		nIdx = m_pFolderInfo->GetFileTypeIdx(GetExtension(), FILE_ATTRIBUTE_NORMAL);

	if (nIdx == FileTypeImpl::IdxErr)
		return L"";
	return m_pFolderInfo->AccessFileTypeVector().at(nIdx);
}

CStringW LocalFile::GetAttributes() const
{
	CStringW ret;
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
		ret += L"R";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
		ret += L"H";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
		ret += L"S";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
		ret += L"A";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)
		ret += L"T";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE)
		ret += L"O";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
		ret += L"C";
	if (m_data.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)
		ret += L"E";
	return ret;
}


// LocalFolder

LocalFolder::LocalFolder(CStringW const& strFolder) 
	: Folder(strFolder)
{
	WIN32_FIND_DATAW findData;
	HANDLE findHandle = FindFirstFileW(strFolder + L"\\*", &findData);
	if (findHandle == INVALID_HANDLE_VALUE)
	{
		if (GetLastError() != ERROR_FILE_NOT_FOUND)
			throw SftpException(L"FindFirstFileW failed.");
	}
	else
	{
		do
		{
			LocalFile fi(findData, this);
			if (!fi.IsFolderNameCurrentOrParent())
				m_vecFileInfo.push_back(fi);
		}
		while (FindNextFileW(findHandle, &findData));

		DWORD lastError = GetLastError();
		FindClose(findHandle);

		if (lastError != ERROR_NO_MORE_FILES)
			throw SftpFatalException(L"FindNextFileW failed.");
	}
}


// RemoteFile

CStringW RemoteFile::GetFullName() const
{
	if (m_pFolderInfo)
		return ConcatPathRemote(m_pFolderInfo->GetName(), GetName());
	return ConcatPathRemote(m_strFolder, GetName());
}

CStringW RemoteFile::GetType() const
{
	if (m_pFolderInfo)
	{
		DWORD const dwFileAttribute = DWORD((IsFolder() || IsFolderSymLink()) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL);
		CStringW str2Extension;
		if (IsFolder() || IsFolderSymLink()) str2Extension = L"Folder";
		else str2Extension = GetExtension();

		UINT nIdx = m_pFolderInfo->GetFileTypeIdx(str2Extension, dwFileAttribute);
		if (nIdx == FileTypeImpl::IdxErr)
			return L"";
		return m_pFolderInfo->AccessFileTypeVector().at(nIdx);
	}
	return L"??";
}

CStringW RemoteFile::GetAttributes() const
{
	if (m_data.m_attrs.m_validAttrFlags & FlowSshC_AttrFlags_Permissions)
	{
		CStringW ret = L"";
		DWORD attr = m_data.m_attrs.m_permissions;

		ret += IsFolder()     ? L"d" : L"-";
		ret += (attr & 0x040) ? L"r" : L"-";
		ret += (attr & 0x080) ? L"w" : L"-";
		ret += (attr & 0x100) ? L"x" : L"-";

		ret += (attr & 0x008) ? L"r" : L"-";
		ret += (attr & 0x010) ? L"w" : L"-";
		ret += (attr & 0x020) ? L"x" : L"-";
		
		ret += (attr & 0x001) ? L"r" : L"-";
		ret += (attr & 0x002) ? L"w" : L"-";
		ret += (attr & 0x004) ? L"x" : L"-";

		return ret;
	}
	return L"na";
}

CStringW RemoteFile::FormatTime(time_t utcUnixTime, bool bLongFormat) const
{
	tm xt;
	tm* ptm = 0;
	if (gmtime_s(&xt, &utcUnixTime) == 0) ptm = &xt;

	if (!ptm)
		return bLongFormat ? L"Invalid Date/Time" : L"invalid";

	SYSTEMTIME systemTime = { 
		WORD(ptm->tm_year + 1900),
		WORD(ptm->tm_mon + 1), 0,
		WORD(ptm->tm_mday), 
		WORD(ptm->tm_hour),
		WORD(ptm->tm_min),
		WORD(ptm->tm_sec), 0 };

		FILETIME fileTime;
		if (!SystemTimeToFileTime(&systemTime, &fileTime))
			return bLongFormat ? L"Invalid Date/Time" : L"invalid";

		return __super::FormatTime(fileTime, bLongFormat);
}


// FolderListCtrl

int FolderListCtrl::InsertColumn(int nColumn, UINT folderSort, LPCWSTR lpszColumnHeading, int nFormat, int nWidth)
{
	nColumn = __super::InsertColumn(nColumn, lpszColumnHeading, nFormat, nWidth);
	if (nColumn >= 0)
		m_vecFolderSortHeader.insert(m_vecFolderSortHeader.begin() + nColumn, folderSort);
	return nColumn;
}

void FolderListCtrl::SetFolderInfo(std::unique_ptr<Folder>&& pNewFolderInfo) 
{ 
	m_pFolderInfo = std::move(pNewFolderInfo); 
	SortFiles(m_nFolderSortType, true);
}

void FolderListCtrl::Display() 
{	
	if (m_pFolderInfo.get())
	{
		DeleteAllItems();

		for (UINT c=0; (c < m_pFolderInfo->GetFileCount()) && (c <= INT_MAX); c++)
		{
			File* pFileInfo = m_pFolderInfo->GetSortedFileInfo(c);
			int nItem = __super::InsertItem((int)c, pFileInfo->GetName());
			SetItemText(nItem, 1, pFileInfo->GetSizeStr());
			SetItemText(nItem, 2, pFileInfo->GetType());
			SetItemText(nItem, 3, pFileInfo->GetModifyTimeStr());
			SetItemText(nItem, 4, pFileInfo->GetAttributes());
		}	
	}
}

std::vector<UINT> FolderListCtrl::GetSelectedItems() const
{
	std::vector<UINT> vecItems;
	vecItems.reserve(GetSelectedCount());

	POSITION pos = GetFirstSelectedItemPosition();
	while (pos)
	{
		int itm = GetNextSelectedItem(pos);
		if (itm >= 0 && itm <= INT_MAX)
			vecItems.push_back((UINT)itm);
	}

	return vecItems;
}

void FolderListCtrl::SortFiles(UINT folderSort, bool bAscending)
{
	m_nFolderSortType = folderSort;
	m_nFolderSortType |= bAscending ? FolderSort::Acsending : FolderSort::Descending;

	if (m_pFolderInfo.get())
		m_pFolderInfo->SortFiles(m_nFolderSortType);

	SetFocus();
}

BEGIN_MESSAGE_MAP(FolderListCtrl, CListCtrl)
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, &FolderListCtrl::OnColumnClick)
	ON_NOTIFY_REFLECT(NM_DBLCLK, &FolderListCtrl::OnListDoubleClick)
END_MESSAGE_MAP()

void FolderListCtrl::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;

	NMLISTVIEW* pNMListView = (NMLISTVIEW*)pNMHDR;
	if (!pNMHDR || pNMListView->iSubItem < 0)
		return;

	UINT nColumn = (UINT)pNMListView->iSubItem;
	if (nColumn < m_vecFolderSortHeader.size())
	{
		UINT folderSort = m_vecFolderSortHeader[nColumn];

		if (m_nFolderSortType != folderSort)
			SortFiles(folderSort, true);
		else
		{
			if (m_nFolderSortType & FolderSort::Descending)
				SortFiles(folderSort, true);
			else
				SortFiles(folderSort, false);
		}
		Display();
	}
}

void FolderListCtrl::OnListDoubleClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	if (GetSelectedCount() == 1)
	{
		std::vector<UINT> vec = GetSelectedItems();
		if (vec.size() != 1)
			return;

		File* pFileInfo = m_pFolderInfo->GetSortedFileInfo(vec[0]);
		if (pFileInfo->IsNavigable())
		{
			CStringW strFolder = m_pFolderInfo->ConcatPath(pFileInfo->GetName());
			SetFolder(m_pFolderInfo->ConcatPath(pFileInfo->GetName()));
		}
	}
	*pResult = 0;	
}


// LocalFolderListCtrl

void LocalFolderListCtrl::SetFolder(CStringW const& folerName)
{
	MainDialog* parent = dynamic_cast<MainDialog*>(GetParent());
	if (parent)
		parent->SetLocalFolder(folerName);
}

void LocalFolderListCtrl::GoUp()
{
	CStringW strFolder = GetFolderName();
	int len = strFolder.GetLength();
	if (len > 1)
	{
		strFolder.TrimRight(L'\\');
		strFolder = strFolder.Left(strFolder.ReverseFind(L'\\'));
		SetFolder(strFolder);
	}
}


// RemoteFolderListCtrl

void RemoteFolderListCtrl::SetFolder(CStringW const& folerName)
{
	MainDialog* parent = dynamic_cast<MainDialog*>(GetParent());
	if (parent && parent->IsConnected())
		parent->SetRemoteFolder(folerName);
}

void RemoteFolderListCtrl::GoUp()
{
	CStringW strFolder = GetFolderName();
	int len = strFolder.GetLength();
	if (len > 1)
	{
		strFolder.TrimRight(L'/');
		strFolder = strFolder.Left(strFolder.ReverseFind(L'/'));
		SetFolder(strFolder);
	}			
}


// TransferListCtrl

void TransferListCtrl::DeleteSelectedItems()
{
	std::vector<UINT> vecItems;
	vecItems.reserve(GetSelectedCount());	

	POSITION pos = GetFirstSelectedItemPosition();
	while (pos)
	{
		int itm = GetNextSelectedItem(pos);
		if (itm >= 0 && itm <= INT_MAX)
			vecItems.push_back((UINT)itm);
	}

	while (!vecItems.empty())
	{
		DeleteItem((int)vecItems.back());
		vecItems.pop_back();
	}
}

void TransferListCtrl::DeleteCompletedItems()
{
	for (int c = GetItemCount() - 1; c >= 0; c--)
		if (!GetItemData(c))
			DeleteItem(c);
}


// UserAuthDialog

UserAuthDialog::UserAuthDialog(Client* client, CWnd* pParent) 
	: CDialog(UserAuthDialog::IDD, pParent)
	, m_client(client)
	, m_furtherAuth(NULL) 
{
	if (!client)
		throw SftpFatalException(L"NULL pointer (Client*) passed to UserAuthDialog.");
}

UserAuthDialog::UserAuthDialog(Client::FurtherAuth* furtherAuth, CWnd* pParent) 
	: CDialog(UserAuthDialog::IDD, pParent)
	, m_client(NULL)
	, m_furtherAuth(furtherAuth) 
{
	if (!furtherAuth)
		throw SftpFatalException(L"NULL pointer (FurtherAuth*) passed to UserAuthDialog.");
}

void UserAuthDialog::SetCtrlSelection(BOOL bPasswordCtrls)
{
	m_edtPassword.EnableWindow(bPasswordCtrls);
	m_edtPassphrase.EnableWindow(!bPasswordCtrls);
	m_edtKeyFile.EnableWindow(!bPasswordCtrls);
	m_btnFileDialog.EnableWindow(!bPasswordCtrls);

	if (m_btnUsePassword.IsWindowEnabled())
		m_btnUsePassword.SetCheck(bPasswordCtrls);
	if (m_btnUsePublicKey.IsWindowEnabled())
		m_btnUsePublicKey.SetCheck(!bPasswordCtrls);
}

void UserAuthDialog::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Control(pDX, RADIO_PASSWORD, m_btnUsePassword);		
	DDX_Control(pDX, RADIO_KEY, m_btnUsePublicKey);
	DDX_Control(pDX, EDT_PASSWORD, m_edtPassword);
	DDX_Text(pDX, EDT_PASSWORD, m_strPassword);
	DDX_Control(pDX, EDT_PASSPHRASE, m_edtPassphrase);
	DDX_Text(pDX, EDT_PASSPHRASE, m_strPassphrase);
	DDX_Control(pDX, EDT_KEYFILE, m_edtKeyFile);
	DDX_Text(pDX, EDT_KEYFILE, m_strKeyFile);
	DDX_Control(pDX, BTN_FILEDLG, m_btnFileDialog);
}

LRESULT UserAuthDialog::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	try { return __super::WindowProc(message, wParam, lParam);	}
	catch (Exception const& e)
	{
		MessageBox(e.What());
		EndDialog(IDABORT);
		return 0;
	}
	catch (CException* e)
	{
		wchar_t desc[200]; e->GetErrorMessage(desc, 200); e->Delete();
		MessageBox(desc);
		EndDialog(IDABORT);
		return 0;
	}
}

BEGIN_MESSAGE_MAP(UserAuthDialog, CDialog)
	ON_CONTROL_RANGE(BN_CLICKED, RADIO_PASSWORD, RADIO_KEY, &UserAuthDialog::OnBnClickedSelChanged)
	ON_BN_CLICKED(BTN_FILEDLG, &UserAuthDialog::OnBnClickedBtnFileDialog)
END_MESSAGE_MAP()

BOOL UserAuthDialog::OnInitDialog()
{
	__super::OnInitDialog();
	
	if (m_furtherAuth)
	{
		m_btnUsePassword.EnableWindow(m_furtherAuth->IsPasswordRemaining());
		m_btnUsePublicKey.EnableWindow(m_furtherAuth->IsPublicKeyRemaining());
		SetCtrlSelection(m_furtherAuth->IsPasswordRemaining());
	}
	else SetCtrlSelection(TRUE);

	return TRUE;			
}

void UserAuthDialog::OnOK()
{
	UpdateData(TRUE);

	BOOL ok = TRUE;
	if (m_btnUsePassword.GetCheck())
		if (m_client) m_client->SetPassword(m_strPassword);
		else   	 m_furtherAuth->SetPassword(m_strPassword);
	else
	{
		if (!m_strKeyFile.GetLength())
			ok = FALSE;
		else 
		{
			Data keyData;
			CFile file(m_strKeyFile, CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary);

			keyData.Resize((UINT)file.GetLength());
			file.Read(keyData.GetWritablePtr(), keyData.GetSize());

			try	// load KeyPair
			{
				RefPtr<Keypair> keyPair(new Keypair(keyData, m_strPassphrase));
				if (m_client) m_client->SetKeypair(keyPair);
				else     m_furtherAuth->SetKeypair(keyPair);
			}
			catch(KeypairLoadException const&) { ok = FALSE; }
		}			
	}

	if (ok) __super::OnOK();
	else
		MessageBox(L"The given key or passphrase is invalid.", L"Invalid key or passphrase");		
}

void UserAuthDialog::OnBnClickedBtnFileDialog()
{
	CFileDialog fd(TRUE, 0, L'\0', OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, L"All (*.*)|||", this);
	fd.DoModal();

	CStringW strKeyFile = fd.GetPathName();
	m_edtKeyFile.SetWindowText(strKeyFile);

	int nLen = strKeyFile.GetLength();
	m_edtKeyFile.SetSel(nLen, nLen, TRUE);
}


// FolderNameDialog

FolderNameDialog::FolderNameDialog(CWnd* pParent)
	: CDialog(FolderNameDialog::IDD, pParent)
	, m_strNewFolder(L"")
{}

void FolderNameDialog::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Text(pDX, EDT_NEW_FOLDER, m_strNewFolder);
}

BEGIN_MESSAGE_MAP(FolderNameDialog, CDialog)
END_MESSAGE_MAP()


// RenameFileDialog

RenameFileDialog::RenameFileDialog(CStringW const& strOldName, CWnd* pParent)
	: CDialog(RenameFileDialog::IDD, pParent)
	, m_strOldName(strOldName)
	, m_strNewName(strOldName)
{}

void RenameFileDialog::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Text(pDX, EDT_OLD_NAME, m_strOldName);
	DDX_Text(pDX, EDT_NEW_NAME, m_strNewName);
}

BEGIN_MESSAGE_MAP(RenameFileDialog, CDialog)
END_MESSAGE_MAP()


// FilePropertiesDialog

FilePropertiesDialog::FilePropertiesDialog(MyStatMsg const* pStat, CWnd* pParent)
	: CDialog(FilePropertiesDialog::IDD, pParent)
	, m_bFileAttrsChanged(FALSE)
{
	m_fileAttrs = pStat->GetFileAttrs();

	// DDX variables
	m_strName = pStat->m_strFile;
	m_strType = pStat->m_strFileType;
	m_strLocation = pStat->m_strFolder;	

	FileInfo fileInfo;
	fileInfo.m_name = pStat->m_strFile;
	fileInfo.m_attrs = m_fileAttrs;
	RemoteFile file(fileInfo, pStat->m_strFolder);
	
	m_strSize = file.GetSizeStr();
	m_strCreated = file.GetCreateTimeStr();
	m_strModified = file.GetModifyTimeStr();
	m_strAcessed = file.GetAccessTimeStr();
	m_strOwner = m_fileAttrs.m_owner.c_str();
	m_strGroup = m_fileAttrs.m_group.c_str();
	m_strAttributes = file.GetAttributes();
}

void FilePropertiesDialog::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Text(pDX, EDT_NAME, m_strName);
	DDX_Text(pDX, EDT_TYPE, m_strType);
	DDX_Text(pDX, EDT_LOCATION, m_strLocation);
	DDX_Text(pDX, EDT_SIZE, m_strSize);
	DDX_Text(pDX, EDT_CREATED, m_strCreated);
	DDX_Text(pDX, EDT_MODIFIED, m_strModified);
	DDX_Text(pDX, EDT_ACESSED, m_strAcessed);
	DDX_Text(pDX, EDT_OWNER, m_strOwner);
	DDX_Text(pDX, EDT_GROUP, m_strGroup);
	DDX_Text(pDX, EDT_ATTRIBUTES, m_strAttributes);
}

void FilePropertiesDialog::OnOK()
{
	__super::OnOK();

	if (m_fileAttrs.m_owner.c_str() != m_strOwner ||
		m_fileAttrs.m_group.c_str() != m_strGroup)
	{
		m_fileAttrs.m_owner = m_strOwner;
		m_fileAttrs.m_group = m_strGroup;
		m_bFileAttrsChanged = TRUE;
	}
}

BEGIN_MESSAGE_MAP(FilePropertiesDialog, CDialog)
END_MESSAGE_MAP()


// MyToolBarCtrl

BOOL MyToolBarCtrl::Create(BOOL bList, CWnd* pParentWnd, UINT nID)
{
	EnsureThrow(pParentWnd && (nID <= INT_MAX));

	RECT rcPos;
	ZeroMemory(&rcPos, sizeof(RECT));
	::GetWindowRect(pParentWnd->GetDlgItem((int)nID)->GetSafeHwnd(), &rcPos);
	pParentWnd->ScreenToClient(&rcPos);

	DWORD dwStyle = WS_CHILD | WS_VISIBLE | CCS_NODIVIDER | CCS_NORESIZE | CCS_TOP | DWORD(bList ? TBSTYLE_LIST : 0);
	return __super::Create(dwStyle, rcPos, pParentWnd, nID);
}

void MyToolBarCtrl::AddSeparator()
{
	TBBUTTON Button;
	::ZeroMemory(&Button, sizeof(Button));
	Button.iBitmap = I_IMAGENONE;
	Button.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE;
	Button.iString = INT_PTR(" | ");
	__super::AddButtons(1, &Button);
}

void MyToolBarCtrl::AddButton(int idxImage,int idCommand, char* pString, BYTE fsStyle)
{
	TBBUTTON Button;
	Button.iBitmap = idxImage;
	Button.idCommand = idCommand;
	Button.fsState = TBSTATE_ENABLED;
	Button.fsStyle = fsStyle;
	Button.dwData = NULL;
	Button.iString = INT_PTR(pString);
	__super::AddButtons(1, &Button);
}


// TransferImpl

void TransferImpl::Init(MainDialog* pMainDialog, TransferListCtrl* pLstTransfer, RefPtr<ClientSftpChannel> sftpChannel)
{
	m_pMainDialog = pMainDialog;
	m_pLstTransfer = pLstTransfer;
	m_sftpChannel = sftpChannel;

	if (!pMainDialog || !m_pLstTransfer || !sftpChannel.Get())
		throw SftpFatalException(L"NULL pointer passed to TransferImpl::Init().");
}

void TransferImpl::UploadSelectedLstItems(LocalFolderListCtrl& lstFolderLocal)
{	
	std::vector<CStringW> vecFolders;
	std::vector<UINT> vec = lstFolderLocal.GetSelectedItems();
	if (!vec.size()) return;

	CStringW strFolder = lstFolderLocal.GetFolderName();
	CStringW strFolderDest = m_pMainDialog->m_strFolderRemote;

	for (size_t c=0; c<vec.size(); c++)
	{		
		File* pFileInfo = lstFolderLocal.GetSortedFileInfo(vec[c]);
		if (pFileInfo->IsNavigable()) vecFolders.push_back(pFileInfo->GetName());
		else			    		  AddToListCtrl(true, pFileInfo->GetName(), strFolder, strFolderDest, pFileInfo->GetSizeStr());
	}

	for (size_t c=0; c<vecFolders.size(); c++)
	{
		CStringW strFolderNext = ConcatPathLocal(strFolder, vecFolders[c]);
		CStringW strFolderDestNext = ConcatPathRemote(strFolderDest, vecFolders[c]);
		ListLocalFiles(strFolderNext, strFolderDestNext);
	}	
	TiggerTransfers();
}

void TransferImpl::DownloadSelectedLstItems(RemoteFolderListCtrl& lstFolderRemote)
{
	if (!m_bFilesListed)
	{
		m_pMainDialog->MessageBox(L"Please try again in a moment.");
		return;
	}

	m_bListingOk = TRUE;
	m_bFilesListed = FALSE;

	std::vector<UINT> vec = lstFolderRemote.GetSelectedItems();
	if (!vec.size())
	{
		OnFilesListed();
		return;
	}

	int nFolders = 0;
	for (size_t c=0; c<vec.size(); c++)
	{
		CStringW strFolderDest = m_pMainDialog->m_strFolderLocal;
		File* pFileInfo = lstFolderRemote.GetSortedFileInfo(vec[c]);

		if (pFileInfo->IsNavigable())
		{
			nFolders++;			
			SftpListTransfer(pFileInfo->GetFullName(), ConcatPathLocal(strFolderDest, pFileInfo->GetName()));
		}
		else AddToListCtrl(false, pFileInfo->GetName(), lstFolderRemote.GetFolderName(), strFolderDest, pFileInfo->GetSizeStr());
	}

	if (!nFolders)
		OnFilesListed();
}

void TransferImpl::EnableTransfers(BOOL bEnable)
{
	m_bTransfersEnabled = bEnable;

	for (int c = m_pLstTransfer->GetItemCount() - 1; c >= 0; c--)
		if (m_pLstTransfer->GetItemData(c))
			m_pLstTransfer->SetItemText(c, 1, bEnable ? L"Pending" : L"Paused");

	if (bEnable)
		TiggerTransfers(FALSE);
}

void TransferImpl::ListLocalFiles(CStringW const& strFolder, CStringW const& strFolderDest)
{
	std::vector<CStringW> vecFolders;
	WIN32_FIND_DATAW findData;
	HANDLE findHandle = FindFirstFileW(strFolder + L"\\*", &findData);

	if (findHandle == INVALID_HANDLE_VALUE)
	{
		if (GetLastError() != ERROR_FILE_NOT_FOUND)
			throw SftpException(L"FindFirstFileW failed.");
	}
	else
	{
		do
		{
			LocalFile fi(findData, strFolder);
			if (!fi.IsFolderNameCurrentOrParent())
				if (fi.IsNavigable()) vecFolders.push_back(fi.GetName());
				else		     	  AddToListCtrl(true, fi.GetName(), strFolder, strFolderDest, fi.GetSizeStr());
		}
		while (FindNextFileW(findHandle, &findData));

		DWORD lastError = GetLastError();
		FindClose(findHandle);

		if (lastError != ERROR_NO_MORE_FILES)
			throw SftpFatalException(L"FindNextFileW failed.");

		for (size_t c=0; c<vecFolders.size(); c++)
		{
			CStringW strFolderNext = ConcatPathLocal(strFolder, vecFolders[c]);
			CStringW strFolderDestNext = ConcatPathRemote(strFolderDest, vecFolders[c]);
			ListLocalFiles(strFolderNext, strFolderDestNext);
		}
	}
}

void TransferImpl::AddToListCtrl(bool bUpload, PCWSTR strFile, PCWSTR strSourceFolder, 
								               PCWSTR strDestinationFolder, PCWSTR strSize)
{
	static DWORD nRequestId = 1;
	if (nRequestId == 0) nRequestId++;	// 0 used for completed requests

	int nItem = m_pLstTransfer->InsertItem(m_pLstTransfer->GetItemCount(), bUpload ? L"" : L" ", bUpload ? 2 : 3);
	m_pLstTransfer->SetItemText(nItem, 1, m_bTransfersEnabled ? L"Pending" : L"Paused");
	m_pLstTransfer->SetItemText(nItem, 2, strFile);
	m_pLstTransfer->SetItemText(nItem, 3, strSourceFolder);
	m_pLstTransfer->SetItemText(nItem, 4, strDestinationFolder);
	m_pLstTransfer->SetItemText(nItem, 5, L"0");
	m_pLstTransfer->SetItemText(nItem, 6, strSize);
	m_pLstTransfer->SetItemText(nItem, 7, L"0,0 KB/s");
	m_pLstTransfer->SetItemText(nItem, 8, L"");
	m_pLstTransfer->SetItemData(nItem, nRequestId);

	m_vecRequestId.push_back(nRequestId++);
}

int TransferImpl::RequestIdToListCtrlItemId(UINT nRequestId)
{
	EnsureThrow(nRequestId <= INT_MAX);

	LVFINDINFO info; ::ZeroMemory(&info, sizeof(info));
	info.flags = LVFI_PARAM;
	info.lParam = (LPARAM)nRequestId;
	return m_pLstTransfer->FindItem(&info);
}

CStringW TransferImpl::FormatTransferErr(TransferErr const& transferErr)
{	
	CStringW strErrCode;
	strErrCode.Format(L"%d", transferErr.m_errCode);
	CStringW strErrMsg = transferErr.m_errMsg.size() ? transferErr.m_errMsg.c_str() : L"<no error message>";
	CStringW strErrDesc;

	if (transferErr.m_transferOp < 100) // codeless errors
		strErrDesc = CStringW(L"File transfer failed: ") + strErrMsg;
	else if (transferErr.m_transferOp < 200) // SFTP errors
	{
		switch(transferErr.m_transferOp)
		{
		case FlowSshC_TransferOp_MakeRemoteDir:   strErrDesc = L"Create remote directory"; break;
		case FlowSshC_TransferOp_OpenRemoteFile:  strErrDesc = L"Opening remote file"; break;
		case FlowSshC_TransferOp_ReadRemoteFile:  strErrDesc = L"Reading remote file"; break;
		case FlowSshC_TransferOp_WriteRemoteFile: strErrDesc = L"Writing remote file"; break;
		default: strErrDesc = L"Transfer";
		}
		strErrDesc += CStringW(L" failed with SFTP error ") + strErrCode + CStringW(L": ") + strErrMsg;
	}
	else if (transferErr.m_transferOp < 300) // WinAPI errors
	{
		switch (transferErr.m_transferOp)
		{
		case FlowSshC_TransferOp_MakeLocalDir:   strErrDesc = L"Create local directory"; break;
		case FlowSshC_TransferOp_OpenLocalFile:  strErrDesc = L"Opening local file"; break;
		case FlowSshC_TransferOp_ReadLocalFile:  strErrDesc = L"Reading local file"; break;
		case FlowSshC_TransferOp_WriteLocalFile: strErrDesc = L"Writing local file"; break;
		default: strErrDesc = L"Transfer";
		}
		strErrDesc += CStringW(L" failed with SFTP error ") + strErrCode + CStringW(L": ") + strErrMsg;
	}
	else // FlowSshC_ResumeErrCode
		strErrDesc = CStringW(L"Resuming file failed: ") + strErrMsg;

	return strErrDesc;
}

CStringW TransferImpl::FormatTimer(DWORD64 nElapsedMs)
{
	if (nElapsedMs < 1000)
		return UInt2Str(nElapsedMs) + L"ms";
	else
	{
		DWORD64 nElapsedSeconds = nElapsedMs / 1000;
		DWORD64 nElapsedHours = nElapsedSeconds / 3600;
		DWORD64 nElapsedMinutes = (nElapsedSeconds % 3600) / 60;
		nElapsedSeconds = nElapsedSeconds % 60;

		return UInt2Str(nElapsedHours) + L":" + UInt2Str(nElapsedMinutes) + L":" + UInt2Str(nElapsedSeconds); 
	}
}

void TransferImpl::OnFilesListed()
{
	m_bFilesListed = TRUE;
	TiggerTransfers();
}

void TransferImpl::OnFilesTransfered(BOOL bRefreshLst)
{
	if (bRefreshLst)
	{
		m_pMainDialog->m_lstFolderLocal.Refresh();
		m_pMainDialog->m_lstFolderRemote.Refresh();		
	}
}

void TransferImpl::TiggerTransfers(BOOL bRefreshLstIfDone)
{	
	if (m_bTransfersEnabled)
	{
		while (!m_vecRequestId.empty() && (m_nOpenTransferRequests < nMaxTransfers))
		{
			UINT nRequestId = m_vecRequestId.front();
			m_vecRequestId.pop_front();

			// SftpTransfer might recursively call TiggerTransfers. Example:
			// TiggerTransfers -> SftpTransfer -> OnSftpTransferDone with error ChannelNotOpen -> TiggerTransfers
			SftpTransfer(nRequestId);
		}

		if (!m_nOpenTransferRequests && m_vecRequestId.empty())
			OnFilesTransfered(bRefreshLstIfDone);
	}
	else if (!m_nOpenTransferRequests)
		OnFilesTransfered(bRefreshLstIfDone);
}

void TransferImpl::SftpListTransfer(CStringW const& strFolder, CStringW const& strFolderDest)
{
	if (!m_bListingOk) return;

	RefPtr<ListTransferMsg> newList(new ListTransferMsg(m_pMainDialog->GetSafeHwnd(), WinMsg::SftpListTransfer));
	newList->m_strFolder = strFolder;
	newList->m_strFolderDest = strFolderDest;
	m_sftpChannel->List(strFolder, newList);
	m_nOpenListRequests++;
}

void TransferImpl::SftpTransfer(UINT nRequestId)
{
	int nItem = RequestIdToListCtrlItemId(nRequestId);
	if (nItem >= 0)
	{
		BOOL bUpload = m_pLstTransfer->GetItemText(nItem, 0) != L" ";
		CStringW strName = m_pLstTransfer->GetItemText(nItem, 2);
		CStringW strSource = m_pLstTransfer->GetItemText(nItem, 3);
		CStringW strDestination = m_pLstTransfer->GetItemText(nItem, 4);
		CStringW strSizeCurrent = m_pLstTransfer->GetItemText(nItem, 5);

		CStringW strPathLocal = ConcatPathLocal(bUpload ? strSource : strDestination, strName);
		CStringW strPathRemote = ConcatPathRemote(bUpload ? strDestination : strSource, strName);

		DWORD dwFlags = FlowSshC_TransferFlags_Binary | FlowSshC_TransferFlags_AutoMkDir;
		if (strSizeCurrent != L"0")	// resume paused download
			dwFlags |= FlowSshC_TransferFlags_Resume | FlowSshC_TransferFlags_Overwrite;

		UINT nPipelineSize = m_pMainDialog->GetPipelineSize();

		RefPtr<MyTransferMsg> transfer(new MyTransferMsg(m_pMainDialog->GetSafeHwnd(), WinMsg::SftpTransfer, WinMsg::SftpTransferDone, nRequestId));
		if (bUpload) m_sftpChannel->Upload(strPathLocal, strPathRemote, dwFlags, nPipelineSize, transfer);
		else		 m_sftpChannel->Download(strPathRemote, strPathLocal, dwFlags, nPipelineSize, transfer);
		m_nOpenTransferRequests++;
	}	
}

LRESULT TransferImpl::OnSftpListTransfer(ListTransferMsg const& list)
{
	m_nOpenListRequests--;

	if (m_bListingOk)
	{				
		if (!list.Success())
		{
			m_bListingOk = FALSE;
			m_pMainDialog->ShowListErr(L"SftpListTransfer failed.", list.GetError());
		}
		else
		{
			std::vector<FileInfo> const& vecFile = list.GetFileInfos();
			for (size_t c=0; c<vecFile.size() && m_bListingOk; c++)
			{
				CStringW const& strFolderDest = list.m_strFolderDest;
				RemoteFile const fi(vecFile[c], list.m_strFolder);

				if (!fi.IsFolderNameCurrentOrParent())
					if (fi.IsNavigable()) SftpListTransfer(fi.GetFullName(), ConcatPathLocal(strFolderDest, fi.GetName()));
					else                  AddToListCtrl(false, fi.GetName(), list.m_strFolder, strFolderDest, fi.GetSizeStr());
			}
		}
	}

	if (!m_nOpenListRequests)
		OnFilesListed();
	return m_bListingOk;
}

LRESULT TransferImpl::OnSftpTransfer(FlowSshC_TransferStat const& transferStat, UINT nRequestId)
{
	int nPercent = !transferStat.m_finalFileSize ? 100 :
		int(100 * transferStat.m_currentFileSize / transferStat.m_finalFileSize);
	double nSpeed = !transferStat.m_timeElapsedMs ? 0 :
		double(transferStat.m_bytesTransferred * 1000 / transferStat.m_timeElapsedMs) / 1024;

	CStringW str;
	int nItem = RequestIdToListCtrlItemId(nRequestId);
	if (nItem >= 0)
	{
		str.Format(L"%d%%", nPercent);
		m_pLstTransfer->SetItemText(nItem, 1, str);

		str = FormatFileSize(transferStat.m_currentFileSize);
		m_pLstTransfer->SetItemText(nItem, 5, str);

		str.Format(L"%.2f KB/s", nSpeed);
		m_pLstTransfer->SetItemText(nItem, 7, str);

		str = FormatTimer(transferStat.m_timeElapsedMs);
		m_pLstTransfer->SetItemText(nItem, 8, str);

		if (m_bTransfersEnabled || (transferStat.m_finalFileSizeValid &&
			transferStat.m_currentFileSize == transferStat.m_finalFileSize))
			return TRUE;
		else 
			return FALSE;	// paused
	}
	else return FALSE;		// aborted -  item removed from listCtrl
}

LRESULT TransferImpl::OnSftpTransferDone(MyTransferMsg const& transfer, UINT nRequestId)
{
	m_nOpenTransferRequests--;

	int nItem = RequestIdToListCtrlItemId(nRequestId);
	if (nItem < 0) { TiggerTransfers(); return FALSE; }

	if (transfer.m_bXfering)
	{
		if (!transfer.Success())
		{
			m_pLstTransfer->SetItemText(nItem, 1, L"Failed");
			m_pLstTransfer->SetItemText(nItem, 9, FormatTransferErr(transfer.GetError()));
		}
		else m_pLstTransfer->SetItemText(nItem, 1, L"Completed");
		m_pLstTransfer->SetItemData(nItem, 0);	// -> 0 used for completed requests

		TiggerTransfers();
		return transfer.Success();
	}
	m_vecRequestId.push_front(nRequestId);	// reinsert; transfer will possibly be resumed
	m_pLstTransfer->SetItemText(nItem, 1, L"Paused");
	TiggerTransfers();
	return TRUE;
}


// EraseImpl

void EraseImpl::Init(MainDialog* pMainDialog, RefPtr<ClientSftpChannel> sftpChannel)
{
	m_pMainDialog = pMainDialog;
	m_sftpChannel = sftpChannel;

	if (!pMainDialog || !sftpChannel.Get())
		throw SftpFatalException(L"NULL pointer passed to EraseImpl::Init().");
}

void EraseImpl::EraseSelectedLstItems(RemoteFolderListCtrl& lstFolderRemote)
{
	if (!m_bEraseDone)
	{
		m_pMainDialog->MessageBox(L"Sorry, only one Erase at once.");
		return;
	}

	m_bOK = TRUE;
	m_bEraseDone = FALSE;
	m_vecFiles.clear();
	m_vecFolders.clear();

	std::vector<UINT> vec = lstFolderRemote.GetSelectedItems();
	if (!vec.size())
	{
		OnEraseDone(FALSE);
		return;
	}

	for (size_t c=0; c<vec.size(); c++)
	{		
		File* pFileInfo = lstFolderRemote.GetSortedFileInfo(vec[c]);
		if (pFileInfo->IsNavigable())
			m_vecFolders.push_back(pFileInfo->GetFullName());
		else m_vecFiles.push_back(pFileInfo->GetFullName());
	}

	if (!m_vecFolders.empty())
	{
		for (size_t c=0; c<m_vecFolders.size() && m_bOK; c++)
			SftpListErase(m_vecFolders[c]);
	}
	else if (!m_vecFiles.empty() && NotificationMsgBox())
		EraseAllFiles();
	else 
		OnEraseDone(FALSE);
}

bool EraseImpl::NotificationMsgBox()
{
	CStringW cs;
	cs.Format(L"Are you sure you want to permanently remove %d files and %d remote folders?", m_vecFiles.size(), m_vecFolders.size());
	return m_pMainDialog->MessageBox(cs, L"Erase Files", MB_YESNO) == IDYES;
}

void EraseImpl::EraseAllFiles()
{
	while(!m_vecFiles.empty() && m_bOK)
	{
		SftpRemove(m_vecFiles.back());
		m_vecFiles.pop_back();
	}
}

void EraseImpl::EraseAllFolders()
{
	while(!m_vecFolders.empty() && m_bOK)
	{
		SftpRmDir(m_vecFolders.back());
		m_vecFolders.pop_back();
	}
}

void EraseImpl::OnFilesListed()
{
	if (m_bOK)
	{
		if (NotificationMsgBox())
		{
			if (!m_vecFiles.empty())
				EraseAllFiles();
			else if (!m_vecFolders.empty())
				EraseAllFolders();
			else 
				OnEraseDone();
		}
		else OnEraseDone();
	}	

	if (!m_bOK)
		OnEraseDone();
}

void EraseImpl::OnFilesErased()
{
	if (m_bOK)
	{
		if (!m_vecFolders.empty())
			EraseAllFolders();
		else OnEraseDone();
	}

	if (!m_bOK)
		OnEraseDone();
}

void EraseImpl::OnFoldersErased()
{
	OnEraseDone();
}

void EraseImpl::OnEraseDone(BOOL bRefreshLst) 
{ 
	m_bEraseDone = TRUE;
	if (bRefreshLst)
		m_pMainDialog->m_lstFolderRemote.Refresh();
}

void EraseImpl::SftpListErase(CStringW const& strFolder)
{
	if (!m_bOK) return;

	RefPtr<ListEraseMsg> newList(new ListEraseMsg(m_pMainDialog->GetSafeHwnd(), WinMsg::SftpListErase));
	newList->m_strFolder = strFolder;
	m_sftpChannel->List(strFolder, newList);
	m_nOpenRequests++;
}

void EraseImpl::SftpRemove(CStringW const& strFile)
{
	if (!m_bOK) return;

	RefPtr<SftpMsg> sftpMsg(new SftpMsg(m_pMainDialog->GetSafeHwnd(), WinMsg::SftpRemove));
	m_sftpChannel->Remove(strFile, sftpMsg);
	m_nOpenRequests++;
}

void EraseImpl::SftpRmDir(CStringW const& strFolder)
{
	if (!m_bOK) return;

	RefPtr<SftpMsg> sftpMsg(new SftpMsg(m_pMainDialog->GetSafeHwnd(), WinMsg::SftpRmDir));
	m_sftpChannel->RmDir(strFolder, sftpMsg);
	m_nOpenRequests++;
}

LRESULT EraseImpl::OnSftpListErase(ListEraseMsg const& list)
{
	m_nOpenRequests--;

	if (m_bOK)
	{				
		if (list.Success())
		{
			m_vecFiles.insert(m_vecFiles.end(), list.m_vecFiles.begin(), list.m_vecFiles.end());
			m_vecFolders.insert(m_vecFolders.end(), list.m_vecFolders.begin(), list.m_vecFolders.end());

			for (size_t c = 0; c < list.m_vecFolders.size() && m_bOK; c++)		
				SftpListErase(list.m_vecFolders[c]);
		}
		else
		{
			m_bOK = FALSE;
			m_pMainDialog->ShowListErr(L"SftpListErase failed.", list.GetError());
		}
	}

	if (!m_nOpenRequests)
		OnFilesListed();
	return m_bOK;
}

LRESULT EraseImpl::OnSftpRemove(SftpMsg const& response)
{
	m_nOpenRequests--;

	if (m_bOK && !response.Success())
	{
		m_bOK = FALSE;
		m_pMainDialog->ShowSftpErr(L"SftpRemove failed", response.GetError());
	}	

	if (!m_nOpenRequests)
		OnFilesErased();
	return m_bOK;
}

LRESULT EraseImpl::OnSftpRmDir(SftpMsg const& response)
{
	m_nOpenRequests--;

	if (m_bOK && !response.Success())
	{
		m_bOK = FALSE;
		m_pMainDialog->ShowSftpErr(L"SftpRmDir failed", response.GetError());
	}

	if (!m_nOpenRequests)
		OnFoldersErased();
	return m_bOK;
}


// MainDialog dialog

MainDialog::MainDialog(CWnd* pParent /*=NULL*/)
	: CDialog(MainDialog::IDD, pParent)
	, m_strHost(L"")
	, m_nPort(0)
	, m_strUser(L"")
	, m_nState(DlgState::Disconnected)
{}

void MainDialog::SetState(UINT nState) 
{
	m_nState = nState;

	switch(m_nState)
	{
	case DlgState::Disconnected:
		{
			m_btnConnect.SetWindowText(L"Connect");
			m_btnConnect.EnableWindow();
			
			m_edtHost.EnableWindow();
			m_edtPort.EnableWindow();
			m_edtUser.EnableWindow();

			m_edtFolderRemote.EnableWindow(FALSE);
			m_tbcNavRemote.EnableWindow(FALSE);
			m_tbcActionLocal.EnableWindow(FALSE);
			m_tbcActionRemote.EnableWindow(FALSE);
			m_tbcActionTransfer.EnableWindow(FALSE);
			m_lstFolderRemote.EnableWindow(FALSE);
			m_lstTransfer.EnableWindow(FALSE);

			m_tbcNavRemote.RedrawWindow();
			m_tbcActionLocal.RedrawWindow();
			m_tbcActionRemote.RedrawWindow();
			m_tbcActionTransfer.RedrawWindow();
		}
		break;	
	case DlgState::Connected:
	case DlgState::SftpActionDone:
		{
			m_btnConnect.SetWindowText(L"Disconnect");
			m_btnConnect.EnableWindow();

			m_edtHost.EnableWindow(FALSE);
			m_edtPort.EnableWindow(FALSE);
			m_edtUser.EnableWindow(FALSE);

			m_edtFolderRemote.EnableWindow();
			m_tbcNavRemote.EnableWindow();
			m_tbcActionLocal.EnableWindow();
			m_tbcActionRemote.EnableWindow();
			m_tbcActionTransfer.EnableWindow();
			m_lstFolderRemote.EnableWindow();
			m_lstTransfer.EnableWindow();
		}
		break;
	case DlgState::Connecting:
	case DlgState::Disconnecting:
		{
			m_btnConnect.EnableWindow(FALSE);

			m_edtHost.EnableWindow(FALSE);
			m_edtPort.EnableWindow(FALSE);
			m_edtUser.EnableWindow(FALSE);
		}
		break;
	case DlgState::SftpAction:
		{
			m_edtFolderRemote.EnableWindow(FALSE);
			m_tbcNavRemote.EnableWindow(FALSE);
			m_tbcActionLocal.EnableWindow(FALSE);
			m_tbcActionRemote.EnableWindow(FALSE);
			m_tbcActionTransfer.EnableWindow(FALSE);
			m_lstFolderRemote.EnableWindow(FALSE);
			m_lstTransfer.EnableWindow(FALSE);
		}
		break;
	default:
		assert(!"Oops. Should never happen.");
	}
}

void MainDialog::SetLocalFolder(CStringW const& strFolder)
{
	m_strFolderLocal = strFolder;
	if (m_strFolderLocal[0] == '\0') 
		m_strFolderLocal = L"C:\\";

	try
	{
		m_lstFolderLocal.SetFolderInfo(std::unique_ptr<Folder>(new LocalFolder(m_strFolderLocal)));
		m_lstFolderLocal.Display();
		UpdateData(FALSE);
	}
	catch (SftpException const& e)
	{ 
		MessageBox(e.What());
		m_strFolderLocal = m_lstFolderLocal.GetFolderName();
		UpdateData(FALSE);
	}
}

void MainDialog::SetRemoteFolder(CStringW const& strFolder)
{
	CStringW strPathRemote(strFolder);
	if (strPathRemote[0] == '\0') 
		strPathRemote = L'/';

	RefPtr<MyListMsg> listMsg(new MyListMsg(GetSafeHwnd(), WinMsg::SftpList));
	listMsg->m_pRemoteFolderInfo.reset(new RemoteFolder(strPathRemote, GetSafeHwnd()));
	m_sftpChannel->List(strPathRemote, listMsg);
}

void MainDialog::ShowListErr(wchar_t const* strCaption, ListErr const& listErr)
{
	CStringW errDesc;
	errDesc.Format(L"Failed with operation=%d and ErrCode=%d.\n\nErrMsg: %s", listErr.m_listOp, listErr.m_errCode, listErr.m_errMsg.c_str());
	MessageBox(errDesc, strCaption);
}

void MainDialog::ShowSftpErr(wchar_t const* strCaption, SftpErr const& sftpErr)
{
	CStringW errDesc;
	errDesc.Format(L"Failed with ErrCode=%d.\n\nErrMsg: %s", sftpErr.m_errCode, sftpErr.m_errMsg.c_str());
	MessageBox(errDesc, strCaption);
}

void MainDialog::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Text(pDX, EDT_HOST, m_strHost);
	DDX_Control(pDX, EDT_HOST, m_edtHost);
	DDX_Text(pDX, EDT_PORT, m_nPort);
	DDX_Control(pDX, EDT_PORT, m_edtPort);
	DDX_Text(pDX, EDT_USER, m_strUser);
	DDX_Control(pDX, EDT_USER, m_edtUser);
	DDX_Control(pDX, BTN_CONNECT, m_btnConnect);	
	DDX_Text(pDX, EDT_PATH_LOCAL, m_strFolderLocal);
	DDX_Text(pDX, EDT_PATH_REMOTE, m_strFolderRemote);
	DDX_Control(pDX, EDT_PATH_REMOTE, m_edtFolderRemote);
	DDX_Control(pDX, LST_FOLDER_LOCAL, m_lstFolderLocal);
	DDX_Control(pDX, LST_FOLDER_REMOTE, m_lstFolderRemote);
	DDX_Control(pDX, LST_TRANSFER, m_lstTransfer);
	DDX_Control(pDX, EDT_PIPELINESIZEKB, m_edtPipelineSizeKb);	
	DDX_Text(pDX, EDT_PIPELINESIZEKB, m_nPipelineSizeKb);
}

void MainDialog::OnOK()
{	
	if (GetFocus() == GetDlgItem(EDT_PATH_LOCAL))
	{
		UpdateData(TRUE);
		SetLocalFolder(m_strFolderLocal);
	}
	else if (GetFocus() == GetDlgItem(EDT_PATH_REMOTE))
	{
		UpdateData(TRUE);
		SetRemoteFolder(m_strFolderRemote);
	}
	else __super::OnOK();
}

LRESULT MainDialog::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	try { return __super::WindowProc(message, wParam, lParam);	}
	catch (Exception const& e)
	{
		MessageBox(e.What());
		EndDialog(IDABORT);
		return 0;
	}
	catch (CException* e)
	{
		wchar_t desc[200]; e->GetErrorMessage(desc, 200); e->Delete();
		MessageBox(desc);
		EndDialog(IDABORT);
		return 0;
	}
}

BEGIN_MESSAGE_MAP(MainDialog, CDialog)
	ON_WM_PAINT()	
	ON_BN_CLICKED(BTN_CONNECT, &MainDialog::OnBnClickedBtnConnect)
	ON_EN_KILLFOCUS(EDT_PIPELINESIZEKB, &MainDialog::OnEnKillFocusPipelineSizeKb)
	ON_MESSAGE(WinMsg::ClientHostKey, &MainDialog::OnClientHostKey)
	ON_MESSAGE(WinMsg::ClientFurtherAuth, &MainDialog::OnClientFurtherAuth)
	ON_MESSAGE(WinMsg::ClientDisconnected, &MainDialog::OnClientDisconnected)
	ON_MESSAGE(WinMsg::ClientConnected, &MainDialog::OnClientConnected)
	ON_MESSAGE(WinMsg::SftpOpened, &MainDialog::OnSftpOpened)
	ON_MESSAGE(WinMsg::SftpRealPath, &MainDialog::OnSftpRealPath)
	ON_MESSAGE(WinMsg::SftpList, &MainDialog::OnSftpList)
	ON_MESSAGE(WinMsg::SftpListTransfer, &MainDialog::OnSftpListTransfer)
	ON_MESSAGE(WinMsg::SftpTransfer, &MainDialog::OnSftpTransfer)
	ON_MESSAGE(WinMsg::SftpTransferDone, &MainDialog::OnSftpTransferDone)
	ON_MESSAGE(WinMsg::SftpListErase, &MainDialog::OnSftpListErase)
	ON_MESSAGE(WinMsg::SftpRemove, &MainDialog::OnSftpRemove)
	ON_MESSAGE(WinMsg::SftpRmDir, &MainDialog::OnSftpRmDir)
	ON_MESSAGE(WinMsg::SftpStat, &MainDialog::OnSftpStat)
	ON_MESSAGE(WinMsg::SftpSetStat, &MainDialog::OnSftpSetStat)
	ON_MESSAGE(WinMsg::SftpRename, &MainDialog::OnSftpRename)
	ON_MESSAGE(WinMsg::SftpMkDir, &MainDialog::OnSftpMkDir)
END_MESSAGE_MAP()

BOOL MainDialog::OnInitDialog()
{
	m_strHost = L"127.0.0.1";
	m_nPort = 22;
	m_nPipelineSizeKb = 512;
	__super::OnInitDialog();

	// init ListCtrls
	m_lstFolderLocal.InsertColumn(0, FolderSort::Name, L"Local Name", LVCFMT_LEFT, 168);
	m_lstFolderLocal.InsertColumn(1, FolderSort::Size, L"Size", LVCFMT_RIGHT, 80);
	m_lstFolderLocal.InsertColumn(2, FolderSort::Type, L"Type", LVCFMT_LEFT, 120);
	m_lstFolderLocal.InsertColumn(3, FolderSort::MTime, L"Date Modified", LVCFMT_LEFT, 100);
	m_lstFolderLocal.InsertColumn(4, FolderSort::Attrs, L"Attributes", LVCFMT_LEFT, 70);

	m_lstFolderRemote.InsertColumn(0, FolderSort::Name, L"Remote Name", LVCFMT_LEFT, 168);
	m_lstFolderRemote.InsertColumn(1, FolderSort::Size, L"Size", LVCFMT_RIGHT, 80);
	m_lstFolderRemote.InsertColumn(2, FolderSort::Type, L"Type", LVCFMT_LEFT, 120);
	m_lstFolderRemote.InsertColumn(3, FolderSort::MTime, L"Date Modified", LVCFMT_LEFT, 100);
	m_lstFolderRemote.InsertColumn(4, FolderSort::Attrs, L"Attributes", LVCFMT_LEFT, 70);

	m_lstTransfer.InsertColumn(0, L"", LVCFMT_LEFT, 25);
	m_lstTransfer.InsertColumn(1, L"Status", LVCFMT_LEFT, 80);
	m_lstTransfer.InsertColumn(2, L"Source File", LVCFMT_LEFT, 200);
	m_lstTransfer.InsertColumn(3, L"Source Directory", LVCFMT_LEFT, 200);
	m_lstTransfer.InsertColumn(4, L"Destination Directory", LVCFMT_LEFT, 200);
	m_lstTransfer.InsertColumn(5, L"Current Size", LVCFMT_RIGHT, 80);
	m_lstTransfer.InsertColumn(6, L"Final Size", LVCFMT_RIGHT, 80);	
	m_lstTransfer.InsertColumn(7, L"Speed", LVCFMT_LEFT, 80);
	m_lstTransfer.InsertColumn(8, L"Time", LVCFMT_LEFT, 80);
	m_lstTransfer.InsertColumn(9, L"Message", LVCFMT_LEFT, 80);
	m_lstTransfer.SetExtendedStyle(m_lstTransfer.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	m_imageList.Attach(ImageList_LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDB_IMAGELIST), 16, 0, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION));
	m_lstTransfer.SetImageList(&m_imageList, LVSIL_SMALL);

	wchar_t szPath[MAX_PATH];
	if (!SHGetSpecialFolderPathW(NULL, szPath, CSIDL_DESKTOP, FALSE))
		throw SftpFatalException(L"SHGetSpecialFolderPathW failed.");
	SetLocalFolder(szPath);

	// init ToolbarCtrls
	m_tbcNavLocal.Create(FALSE, this, TBC_NAVIGATION_LOCAL);
	m_tbcNavLocal.SetImageList(&m_imageList);
	m_tbcNavLocal.AddButton(1, WinCmd::GoUpLocal);
	m_tbcNavLocal.AddButton(0, WinCmd::RefreshLocal);

	m_tbcNavRemote.Create(FALSE, this, TBC_NAVIGATION_REMOTE);
	m_tbcNavRemote.SetImageList(&m_imageList);
	m_tbcNavRemote.AddButton(1, WinCmd::GoUp);
	m_tbcNavRemote.AddButton(0, WinCmd::Refresh);

	m_tbcActionLocal.Create(TRUE, this, TBC_ACTION_LOCAL);
	m_tbcActionLocal.SetImageList(&m_imageList);
	m_tbcActionLocal.AddButton(2, WinCmd::Upload, "Upload");

	m_tbcActionRemote.Create(TRUE, this, TBC_ACTION_REMOTE);
	m_tbcActionRemote.SetImageList(&m_imageList);
	m_tbcActionRemote.AddButton(3, WinCmd::Download, "Download");
	m_tbcActionRemote.AddButton(5, WinCmd::Erase, "Erase");
	m_tbcActionRemote.AddButton(6, WinCmd::Stat, "Properties");
	m_tbcActionRemote.AddButton(7, WinCmd::Rename, "Rename");
	m_tbcActionRemote.AddButton(4, WinCmd::MkDir, "New Folder");

	m_tbcActionTransfer.Create(TRUE, this, TBC_ACTION_TRANSFERS);
	m_tbcActionTransfer.SetImageList(&m_imageList);
	m_tbcActionTransfer.AddButton(8, WinCmd::RemoveSelected, "Remove");
	m_tbcActionTransfer.AddButton(8, WinCmd::RemoveCompleted, "Remove completed");
	m_tbcActionTransfer.AddButton(9, WinCmd::RemoveAll, "Remove all");
	m_tbcActionTransfer.AddSeparator();
	m_tbcActionTransfer.AddButton(10, WinCmd::Pause, "Pause", BTNS_CHECK | BTNS_AUTOSIZE);

	SetState(DlgState::Disconnected);

	// init ErrorHandler, Client & SftpChannel
	m_client = new MyClient(GetSafeHwnd());
	m_sftpChannel = new ClientSftpChannel(m_client);
	TransferImpl::Init(this, &m_lstTransfer ,m_sftpChannel);
	EraseImpl::Init(this, m_sftpChannel);

	return TRUE;
}

BOOL MainDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{	
	UINT id = LOWORD(wParam);
	switch (id)
	{
	case WinCmd::GoUpLocal:		m_lstFolderLocal.GoUp(); break;
	case WinCmd::RefreshLocal:	m_lstFolderLocal.Refresh(); break;
	case WinCmd::GoUp:			m_lstFolderRemote.GoUp(); break;
	case WinCmd::Refresh:		m_lstFolderRemote.Refresh(); break;
	case WinCmd::Upload:		TransferImpl::UploadSelectedLstItems(m_lstFolderLocal); break;
	case WinCmd::Download:		TransferImpl::DownloadSelectedLstItems(m_lstFolderRemote); break;
	case WinCmd::Erase:			EraseImpl::EraseSelectedLstItems(m_lstFolderRemote); break;
	case WinCmd::Stat:
		{
			std::vector<UINT> const vecItems = m_lstFolderRemote.GetSelectedItems();
			for (UINT c = 0; c < vecItems.size(); c++)
			{
				File* file = m_lstFolderRemote.GetSortedFileInfo(vecItems[c]);
				RefPtr<MyStatMsg> stat(new MyStatMsg(this->GetSafeHwnd(), WinMsg::SftpStat));
				stat->m_strFile = file->GetName();
				stat->m_strFileType = file->GetType();
				stat->m_strFolder = m_lstFolderRemote.GetFolderName();				

				m_sftpChannel->Stat(file->GetFullName(), FlowSshC_AttrFlags_Size | FlowSshC_AttrFlags_CreateTime
					| FlowSshC_AttrFlags_ModifyTime | FlowSshC_AttrFlags_AccessTime
					| FlowSshC_AttrFlags_OwnerGroup | FlowSshC_AttrFlags_Permissions, stat);
			}
		} 
		break;
	case WinCmd::Rename:
		{
			std::vector<UINT> vecItems = m_lstFolderRemote.GetSelectedItems();
			for (size_t c=0; c<vecItems.size(); c++)
			{
				File* file = m_lstFolderRemote.GetSortedFileInfo(vecItems[c]);
				RenameFileDialog dlg(file->GetFullName(), this);
				if (dlg.DoModal() == IDOK && dlg.GetOldName() != dlg.GetNewName())
				{
					RefPtr<SftpMsg> sftpEvent(new SftpMsg(this->GetSafeHwnd(), WinMsg::SftpRename));
					m_sftpChannel->Rename(dlg.GetOldName(), dlg.GetNewName(), sftpEvent);
				}
			}
		} 
		break;
	case WinCmd::MkDir:
		{
			FolderNameDialog dlg(this);
			if (dlg.DoModal() == IDOK)
			{
				FileAttrs fileAttrs;
				fileAttrs.m_type = FlowSshC_FileType_Directory;

				RefPtr<SftpMsg> progress(new SftpMsg(this->GetSafeHwnd(), WinMsg::SftpMkDir));
				m_sftpChannel->MkDir(ConcatPathRemote(m_strFolderRemote, dlg.GetNewFolder()), fileAttrs, progress);
			}
		} 
		break;
	case WinCmd::RemoveSelected:	m_lstTransfer.DeleteSelectedItems(); break;
	case WinCmd::RemoveCompleted:	m_lstTransfer.DeleteCompletedItems(); break;
	case WinCmd::RemoveAll:			m_lstTransfer.DeleteAllItems(); break;
	case WinCmd::Pause:				TransferImpl::EnableTransfers(!m_tbcActionTransfer.IsButtonChecked(WinCmd::Pause)); break;
	}
	return __super::OnCommand(wParam, lParam);
}

void MainDialog::OnBnClickedBtnConnect()
{
	if (!IsConnected())	// connect client
	{
		SetState(DlgState::Connecting);
		UpdateData();

		if (m_strHost.GetLength())
			m_client->SetHost(m_strHost);
		if(m_nPort)
			m_client->SetPort(m_nPort);
		if (m_strUser.GetLength())
			m_client->SetUserName(m_strUser);

		UserAuthDialog dlg(m_client.Get(), this);
		if (dlg.DoModal() == IDOK)
		{
			RefPtr<ProgressMsg> progress(new ProgressMsg(this->GetSafeHwnd(), WinMsg::ClientConnected));
			m_client->Connect(progress);			
		}
		else SetState(DlgState::Disconnected);
	}
	else // disconnect client
	{
		SetState(DlgState::Disconnecting);		
		m_sftpChannel->Close(RefPtr<ProgressHandler>(new ProgressHandler));
		m_client->Disconnect(RefPtr<ProgressHandler>(new ProgressHandler));
	}	
}

void MainDialog::OnEnKillFocusPipelineSizeKb()
{
	UpdateData(); // updated m_nPipelineSizeKb
}

LRESULT MainDialog::OnClientHostKey(WPARAM wParam, LPARAM)
{
	PublicKey* pKey = reinterpret_cast<PublicKey*>(wParam);

	CStringW csDesc;
	csDesc =   L"Received the following host key:\n\n";
	csDesc +=  L"  MD5 Fingerprint: " + CStringW(pKey->GetMd5().c_str()) + L'\n';
	csDesc +=  L"  Bubble-Babble: " + CStringW(pKey->GetBubbleBabble().c_str()) + L"\n\n";
	csDesc +=  L"  SHA-256: " + CStringW(pKey->GetSha256().c_str()) + L"\n\n";
	csDesc +=  L"Accept the key? ";

	return MessageBox(csDesc, L"Server authentication", MB_YESNO) == IDYES;
}

LRESULT MainDialog::OnClientFurtherAuth(WPARAM wParam, LPARAM)
{
	Client::FurtherAuth* furtherAuth = reinterpret_cast<Client::FurtherAuth*>(wParam);
	if (furtherAuth->IsPasswordRemaining() || furtherAuth->IsPublicKeyRemaining())
	{
		UserAuthDialog dlg(furtherAuth, this);
		return dlg.DoModal() == IDOK;
	}
	return FALSE;
}

LRESULT MainDialog::OnClientDisconnected(WPARAM wParam, LPARAM lParam)
{
	UINT reason		= static_cast<UINT>(wParam);
	wchar_t* desc	= reinterpret_cast<wchar_t*>(lParam);

	if (reason != FlowSshC_DisconnectReason_ByClient)
		MessageBox(desc, L"Client disconnected");

	SetState(DlgState::Disconnected);
	m_lstFolderLocal.Refresh();
	return TRUE;
}

LRESULT MainDialog::OnClientConnected(WPARAM wParam, LPARAM)
{
	ProgressMsg* progress = reinterpret_cast<ProgressMsg*>(wParam);
	if (!progress->Success())
	{
		CStringW errTitle;
		CStringW errDesc = progress->GetAuxInfo().size() ? progress->GetAuxInfo().c_str() : L"(no additional info)";
		
		switch(progress->GetTaskSpecificStep())
		{
		case FlowSshC_ConnectStep_ConnectToProxy:		errTitle = L"Connecting to proxy server failed: "; break;
		case FlowSshC_ConnectStep_ConnectToSshServer:	errTitle = L"Connecting to SSH server failed: "; break;
		case FlowSshC_ConnectStep_SshVersionString:		errTitle = L"SSH version string failed: "; break;
		case FlowSshC_ConnectStep_SshKeyExchange:		errTitle = L"SSH key exchange failed: "; break;
		case FlowSshC_ConnectStep_SshUserAuth:			errTitle = L"SSH authentication failed: "; break;
		default:										errTitle = L"Connecting failed at unknown step: "; break;
		}
		SetState(DlgState::Disconnected);
		MessageBox(errDesc, errTitle);		
		return FALSE;
	}

	RefPtr<ProgressMsg> progressSftpOpen(new ProgressMsg(this->GetSafeHwnd(), WinMsg::SftpOpened));	
	m_sftpChannel->Open(progressSftpOpen);

	return FALSE;
}

LRESULT MainDialog::OnSftpOpened(WPARAM wParam, LPARAM)
{
	ProgressMsg* progress = reinterpret_cast<ProgressMsg*>(wParam);
	if (!progress->Success())
	{
		CStringW errTitle;
		CStringW errDesc = progress->GetAuxInfo().size() ? progress->GetAuxInfo().c_str() : L"(no additional info)";

		switch(progress->GetTaskSpecificStep())
		{
		case FlowSshC_ClientSftpChannelOpenStep_OpenRequest: errTitle = L"Opening SFTP channel failed"; break;
		case FlowSshC_ClientSftpChannelOpenStep_SftpRequest: errTitle = L"Requesting SFTP subsystem failed"; break;
		case FlowSshC_ClientSftpChannelOpenStep_InitPacket:  errTitle = L"Initializing SFTP protocol failed"; break;
		default:											 errTitle = L"Opening SFTP channel failed at unknown step"; break;
		}
		SetState(DlgState::Disconnected);		
		MessageBox(errDesc, errTitle);

		m_client->Disconnect(RefPtr<ProgressHandler>(new ProgressHandler));
		return FALSE;
	}
	SetState(DlgState::Connected);

	if (m_strFolderRemote.IsEmpty())
	{
		RefPtr<RealPathMsg> realPath(new RealPathMsg(this->GetSafeHwnd(), WinMsg::SftpRealPath));	
		m_sftpChannel->RealPath(L"", realPath);
	}
	else SetRemoteFolder(m_strFolderRemote);
	return TRUE;
}

LRESULT MainDialog::OnSftpRealPath(WPARAM wParam, LPARAM)
{
	RealPathMsg* realPath = reinterpret_cast<RealPathMsg*>(wParam);
	if (!realPath->Success())
	{
		ShowSftpErr(L"SftpRealPath failed", realPath->GetError());
		return FALSE;
	}

	m_strFolderRemote = realPath->GetRealPath().c_str();	
	UpdateData(FALSE);
	SetRemoteFolder(m_strFolderRemote);
	return TRUE;
}

LRESULT MainDialog::OnSftpList(WPARAM wParam, LPARAM)
{
	MyListMsg* list = reinterpret_cast<MyListMsg*>(wParam);
	if (!list->Success())
	{
		ShowListErr(L"SftpList failed.", list->GetError());
		m_strFolderRemote = m_lstFolderRemote.GetFolderName();
		UpdateData(FALSE);
		return FALSE;
	}
		
	m_strFolderRemote = list->m_pRemoteFolderInfo->GetName();
	UpdateData(FALSE);

	m_lstFolderRemote.SetFolderInfo(std::move(list->m_pRemoteFolderInfo));
	m_lstFolderRemote.Display();
	return TRUE;
}

LRESULT MainDialog::OnSftpListTransfer(WPARAM wParam, LPARAM)
{
	ListTransferMsg* list = reinterpret_cast<ListTransferMsg*>(wParam);
	return TransferImpl::OnSftpListTransfer(*list);
}

LRESULT MainDialog::OnSftpTransfer(WPARAM wParam, LPARAM lParam)
{
	FlowSshC_TransferStat* pTransferStat = reinterpret_cast<FlowSshC_TransferStat*>(wParam);
	return TransferImpl::OnSftpTransfer(*pTransferStat, UINT(lParam));
}

LRESULT MainDialog::OnSftpTransferDone(WPARAM wParam, LPARAM lParam)
{
	MyTransferMsg* transfer = reinterpret_cast<MyTransferMsg*>(wParam);
	return TransferImpl::OnSftpTransferDone(*transfer, UINT(lParam));
}

LRESULT MainDialog::OnSftpListErase(WPARAM wParam, LPARAM)
{
	ListEraseMsg* list = reinterpret_cast<ListEraseMsg*>(wParam);
	return EraseImpl::OnSftpListErase(*list);
}

LRESULT MainDialog::OnSftpRemove(WPARAM wParam, LPARAM)
{
	SftpMsg* response = reinterpret_cast<SftpMsg*>(wParam);
	return EraseImpl::OnSftpRemove(*response);
}

LRESULT MainDialog::OnSftpRmDir(WPARAM wParam, LPARAM)
{
	SftpMsg* response = reinterpret_cast<SftpMsg*>(wParam);
	return EraseImpl::OnSftpRmDir(*response);
}

LRESULT MainDialog::OnSftpStat(WPARAM wParam, LPARAM)
{
	MyStatMsg* stat = reinterpret_cast<MyStatMsg*>(wParam);
	if (!stat->Success())
	{
		ShowSftpErr(L"SftpStat failed", stat->GetError());
		return FALSE;
	}

	FilePropertiesDialog dlg(stat, this);
	if (dlg.DoModal() == IDOK && dlg.FileAttrsChanged())
	{		
		CStringW file = ConcatPathRemote(stat->m_strFolder, stat->m_strFile);

		RefPtr<SftpMsg> response(new SftpMsg(this->GetSafeHwnd(), WinMsg::SftpSetStat));
		m_sftpChannel->SetStat(file, dlg.GetFileAttrs(), response);
	}
	return TRUE;
}

LRESULT MainDialog::OnSftpSetStat(WPARAM wParam, LPARAM)
{
	SftpMsg* response = reinterpret_cast<SftpMsg*>(wParam);
	if (!response->Success())
	{
		ShowSftpErr(L"SftpSetStat failed", response->GetError());
		return FALSE;
	}	
	return TRUE;
}

LRESULT MainDialog::OnSftpRename(WPARAM wParam, LPARAM)
{
	SftpMsg* response = reinterpret_cast<SftpMsg*>(wParam);
	if (!response->Success())
	{
		ShowSftpErr(L"SftpRename failed", response->GetError());
		return FALSE;
	}

	m_lstFolderRemote.Refresh();
	return TRUE;
}

LRESULT MainDialog::OnSftpMkDir(WPARAM wParam, LPARAM)
{
	SftpMsg* response = reinterpret_cast<SftpMsg*>(wParam);
	if (!response->Success())
	{
		ShowSftpErr(L"SftpMkDir failed", response->GetError());
		return FALSE;
	}

	m_lstFolderRemote.Refresh();
	return TRUE;
}


// SftpApp

SftpApp theApp;

BOOL SftpApp::InitInstance()
{
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	CWinApp::InitInstance();

	// Initialize FlowSsh and register an ErrorHandler for uncaught exceptions in user-defined handlers.
	// Example: If there is an uncaught exception in MyClient::OnHostKey, 
	//          then this is reported in MyErrorHandler::OnExceptionInHandler.
	Initializer init(new MyErrorHandler());

	// For use in deployed applications where Bitvise SSH Client might not be installed, 
	// provide an activation code using SetActCode to ensure that FlowSsh does not  
	// display an evaluation dialog. On computers with Bitvise SSH Client, use of 
	// FlowSsh is permitted under the same terms as the Client; in this case, there 
	// will be no evaluation dialog, and SetActCode does not have to be called.
	//
	//try { init.SetActCode(L"Enter Your Activation Code Here"); }
	//catch (Exception const& e) { MessageBox(NULL, e.What(), L"SetActCode failed", MB_OK); }
	
	MainDialog dlg;
	m_pMainWnd = &dlg;
	dlg.DoModal();

	return FALSE;
}

BEGIN_MESSAGE_MAP(SftpApp, CWinApp)
END_MESSAGE_MAP()


} // namespace FlowSshCpp_SftpGui