Click here to Skip to main content
15,894,460 members
Articles / Desktop Programming / MFC

The Ultimate Toolbox Home Page

Rate me:
Please Sign up or sign in to vote.
4.97/5 (141 votes)
25 Aug 2007CPOL13 min read 3.2M   91.4K   476  
The Ultimate Toolbox is now Open Source
// TransferDlg.cpp : implementation file
//

#include "stdafx.h"
#include "serial.h"
#include "TransferDlg.h"

#include "OXSCFILE.H"
#include "OXCRCCHK.H"

#include "Globals.h"

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

#define IDM_START_SENDING	(WM_USER + 10)
#define IDM_START_RECEIVING (WM_USER + 11)

//#define __USE_MESSAGES
//
// Uncomment the above directive if you wish MESSAGE-ing between the two
// sides enabled.  Obviosly a "Real" protocol would implement such a device but this 
// application is only intended as a sample and hence does not cover advanced
// topics like synchronization.  Popular protocols are Z-modem, Y-modem, and X-modem.
//
//
#define MESSAGE_TYPE		UINT
#define MESSAGE_SIZE		sizeof(UINT)
#define MESSAGE_NONE		(0x0000)
#define MESSAGE_CANCEL		(0x0001)
#define MESSAGE_STOPPED		(0x0002)
#define MESSAGE_WANTSEND	(0x0004)
#define MESSAGE_OKAYSEND	(0x0008)

/////////////////////////////////////////////////////////////////////////////
// CTransferDlg dialog

CTransferDlg::CTransferDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CTransferDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CTransferDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

	m_pFile = NULL;
	m_pCommFile = NULL;

	m_bSending = TRUE;
	m_bCancelPressed = FALSE;
}

void CTransferDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CTransferDlg)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CTransferDlg, CDialog)
	//{{AFX_MSG_MAP(CTransferDlg)
	//}}AFX_MSG_MAP
	ON_MESSAGE(IDM_START_SENDING, OnStartSending)
	ON_MESSAGE(IDM_START_RECEIVING, OnStartReceiving)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTransferDlg message handlers

BOOL CTransferDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();

	SetDlgItemText(IDC_TRANSFER_TEXT, m_sMessage);

	//
	// Start either Receivng or Sending data
	//
	if (m_bSending)
		PostMessage(IDM_START_SENDING);
	else 
		PostMessage(IDM_START_RECEIVING);

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

