#include #include #include "Main.h" #include "Downloaded.h" #include "BackgroundDownloader.h" #include "Feeds.h" #include "DownloadStatus.h" #include "DownloadsDialog.h" #include "api__ml_wire.h" #include "api/service/waServiceFactory.h" #include "../../..\Components\wac_network\wac_network_http_receiver_api.h" using namespace Nullsoft::Utility; #define SIMULTANEOUS_DOWNLOADS 2 std::vector downloadsQueue; LockGuard downloadsQueueLock; class DownloaderCallback : public ifc_downloadManagerCallback { public: DownloaderCallback( const wchar_t *url, const wchar_t *destination_filepath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate ) { this->hFile = INVALID_HANDLE_VALUE; this->url = _wcsdup( url ); this->destination_filepath = _wcsdup( destination_filepath ); this->channel = _wcsdup( channel ); this->item = _wcsdup( item ); this->publishDate = publishDate; this->totalSize = 0; this->downloaded = 0; } void OnInit(DownloadToken token) { // ---- Inform the download status service of our presence---- downloadStatus.AddDownloadThread(token, this->channel, this->item, this->destination_filepath); } void OnConnect(DownloadToken token) { // ---- retrieve total size api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token); if (http) { this->totalSize = http->content_length(); } // ---- create file handle hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if ( hFile == INVALID_HANDLE_VALUE ) { WAC_API_DOWNLOADMANAGER->CancelDownload(token); } } void OnData(DownloadToken token, void *data, size_t datalen) { // ---- OnConnect copied here due to dlmgr OnData called first // ---- retrieve total size api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token); if ( !this->totalSize && http ) { this->totalSize = http->content_length(); } if ( hFile == INVALID_HANDLE_VALUE ) { // ---- create file handle hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if ( hFile == INVALID_HANDLE_VALUE ) { WAC_API_DOWNLOADMANAGER->CancelDownload(token); return; } } // ---- OnConnect to be removed once dlmgr is fixed // ---- OnData // ---- if file handle is invalid, then cancel download if ( hFile == INVALID_HANDLE_VALUE ) { WAC_API_DOWNLOADMANAGER->CancelDownload(token); return; } this->downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded(token); if ( datalen > 0 ) { // ---- hFile is valid handle, and write to disk DWORD numWritten = 0; WriteFile(hFile, data,(DWORD)datalen, &numWritten, FALSE); // ---- failed writing the number of datalen characters, cancel download if (numWritten != datalen) { WAC_API_DOWNLOADMANAGER->CancelDownload(token); return; } } // if killswitch is turned on, then cancel download if ( downloadStatus.UpdateStatus(token, this->downloaded, this->totalSize) ) { WAC_API_DOWNLOADMANAGER->CancelDownload(token); } } void OnCancel(DownloadToken token) { if ( hFile != INVALID_HANDLE_VALUE ) { CloseHandle(hFile); DeleteFile(destination_filepath); } DownloadsUpdated(token,NULL); downloadStatus.DownloadThreadDone(token); { AutoLock lock( downloadsQueueLock ); size_t l_index = 0; for ( DownloadToken &l_download_token : downloadsQueue ) { if ( l_download_token == token ) { downloadsQueue.erase( downloadsQueue.begin() + l_index ); break; } ++l_index; } } for ( DownloadToken &l_download_token : downloadsQueue ) { if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) ) { WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token ); break; } } this->Release(); } void OnError(DownloadToken token, int error) { if ( hFile != INVALID_HANDLE_VALUE ) { CloseHandle(hFile); DeleteFile(destination_filepath); } DownloadsUpdated(token,NULL); downloadStatus.DownloadThreadDone(token); { AutoLock lock(downloadsQueueLock); for (size_t index = 0; index < downloadsQueue.size(); index++) { if (downloadsQueue.at(index) == token) { downloadsQueue.erase(downloadsQueue.begin() + index); break; } } for (size_t index = 0; index < downloadsQueue.size(); index++) { if(WAC_API_DOWNLOADMANAGER->IsPending(downloadsQueue.at(index))) { WAC_API_DOWNLOADMANAGER->ResumePendingDownload(downloadsQueue.at(index)); break; } } } this->Release(); } void OnFinish( DownloadToken token ) { if ( hFile != INVALID_HANDLE_VALUE ) { CloseHandle( hFile ); DownloadedFile *data = new DownloadedFile( this->url, this->destination_filepath, this->channel, this->item, this->publishDate ); data->bytesDownloaded = this->downloaded; data->totalSize = this->totalSize; { AutoLock lock( downloadedFiles ); downloadedFiles.downloadList.push_back( *data ); addToLibrary_thread( *data ); AddPodcastData( *data ); DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] ); } downloadStatus.DownloadThreadDone( token ); delete data; } else { DownloadsUpdated( token, NULL ); downloadStatus.DownloadThreadDone( token ); } { AutoLock lock( downloadsQueueLock ); size_t l_index = 0; for ( DownloadToken &l_download_token : downloadsQueue ) { if ( l_download_token == token ) { downloadsQueue.erase( downloadsQueue.begin() + l_index ); break; } ++l_index; } for ( DownloadToken &l_download_token : downloadsQueue ) { if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) ) { WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token ); break; } } } this->Release(); } int GetSource( wchar_t *source, size_t source_cch ) { return wcscpy_s( source, source_cch, this->channel ); } int GetTitle( wchar_t *title, size_t title_cch ) { return wcscpy_s( title, title_cch, this->item ); } int GetLocation( wchar_t *location, size_t location_cch ) { return wcscpy_s( location, location_cch, this->destination_filepath ); } size_t AddRef() { return _ref_count.fetch_add( 1 ); } size_t Release() { if ( _ref_count.load() == 0 ) return _ref_count.load(); LONG r = _ref_count.fetch_sub( 1 ); if ( r == 0 ) delete( this ); return r; } private: // private destructor so no one accidentally calls delete directly on this reference counted object ~DownloaderCallback() { if ( url ) free( url ); if ( destination_filepath ) free( destination_filepath ); if ( channel ) free( channel ); if ( item ) free( item ); } protected: RECVS_DISPATCH; private: HANDLE hFile; wchar_t *url; wchar_t *destination_filepath; wchar_t *channel; wchar_t *item; __time64_t publishDate; size_t totalSize; size_t downloaded; std::atomic _ref_count = 1; }; void BackgroundDownloader::Download( const wchar_t *url, const wchar_t *savePath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate ) { DownloaderCallback *callback = new DownloaderCallback( url, savePath, channel, item, publishDate ); { Nullsoft::Utility::AutoLock lock( downloadsQueueLock ); if ( downloadsQueue.size() < SIMULTANEOUS_DOWNLOADS ) { DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_UI ); downloadsQueue.push_back( dt ); } else { DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_PENDING | api_downloadManager::DOWNLOADEX_UI ); downloadsQueue.push_back( dt ); } } } BackgroundDownloader downloader; #define CBCLASS DownloaderCallback START_DISPATCH; VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish ) VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel ) VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError ) VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData ) VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect ) VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit ) CB( IFC_DOWNLOADMANAGERCALLBACK_GETSOURCE, GetSource ) CB( IFC_DOWNLOADMANAGERCALLBACK_GETTITLE, GetTitle ) CB( IFC_DOWNLOADMANAGERCALLBACK_GETLOCATION, GetLocation ) CB( ADDREF, AddRef ) CB( RELEASE, Release ) END_DISPATCH; #undef CBCLASS