赞助

使用libcurl开源库和Duilib做的下载文件并显示进度条的小工具

转载:http://blog.csdn.net/mfcing/article/details/43603525

转载:http://blog.csdn.net/infoworld/article/details/46646933

转载:http://blog.csdn.net/qq_25867649/article/details/52789467?locationNum=2

转载:http://www.cnblogs.com/wing-h/p/3263488.html

转载:http://blog.csdn.net/infoworld/article/details/46646933

转载:http://blog.csdn.net/xiaojun111111/article/details/53032126

转载:http://ysir.me/2015/08/05/libcurl%E5%AE%9E%E7%8E%B0%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0/

转载:http://blog.csdn.net/qq_25867649/article/details/52913501

界面借用了网友mfcing,然后进行了修改

每个下载任务包含的功能:

1.任务名字

2.下载剩余时间

3.下载速度

4.下载进度百分比

5.继续下载

6.暂停下载

7.取消下载任务

 

一、首先扩展Duilib下载List

.h文件

#pragma once

class CDownloadListUI : public CTileLayoutUI
{
public:
     enum { SCROLL_TIMERID = 10 };
    CDownloadListUI(void);
    ~CDownloadListUI(void);
    virtual void DoEvent(TEventUI& event);
    void AddItem();

/*
@param strUrl 下载文件url地址
@param strFileNmae 任务文件名
*/
void AddItem(CDuiString strUrl,CDuiString strFilename); private: UINT m_uButtonState; POINT m_ptLastMouse; LONG m_dwDelayDeltaY; DWORD m_dwDelayNum; DWORD m_dwDelayLeft; }; inline double CalculateDelay(double state) { return pow(state, 2); }

.cpp文件

#include "StdAfx.h"
#include "DownloadListUI.h"


CDownloadListUI::CDownloadListUI(void)
    :m_uButtonState(0)
    , m_dwDelayDeltaY(0)
    , m_dwDelayNum(0)
    , m_dwDelayLeft(0)
{
}


CDownloadListUI::~CDownloadListUI(void)
{
}

void CDownloadListUI::DoEvent( TEventUI& event )
{
    if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
        if( m_pParent != NULL ) m_pParent->DoEvent(event);
        else CTileLayoutUI::DoEvent(event);
        return;
    }

    if( event.Type == UIEVENT_TIMER && event.wParam == SCROLL_TIMERID )
    {
        if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
            POINT pt = m_pManager->GetMousePos();
            LONG cy = (pt.y - m_ptLastMouse.y);
            m_ptLastMouse = pt;
            SIZE sz = GetScrollPos();
            sz.cy -= cy;
            SetScrollPos(sz);
            return;
        }
        else if( m_dwDelayLeft > 0 ) {
            --m_dwDelayLeft;
            SIZE sz = GetScrollPos();
            LONG lDeltaY =  (LONG)(CalculateDelay((double)m_dwDelayLeft / m_dwDelayNum) * m_dwDelayDeltaY);
            if( (lDeltaY > 0 && sz.cy != 0)  || (lDeltaY < 0 && sz.cy != GetScrollRange().cy ) ) {
                sz.cy -= lDeltaY;
                SetScrollPos(sz);
                return;
            }
        }
        m_dwDelayDeltaY = 0;
        m_dwDelayNum = 0;
        m_dwDelayLeft = 0;
        m_pManager->KillTimer(this, SCROLL_TIMERID);
        return;
    }
    if( event.Type == UIEVENT_MOUSEWHEEL )
    {
        LONG lDeltaY = 0;
        if( m_dwDelayNum > 0 ) lDeltaY =  (LONG)(CalculateDelay((double)m_dwDelayLeft / m_dwDelayNum) * m_dwDelayDeltaY);
        switch( LOWORD(event.wParam) ) {
        case SB_LINEUP:
            if( m_dwDelayDeltaY >= 0 ) m_dwDelayDeltaY = lDeltaY + 8;
            else m_dwDelayDeltaY = lDeltaY + 12;
            break;
        case SB_LINEDOWN:
            if( m_dwDelayDeltaY <= 0 ) m_dwDelayDeltaY = lDeltaY - 8;
            else m_dwDelayDeltaY = lDeltaY - 12;
            break;
        }
        if( m_dwDelayDeltaY > 100 ) m_dwDelayDeltaY = 100;
        else if( m_dwDelayDeltaY < -100 ) m_dwDelayDeltaY = -100;
        m_dwDelayNum = (DWORD)sqrt((double)abs(m_dwDelayDeltaY)) * 5;
        m_dwDelayLeft = m_dwDelayNum;
        m_pManager->SetTimer(this, SCROLL_TIMERID, 50U);
        return;
    }
    CTileLayoutUI::DoEvent(event);
}

