#include #include #include #include #include #include #include #include #include #include "api__wac_downloadManager.h" #include "wac_downloadManager.h" #include "wac_download_http_receiver_api.h" #include "..\wac_network\wac_network_http_receiver_api.h" #include "api/service/waservicefactory.h" #include "../nu/threadname.h" #include "../nu/AutoChar.h" #include "../nu/threadpool/timerhandle.hpp" #include "..\WAT\WAT.h" #include "..\Winamp\buildType.h" static const GUID internetConfigGroupGUID = { 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } }; #define DOWNLOAD_TIMEOUT_MS 60000 // 60 second timeout #define DOWNLOAD_SLEEP_MS 50 #define DOWNLOAD_BUFFER_SIZE 1310720 // gives a maximum download rate of 25 mb/sec per file /********************************************************************************** **********************************************************************************/ /********************************************************************************** * PUBLIC * **********************************************************************************/ wa::Components::WAC_DownloadData::WAC_DownloadData( api_wac_download_manager_http_receiver *p_http, const char *p_url, int p_flags, ifc_downloadManagerCallback *p_callback ) { _http = p_http; strcpy_s( this->_url, 1024, p_url ); _flags = p_flags; _callback = p_callback; if ( _callback ) _callback->AddRef(); _hFile = INVALID_HANDLE_VALUE; _filepath[ 0 ] = 0; _fileext = 0; int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & _flags ); switch ( download_method ) { case api_downloadManager::DOWNLOADEX_TEMPFILE: { wchar_t temppath[ MAX_PATH - 14 ] = { 0 }; // MAX_PATH-14 'cause MSDN said so GetTempPathW( MAX_PATH - 14, temppath ); GetTempFileNameW( temppath, L"wdl", 0, _filepath ); _hFile = CreateFileW( _filepath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0 ); } break; case api_downloadManager::DOWNLOADEX_CALLBACK: if ( _callback ) _callback->GetLocation( _filepath, MAX_PATH ); break; } _source[ 0 ] = 0; _title[ 0 ] = 0; if ( _flags & api_downloadManager::DOWNLOADEX_CALLBACK ) { if ( _callback ) { _callback->GetSource( _source, 1024 ); _callback->GetTitle( _title, 1024 ); } } _connectionStart = _lastDownloadTick = GetTickCount(); _last_status = HTTP_RECEIVER_STATUS_ERROR; _pending = ( _flags & api_downloadManager::DOWNLOADEX_PENDING ) > 0; } wa::Components::WAC_DownloadData::~WAC_DownloadData() { ServiceRelease( _http, httpreceiverGUID2 ); _http = NULL; if ( _fileext ) delete _fileext; int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & _flags ); if ( download_method == api_downloadManager::DOWNLOADEX_TEMPFILE && _filepath[ 0 ] ) DeleteFileW( _filepath ); if ( _callback ) _callback->Release(); _callback = NULL; } void wa::Components::WAC_DownloadData::Retain() { this->_refCount.fetch_add( 1 ); } void wa::Components::WAC_DownloadData::Release() { if ( this->_refCount.fetch_sub( 1 ) == 0 ) delete this; } void wa::Components::WAC_DownloadData::Close( ifc_downloadManagerCallback **callbackCopy ) { if ( _hFile != INVALID_HANDLE_VALUE ) CloseHandle( _hFile ); _hFile = INVALID_HANDLE_VALUE; if ( callbackCopy != NULL ) { *callbackCopy = _callback; if ( _callback != NULL ) _callback->AddRef(); } else if ( _callback != NULL ) { _callback->Release(); _callback = NULL; } // don't want to close http here, because someone might still want to get the headers out of it } bool wa::Components::WAC_DownloadData::getExtention() { if ( _fileext && *_fileext ) return _fileext; char l_header_name_content_type[] = "Content-Type"; char *l_content_type = _http->getheader( l_header_name_content_type ); if ( l_content_type && *l_content_type ) { if ( _CONTENT_TYPES_EXTENSIONS.count( l_content_type ) == 1 ) _fileext = _strdup( _CONTENT_TYPES_EXTENSIONS.find( l_content_type )->second.c_str() ); } return _fileext; } /********************************************************************************** * PRIVATE * **********************************************************************************/ /********************************************************************************** **********************************************************************************/ /********************************************************************************** * PUBLIC * **********************************************************************************/ wa::Components::WAC_DownloadManager::WAC_DownloadManager( QObject *parent ) : QNetworkAccessManager( parent ) { this->setObjectName( "DownloadManagerService" ); this->init(); } wa::Components::WAC_DownloadManager::~WAC_DownloadManager() { disconnect( _connection_authentication_required ); } DownloadToken wa::Components::WAC_DownloadManager::Download( const char *p_url, ifc_downloadManagerCallback *p_callback ) { return DownloadEx( p_url, p_callback, api_downloadManager::DOWNLOADEX_TEMPFILE ); } DownloadToken wa::Components::WAC_DownloadManager::DownloadEx( const char *p_url, ifc_downloadManagerCallback *p_callback, int p_flags ) { return DownloadToken(); } /********************************************************************************** * PRIVATE * **********************************************************************************/ void wa::Components::WAC_DownloadManager::init() { QString l_winamp_user_agent = QString( "%1 Winamp/%2" ).arg( QWebEngineProfile::defaultProfile()->httpUserAgent(), STR_WINAMP_PRODUCTVER ).replace( ",", "." ); QWebEngineProfile::defaultProfile()->setHttpUserAgent( l_winamp_user_agent ); _connection_authentication_required = connect( this, &QNetworkAccessManager::authenticationRequired, this, &wa::Components::WAC_DownloadManager::on_s_authentication_required ); } QNetworkReply *wa::Components::WAC_DownloadManager::createRequest( Operation p_operation, const QNetworkRequest &p_request, QIODevice *p_outgoing_data ) { return QNetworkAccessManager::createRequest( p_operation, p_request, p_outgoing_data ); } /********************************************************************************** * PRIVATE SLOTS * **********************************************************************************/ void wa::Components::WAC_DownloadManager::on_s_authentication_required( QNetworkReply *p_reply, QAuthenticator *p_authenticator ) { Q_UNUSED( p_reply ); Q_UNUSED( p_authenticator ); } /********************************************************************************** **********************************************************************************/ DownloadData::DownloadData( api_httpreceiver *p_http, const char *p_url, int p_flags, ifc_downloadManagerCallback *p_callback ) { flags = p_flags; http = p_http; callback = p_callback; if ( callback ) callback->AddRef(); hFile = INVALID_HANDLE_VALUE; filepath[ 0 ] = 0; fileext = 0; int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & flags ); switch ( download_method ) { case api_downloadManager::DOWNLOADEX_TEMPFILE: { wchar_t temppath[ MAX_PATH - 14 ] = { 0 }; // MAX_PATH-14 'cause MSDN said so GetTempPathW( MAX_PATH - 14, temppath ); GetTempFileNameW( temppath, L"wdl", 0, filepath ); hFile = CreateFileW( filepath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0 ); } break; case api_downloadManager::DOWNLOADEX_CALLBACK: if ( callback ) callback->GetLocation( filepath, MAX_PATH ); break; } strcpy_s( this->url, 1024, p_url ); source[ 0 ] = 0; title[ 0 ] = 0; if ( flags & api_downloadManager::DOWNLOADEX_CALLBACK ) { if ( callback ) { callback->GetSource( source, 1024 ); callback->GetTitle( title, 1024 ); } } connectionStart = lastDownloadTick = GetTickCount(); last_status = HTTPRECEIVER_STATUS_ERROR; pending = ( flags & api_downloadManager::DOWNLOADEX_PENDING ) > 0; } DownloadData::~DownloadData() { ServiceRelease( http, httpreceiverGUID ); http = NULL; if ( fileext ) delete fileext; int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & flags ); if ( download_method == api_downloadManager::DOWNLOADEX_TEMPFILE && filepath[ 0 ] ) DeleteFileW( filepath ); if ( callback ) callback->Release(); callback = NULL; } void DownloadData::Retain() { this->_refCount.fetch_add( 1 ); } void DownloadData::Release() { if ( this->_refCount.fetch_sub( 1 ) == 0 ) delete this; } void DownloadData::Close( ifc_downloadManagerCallback **callbackCopy ) { if ( hFile != INVALID_HANDLE_VALUE ) CloseHandle( hFile ); hFile = INVALID_HANDLE_VALUE; if ( callbackCopy != NULL ) { *callbackCopy = callback; if ( callback != NULL ) callback->AddRef(); } else if ( callback != NULL ) { callback->Release(); callback = NULL; } // don't want to close http here, because someone might still want to get the headers out of it } bool DownloadData::getExtention() { if ( fileext && *fileext ) return fileext; char l_header_name_content_type[] = "Content-Type"; char *l_content_type = http->getheader( l_header_name_content_type ); if ( l_content_type && *l_content_type ) { if ( _CONTENT_TYPES_EXTENSIONS.count( l_content_type ) == 1 ) fileext = _strdup( _CONTENT_TYPES_EXTENSIONS.find( l_content_type )->second.c_str() ); } return fileext; } /********************************************************************************** **********************************************************************************/ /********************************************************************************** * PUBLIC * **********************************************************************************/ DownloadManager::DownloadManager() { download_thread = NULL; killswitch = CreateEvent( NULL, TRUE, FALSE, NULL ); InitializeCriticalSection( &downloadsCS ); } void DownloadManager::Kill() { SetEvent( killswitch ); if ( download_thread ) { WaitForSingleObject( download_thread, 3000 ); CloseHandle( download_thread ); } DeleteCriticalSection( &downloadsCS ); CloseHandle( killswitch ); } static void SetUserAgent( api_httpreceiver *p_http ) { char agent[ 256 ] = { 0 }; StringCchPrintfA( agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString() ); p_http->addheader( agent ); //QString l_winamp_user_agent = QString( "User-Agent: %1 Winamp/%2" ).arg( QWebEngineProfile::defaultProfile()->httpUserAgent(), STR_WINAMP_PRODUCTVER ).replace( ",", "." ); //http->addheader( l_winamp_user_agent.toStdString().c_str() ); } DownloadToken DownloadManager::Download( const char *url, ifc_downloadManagerCallback *callback ) { return DownloadEx( url, callback, api_downloadManager::DOWNLOADEX_TEMPFILE ); } DownloadToken DownloadManager::DownloadEx( const char *url, ifc_downloadManagerCallback *callback, int flags ) { if ( InitDownloadThread() ) { api_httpreceiver *http = NULL; ServiceBuild( http, httpreceiverGUID ); if ( http ) { DownloadData *downloadData = new DownloadData( http, url, flags, callback ); int use_proxy = 1; bool proxy80 = AGAVE_API_CONFIG->GetBool( internetConfigGroupGUID, L"proxy80", false ); if ( proxy80 && strstr( url, ":" ) && ( !strstr( url, ":80/" ) && strstr( url, ":80" ) != ( url + strlen( url ) - 3 ) ) ) use_proxy = 0; const wchar_t *proxy = use_proxy ? AGAVE_API_CONFIG->GetString( internetConfigGroupGUID, L"proxy", 0 ) : 0; http->open( API_DNS_AUTODNS, DOWNLOAD_BUFFER_SIZE, ( proxy && proxy[ 0 ] ) ? (const char *)AutoChar( proxy ) : NULL ); SetUserAgent( http ); if ( callback ) callback->OnInit( downloadData ); if ( downloadData->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_status : status_callbacks ) l_status->OnInit( downloadData ); } //only call http->connect when it is not pending download request if ( !( flags & DOWNLOADEX_PENDING ) ) http->connect( url, 1 ); //http->run(); // let's get this party started EnterCriticalSection( &downloadsCS ); downloads.push_back( downloadData ); LeaveCriticalSection( &downloadsCS ); return downloadData; } } return 0; } void DownloadManager::ResumePendingDownload( DownloadToken p_token ) { if ( !p_token ) return; DownloadData *data = (DownloadData *)p_token; if ( data->pending ) { data->pending = false; data->connectionStart = data->lastDownloadTick = GetTickCount(); data->http->connect( data->url ); } } void DownloadManager::CancelDownload( DownloadToken p_token ) { if ( !p_token ) return; DownloadData *data = (DownloadData *)p_token; EnterCriticalSection( &downloadsCS ); if ( downloads.end() != std::find( downloads.begin(), downloads.end(), data ) ) { ifc_downloadManagerCallback *callback; data->Close( &callback ); //downloads.eraseObject(p_data); auto it = std::find( downloads.begin(), downloads.end(), data ); if ( it != downloads.end() ) { downloads.erase( it ); } LeaveCriticalSection( &downloadsCS ); if ( callback ) { callback->OnCancel( p_token ); if ( data->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_status : status_callbacks ) l_status->OnCancel( p_token ); } callback->Release(); } data->Release(); } else LeaveCriticalSection( &downloadsCS ); } void DownloadManager::RetainDownload( DownloadToken p_token ) { if ( !p_token ) return; DownloadData *data = (DownloadData *)p_token; if ( data ) data->Retain(); } void DownloadManager::ReleaseDownload( DownloadToken p_token ) { if ( !p_token ) return; DownloadData *data = (DownloadData *)p_token; if ( data ) data->Release(); } void DownloadManager::RegisterStatusCallback( ifc_downloadManagerCallback *callback ) { EnterCriticalSection( &downloadsCS ); status_callbacks.push_back( callback ); LeaveCriticalSection( &downloadsCS ); } void DownloadManager::UnregisterStatusCallback( ifc_downloadManagerCallback *callback ) { EnterCriticalSection( &downloadsCS ); auto it = std::find( status_callbacks.begin(), status_callbacks.end(), callback ); if ( it != status_callbacks.end() ) status_callbacks.erase( it ); LeaveCriticalSection( &downloadsCS ); } bool DownloadManager::IsPending( DownloadToken token ) { DownloadData *data = (DownloadData *)token; if ( data ) return data->pending; else return FALSE; } /********************************************************************************** * PRIVATE * **********************************************************************************/ bool DownloadManager::DownloadThreadTick() { unsigned int i = 0; char *downloadBuffer = (char *)malloc( DOWNLOAD_BUFFER_SIZE ); while ( WaitForSingleObject( killswitch, 0 ) != WAIT_OBJECT_0 ) { EnterCriticalSection( &downloadsCS ); if ( downloads.empty() ) { // TODO: might be nice to dynamically increase the sleep time if this happens // (maybe to INFINITE and have Download() wake us?) LeaveCriticalSection( &downloadsCS ); free( downloadBuffer ); return true; } if ( i >= downloads.size() ) { LeaveCriticalSection( &downloadsCS ); free( downloadBuffer ); return true; } DownloadData *thisDownload = downloads[ i ]; if ( thisDownload->pending ) { LeaveCriticalSection( &downloadsCS ); } else { thisDownload->Retain(); LeaveCriticalSection( &downloadsCS ); INT tick = Tick( thisDownload, downloadBuffer, DOWNLOAD_BUFFER_SIZE ); switch ( tick ) { case TICK_NODATA: // do nothing break; case TICK_CONNECTING: break; case TICK_CONNECTED: if ( thisDownload->callback ) thisDownload->callback->OnConnect( thisDownload ); if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_status : status_callbacks ) l_status->OnConnect( thisDownload ); } break; case TICK_SUCCESS: if ( thisDownload->callback ) thisDownload->callback->OnTick( thisDownload ); if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_status : status_callbacks ) l_status->OnTick( thisDownload ); } // TODO: send out update l_callback break; case TICK_FINISHED: case TICK_FAILURE: case TICK_TIMEOUT: case TICK_CANT_CONNECT: case TICK_WRITE_ERROR: FinishDownload( thisDownload, tick ); break; } thisDownload->Release(); } i++; } free( downloadBuffer ); return false; // we only get here when killswitch is set } int DownloadManager::DownloadTickThreadPoolFunc( HANDLE handle, void *user_data, intptr_t id ) { DownloadManager *dlmgr = (DownloadManager *)user_data; if ( dlmgr->DownloadThreadTick() ) { TimerHandle t( handle ); t.Wait( DOWNLOAD_SLEEP_MS ); } else { WASABI_API_THREADPOOL->RemoveHandle( 0, handle ); SetEvent( dlmgr->download_thread ); } return 0; } bool DownloadManager::InitDownloadThread() { if ( download_thread == NULL ) { download_thread = CreateEvent( 0, FALSE, FALSE, 0 ); TimerHandle t; WASABI_API_THREADPOOL->AddHandle( 0, t, DownloadTickThreadPoolFunc, this, 0, api_threadpool::FLAG_LONG_EXECUTION ); t.Wait( DOWNLOAD_SLEEP_MS ); } return ( download_thread != NULL ); } void DownloadManager::FinishDownload( DownloadData *p_data, int code ) { if ( p_data == NULL ) return; ifc_downloadManagerCallback *l_callback = NULL; EnterCriticalSection( &downloadsCS ); p_data->Close( &l_callback ); LeaveCriticalSection( &downloadsCS ); if ( l_callback != NULL ) { if ( code == TICK_FINISHED ) { l_callback->OnFinish( p_data ); if ( p_data->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_data : status_callbacks ) l_data->OnFinish( p_data ); } } else { l_callback->OnError( p_data, code ); if ( p_data->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_data : status_callbacks ) l_data->OnError( p_data, code ); } } l_callback->Release(); } EnterCriticalSection( &downloadsCS ); auto it = std::find( downloads.begin(), downloads.end(), p_data ); if ( it != downloads.end() ) downloads.erase( it ); LeaveCriticalSection( &downloadsCS ); p_data->Release(); } int DownloadManager::Tick( DownloadData *thisDownload, void *buffer, int bufferSize ) { if ( !thisDownload ) return api_downloadManager::TICK_FAILURE; int state = thisDownload->http->run(); if ( state == HTTPRECEIVER_RUN_ERROR || thisDownload == NULL ) return api_downloadManager::TICK_FAILURE; if ( !thisDownload->fileext ) thisDownload->getExtention(); int downloaded = thisDownload->http->get_bytes( buffer, bufferSize ); if ( downloaded ) { switch ( thisDownload->flags & DOWNLOADEX_MASK_DOWNLOADMETHOD ) { case api_downloadManager::DOWNLOADEX_BUFFER: { thisDownload->buffer.reserve( thisDownload->http->content_length() ); thisDownload->buffer.add( buffer, downloaded ); } break; case api_downloadManager::DOWNLOADEX_TEMPFILE: { DWORD written = 0; WriteFile( thisDownload->hFile, buffer, downloaded, &written, NULL ); if ( written != downloaded ) return api_downloadManager::TICK_WRITE_ERROR; } break; case api_downloadManager::DOWNLOADEX_CALLBACK: { if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI ) { for ( ifc_downloadManagerCallback *l_status : status_callbacks ) l_status->OnData( thisDownload, buffer, downloaded ); } if ( thisDownload->callback ) thisDownload->callback->OnData( thisDownload, buffer, downloaded ); } } thisDownload->lastDownloadTick = GetTickCount(); thisDownload->bytesDownloaded += downloaded; return api_downloadManager::TICK_SUCCESS; } else // nothing in the buffer { if ( state == HTTPRECEIVER_RUN_CONNECTION_CLOSED ) // see if the connection is closed { return api_downloadManager::TICK_FINISHED; // yay we're done } if ( GetTickCount() - thisDownload->lastDownloadTick > DOWNLOAD_TIMEOUT_MS ) // check for timeout return api_downloadManager::TICK_TIMEOUT; switch ( thisDownload->http->get_status() ) { case HTTPRECEIVER_STATUS_CONNECTING: if ( thisDownload->last_status != HTTPRECEIVER_STATUS_CONNECTING ) { thisDownload->last_status = HTTPRECEIVER_STATUS_CONNECTING; return api_downloadManager::TICK_CONNECTING; } else { return api_downloadManager::TICK_NODATA; } case HTTPRECEIVER_STATUS_READING_HEADERS: if ( thisDownload->last_status != HTTPRECEIVER_STATUS_READING_HEADERS ) { thisDownload->last_status = HTTPRECEIVER_STATUS_READING_HEADERS; return api_downloadManager::TICK_CONNECTED; } else { return api_downloadManager::TICK_NODATA; } } if ( !thisDownload->replyCode ) thisDownload->replyCode = thisDownload->http->getreplycode(); switch ( thisDownload->replyCode ) { case 0: case 100: case 200: case 201: case 202: case 203: case 204: case 205: case 206: return api_downloadManager::TICK_NODATA; default: return api_downloadManager::TICK_CANT_CONNECT; } } } #define CBCLASS DownloadManager START_DISPATCH; CB( API_DOWNLOADMANAGER_DOWNLOAD, Download ) CB( API_DOWNLOADMANAGER_DOWNLOADEX, DownloadEx ) CB( API_DOWNLOADMANAGER_GETRECEIVER, GetReceiver ) CB( API_DOWNLOADMANAGER_GETLOCATION, GetLocation ) VCB( API_DOWNLOADMANAGER_SETLOCATION, SetLocation ) CB( API_DOWNLOADMANAGER_GETEXTENTION, GetExtention ) CB( API_DOWNLOADMANAGER_GETURL, GetUrl ) CB( API_DOWNLOADMANAGER_GETBYTESDOWNLOADED, GetBytesDownloaded ); CB( API_DOWNLOADMANAGER_GETBUFFER, GetBuffer ); VCB( API_DOWNLOADMANAGER_RESUMEPENDINGDOWNLOAD, ResumePendingDownload ); VCB( API_DOWNLOADMANAGER_CANCELDOWNLOAD, CancelDownload ); VCB( API_DOWNLOADMANAGER_RETAINDOWNLOAD, RetainDownload ); VCB( API_DOWNLOADMANAGER_RELEASEDOWNLOAD, ReleaseDownload ); VCB( API_DOWNLOADMANAGER_REGISTERSTATUSCALLBACK, RegisterStatusCallback ); VCB( API_DOWNLOADMANAGER_UNREGISTERSTATUSCALLBACK, UnregisterStatusCallback ); CB( API_DOWNLOADMANAGER_GETSOURCE, GetSource ); CB( API_DOWNLOADMANAGER_GETTITLE, GetTitle ); CB( API_DOWNLOADMANAGER_ISPENDING, IsPending ); END_DISPATCH; #undef CBCLASS