LONG CTransferDlg::OnStartReceiving(WPARAM, LPARAM)
{
	//
	//
	// This function (message handler) will begin receiving data on the
	// COMM port. This function is not multi-threaded, instead it uses
	// the global PumpMessages function to maintain the Applications message
	// pump.  This function should be implemented as another thread, but, as
	// mentioned before, this is left up to you to implement.
	//
	// The function will wait for incoming data to appear in the Receive queue.
	// Once data is found it begins receiving it by first, reading the filesize,
	// next the filename, and then the file checksum. After all this
	// the file is read in 1024 chunks until complete. Again, this is a very simple
	// protocol. You should investigate Z-modem, Y-modem and other popular
	// protocols.
	//
	// The file checksum is only checked once at the end of the transmission. This is
	// done for simplicity.  Idealy, you would check the checksum of each transmitted
	// packet. Thus, if the checksum of a packed failed you could simply ask to have
	// it transmitted instead of having the whole file re-transmitted (as the case
	// would be in this scenario).
	//
	// On completion, CDialog::OnOK or CDialog::OnCancel is called to close the dialog
	// window.
	//
	
	ASSERT_VALID(m_pCommFile);

	if (m_pCommFile == NULL)
	{
		CDialog::OnCancel();
		return -1;
	}
	ShowWindow(SW_SHOW);

	CString sNewFilename = GetAppDir();
	CFile f;
	BYTE bBuffer[1024];
	UINT nBytesReceived = 0;
	UINT nTotalBytesReceived = 0;

	DWORD dwFileSize;
	TCHAR szFilename[MAX_PATH];
	DWORD dwChecksum;

	TRY
	{
		// Wait
		m_sMessage.Format(_T("Waiting for incoming files..."));
		SetDlgItemText(IDC_TRANSFER_TEXT, m_sMessage);

		while (m_pCommFile->IsRxQueueEmpty() && !m_bCancelPressed)
		{
			PumpMessages();
		}

		if (m_bCancelPressed)
		{
			CDialog::OnCancel();
			return 0;
		}

		// first: read the filesize
		m_pCommFile->Read(&dwFileSize, sizeof(dwFileSize));

		// second: read the filename
		m_pCommFile->Read(szFilename, sizeof(szFilename));

		// display message about incoming file
		m_sMessage.Format(_T("file %s (%d bytes long) detected.\r\n"), szFilename, dwFileSize);
		SetDlgItemText(IDC_TRANSFER_TEXT, m_sMessage);

		// third: read the checksum
		m_pCommFile->Read(&dwChecksum, sizeof(dwChecksum));
				
		CProgressCtrl* pProg = (CProgressCtrl*) GetDlgItem(IDC_TRANSFER_PROGRESS);
		ASSERT_VALID(pProg);

		pProg->SetRange(0, 100);
		pProg->SetPos(0);

		// fourth: read the incoming data (streamed into a file object)
		sNewFilename += _T('\\');
		sNewFilename += szFilename;
		if (!f.Open(sNewFilename, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite | CFile::shareDenyRead))
		{
			m_sMessage.Format(_T("Error: Unable to create the new file."));
		}
		else
		{
			TRY
			{
				//
				// Keep reading the file until either the user cancels or 
				// the file has finsihed.
				//
				while ((nTotalBytesReceived < dwFileSize) && !m_bCancelPressed)
				{
					nBytesReceived = m_pCommFile->Read(bBuffer, sizeof(bBuffer));
					nTotalBytesReceived += nBytesReceived;
					
					m_sMessage.Format(_T("%s\n%d of %d bytes received."), sNewFilename, nTotalBytesReceived, dwFileSize);
					SetDlgItemText(IDC_TRANSFER_TEXT, m_sMessage);

					f.Write(bBuffer, nBytesReceived);
					
					pProg->SetPos(((nTotalBytesReceived * 100 / (UINT) dwFileSize)));
					PumpMessages();
				}

				if (m_bCancelPressed)
				{
#ifdef __USE_MESSAGES
					// the transfer has been cancelled; wait for the other side to
					// acknowledge receipt of this message
					MESSAGE_TYPE message = MESSAGE_CANCEL;
					m_pCommFile->Write(&message, MESSAGE_SIZE);
					do
					{
						m_pCommFile->Read(&message, MESSAGE_SIZE);
					}
					while (message != MESSAGE_STOPPED);
#endif

					m_sMessage.Format(_T("Transfer was cancelled\r\n"));

					CDialog::OnCancel();
					return 0;
				}

				f.Close();
			}
			CATCH(CFileException, e)
			{
				TCHAR szError[256];
				e->GetErrorMessage(szError, sizeof(szError));

				m_sMessage.Format(_T("Error: %s\r\n"), szError);
			}
			END_CATCH
		}
	}
	CATCH(COXSerialCommException, e)
	{
		m_sMessage.Format(_T("An Exception occurred (%d)\r\n"), e->m_cause);
	}
	END_CATCH

	// perform simple integrity check on the file
	if (nTotalBytesReceived == dwFileSize)
	{
		COXCheckSum32 checksum;
		DWORD dwNewChecksum;
		TRY
		{
			dwNewChecksum = checksum.CalculateFile(sNewFilename);
			TRACE(_T("%s has checksum of %d\n"), sNewFilename, dwNewChecksum);
		}
		CATCH(CFileException, e)
		{
			m_sMessage.Format(_T("%s successfully received\r\nChecksum test failed.\r\n"), sNewFilename);
			CDialog::OnOK();
			return 0;
		}
		END_CATCH

		if (dwChecksum == dwNewChecksum)
		{
			m_sMessage.Format(_T("%s successfully received\r\nFile passed integrity check (%d)\r\n"), sNewFilename, dwNewChecksum);
		}
		else
		{
			m_sMessage.Format(_T("%s successfully received\r\nFile failed integrity check (remote:%d; local:%d)\r\n"), sNewFilename, dwChecksum, dwNewChecksum);
		}
	}
	else
	{
		m_sMessage.Format(_T("%s was not properly received (%d bytes not received)\r\n"), sNewFilename, dwFileSize - nTotalBytesReceived);
	}


	CDialog::OnOK();

	return 0;
}

