Учимся скачивать файлы программно в Windows Mobile

Разработка приложений, использующих в своей работе сетевое взаимодействие или доступ к ресурсам 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#include #include 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("%s"), i->first.GetData()); DownloadInfo * download = i->second; if(!download) break; switch(download->GetStatus()) { case wxDownloadEvent::DOWNLOAD_INPROGRESS: value += wxString::Format( _(" - downloaded %d bytes"), 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.
Еще интересные посты о программировании для мобильных устройств:
No Comments
Make A CommentNo comments yet.
Comments RSS Feed TrackBack URL