Download file with CURL and wxThread with progressbar

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
psychegr
Earned a small fee
Earned a small fee
Posts: 24
Joined: Wed Mar 05, 2014 1:19 pm

Download file with CURL and wxThread with progressbar

Post by psychegr » Sat Nov 14, 2020 4:49 pm

Hi,

I want to have my piece of software check for available updates on my server, download and install the latest one. Currently i am using wxURL to make it happen and it works but sooner or later the http will change to https and that means the wxURL will stop functioning.
I already use libcurl in my projects for communication in my https server but i need to see some code examples that will allow me to download a file using libcurl and wxThread. Is there anything available, or even better some guides? I tried to follow the examples at the libcurl website but they are all for C language while i am using C++ with classes and all that.

Thank you in advance

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 4829
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Download file with CURL and wxThread with progressbar

Post by ONEEYEMAN » Sat Nov 14, 2020 7:02 pm

Hi,
You just create cURL wrapper and use it in your code.

Just like before - you said you already using cURL...

Thank you.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 380
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: Download file with CURL and wxThread with progressbar

Post by New Pagodi » Sun Nov 15, 2020 12:40 am

Here's some old code I had lying around that for downloading with CURL in a secondary thread. I added the progress callbacks and they seem to work.

Code: Select all

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

// wxWidgets headers
#include <wx/thread.h>
#include <wx/msgqueue.h>

// CURL headears
#include <curl/curl.h>

wxDEFINE_EVENT(wxEVT_MYTHREAD_DOWNLOAD_COMPLETE, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_MYTHREAD_DOWNLOAD_PROGRESS, wxThreadEvent);

class Msg
{
public:
    enum Message
    {
        Fetch,
        Exit,
        MessageLast
    };

    Msg(){Init();}
    Msg(Message m){Init(m);}
    Msg(const wxString& s){Init(Fetch,s);}

    Message GetMsg() const{return m_message;}
    wxString GetFileName() const{return m_file;}

private:
    void Init(Message m = MessageLast, const wxString& s = wxString())
    {
        m_message = m;
        m_file=s;
    }

    Message m_message;
    wxString m_file;
};


////////////////////CurlWriteBuffer////////////////////////

class CurlWriteBuffer
{
    public:
        CurlWriteBuffer():m_writeFailed(false){}
        void Reset(){m_data.clear(); m_writeFailed=false;}
        bool Write(void*,size_t);
        bool WriteFailed() const {return m_writeFailed;}
        char* GerBuffer(){return (m_data.empty() ? NULL : &(m_data[0]) );}
        size_t GetSize()const {return m_data.size();}
        void Reserve(size_t s){m_data.reserve(s);}
        size_t GetCapacity() const { return m_data.capacity();}

    private:
        std::vector<char> m_data;
        bool m_writeFailed;
};

bool CurlWriteBuffer::Write(void* contents,size_t sz)
{
    try
    {
        //method for copying the buffer to the vector based on:
        //http://stackoverflow.com/questions/4758257/how-to-copy-a-range-of-data-from-char-array-into-a-vector
        char* charcontents = reinterpret_cast<char*>(contents);
        m_data.insert(m_data.end(), charcontents, charcontents+sz);
    }
    catch( std::bad_alloc& WXUNUSED(ba) )
    {
        m_writeFailed=true;
        return false;
    }

    return true;
}

class CurlCtrl
{
    public:
        CurlCtrl(wxEvtHandler*);
        ~CurlCtrl();

        CURLcode Fetch(const char *);
        void ClearBuffer();
        char* GetBuffer();
        char* GetErrors();
        size_t GetSize();
        void OnProgress(curl_off_t dltotal, curl_off_t dlnow);
        size_t OnWrite(void *contents, size_t size, size_t nmemb);

    private:
        static size_t WriteMemoryCallback(void*, size_t, size_t, void*);
        static int ProgressCallback(void*, curl_off_t, curl_off_t, curl_off_t,
                                    curl_off_t);

        CURL* m_handle;
        CurlWriteBuffer m_buffer;
        char* m_errorMsgs;

