Improve speed of category tab title updates
[amule.git] / src / HTTPDownload.cpp
blob3dc35737e6cba0673928ad0e431e653857ad9317
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"
38 #include "ScopedPtr.h"
39 #include <wx/filename.h> // Needed for wxFileName
42 #ifndef AMULE_DAEMON
43 #include "inetdownload.h" // Needed for inetDownload
44 #include "muuli_wdr.h" // Needed for ID_CANCEL: Let it here or will fail on win32
45 #include "MuleGifCtrl.h"
47 typedef wxGauge wxGaugeControl;
49 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS, wxANY_ID)
50 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN, wxANY_ID)
53 class CHTTPDownloadDialog : public wxDialog
55 public:
56 CHTTPDownloadDialog(CHTTPDownloadThread* thread)
57 : wxDialog(wxTheApp->GetTopWindow(), -1, _("Downloading..."),
58 wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxSYSTEM_MENU)
60 downloadDlg(this, true)->Show(this, true);
62 m_progressbar = CastChild(ID_HTTPDOWNLOADPROGRESS, wxGaugeControl);
63 m_progressbar->SetRange(100);
65 m_ani = CastChild(ID_ANIMATE, MuleGifCtrl);
66 m_ani->LoadData((const char*)inetDownload, sizeof(inetDownload));
67 m_ani->Start();
69 m_thread = thread;
72 ~CHTTPDownloadDialog() {
73 StopThread();
76 void UpdateGauge(int total, int current) {
77 CFormat label( wxT("( %s / %s )") );
79 label % CastItoXBytes(current);
80 if (total > 0) {
81 label % CastItoXBytes(total);
82 } else {
83 label % _("Unknown");
86 CastChild(IDC_DOWNLOADSIZE, wxStaticText)->SetLabel(label.GetString());
88 if (total && (total != m_progressbar->GetRange())) {
89 m_progressbar->SetRange(total);
92 if (current && (current <= total)) {
93 m_progressbar->SetValue(current);
96 Layout();
99 private:
100 void StopThread() {
101 if (m_thread) {
102 m_thread->Stop();
103 delete m_thread;
104 m_thread = NULL;
108 void OnBtnCancel(wxCommandEvent& WXUNUSED(evt)) {
109 AddLogLineN(_("HTTP download cancelled"));
110 Show(false);
111 StopThread();
114 void OnProgress(CMuleInternalEvent& evt) {
115 UpdateGauge(evt.GetExtraLong(), evt.GetInt());
118 void OnShutdown(CMuleInternalEvent& WXUNUSED(evt)) {
119 Show(false);
120 Destroy();
123 CMuleThread* m_thread;
124 MuleGifCtrl* m_ani;
125 wxGaugeControl* m_progressbar;
127 DECLARE_EVENT_TABLE()
131 BEGIN_EVENT_TABLE(CHTTPDownloadDialog, wxDialog)
132 EVT_BUTTON(ID_HTTPCANCEL, CHTTPDownloadDialog::OnBtnCancel)
133 EVT_MULE_INTERNAL(wxEVT_HTTP_PROGRESS, -1, CHTTPDownloadDialog::OnProgress)
134 EVT_MULE_INTERNAL(wxEVT_HTTP_SHUTDOWN, -1, CHTTPDownloadDialog::OnShutdown)
135 END_EVENT_TABLE()
137 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS)
138 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN)
140 #endif
143 CHTTPDownloadThread::CHTTPDownloadThread(const wxString& url, const wxString& filename, const wxString& oldfilename, HTTP_Download_File file_id,
144 bool showDialog, bool checkDownloadNewer)
145 #ifdef AMULE_DAEMON
146 : CMuleThread(wxTHREAD_DETACHED),
147 #else
148 : CMuleThread(showDialog ? wxTHREAD_JOINABLE : wxTHREAD_DETACHED),
149 #endif
150 m_url(url),
151 m_tempfile(filename),
152 m_result(-1),
153 m_file_id(file_id),
154 m_companion(NULL)
156 if (showDialog) {
157 #ifndef AMULE_DAEMON
158 CHTTPDownloadDialog* dialog = new CHTTPDownloadDialog(this);
159 dialog->Show(true);
160 m_companion = dialog;
161 #endif
163 // Get the date on which the original file was last modified
164 // Only if it's the same URL we used for the last download and if the file exists.
165 if (checkDownloadNewer && thePrefs::GetLastHTTPDownloadURL(file_id) == url) {
166 wxFileName origFile = wxFileName(oldfilename);
167 if (origFile.FileExists()) {
168 AddDebugLogLineN(logHTTP, CFormat(wxT("URL %s matches and file %s exists, only download if newer")) % url % oldfilename);
169 m_lastmodified = origFile.GetModificationTime();
172 wxMutexLocker lock(s_allThreadsMutex);
173 s_allThreads.insert(this);
177 // Format the given date to a RFC-2616 compliant HTTP date
178 // Example: Thu, 14 Jan 2010 15:40:23 GMT
179 wxString CHTTPDownloadThread::FormatDateHTTP(const wxDateTime& date)
181 static const wxChar* s_months[] = { wxT("Jan"), wxT("Feb"), wxT("Mar"), wxT("Apr"), wxT("May"), wxT("Jun"), wxT("Jul"), wxT("Aug"), wxT("Sep"), wxT("Oct"), wxT("Nov"), wxT("Dec") };
182 static const wxChar* s_dow[] = { wxT("Sun"), wxT("Mon"), wxT("Tue"), wxT("Wed"), wxT("Thu"), wxT("Fri"), wxT("Sat") };
184 return CFormat(wxT("%s, %02d %s %d %02d:%02d:%02d GMT")) % s_dow[date.GetWeekDay(wxDateTime::UTC)] % date.GetDay(wxDateTime::UTC) % s_months[date.GetMonth(wxDateTime::UTC)] % date.GetYear(wxDateTime::UTC) % date.GetHour(wxDateTime::UTC) % date.GetMinute(wxDateTime::UTC) % date.GetSecond(wxDateTime::UTC);
188 CMuleThread::ExitCode CHTTPDownloadThread::Entry()
190 if (TestDestroy()) {
191 return NULL;
194 wxHTTP* url_handler = NULL;
196 AddDebugLogLineN(logHTTP, wxT("HTTP download thread started"));
198 const CProxyData* proxy_data = thePrefs::GetProxyData();
199 bool use_proxy = proxy_data != NULL && proxy_data->m_proxyEnable;
201 try {
202 wxFFileOutputStream outfile(m_tempfile);
204 if (!outfile.Ok()) {
205 throw wxString(CFormat(_("Unable to create destination file %s for download!")) % m_tempfile);
208 if (m_url.IsEmpty()) {
209 // Nowhere to download from!
210 throw wxString(_("The URL to download can't be empty"));
213 url_handler = new wxHTTP;
214 url_handler->SetProxyMode(use_proxy);
216 // Build a conditional get request if the last modified date of the file being updated is known
217 if (m_lastmodified.IsValid()) {
218 // Set a flag in the HTTP header that we only download if the file is newer.
219 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
220 AddDebugLogLineN(logHTTP, wxT("If-Modified-Since: ") + FormatDateHTTP(m_lastmodified));
221 url_handler->SetHeader(wxT("If-Modified-Since"), FormatDateHTTP(m_lastmodified));
224 CScopedPtr<wxInputStream> url_read_stream(GetInputStream(url_handler, m_url, use_proxy));
226 if (!url_read_stream.get()) {
227 if (m_response == 304) {
228 m_result = HTTP_Skipped;
229 AddDebugLogLineN(logHTTP, wxT("Skipped download because requested file is not newer."));
230 throw wxString(wxEmptyString);
231 } else {
232 m_result = HTTP_Error;
233 throw wxString(CFormat(_("The URL %s returned: %i - Error (%i)!")) % m_url % m_response % m_error);
237 int download_size = url_read_stream->GetSize();
238 if (download_size == -1) {
239 AddDebugLogLineN(logHTTP, wxT("Download size not received, downloading until connection is closed"));
240 } else {
241 AddDebugLogLineN(logHTTP, CFormat(wxT("Download size: %i")) % download_size);
244 // Here is our read buffer
245 // <ken> Still, I'm sure 4092 is probably a better size.
246 // MP: Most people can download at least at 32kb/s from http...
247 const unsigned MAX_HTTP_READ = 32768;
249 char buffer[MAX_HTTP_READ];
250 int current_read = 0;
251 int total_read = 0;
252 do {
253 url_read_stream->Read(buffer, MAX_HTTP_READ);
254 current_read = url_read_stream->LastRead();
255 if (current_read) {
256 total_read += current_read;
257 outfile.Write(buffer,current_read);
258 int current_write = outfile.LastWrite();
259 if (current_read != current_write) {
260 throw wxString(_("Critical error while writing downloaded file"));
261 } else if (m_companion) {
262 #ifndef AMULE_DAEMON
263 CMuleInternalEvent evt(wxEVT_HTTP_PROGRESS);
264 evt.SetInt(total_read);
265 evt.SetExtraLong(download_size);
266 wxPostEvent(m_companion, evt);
267 #endif
270 } while (current_read && !TestDestroy());
272 if (current_read == 0) {
273 if (download_size == -1) {
274 // Download was probably succesful.
275 AddLogLineN(CFormat(_("Downloaded %d bytes")) % total_read);
276 m_result = HTTP_Success;
277 } else if (total_read != download_size) {
278 m_result = HTTP_Error;
279 throw wxString(CFormat(_("Expected %d bytes, but downloaded %d bytes")) % download_size % total_read);
280 } else {
281 // Download was succesful.
282 m_result = HTTP_Success;
285 } catch (const wxString& error) {
286 if (wxFileExists(m_tempfile)) {
287 wxRemoveFile(m_tempfile);
289 if (!error.IsEmpty()) {
290 AddLogLineC(error);
294 if (m_result == HTTP_Success) {
295 thePrefs::SetLastHTTPDownloadURL(m_file_id, m_url);
298 if (url_handler) {
299 url_handler->Destroy();
302 AddDebugLogLineN(logHTTP, wxT("HTTP download thread ended"));
304 return 0;
308 void CHTTPDownloadThread::OnExit()
310 #ifndef AMULE_DAEMON
311 if (m_companion) {
312 CMuleInternalEvent termEvent(wxEVT_HTTP_SHUTDOWN);
313 wxPostEvent(m_companion, termEvent);
315 #endif
317 // Notice the app that the file finished download
318 CMuleInternalEvent evt(wxEVT_CORE_FINISHED_HTTP_DOWNLOAD);
319 evt.SetInt((int)m_file_id);
320 evt.SetExtraLong((long)m_result);
321 wxPostEvent(wxTheApp, evt);
322 wxMutexLocker lock(s_allThreadsMutex);
323 s_allThreads.erase(this);
327 //! This function's purpose is to handle redirections in a proper way.
328 wxInputStream* CHTTPDownloadThread::GetInputStream(wxHTTP * & url_handler, const wxString& location, bool proxy)
330 if (TestDestroy()) {
331 return NULL;
334 if (!location.StartsWith(wxT("http://"))) {
335 // This is not a http url
336 throw wxString(_("Invalid URL for HTTP download or HTTP redirection (did you forget 'http://' ?)"));
339 // Get the host
341 // Remove the "http://"
342 wxString host = location.Right(location.Len() - 7); // strlen("http://") -> 7
344 // I belive this is a bug...
345 // Sometimes "Location" header looks like this:
346 // "http://www.whatever.com:8080http://www.whatever.com/downloads/something.zip"
347 // So let's clean it...
349 int bad_url_pos = host.Find(wxT("http://"));
350 wxString location_url;
351 if (bad_url_pos != -1) {
352 // Malformed Location field on header (bug above?)
353 location_url = host.Mid(bad_url_pos);
354 host = host.Left(bad_url_pos);
355 // After the first '/' non-http-related, it's the location part of the URL
356 location_url = location_url.Right(location_url.Len() - 7).AfterFirst(wxT('/'));
357 } else {
358 // Regular Location field
359 // After the first '/', it's the location part of the URL
360 location_url = host.AfterFirst(wxT('/'));
361 // The host is everything till the first "/"
362 host = host.BeforeFirst(wxT('/'));
365 // Build the cleaned url now
366 wxString url = wxT("http://") + host + wxT("/") + location_url;
368 int port = 80;
369 if (host.Find(wxT(':')) != -1) {
370 // This http url has a port
371 port = wxAtoi(host.AfterFirst(wxT(':')));
372 host = host.BeforeFirst(wxT(':'));
375 wxIPV4address addr;
376 addr.Hostname(host);
377 addr.Service(port);
378 if (!url_handler->Connect(addr, true)) {
379 throw wxString(_("Unable to connect to HTTP download server"));
382 wxInputStream* url_read_stream = url_handler->GetInputStream(url);
384 /* store the HTTP response code */
385 m_response = url_handler->GetResponse();
387 /* store the HTTP error code */
388 m_error = url_handler->GetError();
390 AddDebugLogLineN(logHTTP, CFormat(wxT("Host: %s:%i\n")) % host % port);
391 AddDebugLogLineN(logHTTP, CFormat(wxT("URL: %s\n")) % url);
392 AddDebugLogLineN(logHTTP, CFormat(wxT("Response: %i (Error: %i)")) % m_response % m_error);
394 if (!m_response) {
395 AddDebugLogLineC(logHTTP, wxT("WARNING: Void response on stream creation"));
396 // WTF? Why does this happen?
397 // This is probably produced by an already existing connection, because
398 // the input stream is created nevertheless. However, data is not the same.
399 delete url_read_stream;
400 throw wxString(_("Invalid response from HTTP download server"));
403 if (m_response == 301 // Moved permanently
404 || m_response == 302 // Moved temporarily
405 // What about 300 (multiple choices)? Do we have to handle it?
408 // We have to remove the current stream.
409 delete url_read_stream;
411 wxString new_location = url_handler->GetHeader(wxT("Location"));
413 url_handler->Destroy();
414 if (!new_location.IsEmpty()) {
415 url_handler = new wxHTTP;
416 url_handler->SetProxyMode(proxy);
417 if (m_lastmodified.IsValid()) {
418 // Set a flag in the HTTP header that we only download if the file is newer.
419 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
420 url_handler->SetHeader(wxT("If-Modified-Since"), FormatDateHTTP(m_lastmodified));
422 url_read_stream = GetInputStream(url_handler, new_location, proxy);
423 } else {
424 AddDebugLogLineC(logHTTP, wxT("ERROR: Redirection code received with no URL"));
425 url_handler = NULL;
426 url_read_stream = NULL;
428 } else if (m_response == 304) { // "Not Modified"
429 delete url_read_stream;
430 url_handler->Destroy();
431 url_read_stream = NULL;
432 url_handler = NULL;
435 return url_read_stream;
438 void CHTTPDownloadThread::StopAll()
440 ThreadSet allThreads;
442 wxMutexLocker lock(s_allThreadsMutex);
443 std::swap(allThreads, s_allThreads);
445 for (ThreadSet::iterator it = allThreads.begin(); it != allThreads.end(); it++) {
446 (*it)->Stop();
450 CHTTPDownloadThread::ThreadSet CHTTPDownloadThread::s_allThreads;
451 wxMutex CHTTPDownloadThread::s_allThreadsMutex;
453 // File_checked_for_headers