| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923 |
- #include "pch.h"
- #include "CUpdateWnd.h"
- // _ftelli64 / _wfopen_s 的声明
- #include <stdio.h>
- // ============================================================================
- // 跨线程通信通道(全部为文件静态区数据)
- // ----------------------------------------------------------------------------
- // 旧实现用 FindWindow(NULL, L"自动更新") 让子线程找回窗口句柄来投递消息。
- // 这个写法有一串硬伤:
- // 1. 新设计去掉了标题栏,窗口标题只是"碰巧"还叫这个名字,
- // 任何一次改名都会让进度条不动、下载完成也不启动安装;
- // 2. FindWindow 按标题全局搜索,同名窗口会被误命中;
- // 3. 每次 curl 进度回调都做一次全局窗口枚举,高频且无谓。
- //
- // 现在改为:OnCreate 里把句柄记进 g_hUpdateWnd,子线程只跟这个静态变量打交道。
- // 之所以用静态区而不是成员变量,是因为下载线程是 detach 的,窗口可能先被关闭
- // 析构,静态区数据活到进程结束,天然免疫 Use-After-Free。
- // ============================================================================
- namespace
- {
- std::atomic<HWND> g_hUpdateWnd(NULL); // 更新窗句柄,由主线程在 OnCreate / OnDestroy 维护
- std::atomic<double> g_dlNow(0.0); // 已下载字节(含断点续传已下部分)
- std::atomic<double> g_dlTotal(0.0); // 服务器文件总字节
- // 字节数格式化:>=1MB 用 MB 保留 1 位小数,>=1KB 用 KB,否则用 B
- // 对照设计稿「10.6 MB / 28.6 MB」
- std::wstring FormatBytes(double bytes)
- {
- const double KB = 1024.0;
- const double MB = KB * 1024.0;
- wchar_t buf[64] = { 0 };
- if(bytes >= MB)
- {
- _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.1f MB", bytes / MB);
- }
- else if(bytes >= KB)
- {
- _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f KB", bytes / KB);
- }
- else
- {
- _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f B", bytes);
- }
- return std::wstring(buf);
- }
- // 窗口是否还活着(子线程判断要不要继续干活)
- bool IsUpdateWndAlive()
- {
- HWND hWnd = g_hUpdateWnd.load();
- return (hWnd != NULL && ::IsWindow(hWnd));
- }
- }
- // ============================================================================
- // 生命周期
- // ============================================================================
- void CUpdateWnd::Init()
- {
- m_nProgress = 0;
- m_bFailed = false;
- m_bFinished = false;
- m_bDownloading = false;
- g_dlNow.store(0.0);
- g_dlTotal.store(0.0);
- // 必须在起线程【之前】登记句柄,否则子线程第一条消息就投不回来
- g_hUpdateWnd.store(m_hWnd);
- ShowDownloading();
- StartDownload();
- }
- void CUpdateWnd::StartDownload()
- {
- if(m_bDownloading)
- {
- return;
- }
- m_bDownloading = true;
- m_bFailed = false;
- m_ctx = std::make_shared<SUpdateCtx>();
- m_ctx->url = m_url;
- m_ctx->dir = m_dir;
- // 传值捕获 shared_ptr:线程栈上多一份引用,ctx 一定活到线程结束
- std::thread(&CUpdateWnd::Run, m_ctx).detach();
- }
- // ============================================================================
- // 三种状态切换(只在主线程调用,全部对照 update_design.html)
- // ============================================================================
- void CUpdateWnd::ShowDownloading()
- {
- CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
- if(pIcon)
- {
- pIcon->SetBkImage(_T("file='update_icon_bg.png'"));
- }
- CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
- if(pSpinner)
- {
- pSpinner->SetVisible(true);
- }
- CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
- if(pMain)
- {
- pMain->SetText(_T("正在下载最新版本…"));
- pMain->SetTextColor(0xFF1F2329);
- }
- CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
- if(pSub)
- {
- pSub->SetText(_T("请保持网络连接"));
- pSub->SetVisible(true);
- }
- CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
- if(pProgress)
- {
- pProgress->SetVisible(true);
- pProgress->SetForeImage(_T("file='update_bar_fill.png'"));
- }
- CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
- if(pPercent)
- {
- pPercent->SetTextColor(0xFF60B177);
- }
- CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
- if(pRetry)
- {
- pRetry->SetVisible(false);
- }
- CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
- if(pFooter)
- {
- pFooter->SetBkColor(0xFFFFFFFF);
- }
- CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
- if(pTip)
- {
- pTip->SetText(_T("更新过程中请勿断开网络"));
- pTip->SetTextColor(0xFF8A9099);
- }
- UpdateProgressInfo();
- }
- void CUpdateWnd::ShowFinish()
- {
- m_bDownloading = false;
- CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
- if(pIcon)
- {
- pIcon->SetBkImage(_T("file='update_icon_done.png'"));
- }
- CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
- if(pSpinner)
- {
- pSpinner->SetVisible(false);
- }
- CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
- if(pMain)
- {
- pMain->SetText(_T("下载完成,正在启动安装程序…"));
- pMain->SetTextColor(0xFF1F2329);
- }
- CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
- if(pSub)
- {
- pSub->SetText(_T("请按安装向导提示完成更新"));
- pSub->SetVisible(true);
- }
- CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
- if(pProgress)
- {
- pProgress->SetForeImage(_T("file='update_bar_fill_done.png'"));
- pProgress->SetValue(100);
- }
- // 完成态把「已下载 / 总大小」拉平成同一个数
- g_dlTotal.store(g_dlNow.load() > 0 ? g_dlNow.load() : g_dlTotal.load());
- UpdateProgressInfo();
- CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
- if(pPercent)
- {
- pPercent->SetText(_T("100%"));
- pPercent->SetTextColor(0xFF60B177);
- }
- CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
- if(pRetry)
- {
- pRetry->SetVisible(false);
- }
- CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
- if(pFooter)
- {
- pFooter->SetBkColor(0xFFFFFFFF);
- }
- CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
- if(pTip)
- {
- pTip->SetText(_T("本窗口即将自动关闭"));
- pTip->SetTextColor(0xFF8A9099);
- }
- }
- void CUpdateWnd::ShowFail(LPCTSTR lpReason)
- {
- m_bDownloading = false;
- m_bFailed = true;
- CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
- if(pIcon)
- {
- pIcon->SetBkImage(_T("file='update_icon_fail.png'"));
- }
- CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
- if(pSpinner)
- {
- pSpinner->SetVisible(false);
- }
- CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
- if(pMain)
- {
- pMain->SetText(_T("下载失败"));
- pMain->SetTextColor(0xFF1F2329);
- }
- CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
- if(pSub)
- {
- pSub->SetText((lpReason && lpReason[0]) ? lpReason : _T("网络连接异常,请检查网络后重试"));
- pSub->SetVisible(true);
- }
- // 进度条保留在当前位置,只把填充切成红色,让用户看到已经下了多少
- CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
- if(pProgress)
- {
- pProgress->SetVisible(true);
- pProgress->SetForeImage(_T("file='update_bar_fill_fail.png'"));
- }
- CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
- if(pPercent)
- {
- pPercent->SetTextColor(0xFFFF4500);
- }
- CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
- if(pRetry)
- {
- pRetry->SetVisible(true);
- }
- // 底部提示条转红底红字
- CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
- if(pFooter)
- {
- pFooter->SetBkColor(0xFFFFF1ED);
- }
- CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
- if(pTip)
- {
- pTip->SetText(_T("重新下载将从断点处继续,不会重新下完整包"));
- pTip->SetTextColor(0xFFFF4500);
- }
- UpdateProgressInfo();
- }
- void CUpdateWnd::UpdateProgressInfo()
- {
- double dlnow = g_dlNow.load();
- double dltotal = g_dlTotal.load();
- int nPercent = 0;
- if(dltotal > 0)
- {
- nPercent = (int)(dlnow * 100.0 / dltotal);
- if(nPercent < 0)
- {
- nPercent = 0;
- }
- if(nPercent > 100)
- {
- nPercent = 100;
- }
- }
- m_nProgress = nPercent;
- CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
- if(pProgress)
- {
- pProgress->SetValue(nPercent);
- }
- CLabelUI* pSize = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_size_text")));
- if(pSize)
- {
- std::wstring sText = FormatBytes(dlnow) + L" / " + FormatBytes(dltotal);
- pSize->SetText(sText.c_str());
- }
- CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
- if(pPercent)
- {
- wchar_t buf[16] = { 0 };
- _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%d%%", nPercent);
- pPercent->SetText(buf);
- }
- }
- // ============================================================================
- // 子线程投递口(不访问任何 this 成员)
- // ============================================================================
- void CUpdateWnd::SetProgressSnapshot(double dlnow, double dltotal)
- {
- g_dlNow.store(dlnow);
- g_dlTotal.store(dltotal);
- }
- void CUpdateWnd::PostProgress()
- {
- HWND hWnd = g_hUpdateWnd.load();
- if(hWnd != NULL && ::IsWindow(hWnd))
- {
- ::PostMessage(hWnd, WM_UPDATEPROGRESS, 0, 0);
- }
- }
- void CUpdateWnd::PostFinish()
- {
- HWND hWnd = g_hUpdateWnd.load();
- if(hWnd != NULL && ::IsWindow(hWnd))
- {
- ::PostMessage(hWnd, WM_DOWNLOAD_FINISH, 0, 0);
- }
- }
- void CUpdateWnd::PostFail(LPCTSTR lpReason)
- {
- HWND hWnd = g_hUpdateWnd.load();
- // 窗口已经关了就不用提示了,同时省掉一次堆分配
- if(hWnd == NULL || !::IsWindow(hWnd))
- {
- return;
- }
- // 失败原因随消息走:堆上分配,由主线程 OnDownloadFail 负责释放
- std::wstring* pReason = new std::wstring((lpReason && lpReason[0])
- ? lpReason
- : L"网络连接异常,请检查网络后重试");
- if(!::PostMessage(hWnd, WM_DOWNLOAD_FAIL, 0, (LPARAM)pReason))
- {
- delete pReason; // 投递失败就地释放,避免泄漏
- }
- }
- // ============================================================================
- // 消息与事件
- // ============================================================================
- void CUpdateWnd::Notify(TNotifyUI& msg)
- {
- if(msg.sType == _T("click"))
- {
- CDuiString sName = msg.pSender->GetName();
- if(sName == _T("closebtn"))
- {
- PostQuitMessage(0);
- return;
- }
- else if(sName == _T("update_retry_btn"))
- {
- // 重新下载:Run() 会读本地已下文件的长度作为续传起点
- ShowDownloading();
- StartDownload();
- return;
- }
- }
- }
- LRESULT CUpdateWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
- styleValue &= ~WS_CAPTION;
- ::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
- // 把自己的窗口句柄与窗口绘制管理器挂接在一起
- m_pm.Init(m_hWnd);
- m_pm.AddPreMessageFilter(this);
- CDialogBuilder builder;
- CControlUI* pRoot = builder.Create(_T("update.xml"), (UINT)0, NULL, &m_pm);
- ASSERT(pRoot && "Failed to parse XML");
- // 把这些控件绘制到本窗口上
- m_pm.AttachDialog(pRoot);
- // 把自己加入到CPaintManagerUI的m_aNotifiers数组中,用于处理Notify函数
- m_pm.AddNotifier(this);
- Init();
- return 0;
- }
- LRESULT CUpdateWnd::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- bHandled = FALSE;
- return 0;
- }
- /*
- *这个是窗口被销毁的时候调用的
- **/
- LRESULT CUpdateWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- // 断开回投通道:子线程下一次回调会发现窗口没了,随即中止下载
- g_hUpdateWnd.store(NULL);
- bHandled = FALSE;
- return 0;
- }
- LRESULT CUpdateWnd::OnNcActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- if(::IsIconic(*this))
- {
- bHandled = FALSE;
- }
- return (wParam == 0) ? TRUE : FALSE;
- }
- LRESULT CUpdateWnd::OnNcCalcSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- return 0;
- }
- LRESULT CUpdateWnd::OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- // 更新页没有输入焦点,无需设置焦点(旧代码残留下来的 accountedit 已随旧版 XML 一起删除)
- return 0;
- }
- LRESULT CUpdateWnd::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- POINT pt;
- pt.x = GET_X_LPARAM(lParam);
- pt.y = GET_Y_LPARAM(lParam);
- ::ScreenToClient(*this, &pt);
- RECT rcClient;
- ::GetClientRect(*this, &rcClient);
- RECT rcCaption = m_pm.GetCaptionRect();
- if(pt.x >= rcClient.left + rcCaption.left && pt.x < rcClient.right - rcCaption.right \
- && pt.y >= rcCaption.top && pt.y < rcCaption.bottom)
- {
- CControlUI* pControl = static_cast<CControlUI*>(m_pm.FindControl(pt));
- if(pControl && _tcscmp(pControl->GetClass(), L"ButtonUI") != 0)
- {
- return HTCAPTION;
- }
- }
- return HTCLIENT;
- }
- LRESULT CUpdateWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- SIZE szRoundCorner = m_pm.GetRoundCorner();
- if(!::IsIconic(*this) && (szRoundCorner.cx != 0 || szRoundCorner.cy != 0))
- {
- CDuiRect rcWnd;
- ::GetWindowRect(*this, &rcWnd);
- rcWnd.Offset(-rcWnd.left, -rcWnd.top);
- rcWnd.right++;
- rcWnd.bottom++;
- HRGN hRgn = ::CreateRoundRectRgn(rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy);
- ::SetWindowRgn(*this, hRgn, TRUE);
- ::DeleteObject(hRgn);
- }
- bHandled = FALSE;
- return 0;
- }
- LRESULT CUpdateWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- LRESULT lRes = 0;
- BOOL bHandled = TRUE;
- switch(uMsg)
- {
- case WM_CREATE:
- lRes = OnCreate(uMsg, wParam, lParam, bHandled);
- break;
- case WM_CLOSE:
- lRes = OnClose(uMsg, wParam, lParam, bHandled);
- break;
- case WM_DESTROY:
- lRes = OnDestroy(uMsg, wParam, lParam, bHandled);
- break;
- case WM_NCACTIVATE:
- lRes = OnNcActivate(uMsg, wParam, lParam, bHandled);
- break;
- case WM_NCCALCSIZE:
- lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled);
- break;
- case WM_NCPAINT:
- lRes = OnNcPaint(uMsg, wParam, lParam, bHandled);
- break;
- case WM_NCHITTEST:
- lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled);
- break;
- case WM_SIZE:
- lRes = OnSize(uMsg, wParam, lParam, bHandled);
- break;
- case WM_UPDATEPROGRESS:
- lRes = OnRefresh(uMsg, wParam, lParam, bHandled);
- break;
- case WM_DOWNLOAD_FINISH:
- lRes = OnDownloadFinish(uMsg, wParam, lParam, bHandled);
- break;
- case WM_DOWNLOAD_FAIL:
- lRes = OnDownloadFail(uMsg, wParam, lParam, bHandled);
- break;
- default:
- bHandled = FALSE;
- }
- if(bHandled)
- {
- return lRes;
- }
- if(m_pm.MessageHandler(uMsg, wParam, lParam, lRes))
- {
- return lRes;
- }
- return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
- }
- LRESULT CUpdateWnd::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled)
- {
- return false;
- }
- double CUpdateWnd::getDownloadFileLength(std::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());
- if (url.find("https://") == 0)
- {
- ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, false);
- ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, false);
- }
- 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;
- }
- // ---------------------------------------------------------------------------
- // 下载线程主体
- // 只读写 ctx(shared_ptr 保证生命周期)与文件静态区,绝不涉及窗口成员
- // ---------------------------------------------------------------------------
- void CUpdateWnd::Run(std::shared_ptr<SUpdateCtx> ctx)
- {
- ctx->filepath = ctx->dir + CLewaimaiString::UTF8ToUnicode(CLewaimaiString::GetFilenameFromUrl(ctx->url));
- // 断点续传:本地已有半成品则从其末尾接着下
- std::error_code ecSize;
- uintmax_t nLocalSize = std::filesystem::file_size(ctx->filepath, ecSize);
- ctx->resumeByte = (ecSize || nLocalSize == 0) ? 0L : (long)nLocalSize;
- SetProgressSnapshot((double)ctx->resumeByte, 0.0);
- PostProgress();
- ctx->curl = curl_easy_init();
- if(!ctx->curl)
- {
- PostFail(L"网络组件初始化失败,请重启程序后重试");
- return;
- }
- // 先探一次总大小,探不到就没必要开文件了
- ctx->total = getDownloadFileLength(ctx->url);
- if(ctx->total <= 0)
- {
- curl_easy_cleanup(ctx->curl);
- ctx->curl = nullptr;
- PostFail(L"无法连接更新服务器,请检查网络后重试");
- return;
- }
- SetProgressSnapshot((double)ctx->resumeByte, ctx->total);
- PostProgress();
- // 上次已经下完(比如下完没装就退了),直接进完成态
- if((double)ctx->resumeByte >= ctx->total)
- {
- curl_easy_cleanup(ctx->curl);
- ctx->curl = nullptr;
- SetProgressSnapshot(ctx->total, ctx->total);
- PostFinish();
- return;
- }
- errno_t fopenErr = _wfopen_s(&ctx->outfile, ctx->filepath.c_str(), L"ab+");
- if(fopenErr != 0 || ctx->outfile == nullptr)
- {
- curl_easy_cleanup(ctx->curl);
- ctx->curl = nullptr;
- PostFail(L"无法写入本地文件,请检查磁盘空间与权限");
- return;
- }
- curl_easy_setopt(ctx->curl, CURLOPT_URL, ctx->url.c_str());//在此要注意,此url必须是多字节
- if (ctx->url.find("https://") == 0)
- {
- curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYPEER, false);
- curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYHOST, false);
- }
- curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, 0);
- curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, my_write_func);
- curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, ctx->outfile);
- curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, FALSE);
- curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
- curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSDATA, ctx.get());//进度回调需要拿文件指针来算真实已下字节
- curl_easy_setopt(ctx->curl, CURLOPT_RESUME_FROM, ctx->resumeByte);
- CURLcode res = curl_easy_perform(ctx->curl);
- fclose(ctx->outfile);
- ctx->outfile = nullptr;
- curl_easy_cleanup(ctx->curl);
- ctx->curl = nullptr;
- if(res != CURLE_OK)
- {
- // 窗口已经关了就别再喊了,用户看不到
- if(IsUpdateWndAlive())
- {
- PostFail(L"网络连接中断,请检查网络后重试");
- }
- return;
- }
- // 校验实际落盘大小,防止拿到一个残缺包就去安装
- std::error_code ecDone;
- uintmax_t nDoneSize = std::filesystem::file_size(ctx->filepath, ecDone);
- if(!ecDone)
- {
- SetProgressSnapshot((double)nDoneSize, ctx->total);
- if((double)nDoneSize + 1.0 < ctx->total)
- {
- if(IsUpdateWndAlive())
- {
- PostFail(L"下载内容不完整,请重新下载");
- }
- return;
- }
- }
- PostFinish();
- }
- size_t CUpdateWnd::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;
- }
- }
- int CUpdateWnd::my_progress_func(void *progress_data,
- double t, /* dltotal */
- double d, /* dlnow */
- double ultotal,
- double ulnow)
- {
- (void)t;
- (void)d;
- (void)ultotal;
- (void)ulnow;
- // 窗口已经关闭 → 返回非 0 让 curl 中止这次传输,避免留下僵尸下载线程
- if(!IsUpdateWndAlive())
- {
- return 1;
- }
- const SUpdateCtx* pCtx = static_cast<const SUpdateCtx*>(progress_data);
- if(!pCtx)
- {
- return 0;
- }
- // 直接读文件当前长度作为已下载量:
- // 一是天然包含断点续传已下的部分,不必猜 libcurl 对 dlnow 的口径;
- // 二是 CURLOPT_RESUME_FROM 下 dltotal 的语义各版本不一致,统一用自己探到的总大小。
- double dlnow = 0.0;
- if(pCtx->outfile)
- {
- __int64 nPos = _ftelli64(pCtx->outfile);
- if(nPos > 0)
- {
- dlnow = (double)nPos;
- }
- }
- SetProgressSnapshot(dlnow, pCtx->total);
- PostProgress();
- return 0;
- }
- size_t CUpdateWnd::nousecb(char *buffer, size_t x, size_t y, void *userdata)
- {
- (void)buffer;
- (void)userdata;
- return x * y;
- }
- LRESULT CUpdateWnd::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- UpdateProgressInfo();
- return 0;
- }
- LRESULT CUpdateWnd::OnDownloadFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- // curl 完成与「本地文件已完整」都投过 WM_DOWNLOAD_FINISH,这里挡掉重复触发
- if(m_bFinished)
- {
- return 0;
- }
- m_bFinished = true;
- ShowFinish();
- if(m_ctx && !m_ctx->filepath.empty())
- {
- ShellExecute(NULL, L"open", m_ctx->filepath.c_str(), NULL, NULL, SW_SHOW);
- }
- PostQuitMessage(0);
- return 0;
- }
- LRESULT CUpdateWnd::OnDownloadFail(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
- {
- // 失败原因由子线程在堆上分配并随消息传来,主线程负责释放
- std::wstring* pReason = reinterpret_cast<std::wstring*>(lParam);
- std::wstring sReason = pReason ? *pReason : L"";
- delete pReason;
- pReason = nullptr;
- ShowFail(sReason.c_str());
- return 0;
- }
|