Downloading files via FTP

Environment: Visual C++ 6.0

I wrote some classes that download ftp files with a status dialog. My classes use the internet
service classes included in MFC CInternetSession, CFtpConnection, and CFileFind. The downloading
is done in a seperate thread so that the cancel button can cancel a download operation.

I want to mention two things up front on using CInternetSession.

  1. When you created your App with AppWizard if you selected link to MFC librarys dynamically be sure to
    add wininet.lib to you project settings link tab. If you link statically you should not get any errors
    for unresolved externals.

  2. If you try a debug build of a CInternetSession enabled app on computer that does not contain
    a late version of Internet Explorer it will fail. Only bother trying release versions on other
    computers. If you have VC++ 6.0, debug builds will work on the programmers computer
    becuase VC++ 6.0 installation installs a late version of Internet Explorer.

There are three classes:

  • FTPDownload() creates the dialog, monitors the cancel button and starts the seperate downloading thread.
  • FTPDownLoadFiles() Does the actual downloading in a seperate thread.
  • CProgressDlg() Displays the percent complete and has cancel button.

I have included a zip file containing the three classes each in thier own component (.ogx) file. I did
this to simplify adding these classes to your program and becuase the progress dialog resource is included.
If you are not sure of how to add components through the component gallery read about it in VC++ 6.0 help.
After the following implementation notes, I show code for the FTPDownload, and FTPDownLoadFiles classes.
I do not show the code for CProgressDlg because this code is pretty straight forward and the size of this
article would be much larger. To see the code for CProgressDlg just download the zip file.

The only thing you need to know about to use these classes is the FTPDownload constructor.

FTPDownload(CStringArray * t_Csa, DWORD ctout);//Constructor

To use pass a CStringArray * to the constructor

This CStringArray has certain important CStrings in each position.

Csa[0] == Server Name

Csa[1] == User Name

Csa[2] == Password

Csa[3] = Ftp file name

Csa[4] = Local file name (name on local drive after downloaded)

As many Files as desired can be downloaded. Just begin filenames at index 3.
File names are to be stored in alternating order, first the ftp file name then the local file name
An optional second argument specifies the connection timeout value. In my opinion 3000 seemed the best.
The default argument timeout of 0 will not change any timeout value in CInternetSession.

EXAMPLE:


//***************************ONDOWNLOADFILES*****************************
#include “FTPDownload.h”
void CMainFrame::OnDownloadFiles()
{
CStringArray Csa;
//Lets use SetSize to prevent memory fragmentation and for efficiency
Csa.SetSize(0,7);//We want at most 4 file names + first 3 CStrings stored in the C String Array Csa
Csa.Add(server_name);//Csa[0] holds server name
Csa.Add(user_name);//Csa[1] == user name
Csa.Add(pass_word);//Csa[2] == pass word

//Filenames are to be stored in alternating order.
//First the ftp file name then the local file name (what the file should be named after downloaded)
Csa.Add(ftp_fname1);//ftp file name
Csa.Add(fname1);//then local file name
Csa.Add(ftp_fname2);//ftp file name
Csa.Add(fname2);//the local file name

FTPDownload dl(&Csa, connection_timeout);//connection_timeout is optional
m_wndStatusBar.SetPaneText(0, dl.result_str);//Note that the result_str will contain text to display
//in the status bar (or wherever you want) after the download is complete.
return;
}

THE CLASSES—————————————————————-
FTPDownload CLASS HEADER FILE—————————————–
#ifndef G_FTPDOWNLOAD_H
#define G_FTPDOWNLOAD_H

#include <afxinet.h>
#include “ProgDlg.h”
//This class handles the the progress dialog, and then launches a seperate thread to handle downloading files.
class FTPDownload
{
public:
unsigned int thread_finished_flag;
unsigned int abort_flag;
CString result_str;
CProgressDlg cpd;

CStringArray * Csa;
DWORD connection_timeout;
FTPDownload(CStringArray * t_Csa, DWORD ctout = 0);//Constructor
};
#endif

FTPDownload CLASS CPP FILE—————————————–
#include “stdafx.h”
#include “FTPDownload.h”
#include “FTPDownLoadFiles.h”

UINT DownLoadFunction(LPVOID lParam);//Global Prototype for thread function

