Upstream tarball 9401
[amule.git] / src / HTTPDownload.cpp
blob7d9b03461a10932e40f26e8ca443acd43a5a18e1
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Timo Kujala ( tiku@users.sourceforge.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 #include <wx/wfstream.h>
28 #include <wx/protocol/http.h>
31 #include "HTTPDownload.h" // Interface declarations
32 #include <common/StringFunctions.h> // Needed for unicode2char
33 #include "OtherFunctions.h" // Needed for CastChild
34 #include "Logger.h" // Needed for AddLogLine*
35 #include <common/Format.h> // Needed for CFormat
36 #include "InternalEvents.h" // Needed for CMuleInternalEvent
37 #include "Preferences.h"
40 #ifndef AMULE_DAEMON
41 #include "inetdownload.h" // Needed for inetDownload
42 #include "muuli_wdr.h" // Needed for ID_CANCEL: Let it here or will fail on win32
43 #include "MuleGifCtrl.h"
45 typedef wxGauge wxGaugeControl;
47 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS, wxANY_ID)
48 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN, wxANY_ID)
51 class CHTTPDownloadDialog : public wxDialog
53 public:
54 CHTTPDownloadDialog(CHTTPDownloadThread* thread)
55 : wxDialog(wxTheApp->GetTopWindow(), -1, _("Downloading..."),
56 wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxSYSTEM_MENU)
58 downloadDlg(this, true)->Show(this, true);
60 m_progressbar = CastChild(ID_HTTPDOWNLOADPROGRESS, wxGaugeControl);
61 m_progressbar->SetRange(100);
63 m_ani = CastChild(ID_ANIMATE, MuleGifCtrl);
64 m_ani->LoadData((const char*)inetDownload, sizeof(inetDownload));
65 m_ani->Start();
67 m_thread = thread;
70 ~CHTTPDownloadDialog() {
71 StopThread();
74 void UpdateGauge(int total, int current) {
75 CFormat label(_("( %s / %s )"));
77 label % CastItoXBytes(current);
78 if (total > 0) {
79 label % CastItoXBytes(total);
80 } else {
81 label % _("Unknown");
84 CastChild(IDC_DOWNLOADSIZE, wxStaticText)->SetLabel(label.GetString());
86 if (total && (total != m_progressbar->GetRange())) {
87 m_progressbar->SetRange(total);
90 if (current && (current <= total)) {
91 m_progressbar->SetValue(current);
94 Layout();
97 private:
98 void StopThread() {
99 if (m_thread) {
100 m_thread->Stop();
101 delete m_thread;
102 m_thread = NULL;
106 void OnBtnCancel(wxCommandEvent& WXUNUSED(evt)) {
107 AddLogLineNS(_("HTTP download cancelled"));
108 Show(false);
109 StopThread();
112 void OnProgress(CMuleInternalEvent& evt) {
113 UpdateGauge(evt.GetExtraLong(), evt.GetInt());
116 void OnShutdown(CMuleInternalEvent& WXUNUSED(evt)) {
117 Show(false);
118 Destroy();
121 CMuleThread* m_thread;
122 MuleGifCtrl* m_ani;
123 wxGaugeControl* m_progressbar;
125 DECLARE_EVENT_TABLE()
129 BEGIN_EVENT_TABLE(CHTTPDownloadDialog, wxDialog)
130 EVT_BUTTON(ID_HTTPCANCEL, CHTTPDownloadDialog::OnBtnCancel)
131 EVT_MULE_INTERNAL(wxEVT_HTTP_PROGRESS, -1, CHTTPDownloadDialog::OnProgress)
132 EVT_MULE_INTERNAL(wxEVT_HTTP_SHUTDOWN, -1, CHTTPDownloadDialog::OnShutdown)
133 END_EVENT_TABLE()
135 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS)
136 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN)
138 #endif
141 CHTTPDownloadThread::CHTTPDownloadThread(const wxChar* url, const wxChar* filename, HTTP_Download_File file_id, bool showDialog)
142 #ifdef AMULE_DAEMON
143 : CMuleThread(wxTHREAD_DETACHED),
144 #else
145 : CMuleThread(showDialog ? wxTHREAD_JOINABLE : wxTHREAD_DETACHED),
146 #endif
147 m_url(url),
148 m_tempfile(filename),
149 m_result(-1),
150 m_file_id(file_id),
151 m_companion(NULL)
153 if (showDialog) {
154 #ifndef AMULE_DAEMON
155 CHTTPDownloadDialog* dialog = new CHTTPDownloadDialog(this);
156 dialog->Show(true);
157 m_companion = dialog;
158 #endif
163 CMuleThread::ExitCode CHTTPDownloadThread::Entry()
165 if (TestDestroy()) {
166 return NULL;
169 wxHTTP* url_handler = NULL;
170 wxInputStream* url_read_stream = NULL;
172 AddLogLineNS(_("HTTP download thread started"));
174 const CProxyData* proxy_data = thePrefs::GetProxyData();
175 bool use_proxy = proxy_data != NULL && proxy_data->m_proxyEnable;
177 try {
178 wxFFileOutputStream outfile(m_tempfile);
180 if (!outfile.Ok()) {
181 throw wxString(CFormat(wxT("Unable to create destination file %s for download!\n")) % m_tempfile);
184 if ( m_url.IsEmpty() ) {
185 // Nowhere to download from!
186 throw wxString(wxT("The URL to download can't be empty\n"));
189 url_handler = new wxHTTP;
190 url_handler->SetProxyMode(use_proxy);
192 url_read_stream = GetInputStream(&url_handler, m_url, use_proxy);
194 if (!url_read_stream) {
195 throw wxString(CFormat(wxT("The URL %s returned: %i - Error (%i)!")) % m_url % url_handler->GetResponse() % url_handler->GetError());
198 int download_size = url_read_stream->GetSize();
199 AddLogLineNS(CFormat(_("Download size: %i")) % download_size);
201 // Here is our read buffer
202 // <ken> Still, I'm sure 4092 is probably a better size.
203 // MP: Most people can download at least at 32kb/s from http...
204 const unsigned MAX_HTTP_READ = 32768;
206 char buffer[MAX_HTTP_READ];
207 int current_read = 0;
208 int total_read = 0;
209 do {
210 url_read_stream->Read(buffer, MAX_HTTP_READ);
211 current_read = url_read_stream->LastRead();
212 if (current_read) {
213 total_read += current_read;
214 outfile.Write(buffer,current_read);
215 int current_write = outfile.LastWrite();
216 if (current_read != current_write) {
217 throw wxString(wxT("Critical error while writing downloaded file"));
218 } else if (m_companion) {
219 #ifndef AMULE_DAEMON
220 CMuleInternalEvent evt(wxEVT_HTTP_PROGRESS);
221 evt.SetInt(total_read);
222 evt.SetExtraLong(download_size);
223 wxPostEvent(m_companion, evt);
224 #endif
227 } while (current_read && !TestDestroy());
229 if (current_read == 0) {
230 if (total_read != download_size) {
231 throw wxString(CFormat(_("Expected %d bytes, but downloaded %d bytes")) % download_size % total_read);
232 } else {
233 // Download was succesful.
234 m_result = 1;
237 } catch (const wxString& error) {
238 if (wxFileExists(m_tempfile)) {
239 wxRemoveFile(m_tempfile);
242 AddLogLineNS(error); // If there's console output anyway there should also be console output on error.
245 delete url_read_stream;
246 if (url_handler) {
247 url_handler->Destroy();
250 AddLogLineNS(_("HTTP download thread ended"));
252 return 0;
256 void CHTTPDownloadThread::OnExit()
258 #ifndef AMULE_DAEMON
259 if (m_companion) {
260 CMuleInternalEvent termEvent(wxEVT_HTTP_SHUTDOWN);
261 wxPostEvent(m_companion, termEvent);
263 #endif
265 // Notice the app that the file finished download
266 CMuleInternalEvent evt(wxEVT_CORE_FINISHED_HTTP_DOWNLOAD);
267 evt.SetInt((int)m_file_id);
268 evt.SetExtraLong((long)m_result);
269 wxPostEvent(wxTheApp, evt);
273 //! This function's purpose is to handle redirections in a proper way.
274 wxInputStream* CHTTPDownloadThread::GetInputStream(wxHTTP** url_handler, const wxString& location, bool proxy)
276 if (TestDestroy()) {
277 return NULL;
280 if (!location.StartsWith(wxT("http://"))) {
281 // This is not a http url
282 throw wxString(wxT("Invalid URL for http download or http redirection (did you forget 'http://' ?)"));
285 // Get the host
287 // Remove the "http://"
288 wxString host = location.Right(location.Len() - 7); // strlen("http://") -> 7
290 // I belive this is a bug...
291 // Sometimes "Location" header looks like this:
292 // "http://www.whatever.com:8080http://www.whatever.com/downloads/something.zip"
293 // So let's clean it...
295 int bad_url_pos = host.Find(wxT("http://"));
296 wxString location_url;
297 if (bad_url_pos != -1) {
298 // Malformed Location field on header (bug above?)
299 location_url = host.Mid(bad_url_pos);
300 host = host.Left(bad_url_pos);
301 // After the first '/' non-http-related, it's the location part of the URL
302 location_url = location_url.Right(location_url.Len() - 7).AfterFirst(wxT('/'));
303 } else {
304 // Regular Location field
305 // After the first '/', it's the location part of the URL
306 location_url = host.AfterFirst(wxT('/'));
307 // The host is everything till the first "/"
308 host = host.BeforeFirst(wxT('/'));
311 // Build the cleaned url now
312 wxString url = wxT("http://") + host + wxT("/") + location_url;
314 int port = 80;
315 if (host.Find(wxT(':')) != -1) {
316 // This http url has a port
317 port = wxAtoi(host.AfterFirst(wxT(':')));
318 host = host.BeforeFirst(wxT(':'));
321 wxIPV4address addr;
322 addr.Hostname(host);
323 addr.Service(port);
324 if (!(*url_handler)->Connect(addr, true)) {
325 throw wxString(wxT("Unable to connect to http download server"));
328 wxInputStream* url_read_stream = (*url_handler)->GetInputStream(url);
330 AddLogLineNS(CFormat(_("Host: %s:%i\n")) % host % port);
331 AddLogLineNS(CFormat(wxT("URL: %s\n")) % url);
332 AddLogLineNS(CFormat(_("Response: %i (Error: %i)")) % (*url_handler)->GetResponse() % (*url_handler)->GetError());
334 if (!(*url_handler)->GetResponse()) {
335 AddLogLineNS(_("WARNING: Void response on stream creation"));
336 // WTF? Why does this happen?
337 // This is probably produced by an already existing connection, because
338 // the input stream is created nevertheless. However, data is not the same.
339 delete url_read_stream;
340 throw wxString(wxT("Invalid response from http download server"));
343 if ((*url_handler)->GetResponse() == 301 // Moved permanently
344 || (*url_handler)->GetResponse() == 302 // Moved temporarily
345 // What about 300 (multiple choices)? Do we have to handle it?
346 ) {
348 // We have to remove the current stream.
349 delete url_read_stream;
351 wxString new_location = (*url_handler)->GetHeader(wxT("Location"));
353 (*url_handler)->Destroy();
354 if (!new_location.IsEmpty()) {
355 (*url_handler) = new wxHTTP;
356 (*url_handler)->SetProxyMode(proxy);
357 url_read_stream = GetInputStream(url_handler, new_location, proxy);
358 } else {
359 AddLogLineCS(_("ERROR: Redirection code received with no URL"));
360 url_handler = NULL;
361 url_read_stream = NULL;
365 return url_read_stream;
368 // File_checked_for_headers