Загрузка файла в фоновом режиме
Исходный вариант приложения FtpView и его модификация обладают еще одним очень неприятным недостатком. Во время загрузки файлов с сервера FTP интерфейс приложения блокируется практически полностью. Только по линейному индикатору, который используется в доработанном варианте приложения мы можем судить о том, что оно все же работает.
В остальном складывается такое впечатление, что приложение “зависло”. Оно даже не перерисовывает свое окно и если временно переключится на другое приложение, окно которого перекроет окно FtpView, то изображение в окне FtpView будет испорчено. Только по окончании загрузки файла наше приложение спохватится и восстановит свое окно.
Как мы уже говорили ранее, эффекта зависания можно избежать, если использовать WinInet в асинхронном режиме или выполнять наиболее длительные операции по загрузке файлов в отдельной задаче.
Сейчас мы предлагаем вам испробовать второй путь и создать для загрузки файлов с сервера отдельную задачу, которая будет выполняться параллельно с основной задачей приложения. Таким образом, загрузка файла будет осуществляться в фоновом режиме и не будет блокировать пользовательский интерфейс приложения.
За основу мы взяли базовый вариант приложения FtpView, выполняющий загрузку файлов с сервера FTP при помощи метода GetFile. Мы постарались свести все изменения в исходных текстах приложения к минимуму. Вы должны будете только добавить три глобальные переменные, изменить метод FtpFileDownload класса CFtpViewDlg, и добавить функцию ThreadFileLoadProc.
Определение глобальных переменных добавьте в самом начале файла FtpViewDlg.cpp непосредственно после директив препроцессора. Все три новых глобальных переменных global_sFtpAddress, global_sFileName и global_sLocalFileName являются объектами класса CString. Эти переменные используются для передачи задаче, осуществляющей загрузку файла, адреса сервера FTP, пути каталога и имени исходного файла на сервере FTP, а также полного пути файла на диске локального компьютера в который будет записан файл с сервера:
//================== Глобальные переменные ===================
// Адрес сервера FTP
CString global_sFtpAddress = "";
// Полный путь файла на диске локального компьютера
CString global_sFileName = "";
// Путь каталога и имя файла на сервере FTP
CString global_sLocalFileName = "";
Задача, осуществляющая загрузку файла с сервера FTP, определяется функцией ThreadFileLoadProc. Как видите, мы не стали ее особенно усложнять и просто создали еще один сеанс связи с Internet, а затем в его рамках осуществили соединение с сервером FTP и получили с него заданный файл.
Адрес сервера FTP мы берем из глобальной переменной global_sFtpAddress, путь файла, который надо загрузить с сервера и его название - из переменной global_sFileName, а полный путь файла на диске локального компьютера, куда полученный файл должен быть записан - из переменной global_sLocalFileName:
//============================================================
// Задача, выполняющая загрузку файла с сервера FTP
//============================================================
UINT ThreadFileLoadProc(LPVOID param)
{
//=========================================================
// Инициализируем сеанс связи с Internet
//=========================================================
CFtpConnection* m_FtpConnection; // Сервер FTP
CInternetSession* m_InternetSession; // Сеанс связи
// Создаем сеанс связи с Internet, указываем в качестве
// имени программы-клиента строку FtpProcess
m_InternetSession = new CInternetSession("FtpProcess");
// В случае ошибки отображаем сообщение и завершаем
// задачу с кодом завершения 1
if(!m_InternetSession)
{
AfxMessageBox("New Session Error", MB_OK);
return 1;
}
// Инициализируем указатель m_FtpConnection
m_FtpConnection = NULL;
// Пытаемся соединиться с сервером FTP
try
{
// Соединяемся с сервером FTP. Эта операция
// может вызвать исключение CInternetException
m_FtpConnection = m_InternetSession ->
GetFtpConnection(global_sFtpAddress);
}
catch (CInternetException* pEx)
{
// Выводим сообщение об ошибке
AfxMessageBox("GetFtpConnection Error");
// Удаляем исключение
pEx->Delete();
// Обнуляем указатель m_FtpConnection
m_FtpConnection = NULL;
}
if(m_FtpConnection != NULL)
{
BOOL fResult;
// Загружаем файл с сервера
fResult = m_FtpConnection ->
GetFile(global_sFileName.GetBuffer(MIN_LEN_BUF),
global_sLocalFileName.GetBuffer(MIN_LEN_BUF),
FALSE
);
// Закрываем соединение с сервером
m_FtpConnection -> Close();
delete m_FtpConnection;
// После успешного завершения загрузки выводим сообщение
if( fResult )
AfxMessageBox("File load complited:" +
global_sFileName);
}
// Завершаем сеанс связи с Internet
m_InternetSession -> Close();
delete m_InternetSession;
return 0;
}
Функция ThreadFileLoadProc выполняет загрузку файла с сервера FTP в файл на диске локального компьютера. Надо только заполнить глобальные переменные global_sFtpAddress, global_sFileName и global_sLocalFileName, а затем запустить функцию ThreadFileLoadProc как отдельную задачу. Для этого измените метод FtpFileDownload класса CFtpViewDlg как это показано ниже:
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName )
{
BOOL fResult;
CString sLocalFileName;
// Определяем объект класса CFileDialog, представляющий
// стандартную диалоговую панель Save As, в которой
// по умолчанию выбрано имя файла sFileName
CFileDialog mFileOpen(FALSE, NULL, sFileName);
// Отображаем диалоговую панель Open и позволяем
// пользователю выбрать с помощью нее один файл
int result = mFileOpen.DoModal();
// Проверяем как была закрыта диалоговая панель Open -
// по нажатию кнопки OK или Cancel
if(result == IDCANCEL)
{
// Если пользователь отказался от выбора файлов и
// нажал кнопку Cancel отображаем соответствующее
// сообщение и возвращаем значение FALSE
AfxMessageBox("File not selected");
return FALSE;
}
// Если пользователь нажал кнопку OK
else if(result == IDOK)
{
// Записываем полный путь файла на диске локального
// компьютера
global_sLocalFileName = mFileOpen.GetPathName();
// Определяем путь и имя файла, который надо загрузить
// с сервера FTP
global_sFileName = sCurentDirectory + "/" + sFileName;
// Запоминаем адрес сервера FTP
global_sFtpAddress = m_FtpAddress;
// Запускаем новую задачу, определенную функцией
// ThreadFileLoadProc
AfxBeginThread( ThreadFileLoadProc, NULL,
THREAD_PRIORITY_NORMAL);
}
return fResult;
}
Мы не будем подробно описывать функцию ThreadFileLoadProc и новый вариант метода FtpFileDownload класса CFtpViewDlg. В них используются те же приемы, что продемонстрированы в методе FtpFileDownload класса CFtpViewDlg базового варианта приложения FtpView.