//This class handles the progress dialog, and then launches a seperate thread to handle
//downloading files.
//**********************Default CONSTRUCTOR*********************
FTPDownload::FTPDownload(CStringArray * t_Csa, DWORD ctout)
{
Csa = t_Csa;
connection_timeout = ctout;
result_str =””;
abort_flag = FALSE;
thread_finished_flag = FALSE;//When this == TRUE the other thread is finished with its job

cpd.Create();//Create the progress dialog box.

CWinThread * dl_thread = AfxBeginThread(::DownLoadFunction, this);//Start downloading thread

while (thread_finished_flag == FALSE)
if (cpd.CheckCancelButton()) abort_flag = TRUE;//If cancel pushed let other thread know it’s time to split

if (result_str.IsEmpty()==FALSE) return;//Then an exception was thrown and caught in the other thread!!
if (abort_flag) result_str = “User Cancelled Download Operation”;//Now set up result string
else result_str = “Internet Download Completed Successfully”;
return;
}

//NOT A MEMEBER FUNCTION !!!!!
//This is the seperate thread function
//****************************DOWNLOADFUNCTION***********************************
UINT DownLoadFunction(LPVOID lParam)
{
FTPDownload * pFtpI = (FTPDownload *)lParam;//I want a try block to catch any exceptions
try { FTPDownLoadFiles dl(pFtpI); }//This performs the downloading
catch(char * str)//If a file can’t be opened this is thrown
{ pFtpI->result_str = str; }
catch (CFileException * pEx)
{ pFtpI->result_str.Format(“File Error %d %s”, pEx->m_cause, pEx->m_strFileName); }
catch (CInternetException* pEx)
{
pFtpI->result_str.Format(“Internet Exception Error %d”, pEx->m_dwError);
if (pEx->m_dwError == ERROR_INTERNET_EXTENDED_ERROR)
{
char temp_str[1024];
DWORD length = 1024;
DWORD temp_int = 0;
::InternetGetLastResponseInfo(&temp_int, temp_str, &length);
pFtpI->result_str += temp_str;
}
}

pFtpI->thread_finished_flag = TRUE;//Let the main thread know we finished
return 0;//Don’t care about return value
}

FTPDownLoadFiles CLASS HEADER FILE—————————————–
#ifndef G_FTPDOWNLOAD_FILES_H
#define G_FTPDOWNLOAD_FILES_H

#include “FTPDownload.h”

//CInternetSession can throw an exception catch it like so: catch (CInternetException* pEx)

class FTPDownLoadFiles
{
protected:
CInternetSession Cis;//All argument defaults are OK.
CFtpConnection* m_Ftp_Conn;//The ftp_connection

FTPDownload * pFtpI;//Pointer to FTPDownload object in other thread that also holds dialog
CFile cfo;//CFileObject used to write file

void ReadFile(CString & source, CString & dest);
BOOL ftp_file_exists(CString & source);
void file_not_found(CString & source);

void UpdateStatus(void);
char status_str[1024];

unsigned int file_size;
CString temp_ftp_name;

public:
FTPDownLoadFiles(FTPDownload * t_pFtpI);//Constructor
};

#endif

FTPDownLoadFiles CLASS CPP FILE—————————————–

#include “stdafx.h”
#include “FTPDownLoadFiles.h”

#define DL_BUFFER_SIZE 4096

//**********************CONSTRUCTOR*********************
FTPDownLoadFiles::FTPDownLoadFiles(FTPDownload * t_pFtpI)
{//CinternetSession is contained and is created with defaults
pFtpI = t_pFtpI;
m_Ftp_Conn = 0;
//I have found that by reducing the timeout connection, the internet connection speed is faster.
if (pFtpI->connection_timeout)//Only if not 0 do we bother changing the timeout value
Cis.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, pFtpI->connection_timeout);//I like 3000

int csa_size = pFtpI->Csa->GetSize();//Get the CStringArray size
for (int i = 3;i<csa_size;i+=2)//start at index 3 past logon info
ReadFile(pFtpI->Csa->GetAt(i), pFtpI->Csa->GetAt(i+1));//Download file name, disk file name

Cis.Close();//Close this session
return;
}