        wxEvtHandler* m_handler;
        wxDateTime m_lastUpdate;
};

CurlCtrl::CurlCtrl(wxEvtHandler* handler):m_buffer(),m_handler(handler)
{
    m_lastUpdate = wxDateTime::UNow();

    curl_global_init(CURL_GLOBAL_ALL);

    /* init the curl session */
    m_handle = curl_easy_init();

    // Setup a buffer for any error messages
    m_errorMsgs = new char[CURL_ERROR_SIZE];
    curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorMsgs);

    /* send all data to this function  */
    curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);

    // Handle progress updates
    curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, this);
    curl_easy_setopt(m_handle, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
    curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0L);

    /* some servers don't like requests that are made without a user-agent
     field, so we provide one */
    curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");

    curl_easy_setopt(m_handle, CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );

    curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYHOST, 0L);


    curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1 );
}

CurlCtrl::~CurlCtrl()
{
    /* cleanup curl stuff */
    curl_easy_cleanup(m_handle);

    /* we're done with libcurl, so clean it up */
    curl_global_cleanup();

    delete[] m_errorMsgs;
}

CURLcode CurlCtrl::Fetch(const char * url)
{
    CURLcode rv;
    m_buffer.Reset();

    curl_easy_setopt(m_handle, CURLOPT_URL, url);

    /* get it! */
    rv = curl_easy_perform(m_handle);

    if ( m_buffer.WriteFailed() )
    {
        return CURLE_OUT_OF_MEMORY;
    }
    else
    {
        return rv;
    }
}

void CurlCtrl::OnProgress(curl_off_t dltotal, curl_off_t dlnow)
{
    wxDateTime curTime = wxDateTime::UNow();

    if ( curTime.GetValue() - m_lastUpdate.GetValue() > 50  )
    {
        wxThreadEvent* ev = new wxThreadEvent(wxEVT_MYTHREAD_DOWNLOAD_PROGRESS);

        wxLongLong dlCur(dlnow);
        wxLongLong dlTotal(dltotal);
        wxLongLong perc = ( dlTotal == 0 ? 0 : (100*dlnow)/dltotal );

        wxString s = wxString::Format("%s/%s = %s %%", dlCur.ToString(),
                                      dlTotal.ToString(), perc.ToString());

        size_t d = static_cast<size_t>(dlTotal.GetValue());

        if ( d > m_buffer.GetCapacity() )
        {
            m_buffer.Reserve(d);
        }

        ev->SetExtraLong(perc.ToLong());
        ev->SetString(s);
        wxQueueEvent(m_handler, ev);

        m_lastUpdate = curTime;
    }
}

int CurlCtrl::ProgressCallback(void* clientp, curl_off_t dltotal,
                                    curl_off_t dlnow, curl_off_t ultotal,
                                    curl_off_t ulnow)
{
    CurlCtrl* curlCtrl = reinterpret_cast<CurlCtrl*>(clientp);
    curlCtrl->OnProgress(dltotal, dlnow);

    return CURL_PROGRESSFUNC_CONTINUE;
}

size_t CurlCtrl::OnWrite(void *contents, size_t size, size_t nmemb)
{
    size_t realsize = size * nmemb;

    if( m_buffer.Write(contents,realsize) )
    {
        return realsize;
    }
    else
    {
        return realsize+1;
    }
}

size_t CurlCtrl::WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    CurlCtrl* curlCtrl = reinterpret_cast<CurlCtrl*>(userp);
    return curlCtrl->OnWrite(contents, size, nmemb);
}

void CurlCtrl::ClearBuffer()
{
    m_buffer.Reset();
}

char* CurlCtrl::GetBuffer()
{
    return m_buffer.GerBuffer();
}

size_t CurlCtrl::GetSize()
{
    return m_buffer.GetSize();
}

char* CurlCtrl::GetErrors()
{
    return m_errorMsgs;
}

