Метод FtpFileDownload класса CFtpViewDlg
Метод FtpFileDownload класса CFtpViewDlg имеет единственный параметр sFileName, через который ему передается строка, содержащая имя файла на сервере FTP. Этот файл надо загрузить на локальный компьютер:
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName )
{
}
Первая часть метода FtpFileDownload практически полностью соответствует более ранним версиям этого метода. В ней создается и отображается на экране стандартная диалоговая панель Save As. Эта панель позволяет пользователю указать имя файла и каталог на локальном компьютере в который записывается файл, загружаемый с сервера FTP. По умолчанию в качестве имени файла предлагается использовать имя загружаемого файла. Это имя передается конструктору класса CFileDialog через параметр sFileName:
// Создаем объект, управляющий панелью Save As
CFileDialog mFileOpen(FALSE, NULL, sFileName);
// Отображаем панель Save As на экране
int result = mFileOpen.DoModal();
После того как пользователь выберет файл и нажмет кнопку OK, или откажется от выбора и нажмет кнопку Cancel, диалоговая панель Save As закрывается и метод DoModal возвращает соответствующий код завершения.
Если пользователь отказался от выбора файла и нажал кнопку Cancel, то метод DoModal возвращает значение IDCANCEL. В этом случае мы только выводим соответствующее предупреждающее сообщение и завершаем работу метода FtpFileDownload не выполняя загрузки файла:
if(result == IDCANCEL)
{
AfxMessageBox("File not selected");
return FALSE;
}
Если пользователь выбрал файл и нажал кнопку OK (или выбрал файл, выполнив по его имени двойной щелчок левой кнопкой мыши), то метод DoModal возвращает значение IDOK. В этом случае мы определяем полный путь выбранного пользователем файла и записываем его в строку sLocalFileName:
CString sLocalFileName;
. . .
else if(result == IDOK)
{
sLocalFileName = mFileOpen.GetPathName();
}
Через единственный параметр метода FtpFileDownload ему передается строка sFileName, содержащая имя файла выбранного пользователем для загрузки с сервера. Чтобы получить полный путь этого файла на сервере мы добавляем имя файла к текущему каталогу. Результат записываем обратно в строку sFileName:
// Формируем полное имя файла для загрузки его с сервера FTP
sFileName = sCurentDirectory + "/" + sFileName;
Чтобы мы смогли определить во время загрузки файла какая его часть загружена, необходимо узнать размер файла. Для этой цели мы создаем объект класса CFtpFileFind и выполняем поиск данного файла на сервере.
Сначала мы вызываем метод FindFile, передавая ему в качестве параметра полный путь файла, который надо найти на сервере, а затем, в случае успеха, вызываем метод FindNextFile который позволит нам определить характеристики, и в том числе длину, обнаруженного файла.
Надо заметить, что файл sFileName обязательно будет обнаружен, так как мы уже нашли его раньше (см. метод DirectoryView). Конечно мы не учитываем в этом случае возможность разрыва связи, удаление файла администратором и сервера ит. д.:
// Создаем объект класса CFtpFileFind
CFtpFileFind m_FtpFileFind(m_FtpConnection);
// Выполняем поиск выбранного нами файла
if(fResult =
m_FtpFileFind.FindFile(_T(sFileName)))
{
// Если поиск закончился успешно, получаем его
// характеристики
fResult = m_FtpFileFind.FindNextFile();
. . .
}
Повторный поиск файла мы выполняем исключительно потому, что так наиболее просто узнать его размер. Для этого достаточно вызвать метод GetLength:
// Временная переменная для хранения размера файла
DWORD dwLength = 0;
// Определяем длину файла
dwLength = m_FtpFileFind.GetLength();
В соответствии с длинной загружаемого файла устанавливаются новые границы изменения значений для линейного индикатора и новый шаг приращения.
Мы будем загружать файл с сервера FTP блоками по READ_BLOCK байт в каждом (последний блок может иметь меньшую длину). Поэтому мы сможем изменить состояние линейного индикатора dwLength / READ_BLOCK раз:
// Устанавливаем новые границы для линейного индикатора
m_LoadProgress.SetRange(0, (int)(dwLength/READ_BLOCK) );
// Устанавливаем шаг приращения равный единице
m_LoadProgress.SetStep(1);
Заметим, что чем меньше загружаемый файл, тем более резко будет меняться значение линейного индикатора, отражающего процесс загрузки. Если размер загружаемого файла будет меньше, чем размер буфера (READ_BLOCK), то линейный индикатор вообще не будет задействован. В принципе, вы можете сделать линейный индикатор более чувствительным, если будете загружать файл меньшими порциями - уменьшите значение READ_BLOCK.
Перед тем как приступить к самому интересному - загрузке файла с сервера FTP, заканчиваем поиск и вызываем метод Close для объекта m_FtpFileFind:
// Так как мы искали только один файл, заканчиваем поиск
m_FtpFileFind.Close();
Перед тем как приступить к загрузке файла с сервера мы должны его открыть. Для этого следует воспользоваться методом OpenFile, входящим в состав класса CFtpConnection. Он возвращает указатель на объект класса CInternetFile:
// Определяем указатель на файл Internet
CInternetFile* iFile;
// Открываем выбранный нами файл на сервере FTP
iFile = m_FtpConnection -> OpenFile(
sFileName.GetBuffer(MIN_LEN_BUF),
GENERIC_READ,
FTP_TRANSFER_TYPE_BINARY
);
Первый параметр метода OpenFile задает имя файла, который надо открыть. Второй параметр выбирает режим в котором открывается файл. Константа GENERIC_READ означает, что файл открывается для чтения. Третий и последний параметр метода OpenFile устанавливает режим передачи двоичных данных. Мы выбрали этот режим чтобы иметь возможность загружать не только текстовые, но и двоичные файлы.
Файл, получаемый с сервера FTP, мы должны сохранить на локальном компьютере в файле под именем sLocalFileName. Создаем файл sLocalFileName, используя возможности класса CFile:
// Создаем и открываем файл на локальном диске компьютера
CFile fLocalFile(
sLocalFileName,
CFile::modeCreate | CFile::modeWrite
);
Файл создается с атрибутами CFile::modeCreate и CFile::modeWrite, поэтому если файла с именем sLocalFileName нет - он будет создан, а если такой файл уже есть, то он перезаписывается. У пользователя при этом запрашивается подтверждение на перезапись файла.
Для чтения информации из файла на сервере мы будем обращаться к методу Read класса CInternetFile. Этот метод считывает порцию данных из файла и записывает их во временный буфер. Буфер мы предварительно создаем, при помощи функции malloc:
// Создаем буфер для чтения файла с сервера FTP
void* ptrBuffer;
ptrBuffer = malloc( READ_BLOCK );
Затем в цикле мы приступаем к чтению данных из файла на сервере и записи их в файл на локальном компьютере. Цикл будет продолжаться до тех пор, пока не будет достигнут конец файла.
Операция чтения файла с сервера выполняется методом Read, определенным в классе CInternetFile. Каждый вызов метода Read считывает READ_BLOCK байт и записывает их в буфер ptrBuffer. Количество байт, которое действительно было считано из файла на сервере, возвращается методом Read и записывается во временную переменную nReadCount.
Как только после вызова метода Read значение nReadCount станет меньше READ_BLOCK значит достигнут конец файла и можно выходить из цикла чтения:
UINT nReadCount = 0;
do
{
. . .
// Читаем из файла на сервере READ_BLOCK байт
nReadCount = iFile -> Read( ptrBuffer, READ_BLOCK );
. . .
} while (nReadCount == READ_BLOCK);
Если во время чтения файла с сервера возникнет ошибка, например разорвется связь, то метод Read вызовет соответствующее исключение CInternetException. Мы организуем обработку такой ситуации и помещаем вызов метода Read в блок try, а ниже определяем блок catch:
try
{
// Читаем из файла на сервере READ_BLOCK байт
nReadCount = iFile -> Read( ptrBuffer, READ_BLOCK );
}
Блок catch выполняет обработку исключения CInternetException. Обращаясь к методу GetErrorMessage мы получаем текстовое описание причины, вызвавшей исключение и отображаем ее на экране. Если метод GetErrorMessage не может определить причину исключения, он возвращает нулевое значение и мы отображаем на экране строку GetFtpConnection Error:
catch (CInternetException* pEx)
{
// Обрабатываем исключение CInternetException
TCHAR szErr[1024]; // временный буфер для сообщения
// Выводим сообщение об ошибке
if (pEx->GetErrorMessage(szErr, 1024))
AfxMessageBox(szErr);
else
AfxMessageBox("GetFtpConnection Error");
// Так как возникла ошибка, значит данные не считаны
nReadCount = 0;
// Удаляем исключение
pEx->Delete();
}
Чтобы прервать дальнейшее выполнение цикла загрузки файла мы записываем в переменную nReadCount нулевое значение. Затем удаляем исключение, вызывая для него метод Delete.
Данные, которые считаны из файла на сервере и находятся в буфере ptrBuffer, надо записать в файл fLocalFile на локальном диске компьютера. Для этого передаем буфер ptrBuffer методу Write и указываем, что надо записать в файл nReadCount байт. Напомним, что при достижении конца файла на сервере метод Read считывает остаток файла, размер которого может быть меньше размера буфера:
fLocalFile.Write( ptrBuffer, nReadCount );
Когда очередной блок из файла считан, увеличиваем значение линейного индикатора на одну позицию. За время загрузки файла линейный индикатор изменит свое значение от крайне левого до крайне правого:
// Увеличиваем значение на линейном индикаторе
m_LoadProgress.StepIt();
После того, как файл полностью получен, переводим линейный индикатор загрузки в начальное положение. Вызываем для этого метод SetPos с нулевым значением в качестве параметра:
// После окончания загрузки файла сбрасываем
// линейный индикатор
m_LoadProgress.SetPos( 0 );
Так как открытый файл на сервере FTP более нам не нужен, закрываем его, а затем удаляем из памяти соответствующий управляющий объект:
// Закрываем файл на сервере
iFile -> Close();
// Удаляем объект iFile
delete iFile;
Также закрываем и локальный файл, в который скопирован файл с сервера FTP:
// Закрываем файл на локальном компьютере
fLocalFile.Close();
Освобождаем буфер ptrBuffer, вызывая функцию free:
// Освобождаем буфер ptrBuffer
free( ptrBuffer );