//m_pConn must be set B4 calling this function
//*********************READFILE*****************************
void FTPDownLoadFiles::ReadFile(CString &source, CString &dest)
{
if (pFtpI->abort_flag) return;//Has cancel been pressed ? If yes then split!!!

pFtpI->cpd.SetHeader(source);
//Unfortunately we have to reopen the ftp connection for each file.
wsprintf(status_str, “Connecting to %s”, pFtpI->Csa->GetAt(0)); UpdateStatus();
if (pFtpI->abort_flag) return;//Make sure operation hasn’t been cancelled yet b4 calling inet function
m_Ftp_Conn = Cis.GetFtpConnection(pFtpI->Csa->GetAt(0),
pFtpI->Csa->GetAt(1), pFtpI->Csa->GetAt(2));//Connect to FTP Server

strcpy(status_str, source); UpdateStatus();//Show source file name

if (ftp_file_exists(source)==FALSE) return;//If file ain’t there we can’t download it so split!!
pFtpI->cpd.SetUpper(file_size);

if (cfo.Open(dest, CFile::modeCreate | CFile::modeWrite, NULL)==FALSE)//Now open our disk file
{wsprintf(status_str, “Unable to create file %s”, dest); AfxMessageBox(status_str); throw status_str; }

pFtpI->cpd.SetHeader(temp_ftp_name);
wsprintf(status_str, “Opening %s”, temp_ftp_name); UpdateStatus();
if (pFtpI->abort_flag) return;//Make sure operation hasn’t been cancelled yet b4 calling inet function
CInternetFile* ifp = m_Ftp_Conn->OpenFile(temp_ftp_name);

char buffer[DL_BUFFER_SIZE];
unsigned int amount_read = DL_BUFFER_SIZE;
unsigned int total_read = 0;
while (amount_read == DL_BUFFER_SIZE && pFtpI->abort_flag == FALSE)
{
amount_read = ifp->Read(buffer, DL_BUFFER_SIZE);
cfo.Write(buffer, amount_read);//Write this to our data file
total_read += amount_read;
wsprintf(status_str, “%d of %d bytes read”, total_read, file_size); UpdateStatus();
pFtpI->cpd.SetPos(total_read);
}

cfo.Close();//Close the file
//Unforunately we have to close the FTP session in order to be able to change to root folder.
//There is no way around it. We have reopen the ftp connection for each file. Oh well.
wsprintf(status_str, “Closing connection to %s”, pFtpI->Csa->GetAt(0)); UpdateStatus();
m_Ftp_Conn->Close();
delete m_Ftp_Conn;//Delete the ftp connection just to be safe
return;
}

//*********************************UPDATESTATUS***********************************
void FTPDownLoadFiles::UpdateStatus(void)
{
pFtpI->cpd.SetStatus(status_str);
return;
}

//This function performs three important functions.
//I have learned that calling OpenFile() on an FTP server when the files isn’t there makes my
//program go BOOM so we have to check to see if the file is there first. I have also learned
//that in checking for the files existence we can also get the file size.

//1. It checks to see wether file exists on ftp server. If it doesn’t it returns FALSE.
//2. Using CFtpFileFind it gets the FIRST file name matching input string. This means that if
//a string like amex*.txt is passed the FIRST and only the first file matching that string will
//be downloaded. This suits my purposes (for now) because I have to download daily data from a folder
//that is updated daily with the latest data file. This file name changes according to the date with
//the first four leters being the same. Using this logic you can still pass a specific file name
//or use wilcards, but just remember that if you use wildcards only the first file matching will
//be downloaded.

//3. Gets file size. we cannot however depend on this being correct. I read something about headers
//on the FTP site having to be updated, and also that CERN proxys can’t get the file info or something
//Since this is the case and if it is less than DL_BUFFER_SIZE I just make it 0. If it is 0
//the progess dialog knows to ignore the percent stuff.

//This function sets temp_ftp_name. Use this name for the actual download.
//This function also sets file_size.
//*****************************FTP_FILE_EXISTS******************************
BOOL FTPDownLoadFiles::ftp_file_exists(CString &source)
{
wsprintf(status_str, “Getting File Information %s”, source); UpdateStatus();

if (pFtpI->abort_flag) return FALSE;
CFtpFileFind finder(m_Ftp_Conn);

//Lets check for the files existince first using a standard FindFile call
if (pFtpI->abort_flag) return FALSE;
if (finder.FindFile(source) == FALSE)
{ file_not_found(source); return FALSE; }

//We have to use a FindNextFile to get info on first file, why ?. Becuase FindNextFile doesn’t get the
//next file on its first invocation. It gets the first file! Makes sense doesn’t it? Pure Genius…
if (pFtpI->abort_flag) return FALSE;
finder.FindNextFile();
temp_ftp_name = “”;//Empty the CString
temp_ftp_name = finder.GetFilePath();//Get the actual file name in case wildcards were used
if (temp_ftp_name.IsEmpty()) temp_ftp_name = source;//Make sure we got something. If not use source
file_size = 0;
file_size = finder.GetLength();
if (file_size < DL_BUFFER_SIZE) file_size = 0;//This tells the progress dialog to ignore the progress.

return TRUE;//If here file definitely exists
}

//***************************FILE_NOT_FOUND*************************
void FTPDownLoadFiles::file_not_found(CString & source)
{
wsprintf(status_str, “File NOT found on Server: %s”, source);
UpdateStatus();
AfxMessageBox(status_str);//Put up message box to let user Know!!
return;
}

Download demo project – 23.1 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read