class FetchThread : public wxThread
{
public:
    FetchThread(wxEvtHandler* handler, wxMessageQueue<Msg>& q,wxCriticalSection&,bool*);
    ~FetchThread();

protected:
    virtual ExitCode Entry();

private:
    void ProcessMessage(Msg& m);
    void FetchFile();

    wxEvtHandler* m_handler;
    wxMessageQueue<Msg>& m_queue;
    wxCriticalSection& m_critsect;
    bool* m_isRunning;

    std::queue<wxString> m_filesToFetch;
    CurlCtrl m_curl;
    bool m_shutDown;
};

FetchThread::FetchThread(wxEvtHandler* handler, wxMessageQueue<Msg>& q, wxCriticalSection& c,bool* d)
           :wxThread(wxTHREAD_DETACHED),m_queue(q),m_critsect(c),m_curl(handler)
{
    m_handler = handler;
    m_isRunning = d;
    m_shutDown = false;
}

FetchThread::~FetchThread()
{
    {
        wxCriticalSectionLocker enter(m_critsect);
        // the thread is being destroyed.
        *m_isRunning = false;
    }
}

void FetchThread::ProcessMessage(Msg& m)
{
    if ( m.GetMsg() == Msg::Fetch )
    {
        m_filesToFetch.push(m.GetFileName());
    }
    else if ( m.GetMsg() == Msg::Exit )
    {
        m_shutDown = true;
    }
}

void FetchThread::FetchFile()
{
    wxString fileToFetch = m_filesToFetch.front();
    m_filesToFetch.pop();

    CURLcode r = m_curl.Fetch(fileToFetch);
    wxThreadEvent* ev = new wxThreadEvent(wxEVT_MYTHREAD_DOWNLOAD_COMPLETE);

    void* buffer = NULL;

    if ( r == CURLE_OK )
    {
        size_t sz = m_curl.GetSize();
        buffer = malloc(sz);

        memcpy(buffer, m_curl.GetBuffer(), sz);
        ev->SetInt(sz);
    }

    ev->SetString(fileToFetch);
    ev->SetPayload<void*>(buffer);
    wxQueueEvent(m_handler, ev);
}

wxThread::ExitCode FetchThread::Entry()
{
    wxMessageQueueError e;
    Msg m;

    while ( true )
    {
        // Check if termination was requested
        if ( m_shutDown || TestDestroy() )
        {
            break;
        }

        // Check if there is a message available immediately.
        e = m_queue.ReceiveTimeout(0, m);
        if ( e == wxMSGQUEUE_NO_ERROR )
        {
            ProcessMessage(m);
            continue;
        }

        if ( !m_filesToFetch.empty() )
        {
            FetchFile();
            continue;
        }

        // Now wait for a message.
        e = m_queue.ReceiveTimeout(100, m);

        if ( e == wxMSGQUEUE_NO_ERROR )
        {
            ProcessMessage(m);
        }
    }

    return (wxThread::ExitCode)0;     // success
}

class MyFrame: public wxFrame
{
    public:
        MyFrame();
        ~MyFrame();
    private:
        void OnFetch(wxCommandEvent& event);
        void OnFetchComplete(wxThreadEvent& event);
        void OnFetchProgress(wxThreadEvent& event);

        wxMessageQueue<Msg> m_queue;
        wxCriticalSection m_pThreadCS;
        bool m_threadIsRunning;

        wxTextCtrl* m_urlTextCtrl;
        wxTextCtrl* m_msgArea;
        wxGauge* m_gauge;
};

