Разработка приложений, использующих в своей работе сетевое взаимодействие или доступ к ресурсам Internet – это довольно популярная штука в наши дни. И в этот раз мы рассмотрим такую часто используемую задачу как загрузка файлов из Internet.
Для того чтобы скачать файл в C++ приложении с wxWinCE надо совсем немного кода. В простейшем случае для реализации однопоточной загрузки файла мы можем использовать класс wxURL
, скормив ему адрес загружаемого ресурса.
void MobileDownloaderMainFrame::OnDOWNLOADClick( wxCommandEvent& event )
{
do
{
wxString address = wxGetTextFromUser(_("Input URL"),
wxGetTextFromUserPromptStr,
wxT("http://wxwidgets.info"));
if(address.IsEmpty()) break;
wxURL url(address);
if(!url.IsOk()) break;
wxInputStream * stream = url.GetInputStream();
if(!stream) break;
wxString result;
wxStringOutputStream out(&result);
out.Write(*stream);
delete stream;
m_SingleThreadedResultTextCtrl->SetValue(result);
}
while(false);
}
Приведенный пример хорош всем кроме того, что пока выполняется загрузка, приложение не отвечает на действия пользователя, т.к. загрузка происходит в основном потоке и пока выполнение функции не закончится, приложение не может продолжить работу.
Чтобы таких ситуаций не случалось можно использовать библиотеку wxDownloadFile, которая позволяет загружать каждый файл в отдельном потоке и получать уведомления о состоянии загрузки.
Наш следующий пример демонстрирует как можно реализовать многопоточную загрузку файлов с минимальными усилиями.
Для начала создадим класс, который будет содержать информацию о загрузке:
#ifndef _DOWNLOAD_INFO_H
#define _DOWNLOAD_INFO_H
#include <wx/wx.h>
#include <DownloadFile.h>
#include <DownloadEvent.h>
class DownloadInfo
{
private:
wxInt64 m_DownloadedSize;
wxString m_URL;
wxDownloadEvent::DownloadSTATUS m_Status;
wxDownloadFile * m_Downloader;
public:
DownloadInfo(const wxString & url)
: m_DownloadedSize(0), m_URL(url),
m_Status(wxDownloadEvent::DOWNLOAD_NONE), m_Downloader(NULL) {}
const wxString & GetURL() {return m_URL;}
wxInt64 GetDownloadedSize() {return m_DownloadedSize;}
void SetDownloadedSize(wxInt64 value) {m_DownloadedSize = value;}
wxDownloadEvent::DownloadSTATUS GetStatus() {return m_Status;}
void SetStatus(wxDownloadEvent::DownloadSTATUS value) {m_Status = value;}
wxDownloadFile * GetDownloader() {return m_Downloader;}
void SetDownloader(wxDownloadFile * value) {m_Downloader = value;}
};
WX_DECLARE_STRING_HASH_MAP(DownloadInfo *, DownloadsHash);
#endif
Кроме самого класса с информацией о загрузке нам необходима еще хеш-таблица, которая будет содержать информацию об активных загрузках в приложении. Доступ к элементам хеш-таблицы будет производиться по строковому ключу, в качестве которого мы будем использовать URL.
Для отображения информации о загрузках мы будем использовать компонент wxSimpleHtmlListBox
. Компонент будет содержать указатель на хеш-таблицу с загрузками.
Чтобы в списке загрузок отображалась актуальная информация, нам необходимо переопределить метод OnGetItem().
#include "DownloadInfo.h"
...
class wxDownloadListBox: public wxSimpleHtmlListBox
{
...
DownloadsHash * GetDownloads() const { return m_Downloads ; }
void SetDownloads(DownloadsHash * value) { m_Downloads = value ; }
virtual wxString OnGetItem(size_t n) const;
void AddNew() {SetItemCount(GetItemCount()+1);}
DownloadsHash * m_Downloads;
};
wxString wxDownloadListBox::OnGetItem(size_t n) const
{
wxString value;
do
{
DownloadsHash::iterator i = m_Downloads->begin();
for(size_t j = 0; (j < n) && (i != m_Downloads->end()); j++)
{
i++;
}
if(i == m_Downloads->end()) break;
value = wxString::Format(wxT("<b>%s</b>"), i->first.GetData());
DownloadInfo * download = i->second;
if(!download) break;
switch(download->GetStatus())
{
case wxDownloadEvent::DOWNLOAD_INPROGRESS:
value += wxString::Format(
_(" - <i>downloaded %d bytes</i>"),
download->GetDownloadedSize());
break;
case wxDownloadEvent::DOWNLOAD_COMPLETE:
value += _(" (finished)");
break;
case wxDownloadEvent::DOWNLOAD_FAIL:
value += _(" (failed)");
break;
}
}
while (false);
return value;
}
Метод AddNew()
указывает компоненту, что у него добавился новый элемент.
Теперь рассмотрим код, создающий новую загрузку и добавляющий ее в список:
void MobileDownloaderMainFrame::OnADDClick( wxCommandEvent& event )
{
do
{
DownloadParametersDialog * dlg = new DownloadParametersDialog(this);
int result = dlg->ShowModal();
wxString url = dlg->m_URLTextCtrl->GetValue();
wxString fileName = dlg->m_FileNamePicker->GetPath();
dlg->Destroy();
if(result != wxID_OK) break;
DownloadInfo * info = m_Downloads[url];
bool needAddNew(true);
if(info != NULL)
{
info->GetDownloader()->CancelDownload();
needAddNew = false;
}
else
{
info = new DownloadInfo(url);
}
wxDownloadFile * download = new wxDownloadFile(this, url, fileName, true);
info->SetDownloader(download);
m_Downloads[url] = info;
download->Run();
if(needAddNew)m_DownloadsListBox->AddNew();
m_DownloadsListBox->RefreshLines(
m_DownloadsListBox->GetFirstVisibleLine(),
m_DownloadsListBox->GetLastVisibleLine());
}
while (false);
}
Собственно, что у нас здесь происходит:
- отображается диалог создания новой загрузки. В нем необходимо ввести URL и путь к результирующему файлу
- В случае если диалог отработал успешно, мы проверяем наличие загрузки с указанным URL в хеш-таблице
- Если загрузка присутствует, то она останавливается
- Если загрузки с указанным URL в таблице нет, то создается новый объект
DownloadInfo
- Затем создается объект
wxDownloadFile
и запускается
- После этого происходит обновление списка загрузок
Как уже было сказано ранее, класс wxDownloadFile
позволяет получать уведомления о состоянии загрузки. Для этого используется обработчик события EVT_DOWNLOAD():
BEGIN_EVENT_TABLE( MobileDownloaderMainFrame, wxFrame )
...
EVT_DOWNLOAD(MobileDownloaderMainFrame::OnDownloadStatus)
END_EVENT_TABLE()
void MobileDownloaderMainFrame::OnDownloadStatus(wxDownloadEvent & event)
{
do
{
wxString url = event.GetDownLoadURL();
DownloadInfo * downloadInfo = m_Downloads[url];
wxDownloadFile * download = downloadInfo->GetDownloader();
if(!download) break;
downloadInfo->SetStatus((wxDownloadEvent::DownloadSTATUS)event.GetDownLoadStatus());
switch(event.GetDownLoadStatus())
{
case wxDownloadEvent::DOWNLOAD_FAIL:
wxLogDebug(wxT("DOWNLOAD_FAIL"));
downloadInfo->SetDownloader(NULL);
break;
case wxDownloadEvent::DOWNLOAD_COMPLETE:
wxLogDebug(wxT("DOWNLOAD_COMPLETE"));
downloadInfo->SetDownloader(NULL);
break;
case wxDownloadEvent::DOWNLOAD_INPROGRESS:
wxLogDebug(wxT("DOWNLOAD_INPROGRESS"));
downloadInfo->SetDownloadedSize(event.GetDownLoadedBytesCount());
break;
default:
break;
}
m_DownloadsListBox->RefreshLines(
m_DownloadsListBox->GetFirstVisibleLine(),
m_DownloadsListBox->GetLastVisibleLine());
}
while(false);
}
В обработчике события мы получаем состояние загрузки из объекта wxDownloadEvent
и, в зависимости от этого значения, выполняем обновление объекта DownloadInfo
в хеш-таблице загрузок.
О чем еще важно помнить? Приложение остается висеть в памяти пока не будут завершены все его потоки. wxDownloadFile
создает поток на каждую загрузку и если мы попытаемся закрыть приложение во время скачивания файлов, то приложение будет выгружено только по завершению всех потоков. Чтобы такой ситуации у нас не было, нам необходимо принудительно завершить все потоки при закрытии главной формы приложения. Для этого мы будем использовать обработчик EVT_CLOSE():
void MobileDownloaderMainFrame::OnCloseWindow( wxCloseEvent& event )
{
for(DownloadsHash::iterator i = m_Downloads.begin(); i != m_Downloads.end(); i++)
{
DownloadInfo * download = i->second;
if(!download) continue;
if(download->GetDownloader()) download->GetDownloader()->CancelDownload();
wxDELETE(download);
i->second = NULL;
}
event.Skip();
}
Вот теперь все.

Скачать исходный код приложения и проекты для Win32 и Windows Mobile.