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

时间:2023-03-09 02:37:46
使用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,然后进行了修改

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

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

1.任务名字

2.下载剩余时间

3.下载速度

4.下载进度百分比

5.继续下载

6.暂停下载

7.取消下载任务

一、首先扩展Duilib下载List

.h文件

#pragma once

class CDownloadListUI : public CTileLayoutUI
{
public:
enum { SCROLL_TIMERID = };
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, );
}

.cpp文件

#include "StdAfx.h"
#include "DownloadListUI.h" CDownloadListUI::CDownloadListUI(void)
:m_uButtonState()
, m_dwDelayDeltaY()
, m_dwDelayNum()
, m_dwDelayLeft()
{
} 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) != ) {
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 > ) {
--m_dwDelayLeft;
SIZE sz = GetScrollPos();
LONG lDeltaY = (LONG)(CalculateDelay((double)m_dwDelayLeft / m_dwDelayNum) * m_dwDelayDeltaY);
if( (lDeltaY > && sz.cy != ) || (lDeltaY < && sz.cy != GetScrollRange().cy ) ) {
sz.cy -= lDeltaY;
SetScrollPos(sz);
return;
}
}
m_dwDelayDeltaY = ;
m_dwDelayNum = ;
m_dwDelayLeft = ;
m_pManager->KillTimer(this, SCROLL_TIMERID);
return;
}
if( event.Type == UIEVENT_MOUSEWHEEL )
{
LONG lDeltaY = ;
if( m_dwDelayNum > ) lDeltaY = (LONG)(CalculateDelay((double)m_dwDelayLeft / m_dwDelayNum) * m_dwDelayDeltaY);
switch( LOWORD(event.wParam) ) {
case SB_LINEUP:
if( m_dwDelayDeltaY >= ) m_dwDelayDeltaY = lDeltaY + ;
else m_dwDelayDeltaY = lDeltaY + ;
break;
case SB_LINEDOWN:
if( m_dwDelayDeltaY <= ) m_dwDelayDeltaY = lDeltaY - ;
else m_dwDelayDeltaY = lDeltaY - ;
break;
}
if( m_dwDelayDeltaY > ) m_dwDelayDeltaY = ;
else if( m_dwDelayDeltaY < - ) m_dwDelayDeltaY = -;
m_dwDelayNum = (DWORD)sqrt((double)abs(m_dwDelayDeltaY)) * ;
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", ));
if ( pItem )
{
int i=GetCount();
CDuiString strText;
strText.Format(L"%02d", i+);
CControlUI* pControl=pItem->GetItemAt();
if ( pControl )
pControl->SetText(strText);
pControl=pItem->GetItemAt();
CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
if ( pProgress )
pProgress->SetValue(i+); pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"%02d:%02d:%02d", ,,);
pControl->SetText(strText);
} pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"%dM/s", i+);
pControl->SetText(strText);
} pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"%d%%", i+);
pControl->SetText(strText);
}
pControl=pItem->GetItemAt();
if ( pControl )
pControl->SetText(L"正在下载");
pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"BtnLoad1%d", i);
pControl->SetName(strText);
pControl->SetTag(i);
}
pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"BtnLoad2%d", i);
pControl->SetName(strText);
pControl->SetTag(i);
}
pControl=pItem->GetItemAt();
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", )); pItem->SetName(strUrl); if ( pItem )
{
int i=GetCount();
CDuiString strText;
strText.Format(L"%02d", i+);
CControlUI* pControl=pItem->GetItemAt();
if ( pControl )
pControl->SetText(strText); pControl=pItem->GetItemAt();
if (pControl)
{
pControl->SetText(strFilename);
pControl->SetToolTip(strFilename);
} pControl=pItem->GetItemAt();
CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
if ( pProgress )
pProgress->SetValue(); pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"%d%%", );
pControl->SetText(strText);
}
pControl=pItem->GetItemAt();
if ( pControl )
pControl->SetText(L"正在下载");
pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"BtnLoad1%d", i);
pControl->SetName(strText);
pControl->SetTag(i);
}
pControl=pItem->GetItemAt();
if ( pControl )
{
strText.Format(L"BtnLoad2%d", i);
pControl->SetName(strText);
pControl->SetTag(i);
}
pControl=pItem->GetItemAt();
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(-)
,m_resumeByte(-)
{
m_handle = (HANDLE)_beginthreadex(NULL,,ThreadFunction,(void*)this,,&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 == )
{
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[] = {};
//sprintf_s(text,sizeof(text),"%d",d*100.0/t); char timeFormat[]= {}; 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 != )
{
double leftTime = ((*data->downloadFileLength) - d - (*data->resumeByte)) / speed; hours = leftTime / ;
minutes = (leftTime - hours * ) / ;
seconds = leftTime - hours * - minutes * ;
} sprintf_s(timeFormat, sizeof(timeFormat), "%02d:%02d:%02d", hours, minutes, seconds); if (speed > * * )
{
unit = "G";
speed /= * * ;
}
else if (speed > * )
{
unit = "MB";
speed /= * ;
}
else if (speed > )
{
unit = "KB";
speed /= ;
} 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,,(LPARAM)&params);
} return ;
} std::string CDownloadFileThread::UnicodeToANSI( const wstring& wstr )
{
int unicodeLen = ::WideCharToMultiByte(CP_ACP,,wstr.c_str(),-,NULL,, NULL ,NULL); if(unicodeLen == ) return std::string(""); char *pChar= new char[unicodeLen+]; memset(pChar , , sizeof( char ) * (unicodeLen+)); ::WideCharToMultiByte(CP_ACP,,wstr.c_str(),-,pChar,unicodeLen, NULL ,NULL); pChar[unicodeLen]=; 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 = -; 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[] = {};
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 (); curl_easy_cleanup(easy_handle);
return size;
} //C语言 下获取文件长度
int CDownloadFileThread::getLocalFileLength(string filepath)
{
FILE *fp=fopen(filepath.c_str(),"r");
if(!fp) return -;
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, );
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[]; hThrds[] = pthis->m_StartEvent;
hThrds[] = pthis->m_EndEvent; while(true)
{
DWORD result = WaitForMultipleObjects(,hThrds,FALSE, INFINITE); if (result == WAIT_OBJECT_0)
{
pthis->Run();
}
else if (result == WAIT_OBJECT_0+)
{
break;
}
} return ;
}

因为下载任务比较耗时,所以在工作线程中进行下载,然后在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[] = {};
char fName[] = {};
strcpy(mUrl, url);
int cutIndex = ;
int i = urlLen - , j = ;
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 = ; i < m_pDownloadList->GetCount();i++)
{
CControlUI* pControl = m_pDownloadList->GetItemAt(i); if ( _tcscmp(pParam->url.GetBuffer(), pControl->GetName())== )
{
CContainerUI* pContain = static_cast<CContainerUI*>(pControl); CControlUI* pControl=pContain->GetItemAt();
CProgressUI* pProgress=(CProgressUI*)pControl->GetInterface(L"Progress");
if ( pProgress )
pProgress->SetValue(pParam->current); pControl=pContain->GetItemAt();
if ( pControl )
{
pControl->SetText(pParam->remaintime);
} pControl=pContain->GetItemAt();
if ( pControl )
{ pControl->SetText(pParam->speed.GetBuffer());
} pControl=pContain->GetItemAt();
if ( pControl )
{
strText.Format(L"%d%%", pParam->current);
pControl->SetText(strText);
} break;
}
} return ;
}

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

最后效果:

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

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

下载源码Demo