MyFrame::MyFrame()
        :wxFrame(NULL, wxID_ANY, "CURL Fetch frame", wxDefaultPosition,
                 wxSize(600, 400))
{
    wxPanel* mainPanel = new wxPanel(this,wxID_ANY);

    wxStaticText* urlPrompt = new wxStaticText(mainPanel, wxID_ANY, "Url:");
    m_urlTextCtrl = new wxTextCtrl(mainPanel, wxID_ANY, wxEmptyString);
    wxButton* fetchButton = new wxButton(mainPanel, wxID_ANY, "Fetch");


    m_gauge = new wxGauge( mainPanel, wxID_ANY, 100 );
    m_gauge->SetValue( 0 );

    m_msgArea = new wxTextCtrl( mainPanel, wxID_ANY, wxEmptyString,
                              wxDefaultPosition, wxDefaultSize,
                              wxTE_DONTWRAP|wxTE_MULTILINE );

    wxBoxSizer* topSizer = new wxBoxSizer( wxHORIZONTAL );

    topSizer->Add(urlPrompt, wxSizerFlags(0).CenterVertical().Border(wxALL));
    topSizer->Add(m_urlTextCtrl, wxSizerFlags(1).CenterVertical().Border(wxTOP|wxBOTTOM|wxRIGHT));
    topSizer->Add(fetchButton, wxSizerFlags(0).CenterVertical().Border(wxTOP|wxBOTTOM|wxRIGHT));


    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

    mainSizer->Add(topSizer,wxSizerFlags(0).Expand());
    mainSizer->Add(m_gauge,wxSizerFlags(0).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));
    mainSizer->Add(m_msgArea,wxSizerFlags(1).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));

    mainPanel->SetSizer(mainSizer);
    Layout();


    m_threadIsRunning = true;

    {
        wxThread* t = new FetchThread(this,m_queue,m_pThreadCS,&m_threadIsRunning);
        if ( t->Run() != wxTHREAD_NO_ERROR )
        {
            m_threadIsRunning = false;
        }
    }

    if ( !m_threadIsRunning )
    {
        m_msgArea->AppendText("Can't create the thread!\n");
        fetchButton->Disable();
        m_urlTextCtrl->Disable();
    }

    Bind(wxEVT_MYTHREAD_DOWNLOAD_COMPLETE, &MyFrame::OnFetchComplete, this);
    Bind(wxEVT_MYTHREAD_DOWNLOAD_PROGRESS, &MyFrame::OnFetchProgress, this);
    fetchButton->Bind(wxEVT_BUTTON, &MyFrame::OnFetch, this);
}

MyFrame::~MyFrame()
{
    Msg m(Msg::Exit);
    m_queue.Post(m);

    while (1)
    {
        { // was the ~MyThread() function executed?
            wxCriticalSectionLocker enter(m_pThreadCS);
            if ( !m_threadIsRunning )
            {
                break;
            }
        }
        // wait for thread completion
        wxThread::This()->Sleep(1);
    }
}

void MyFrame::OnFetch(wxCommandEvent &event)
{
    if ( m_threadIsRunning )
    {
        wxString url = m_urlTextCtrl->GetValue();
        Msg m(url);

        wxString s = wxString::Format("Fetching %s.\n",url);
        m_msgArea->AppendText(s);

        m_queue.Post(m);
    }
    else
    {
        m_msgArea->AppendText("Fetch thread is not running.\n");
    }
}

void MyFrame::OnFetchComplete(wxThreadEvent& event)
{
    void* buffer = event.GetPayload<void*>();
    wxString label = event.GetString();

    wxString s = wxString::Format("%s fetched.\n",label);
    m_msgArea->AppendText(s);

    m_gauge->SetValue(100);

    free(buffer);
}

void MyFrame::OnFetchProgress(wxThreadEvent& event)
{
    wxString perc = event.GetString();

    wxString s = wxString::Format("FetchProgress %s .\n",perc);
    m_msgArea->AppendText(s);

    m_gauge->SetValue(event.GetExtraLong());
}

class MyApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            ::wxInitAllImageHandlers();
            MyFrame* frame = new MyFrame();
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(MyApp);

curl.png
curl.png (17.04 KiB) Viewed 197 times
This only downloads to memory and uses a std::vector for the memory buffer, so it can only be used for small files. This code does nothing but delete the downloaded contents, but presumably real code would save it to a file or use it in some other way before deleting it from memory. I'm not sure I'd do things the same way if I was rewriting this now, so please take it as an example, but not necessarily a good example.

Also, the options set in CurlCtrl::CurlCtrl are based on using the windows built in cert store for https. I think slightly different options would be needed for linux and mac, but I'm not sure what changes will be needed.

Post Reply