www.pudn.com > 完整的FTP客户端ftpwanderersrc.zip > DownloadThread.cpp


/****************************************************************/ 
/*																*/ 
/*  DownloadThread.cpp											*/ 
/*																*/ 
/*  Implementation of the CDownloadThread class.				*/ 
/*	This class downloads a file from a FTP server in a seperate	*/ 
/*  thread. It sends a notification when finished/aborted.		*/ 
/*																*/ 
/*  Programmed by Pablo van der Meer							*/ 
/*  Copyright Pablo Software Solutions 2002						*/ 
/*	http://www.pablovandermeer.nl								*/ 
/*																*/ 
/*  Last updated: 15 may 2002									*/ 
/*																*/ 
/****************************************************************/ 
 
 
#include "stdafx.h" 
#include "DownloadThread.h" 
 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE 
static char THIS_FILE[] = __FILE__; 
#endif 
 
IMPLEMENT_DYNCREATE(CDownloadThread, CWinThread) 
 
CDownloadThread::CDownloadThread() 
{ 
	m_bAutoDelete = FALSE; 
	m_dwFileLength = 0; 
	m_bTransferFailed = FALSE; 
	m_bDirectoryCreated  = FALSE; 
 
	m_nConnectionTimeout = 3000; 
	m_pFtpConnection = NULL; 
	m_nRetries = 1; 
	m_nRetryDelay = 10; 
	m_nPort = 21; 
	m_bUsePASVMode = FALSE; 
	 
    // kill event starts out in the signaled state 
    m_hEventKill = CreateEvent(NULL, TRUE, FALSE, NULL); 
    m_hEventDead = CreateEvent(NULL, TRUE, FALSE, NULL); 
 
	// default to binary transfer 
	m_dwTransferType = FTP_TRANSFER_TYPE_BINARY; 
} 
 
 
CDownloadThread::~CDownloadThread() 
{ 
	CloseHandle(m_hEventKill); 
	CloseHandle(m_hEventDead); 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : InitInstance										*/ 
/* Description   : Initialize this instance of the thread.			*/ 
/*				   In this case it's actually the place where		*/ 
/*				   everything takes place.							*/ 
/*																	*/ 
/********************************************************************/ 
BOOL CDownloadThread::InitInstance() 
{ 
	if (WaitForSingleObject(m_hEventKill, 0) != WAIT_TIMEOUT) 
	{ 
		// aborted by user 
		m_strResult = "\"" + m_strRemoteName + "\" download cancelled."; 
 
		// Let the main thread know we are finished 
		::PostMessage(m_pTransferManager->m_hWnd, WM_DOWNLOAD_FINISHED, (WPARAM)this, 0); 
 
		// event is signaled 
		return FALSE; 
	} 
	 
	PostDownloadStatus("Initializing"); 
 
	// set animation to 'Downloading' 
	m_ProgressDlg.m_nAnimationID = IDR_AVI1; 
	 
	// create dummy window as parent for the progress dialog 
	if (!m_wndDummy.CreateEx(0, AfxRegisterWndClass(0), "CDownload Dummy Window", 
			WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL)) 
		return FALSE; 
 
	// Create the progress dialog box. 
	if (!m_ProgressDlg.Create(&m_wndDummy, m_hEventKill)) 
		return FALSE; 
	 
	// wait until dialog is created 
	WaitForProgressDialog(); 
 
	// when reducing the timeout connection, the internet connection speed is faster. 
	if (m_nConnectionTimeout)  
	{ 
		m_InternetSession.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, m_nConnectionTimeout); 
		m_InternetSession.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, m_nConnectionTimeout); 
		m_InternetSession.SetOption(INTERNET_OPTION_SEND_TIMEOUT, m_nConnectionTimeout); 
	} 
 
	// catch all exceptions 
	try	 
	{	 
		// start receiving file  
		DownloadFile(m_strRemoteName, m_strLocalName); 
	} 
	catch(char *pszError) 
	{ 		 
		m_strResult = pszError; 		 
	} 
	catch (CFileException * pEx) 
	{ 	 
		// file can't be opened 
		m_strResult.Format("File Error %d %s", pEx->m_cause, pEx->m_strFileName); 		 
	} 
	catch (CInternetException* pEx) 
	{ 
		// internet exception 
		m_strResult.Format("Internet Exception Error %d", pEx->m_dwError); 
		if (pEx->m_dwError == ERROR_INTERNET_EXTENDED_ERROR) 
		{ 
			char szBuffer[1024]; 
			DWORD dwBufferLength = 1024; 
			DWORD dwError = 0; 
			::InternetGetLastResponseInfo(&dwError, szBuffer, &dwBufferLength); 
			m_strResult += szBuffer; 
		} 
	} 
	// fix error message 
	m_strResult.Replace('\n', ' '); 
	m_strResult.Remove('\r'); 
 
	if (!m_strResult.IsEmpty()) 
	{ 
		// exception was thrown 
		m_bTransferFailed = TRUE; 
	} 
	else 
	if (WaitForSingleObject(m_hEventKill, 0) != WAIT_TIMEOUT) 
	{ 
		// aborted by user 
		m_strResult = "\"" + m_strRemoteName + "\" download cancelled."; 
	} 
	else  
	{ 
		if (m_bDirectoryCreated) 
		{ 
			// successfull created folder 
			m_strResult = "\"" + m_strLocalName + "\" folder successfully created."; 
		} 
		else 
		{ 
			// successfull downloaded file 
			m_strResult = "\"" + m_strRemoteName + "\" successfully downloaded."; 
		} 
	} 
 
	// Let the main thread know we are done 
	::PostMessage(m_pTransferManager->m_hWnd, WM_DOWNLOAD_FINISHED, (WPARAM)this, 0); 
 
	// avoid entering standard message loop by returning FALSE 
    return FALSE; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : ExitInstance										*/ 