void CDownloadListUI::AddItem()
{
    CDialogBuilder builder;
    CContainerUI* pItem=static_cast<CContainerUI*>(builder.Create(L"Item_load.xml", 0));
    if ( pItem )
    {
        int i=GetCount();
        CDuiString strText;
        strText.Format(L"%02d", i+1);
        CControlUI* pControl=pItem->GetItemAt(0);
        if ( pControl )
            pControl->SetText(strText);
        pControl=pItem->GetItemAt(2);
        CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
        if ( pProgress )
            pProgress->SetValue(i+1);

        pControl=pItem->GetItemAt(3);
        if ( pControl )
        {
            strText.Format(L"%02d:%02d:%02d", 1,1,1);
            pControl->SetText(strText);
        }

        pControl=pItem->GetItemAt(4);
        if ( pControl )
        {
            strText.Format(L"%dM/s", i+1);
            pControl->SetText(strText);
        }

        pControl=pItem->GetItemAt(5);
        if ( pControl )
        {
            strText.Format(L"%d%%", i+1);
            pControl->SetText(strText);
        }
        pControl=pItem->GetItemAt(6);
        if ( pControl )
            pControl->SetText(L"正在下载");
        pControl=pItem->GetItemAt(7);
        if ( pControl )
        {
            strText.Format(L"BtnLoad1%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        pControl=pItem->GetItemAt(8);
        if ( pControl )
        {
            strText.Format(L"BtnLoad2%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        pControl=pItem->GetItemAt(9);
        if ( pControl )
        {
            strText.Format(L"BtnLoad3%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        Add(pItem);
    }
}

void CDownloadListUI::AddItem( CDuiString strUrl ,CDuiString strFilename)
{
    CDialogBuilder builder;
    CContainerUI* pItem=static_cast<CContainerUI*>(builder.Create(L"Item_load.xml", 0));

    pItem->SetName(strUrl);
    
    if ( pItem )
    {
        int i=GetCount();
        CDuiString strText;
        strText.Format(L"%02d", i+1);
        CControlUI* pControl=pItem->GetItemAt(0);
        if ( pControl )
            pControl->SetText(strText);

        pControl=pItem->GetItemAt(1);
        if (pControl)
        {
            pControl->SetText(strFilename);
            pControl->SetToolTip(strFilename);
        }

        pControl=pItem->GetItemAt(2);
        CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
        if ( pProgress )
            pProgress->SetValue(1);


        pControl=pItem->GetItemAt(5);
        if ( pControl )
        {
            strText.Format(L"%d%%", 1);
            pControl->SetText(strText);
        }
        pControl=pItem->GetItemAt(6);
        if ( pControl )
            pControl->SetText(L"正在下载");
        pControl=pItem->GetItemAt(7);
        if ( pControl )
        {
            strText.Format(L"BtnLoad1%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        pControl=pItem->GetItemAt(8);
        if ( pControl )
        {
            strText.Format(L"BtnLoad2%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        pControl=pItem->GetItemAt(9);
        if ( pControl )
        {
            strText.Format(L"BtnLoad3%d", i);
            pControl->SetName(strText);
            pControl->SetTag(i);
        }
        Add(pItem);
    }
}

 把任务项写到Item_load.xml中

<?xml version="1.0" encoding="UTF-8"?>
<Window>
    <Container width="430" height="40" inset="2,2,2,2"><!--Container-->
        <Label float="true" pos="4,10,30,30" align="left" font="1" textcolor="#FFF9F9F9" />
        <Label text="软件名称" float="true" pos="34,10,110,30" endellipsis="true" textcolor="#FFF9F9F9" align="left" font="0" />
        <Progress float="true" pos="110,18,230,22" bkimage="pro1.jpg" foreimage="pro2.jpg" min="0" max="100" value="0"/> 
        <Label text="剩余时间" float="true" pos="110,24,160,54" textcolor="#FFF9F9F9" align="left" font="0" />
        <Label text="下载速度" float="true" pos="165,24,230,54" textcolor="#FFF9F9F9" align="left" font="0" />
        <Label text="下载进度" float="true" pos="235,14,270,54" textcolor="#FFF9F9F9" align="left" font="0" />
        <Label float="true" pos="280,14,350,54" textcolor="#FFF9F9F9" align="left" font="0" />
        <Button tooltip="取消" float="true" pos="370,16,383,28" normalimage="file='buttons.png' source='28,0,41,12'" hotimage="file='buttons.png' source='28,12,41,24'" pushedimage="file='buttons.png' source='28,24,41,36'" />
        <Button tooltip="暂停" float="true" pos="340,16,353,28" normalimage="file='buttons.png' source='14,0,27,12'" hotimage="file='buttons.png' source='14,12,27,24'" pushedimage="file='buttons.png' source='14,24,27,36'" />
        <Button tooltip="继续" visible="false" float="true" pos="340,16,353,28" normalimage="file='buttons.png' source='0,0,13,12'" hotimage="file='buttons.png' source='0,12,13,24'" pushedimage="file='buttons.png' source='0,24,13,36'" />
    </Container>
</Window>

 

二、把工作线程封装成一个类

.h文件

#ifndef _DOWNLOADFILETHREAD_H
#define _DOWNLOADFILETHREAD_H
#include   <windows.h>  
#include   <process.h>  

typedef struct
{
    CString url;
    int current;
    CString speed;
    CString remaintime;
}PARAMS, *PPARAMS;

typedef struct
{
    CString *sender;//url
    CURL *handle;
    double* downloadFileLength;
    int* resumeByte;
}Progress_User_Data;

class CDownloadFileThread
{
public:
    CDownloadFileThread(CString url,string filename);
    ~CDownloadFileThread();

    void InitTask();

    /** 
    开始运行线程  
    **/  
    void Start();  

    void Run(); 

    //暂停下载
    void PauseTask();

    //恢复下载
    void ResumeTask();

    //退出下载
    void ExitDownload();

    std::string UnicodeToANSI(const wstring& wstr);

    static size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream);

    static int my_progress_func(void *progress_data,double t, /* dltotal */double d, /* dlnow */double ultotal,double ulnow);


    static unsigned int WINAPI ThreadFunction(LPVOID pParam); 

    static size_t nousecb(char *buffer, size_t x, size_t y, void *userdata);

    // Get the file size on the server
    double getDownloadFileLength(string url);

    // Get the local file size
    int  getLocalFileLength(string filepath);

    CString GetCurrentUrl();

private:  
    HANDLE m_handle;

    HANDLE m_StartEvent;

    HANDLE m_EndEvent;

    unsigned int m_ThreadID;

    /*volatile*/ bool m_bIsCancel;//取消下载

    CURL* m_curl;//libcurl句柄

    FILE* m_outfile;//文件指针

    CString m_url;//下载地址

    string m_filename;//文件名

    double m_downloadFileLength;//服务器文件长度

    int m_resumeByte;//本地已下载的文件长度
};
#endif//_DOWNLOADFILETHREAD_H

 

.cpp文件

#include "stdafx.h"
#include "DownloadFileThread.h"

CDownloadFileThread::CDownloadFileThread(CString url,string filename)
:m_url(url)
,m_curl(NULL)
,m_outfile(NULL)
,m_bIsCancel(false)
,m_filename(filename)
,m_downloadFileLength(-1)
,m_resumeByte(-1)
{
    m_handle = (HANDLE)_beginthreadex(NULL,0,ThreadFunction,(void*)this,0,&m_ThreadID);

    m_StartEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

    m_EndEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

    curl_easy_init();
}

CDownloadFileThread::~CDownloadFileThread()
{

    SetEvent(m_EndEvent);


    if (m_handle)
    {
        CloseHandle(m_handle);
    }


    if (m_StartEvent)
    {
        CloseHandle(m_StartEvent);
    }

    if (m_EndEvent)
    {
        CloseHandle(m_EndEvent);
    }
}

void CDownloadFileThread::Start()
{
    SetEvent(m_StartEvent);
}

size_t CDownloadFileThread::my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    size_t nWrite =fwrite(ptr, size, nmemb, stream);

    if (nWrite == 0)
    {
        return CURL_WRITEFUNC_PAUSE;
    }
    else
    {
        return nWrite;
    }

    //return fwrite(ptr, size, nmemb, stream);
} 

int CDownloadFileThread::my_progress_func(void *progress_data,
                     double t, /* dltotal */
                     double d, /* dlnow */
                     double ultotal,
                     double ulnow)
{
    //printf("%s %g / %g (%g %%)\n", progress_data, d, t, d*100.0/t);

    Progress_User_Data *data = static_cast<Progress_User_Data *>(progress_data);

    char text[256] = {0};
    //sprintf_s(text,sizeof(text),"%d",d*100.0/t);

    char timeFormat[256]= {0};

    
    CURL *easy_handle = data->handle;

    // Defaults to bytes/second
    double speed;
    string unit = "B";
    int hours,minutes,seconds;
    curl_easy_getinfo(easy_handle, CURLINFO_SPEED_DOWNLOAD, &speed); // curl_get_info必须在curl_easy_perform之后调用

    if (speed != 0)
    {
        double leftTime = ((*data->downloadFileLength) - d - (*data->resumeByte)) / speed;

        hours = leftTime / 3600;
        minutes = (leftTime - hours * 3600) / 60;
        seconds = leftTime - hours * 3600 - minutes * 60;
    }

    sprintf_s(timeFormat, sizeof(timeFormat), "%02d:%02d:%02d", hours, minutes, seconds);


    if (speed > 1024 * 1024 * 1024)
    {
        unit = "G";
        speed /= 1024 * 1024 * 1024;
    }
    else if (speed > 1024 * 1024)
    {
        unit = "MB";
        speed /= 1024 * 1024;
    }
    else if (speed > 1024)
    {
        unit = "KB";
        speed /= 1024;
    }

    sprintf_s(text,sizeof(text),"%.2f%s/s",speed, unit.c_str());


    CString* url = (CString*)(data->sender);
    PARAMS params;
    params.url = url->GetBuffer();
    params.current = d*100.0/t;
    params.speed = text;
    params.remaintime = timeFormat;

    HWND hWnd = FindWindow( NULL , L"下载管理器" );
    if (hWnd)
    {
        ::SendMessage(hWnd,WM_UPDATEPROGRESS,0,(LPARAM)&params);
    }
    
    return 0;
}

std::string CDownloadFileThread::UnicodeToANSI( const wstring& wstr )
{
    int unicodeLen = ::WideCharToMultiByte(CP_ACP,0,wstr.c_str(),-1,NULL,0, NULL ,NULL);

    if(unicodeLen == 0) return std::string("");

    char *pChar= new char[unicodeLen+1];

    memset(pChar , 0 , sizeof( char ) * (unicodeLen+1));

    ::WideCharToMultiByte(CP_ACP,0,wstr.c_str(),-1,pChar,unicodeLen, NULL ,NULL);

    pChar[unicodeLen]=0;

    string str = pChar;

    delete [] pChar;
    pChar=NULL;

    return str;
}

size_t CDownloadFileThread::nousecb(char *buffer, size_t x, size_t y, void *userdata)
{
    (void)buffer;
    (void)userdata;
    return x * y;
}

void CDownloadFileThread::InitTask()
{
    m_curl = curl_easy_init();
}

void CDownloadFileThread::PauseTask()
{
    m_bIsCancel = true;
     curl_easy_pause(m_curl,CURLPAUSE_RECV);
}

void CDownloadFileThread::ResumeTask()
{
    m_bIsCancel = false;
    if (m_curl)
    {
        curl_easy_pause(m_curl,CURLPAUSE_RECV_CONT);
    }
}

void CDownloadFileThread::ExitDownload()
{
    curl_easy_pause(m_curl,CURLPAUSE_RECV);
    
}

CString CDownloadFileThread::GetCurrentUrl()
{
    return m_url;
}
double CDownloadFileThread::getDownloadFileLength( string url ) { CURL *easy_handle = NULL; int ret = CURLE_OK; double size = -1; do { easy_handle = curl_easy_init(); if (!easy_handle) { break; } // Only get the header data ret = curl_easy_setopt(easy_handle, CURLOPT_URL, url.c_str()); ret |= curl_easy_setopt(easy_handle, CURLOPT_HEADER, 1L); ret |= curl_easy_setopt(easy_handle, CURLOPT_NOBODY, 1L); ret |= curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, nousecb); // libcurl_a.lib will return error code 23 without this sentence on windows if (ret != CURLE_OK) { break; } ret = curl_easy_perform(easy_handle); if (ret != CURLE_OK) { char s[100] = {0}; sprintf_s(s, sizeof(s), "error:%d:%s", ret, curl_easy_strerror(static_cast<CURLcode>(ret))); break; } // size = -1 if no Content-Length return or Content-Length=0 ret = curl_easy_getinfo(easy_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size); if (ret != CURLE_OK) { break; } } while (0); curl_easy_cleanup(easy_handle); return size; } //C语言 下获取文件长度 int CDownloadFileThread::getLocalFileLength(string filepath) { FILE *fp=fopen(filepath.c_str(),"r"); if(!fp) return -1; fseek(fp,0L,SEEK_END); int size=ftell(fp); fclose(fp); return size; }
void CDownloadFileThread::Run() { CURLcode res; char *progress_data = "* "; m_curl = curl_easy_init(); if(m_curl) { m_resumeByte = getLocalFileLength(m_filename); // Get the file size on the server m_downloadFileLength = getDownloadFileLength(UnicodeToANSI(m_url.GetBuffer())); if(m_resumeByte >= (int)m_downloadFileLength) return; m_outfile = fopen(m_filename.c_str(), "ab+"); Progress_User_Data data = { &m_url, m_curl,&m_downloadFileLength,&m_resumeByte}; curl_easy_setopt(m_curl, CURLOPT_URL, UnicodeToANSI(m_url.GetBuffer()).c_str());//在此要注意,此url必须是多字节 curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 0); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, m_outfile); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, my_write_func); curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, FALSE); curl_easy_setopt(m_curl, CURLOPT_PROGRESSFUNCTION, my_progress_func); curl_easy_setopt(m_curl, CURLOPT_PROGRESSDATA, &data);//给下载进度回调函数传参数,在此是指针 res = curl_easy_perform(m_curl); fclose(m_outfile); /* always cleanup */ curl_easy_cleanup(m_curl); } } unsigned int WINAPI CDownloadFileThread::ThreadFunction( LPVOID pParam ) { CDownloadFileThread* pthis = (CDownloadFileThread*)pParam; HANDLE hThrds[2]; hThrds[0] = pthis->m_StartEvent; hThrds[1] = pthis->m_EndEvent; while(true) { DWORD result = WaitForMultipleObjects(2,hThrds,FALSE, INFINITE); if (result == WAIT_OBJECT_0) { pthis->Run(); } else if (result == WAIT_OBJECT_0+1) { break; } } return 0; }

 

因为下载任务比较耗时,所以在工作线程中进行下载,然后在UI线程显示进度,在此我用的是工作线程发消息给UI线程,然后进行进度显示。

1.这里要给界面上任务Item设置一个控件ID,这样更新进度,可以知道具体更新哪个任务的进度

2.界面添加任务时,创建一个下载对象,把url传给下载对象。

3.工作线程拿到下载进度后,用SendMessage发给UI线程,发送时要把下载用的url传回去(其实也可以用索引作为作为ID)

 

typedef struct
{
    CString url;//控件ID
    int current;//进度
    CString speed;//下载速速
    CString remaintime;//下载剩余时间
}PARAMS, *PPARAMS;

 

typedef struct
{
    CString *sender;//控件ID
    CURL *handle;libcurl指针
    double* downloadFileLength;//服务上文件长度
    int* resumeByte;//本地已下载文件长度
}Progress_User_Data;

通过url拿到文件名,这个不是所有的都可以拿到,因为有的url中没有名字

void GetFileNameFormUrl( char* fileName, const char* url )
{
    int urlLen = strlen(url);
    char mUrl[512] = {0};
    char fName[256] = {0};
    strcpy(mUrl, url);
    int cutIndex = 0;
    int i = urlLen - 1, j = 0;
    while(mUrl[--i] != '/');
    i++;
    while(mUrl[i] != '\0' && mUrl[i] != '?' &&mUrl[i] != '&')
    {
        fName[j++] = mUrl[i++];
    }
    fName[j] = '\0';
    strcpy(fileName, fName);

    return ;
}

 

更新进度的消息响应函数

LRESULT CMainWnd::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam,BOOL& bHandled)
{
    PARAMS* pParam = (PARAMS*)lParam;
    CDuiString strText;

    for (int i = 0; i < m_pDownloadList->GetCount();i++)
    {
        CControlUI* pControl = m_pDownloadList->GetItemAt(i);

        if ( _tcscmp(pParam->url.GetBuffer(), pControl->GetName())==0 )
        {
            CContainerUI* pContain = static_cast<CContainerUI*>(pControl);


            CControlUI* pControl=pContain->GetItemAt(2);
            CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
            if ( pProgress )
                pProgress->SetValue(pParam->current);

            pControl=pContain->GetItemAt(3);
            if ( pControl )
            {
                pControl->SetText(pParam->remaintime);
            }

            pControl=pContain->GetItemAt(4);
            if ( pControl )
            {
                
                pControl->SetText(pParam->speed.GetBuffer());
            }

            pControl=pContain->GetItemAt(5);
            if ( pControl )
            {
                strText.Format(L"%d%%", pParam->current);
                pControl->SetText(strText);
            }

            break;
        }
    }

    return 0;
}

 

三、libcurl库支持下载过程中暂停,然后可以恢复下载,但在实际使用过程中遇到一个问题,暂停时间不长,可以正常恢复下载,如果暂停时间很长的话,再恢复时就没法继续下载了,这个问题还没解决。

 

最后效果:

 

 

下载源码Demo

posted @ 2017-08-10 16:29  车臣  阅读(3001)  评论(1编辑  收藏  举报