Search code examples
multithreadingc++builderindyidhttp

How to download multiple files at once with TIdHTTP


I'm currently using TIdHTTP from Indy with my Embarcadero C++Builder 10.1 Berlin. I have read some online tutorials on how to make TIdHTTP multi-threaded, but the main issue is I already tested this component in a thread.

So here is how it works. I created a thread object and I made a function to download a file in that thread, apparently the thread works fine and the file get downloaded to my Disk. But, when I create additional threads for file downloads, the first thread stops. I don't want that, I want both files to continue to download at the same time (without pausing the first thread), like in IDM (Internet Download Manager).

The thread class is like in the code below:

class TIdHTTPThread : public TThread
{
protected:
        void __fastcall Execute();
        void __fastcall PutDownloadedFile();
    public:
        __fastcall TIdHTTPThread(bool CreateSuspended);
        void __fastcall IdHTTPBeginWork(TObject *ASender, TWorkMode AWorkMode,
              __int64 AWorkCountMax);
        void __fastcall IdHTTPWork(TObject *ASender, TWorkMode AWorkMode,
          __int64 AWorkCount);
        void __fastcall IdHTTPEndWork(TObject *ASender, TWorkMode AWorkMode);
        void __fastcall DownloadFile(UnicodeString AFileURL, UnicodeString AFileDest);
        void __fastcall CreateQueue(TWinControl* wcParent, TAlign alAlign);
    private:
        TIdHTTP* IdHTTP;
        TMemoryStream* msMemoryStream;
        UnicodeString uFileURL;
        UnicodeString uFileDest;
        int iDownProgress;
        int iFileSize;
        int iMaxProgress;
        int iDownSpeed;
        TWinControl* wcParent;
        TIFDQueue *ifdQueue;
};

Please don't bother about the additional properties and methods in the class, I just want to achieve what I need in my question.

CPP File:

void __fastcall TIdHTTPThread::CreateQueue(TWinControl* wcParent, TAlign alAlign)
{
    this->wcParent = wcParent;
    ifdQueue = new TIFDQueue(this->wcParent, alAlign);
}

void __fastcall TIdHTTPThread::IdHTTPBeginWork(TObject *ASender, TWorkMode AWorkMode,
              __int64 AWorkCountMax)
{
    this->iFileSize     = AWorkCountMax;
    this->iMaxProgress  = AWorkCountMax;
    ifdQueue->SetFileSize(this->iFileSize);
    ifdQueue->SetMaxProgress(this->iMaxProgress);
    ifdQueue->SetFileURL(this->uFileURL);
    ifdQueue->SetFilePath(this->uFileDest);
    ifdQueue->OnBeginUpdate();
}

void __fastcall TIdHTTPThread::IdHTTPWork(TObject *ASender, TWorkMode AWorkMode,
          __int64 AWorkCount)
{
    this->iDownProgress = AWorkCount;
    this->iDownSpeed    = AWorkCount / 1024;
    ifdQueue->SetDownProgress(this->iDownProgress);
    ifdQueue->SetDownSpeed(this->iDownSpeed);
    ifdQueue->OnWorkUpdate();
}

void __fastcall TIdHTTPThread::IdHTTPEndWork(TObject *ASender, TWorkMode AWorkMode)
{
    ifdQueue->OnEndUpdate();
    this->Terminate();
}
//**//

void __fastcall TIdHTTPThread::DownloadFile(UnicodeString AFileURL, UnicodeString AFileDest)
{
    this->uFileURL  = AFileURL;
    this->uFileDest = AFileDest;
}

void __fastcall TIdHTTPThread::PutDownloadedFile()
{
    try {
        this->msMemoryStream = new TMemoryStream;
        this->IdHTTP                = new TIdHTTP(NULL);
        this->IdHTTP->OnWorkBegin   = this->IdHTTPBeginWork;
        this->IdHTTP->OnWork        = this->IdHTTPWork;
        this->IdHTTP->OnWorkEnd     = this->IdHTTPEndWork;
        this->IdHTTP->ConnectTimeout = 20000;
        this->IdHTTP->ReadTimeout   = 60000;
        this->IdHTTP->Get(this->uFileURL, this->msMemoryStream);
        this->msMemoryStream->SaveToFile(this->uFileDest);
    } __finally {
        delete this->msMemoryStream;
        delete this->IdHTTP;
    }
}

__fastcall TIdHTTPThread::TIdHTTPThread(bool CreateSuspended)
    : TThread(CreateSuspended)
{

}
//---------------------------------------------------------------------------
void __fastcall TIdHTTPThread::Execute()
{
    //---- Place thread code here ----
    FreeOnTerminate = true;
    Synchronize(&this->PutDownloadedFile);
}
//---------------------------------------------------------------------------

UPDATE:

void __fastcall TIdHTTPThread::PutDownloadedFile()
{
    try {
        this->CookieManager = new TIdCookieManager(NULL);
        this->SSLIOHandlerSocket = new TIdSSLIOHandlerSocketOpenSSL(NULL);
        this->msMemoryStream = new TMemoryStream;
        // Configure SSL IOHandler
        this->SSLIOHandlerSocket->SSLOptions->Method = sslvSSLv23;
        this->SSLIOHandlerSocket->SSLOptions->SSLVersions = TIdSSLVersions() << sslvTLSv1_2 << sslvTLSv1_1 << sslvTLSv1;
        // Setup HTTP
        this->IdHTTP                  = new TIdHTTP(NULL);
        this->ifdQueue->StopDownload(this->IdHTTP);  // Function To stop download When Fired (doesn't fire imidiatly)
        this->IdHTTP->OnWorkBegin     = this->IdHTTPBeginWork;
        this->IdHTTP->OnWork          = this->IdHTTPWork;
        this->IdHTTP->OnWorkEnd       = this->IdHTTPEndWork;
        this->IdHTTP->OnRedirect      = this->IdHTTPRedirect;
        this->IdHTTP->HandleRedirects = true;
        this->IdHTTP->AllowCookies    = true;
        this->IdHTTP->CookieManager   = this->CookieManager;
        this->IdHTTP->IOHandler       = this->SSLIOHandlerSocket;
        this->IdHTTP->Get(this->uFileURL, this->msMemoryStream);
        if ( this->msMemoryStream->Size >= this->IdHTTP->Response->ContentLength ) {
            this->msMemoryStream->SaveToFile(this->uFileName);
        }
    } __finally {
        delete this->msMemoryStream;
        delete this->CookieManager;
        delete this->SSLIOHandlerSocket;
        delete this->IdHTTP;
    }
}

Solution

  • The problem is your thread's Execute() method is doing ALL of its work inside a Synchronize() call, so ALL of its work is actually being done inside the main UI thread instead, thus serializing your downloads and defeating the whole point of using a worker thread.

    DO NOT call PutDownloadedFile() itself with Synchronize(). You need to instead change your individual TIdHTTP status events to use Synchronize() (or Queue()) when updating your UI controls.