winamp/Src/Plugins/Library/ml_wire/BackgroundDownloader.cpp
2024-09-24 14:54:57 +02:00

338 lines
8.6 KiB
C++

#include <vector>
#include <atomic>
#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<DownloadToken> 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<std::size_t> _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