/* Description   : Perform any per-thread cleanup here.				*/ 
/*																	*/ 
/********************************************************************/ 
int CDownloadThread::ExitInstance() 
{ 
	if (m_pFtpConnection) 
	{ 
		m_pFtpConnection->Close(); 
 
		// delete ftp connection 
		delete m_pFtpConnection; 
		m_pFtpConnection = NULL; 
	} 
 
	// Close this session 
	m_InternetSession.Close(); 
 
    m_ProgressDlg.DestroyWindow(); 
 
	m_wndDummy.DestroyWindow(); 
	 
	return CWinThread::ExitInstance(); 
} 
 
BEGIN_MESSAGE_MAP(CDownloadThread, CWinThread) 
	//{{AFX_MSG_MAP(CDownloadThread) 
		// NOTE - the ClassWizard will add and remove mapping macros here. 
	//}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : KillThread										*/ 
/* Description   : With this public member you can kill this thread	*/ 
/*				   for the outside.									*/ 
/*																	*/ 
/********************************************************************/ 
void CDownloadThread::KillThread() 
{ 
    // reset the m_hEventKill which signals the thread to shutdown 
    VERIFY(SetEvent(m_hEventKill)); 
 
	DWORD dwResult; 
	// make sure it is running 
	while (dwResult = ResumeThread() > 1) 
	{ 
		if (dwResult == 0xFFFFFFFF) 
			break; 
	} 
 
    // allow thread to run at higher priority during kill process 
    SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL); 
    WaitForSingleObject(m_hEventDead, INFINITE); 
    WaitForSingleObject(m_hThread, INFINITE); 
 
    // now delete CWinThread object since it's no longer necessary 
    delete this; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : Delete											*/ 