LONG CTransferDlg::OnStartSending(WPARAM, LPARAM)
{
	// 
	//
	// The function will begin sending the file over the COMM port. It assumes that
	// the awaiting client is ready to receive. Really however, A 
	// "Are you ready to receive" message should be send to the client to ensure
	// the transfer works properly. In this application if the user isn't waiting to
	// receive a file, the data will appear in his/her "Terminal" window.
	//
	// This function should run in a thread. Instead, for simplicity's sake it uses the
	// simple PumpMessage function to maintain the application's message pump.
	//
	// See CTransferDlg::OnStartReceiving for more information about the protocol used
	// to transfer files.
	//

	ASSERT_VALID(m_pFile);
	ASSERT_VALID(m_pCommFile);

	if (m_pFile == NULL || m_pCommFile == NULL || m_sFilename.IsEmpty())
	{
		CDialog::OnCancel();
		return -1;
	}

	ShowWindow(SW_SHOW);

	CProgressCtrl* pProg = (CProgressCtrl*) GetDlgItem(IDC_TRANSFER_PROGRESS);
	ASSERT_VALID(pProg);

	DWORD dwTotal = 0;
	DWORD dwCount = 0;

	// CFile object m_pFile already open
	COXCheckSum32 checksum;
	DWORD dwChecksum;
	TRY
	{
		// calculate the file checksum
		dwChecksum = checksum.CalculateFile(m_pFile);
		m_pFile->SeekToBegin();

		TRACE(_T("%s has checksum of %d\n"), m_sFilename, dwChecksum);
	}
	CATCH(CFileException, e)
	{
		m_sMessage.Format(_T("Fatal Error: Unable to calculate file checksum\r\n"));
		TRACE(_T("Unable to calculate file checksum\r\n"));
		return -1;
	}
	END_CATCH
	
	BYTE bBuffer[1024];
	TCHAR szFilename[MAX_PATH];

	lstrcpy(szFilename, (LPCTSTR) m_sFilename);

	// first: send number of bytes this file is
	DWORD dwFileSize = m_pFile->GetLength();
	m_pCommFile->Write(&dwFileSize, sizeof(DWORD));

	// second: send filename
	m_pCommFile->Write(&szFilename, sizeof(szFilename));

	// third: send checksum
	m_pCommFile->Write(&dwChecksum, sizeof(dwChecksum));
	
	pProg->SetRange(0, 100);
	pProg->SetPos(0);

	// fourth: write the file
	while ((dwCount = m_pFile->Read(bBuffer, sizeof(bBuffer))) != 0 && !m_bCancelPressed)
	{
		dwTotal += dwCount;
		m_pCommFile->Write(bBuffer, dwCount);

		m_sMessage.Format(_T("%s\n%d of %d bytes sent"), m_sFilename, dwTotal, dwFileSize);
		SetDlgItemText(IDC_TRANSFER_TEXT, m_sMessage);

		pProg->SetPos(((dwTotal * 100 / dwFileSize)));

#ifdef __USE_MESSAGES
		m_pCommFile->Read(&message, MESSAGE_SIZE);
		if (message == MESSAGE_CANCEL)
		{
			m_bCancelPressed = TRUE;
			message = MESSAGE_STOPPED;
			m_pCommFile->Write(&message, MESSAGE_SIZE);
			break;
		}
#endif

		PumpMessages();
	}

#ifdef __USE_MESSAGES
	if (message == MESSAGE_CANCEL)
		m_sMessage.Format(_T("Other side cancelled transfer; %d of %d bytes sent\r\n"), dwTotal, dwFileSize);
	else
		m_sMessage.Format(_T("Finished. Successfully sent %d byte(s).\r\n"), dwTotal);
#else
	m_sMessage.Format(_T("Finished. Successfully sent %d byte(s).\r\n"), dwTotal);
#endif

	if (m_bCancelPressed)
		CDialog::OnCancel();
	else
		CDialog::OnOK();

	return 0;
}

void CTransferDlg::OnCancel() 
{
	// User has pressed the cancel button; Disable the button and set
	// flag
	//
	m_bCancelPressed = TRUE;
	GetDlgItem(IDCANCEL)->EnableWindow(FALSE);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Canada Canada
In January 2005, David Cunningham and Chris Maunder created TheUltimateToolbox.com, a new group dedicated to the continued development, support and growth of Dundas Software’s award winning line of MFC, C++ and ActiveX control products.

Ultimate Grid for MFC, Ultimate Toolbox for MFC, and Ultimate TCP/IP have been stalwarts of C++/MFC development for a decade. Thousands of developers have used these products to speed their time to market, improve the quality of their finished products, and enhance the reliability and flexibility of their software.
This is a Organisation

476 members

Comments and Discussions