/* Description   : Called when thread is destructed.				*/ 
/*																	*/ 
/********************************************************************/ 
void CDownloadThread::Delete() 
{ 
    // calling the base here won't do anything but it is a good habit 
	CWinThread::Delete(); 
 
	// acknowledge receipt of kill notification 
	VERIFY(SetEvent(m_hEventDead)); 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : DownloadFile										*/ 
/* Description   : This is where the real downloading takes place.	*/ 
/*																	*/ 
/********************************************************************/ 
void CDownloadThread::DownloadFile(CString &source, CString &dest) 
{ 
	m_ProgressDlg.SetWindowTitle("Receiving " + source); 
	m_ProgressDlg.SetPos(0); 
	m_ProgressDlg.SetSecondStatus(""); 
 
	// only a directory name was specified 
	if (dest.Right(1) == "\\") 
	{ 
		// try to create directory structure 
		CreateLocalDirectory(dest); 
 
		m_bDirectoryCreated = TRUE; 
		return; 
	} 
 
	PostDownloadStatus("Connecting"); 
 
	// try x times to connect 
	int nRetries = m_nRetries; 
	while (nRetries > 0) 
	{ 
		nRetries--; 
		try 
		{ 
			// Create new ftp connection to retrieve file 
			wsprintf(m_szStatus, "Connecting to %s (Attempt %d of %d)", m_strServerName, m_nRetries - nRetries, m_nRetries);  
			m_ProgressDlg.SetStatus(m_szStatus); 
 
			DoEvents(); 
			 
			// Connect to FTP Server 
			m_pFtpConnection = m_InternetSession.GetFtpConnection(m_strServerName, m_strUserName, m_strPassword, m_nPort, m_bUsePASVMode); 
			nRetries = 0; 
		} 
		catch (CInternetException* pEx) 
		{ 
			m_pFtpConnection = NULL; 
 
			// catch errors from WinINet 
			pEx->GetErrorMessage(m_szStatus, sizeof(m_szStatus)); 
			pEx->Delete(); 
 
			if (nRetries > 0) 
			{ 
				wsprintf(m_szStatus, "Failed to connect to %s (Attempt %d of %d)", m_strServerName, m_nRetries - nRetries, m_nRetries);  
				m_ProgressDlg.SetStatus(m_szStatus); 
				 
				wsprintf(m_szStatus, "Retrying after %d seconds...", m_nRetryDelay);  
				m_ProgressDlg.SetSecondStatus(m_szStatus); 
 
				// wait for m_nRetryDelay seconds, while m_hEventKill is not signaled 
				if (WaitWithMessageLoop(m_hEventKill, m_nRetryDelay * 1000) == TRUE) 
				{ 
					// user canceled download 
					throw m_szStatus;  
				} 
				PostDownloadStatus("Retrying"); 
				m_ProgressDlg.SetSecondStatus(""); 
			} 
			else 
			{ 
				throw m_szStatus;  
			} 
		} 
	} 
 
	// split folder and file (if complete path is given) 
	int nPos = source.ReverseFind('/'); 
	if (nPos != -1) 
	{ 
		m_strCurrentDirectory = source.Left(nPos+1); 
		source = source.Mid(nPos+1); 
	} 
 
	// set current directory 
	if (!m_pFtpConnection->SetCurrentDirectory(m_strCurrentDirectory)) 
	{ 
		DWORD dwLength = 255, dwError; 
		CString strInfo; 
		InternetGetLastResponseInfo(&dwError, strInfo.GetBuffer(dwLength), &dwLength); 
		strInfo.ReleaseBuffer(); 
		strInfo.Remove('\n'); 
		strInfo.Remove('\r'); 
 
		wsprintf(m_szStatus, "%s", strInfo); 
		throw m_szStatus;  
	} 
 
 
	lstrcpy(m_szStatus, source);  
	// Show source file name 
	m_ProgressDlg.SetStatus(m_szStatus); 
	// Show destination file name 
	m_ProgressDlg.SetSecondStatus("to: " + dest); 
 
	// open destination file 
	if (m_File.Open(dest, CFile::modeCreate | CFile::modeWrite, NULL) == FALSE) 
	{ 
		wsprintf(m_szStatus, "Unable to create file %s", dest);  
		throw m_szStatus;  
	} 
	 
	wsprintf(m_szStatus, "Opening %s", source);  
	m_ProgressDlg.SetStatus(m_szStatus); 
 
	CInternetFile* pInternetFile = m_pFtpConnection->OpenFile(source, GENERIC_READ, m_dwTransferType); 
	if (!pInternetFile) 
	{ 
		wsprintf(m_szStatus, "Unable to open remote file %s", source);  
		throw m_szStatus;  
	} 
	 
	PostDownloadStatus("Downloading"); 
 
	// set max to 100% 
	m_ProgressDlg.SetUpper(100); 
 
	char buffer[BUF_SIZE]; 
	unsigned int nRead = BUF_SIZE; 
	unsigned int nTotalRead = 0; 
	while (nRead == BUF_SIZE && (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT)) 
	{ 
		// read remote data into buffer 
		nRead = pInternetFile->Read(buffer, BUF_SIZE); 
		// write buffer to data file 
		m_File.Write(buffer, nRead); 
		nTotalRead += nRead; 
		wsprintf(m_szStatus, "%s (%d of %d  bytes transferred)", source, nTotalRead, m_dwFileLength); 	 
		m_ProgressDlg.SetStatus(m_szStatus); 
 
		if (m_dwFileLength > 0) 
		{ 
			int nPos = (nTotalRead*100)/m_dwFileLength; 
			m_ProgressDlg.SetPos(nPos); 
		} 
		else 
		{ 
			m_ProgressDlg.SetPos(0); 
		} 
	} 
 
	// close the file 
	m_File.Close(); 
 
	// close internet file 
	pInternetFile->Close(); 
 
	delete pInternetFile; 
	 
	// close FTP connection 
	wsprintf(m_szStatus, "Closing connection to %s", m_strServerName);  
	m_ProgressDlg.SetStatus(m_szStatus); 
 
	PostDownloadStatus("Finished"); 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : GetLastError										*/ 
/* Description   : Get last error string.							*/ 
/*																	*/ 
/********************************************************************/ 
CString CDownloadThread::GetLastError() 
{ 
	return m_strResult; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : CreateLocalDirectory								*/ 
/* Description   : Create directory tree.							*/ 
/*																	*/ 
/********************************************************************/ 
BOOL CDownloadThread::CreateLocalDirectory(LPCTSTR lpszDirectory) 
{ 
	CString strResult = lpszDirectory; 
	 
	CString strDir; 
	BOOL bResult; 
	// create directory structure one part at a time 
	while (strResult != "") 
	{ 
		strDir += strResult.Left(strResult.Find("\\")+1); 
		strResult = strResult.Mid(strResult.Find("\\")+1); 
		bResult = CreateDirectory(strDir, 0); 
	} 
	return bResult; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : WaitForProgressDialog							*/ 
/* Description   : If the thread has just started, it may not have	*/ 
/*				   had time yet to initialize the dialog window.	*/ 
/*																	*/ 
/********************************************************************/ 
void CDownloadThread::WaitForProgressDialog() 
{ 
	if(m_ProgressDlg.m_hWnd == NULL) 
	{ 
		while(m_ProgressDlg.m_hWnd == NULL) 
		{ 
			DoEvents(); 
		} 
	} 
	if(!::IsWindow(m_ProgressDlg.m_hWnd)) 
	{ 
		while(!::IsWindow(m_ProgressDlg.m_hWnd)) 
		{ 
			DoEvents(); 
		} 
	} 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : PostDownloadStatus								*/ 
/* Description   : Post status to Transfer Manager.					*/ 
/*																	*/ 
/********************************************************************/ 
void CDownloadThread::PostDownloadStatus(LPCTSTR lpszStatus) 
{ 
	int nLength = lstrlen(lpszStatus); 
	// dynamically allocate memory for status message (receiver will delete it!) 
	LPSTR lpszData = new char[nLength+1]; 
	lstrcpy(lpszData, lpszStatus); 
	::PostMessage(m_pTransferManager->m_hWnd, WM_FTP_STATUS, (WPARAM)this, (LPARAM)lpszData); 
}