CUpdateWnd.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. #include "pch.h"
  2. #include "CUpdateWnd.h"
  3. // _ftelli64 / _wfopen_s 的声明
  4. #include <stdio.h>
  5. // ============================================================================
  6. // 跨线程通信通道(全部为文件静态区数据)
  7. // ----------------------------------------------------------------------------
  8. // 旧实现用 FindWindow(NULL, L"自动更新") 让子线程找回窗口句柄来投递消息。
  9. // 这个写法有一串硬伤:
  10. // 1. 新设计去掉了标题栏,窗口标题只是"碰巧"还叫这个名字,
  11. // 任何一次改名都会让进度条不动、下载完成也不启动安装;
  12. // 2. FindWindow 按标题全局搜索,同名窗口会被误命中;
  13. // 3. 每次 curl 进度回调都做一次全局窗口枚举,高频且无谓。
  14. //
  15. // 现在改为:OnCreate 里把句柄记进 g_hUpdateWnd,子线程只跟这个静态变量打交道。
  16. // 之所以用静态区而不是成员变量,是因为下载线程是 detach 的,窗口可能先被关闭
  17. // 析构,静态区数据活到进程结束,天然免疫 Use-After-Free。
  18. // ============================================================================
  19. namespace
  20. {
  21. std::atomic<HWND> g_hUpdateWnd(NULL); // 更新窗句柄,由主线程在 OnCreate / OnDestroy 维护
  22. std::atomic<double> g_dlNow(0.0); // 已下载字节(含断点续传已下部分)
  23. std::atomic<double> g_dlTotal(0.0); // 服务器文件总字节
  24. // 字节数格式化:>=1MB 用 MB 保留 1 位小数,>=1KB 用 KB,否则用 B
  25. // 对照设计稿「10.6 MB / 28.6 MB」
  26. std::wstring FormatBytes(double bytes)
  27. {
  28. const double KB = 1024.0;
  29. const double MB = KB * 1024.0;
  30. wchar_t buf[64] = { 0 };
  31. if(bytes >= MB)
  32. {
  33. _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.1f MB", bytes / MB);
  34. }
  35. else if(bytes >= KB)
  36. {
  37. _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f KB", bytes / KB);
  38. }
  39. else
  40. {
  41. _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f B", bytes);
  42. }
  43. return std::wstring(buf);
  44. }
  45. // 窗口是否还活着(子线程判断要不要继续干活)
  46. bool IsUpdateWndAlive()
  47. {
  48. HWND hWnd = g_hUpdateWnd.load();
  49. return (hWnd != NULL && ::IsWindow(hWnd));
  50. }
  51. }
  52. // ============================================================================
  53. // 生命周期
  54. // ============================================================================
  55. void CUpdateWnd::Init()
  56. {
  57. m_nProgress = 0;
  58. m_bFailed = false;
  59. m_bFinished = false;
  60. m_bDownloading = false;
  61. g_dlNow.store(0.0);
  62. g_dlTotal.store(0.0);
  63. // 必须在起线程【之前】登记句柄,否则子线程第一条消息就投不回来
  64. g_hUpdateWnd.store(m_hWnd);
  65. ShowDownloading();
  66. StartDownload();
  67. }
  68. void CUpdateWnd::StartDownload()
  69. {
  70. if(m_bDownloading)
  71. {
  72. return;
  73. }
  74. m_bDownloading = true;
  75. m_bFailed = false;
  76. m_ctx = std::make_shared<SUpdateCtx>();
  77. m_ctx->url = m_url;
  78. m_ctx->dir = m_dir;
  79. // 传值捕获 shared_ptr:线程栈上多一份引用,ctx 一定活到线程结束
  80. std::thread(&CUpdateWnd::Run, m_ctx).detach();
  81. }
  82. // ============================================================================
  83. // 三种状态切换(只在主线程调用,全部对照 update_design.html)
  84. // ============================================================================
  85. void CUpdateWnd::ShowDownloading()
  86. {
  87. CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
  88. if(pIcon)
  89. {
  90. pIcon->SetBkImage(_T("file='update_icon_bg.png'"));
  91. }
  92. CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
  93. if(pSpinner)
  94. {
  95. pSpinner->SetVisible(true);
  96. }
  97. CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
  98. if(pMain)
  99. {
  100. pMain->SetText(_T("正在下载最新版本…"));
  101. pMain->SetTextColor(0xFF1F2329);
  102. }
  103. CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
  104. if(pSub)
  105. {
  106. pSub->SetText(_T("请保持网络连接"));
  107. pSub->SetVisible(true);
  108. }
  109. CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
  110. if(pProgress)
  111. {
  112. pProgress->SetVisible(true);
  113. pProgress->SetForeImage(_T("file='update_bar_fill.png'"));
  114. }
  115. CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
  116. if(pPercent)
  117. {
  118. pPercent->SetTextColor(0xFF60B177);
  119. }
  120. CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
  121. if(pRetry)
  122. {
  123. pRetry->SetVisible(false);
  124. }
  125. CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
  126. if(pFooter)
  127. {
  128. pFooter->SetBkColor(0xFFFFFFFF);
  129. }
  130. CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
  131. if(pTip)
  132. {
  133. pTip->SetText(_T("更新过程中请勿断开网络"));
  134. pTip->SetTextColor(0xFF8A9099);
  135. }
  136. UpdateProgressInfo();
  137. }
  138. void CUpdateWnd::ShowFinish()
  139. {
  140. m_bDownloading = false;
  141. CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
  142. if(pIcon)
  143. {
  144. pIcon->SetBkImage(_T("file='update_icon_done.png'"));
  145. }
  146. CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
  147. if(pSpinner)
  148. {
  149. pSpinner->SetVisible(false);
  150. }
  151. CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
  152. if(pMain)
  153. {
  154. pMain->SetText(_T("下载完成,正在启动安装程序…"));
  155. pMain->SetTextColor(0xFF1F2329);
  156. }
  157. CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
  158. if(pSub)
  159. {
  160. pSub->SetText(_T("请按安装向导提示完成更新"));
  161. pSub->SetVisible(true);
  162. }
  163. CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
  164. if(pProgress)
  165. {
  166. pProgress->SetForeImage(_T("file='update_bar_fill_done.png'"));
  167. pProgress->SetValue(100);
  168. }
  169. // 完成态把「已下载 / 总大小」拉平成同一个数
  170. g_dlTotal.store(g_dlNow.load() > 0 ? g_dlNow.load() : g_dlTotal.load());
  171. UpdateProgressInfo();
  172. CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
  173. if(pPercent)
  174. {
  175. pPercent->SetText(_T("100%"));
  176. pPercent->SetTextColor(0xFF60B177);
  177. }
  178. CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
  179. if(pRetry)
  180. {
  181. pRetry->SetVisible(false);
  182. }
  183. CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
  184. if(pFooter)
  185. {
  186. pFooter->SetBkColor(0xFFFFFFFF);
  187. }
  188. CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
  189. if(pTip)
  190. {
  191. pTip->SetText(_T("本窗口即将自动关闭"));
  192. pTip->SetTextColor(0xFF8A9099);
  193. }
  194. }
  195. void CUpdateWnd::ShowFail(LPCTSTR lpReason)
  196. {
  197. m_bDownloading = false;
  198. m_bFailed = true;
  199. CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
  200. if(pIcon)
  201. {
  202. pIcon->SetBkImage(_T("file='update_icon_fail.png'"));
  203. }
  204. CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
  205. if(pSpinner)
  206. {
  207. pSpinner->SetVisible(false);
  208. }
  209. CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
  210. if(pMain)
  211. {
  212. pMain->SetText(_T("下载失败"));
  213. pMain->SetTextColor(0xFF1F2329);
  214. }
  215. CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
  216. if(pSub)
  217. {
  218. pSub->SetText((lpReason && lpReason[0]) ? lpReason : _T("网络连接异常,请检查网络后重试"));
  219. pSub->SetVisible(true);
  220. }
  221. // 进度条保留在当前位置,只把填充切成红色,让用户看到已经下了多少
  222. CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
  223. if(pProgress)
  224. {
  225. pProgress->SetVisible(true);
  226. pProgress->SetForeImage(_T("file='update_bar_fill_fail.png'"));
  227. }
  228. CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
  229. if(pPercent)
  230. {
  231. pPercent->SetTextColor(0xFFFF4500);
  232. }
  233. CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
  234. if(pRetry)
  235. {
  236. pRetry->SetVisible(true);
  237. }
  238. // 底部提示条转红底红字
  239. CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
  240. if(pFooter)
  241. {
  242. pFooter->SetBkColor(0xFFFFF1ED);
  243. }
  244. CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
  245. if(pTip)
  246. {
  247. pTip->SetText(_T("重新下载将从断点处继续,不会重新下完整包"));
  248. pTip->SetTextColor(0xFFFF4500);
  249. }
  250. UpdateProgressInfo();
  251. }
  252. void CUpdateWnd::UpdateProgressInfo()
  253. {
  254. double dlnow = g_dlNow.load();
  255. double dltotal = g_dlTotal.load();
  256. int nPercent = 0;
  257. if(dltotal > 0)
  258. {
  259. nPercent = (int)(dlnow * 100.0 / dltotal);
  260. if(nPercent < 0)
  261. {
  262. nPercent = 0;
  263. }
  264. if(nPercent > 100)
  265. {
  266. nPercent = 100;
  267. }
  268. }
  269. m_nProgress = nPercent;
  270. CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
  271. if(pProgress)
  272. {
  273. pProgress->SetValue(nPercent);
  274. }
  275. CLabelUI* pSize = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_size_text")));
  276. if(pSize)
  277. {
  278. std::wstring sText = FormatBytes(dlnow) + L" / " + FormatBytes(dltotal);
  279. pSize->SetText(sText.c_str());
  280. }
  281. CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
  282. if(pPercent)
  283. {
  284. wchar_t buf[16] = { 0 };
  285. _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%d%%", nPercent);
  286. pPercent->SetText(buf);
  287. }
  288. }
  289. // ============================================================================
  290. // 子线程投递口(不访问任何 this 成员)
  291. // ============================================================================
  292. void CUpdateWnd::SetProgressSnapshot(double dlnow, double dltotal)
  293. {
  294. g_dlNow.store(dlnow);
  295. g_dlTotal.store(dltotal);
  296. }
  297. void CUpdateWnd::PostProgress()
  298. {
  299. HWND hWnd = g_hUpdateWnd.load();
  300. if(hWnd != NULL && ::IsWindow(hWnd))
  301. {
  302. ::PostMessage(hWnd, WM_UPDATEPROGRESS, 0, 0);
  303. }
  304. }
  305. void CUpdateWnd::PostFinish()
  306. {
  307. HWND hWnd = g_hUpdateWnd.load();
  308. if(hWnd != NULL && ::IsWindow(hWnd))
  309. {
  310. ::PostMessage(hWnd, WM_DOWNLOAD_FINISH, 0, 0);
  311. }
  312. }
  313. void CUpdateWnd::PostFail(LPCTSTR lpReason)
  314. {
  315. HWND hWnd = g_hUpdateWnd.load();
  316. // 窗口已经关了就不用提示了,同时省掉一次堆分配
  317. if(hWnd == NULL || !::IsWindow(hWnd))
  318. {
  319. return;
  320. }
  321. // 失败原因随消息走:堆上分配,由主线程 OnDownloadFail 负责释放
  322. std::wstring* pReason = new std::wstring((lpReason && lpReason[0])
  323. ? lpReason
  324. : L"网络连接异常,请检查网络后重试");
  325. if(!::PostMessage(hWnd, WM_DOWNLOAD_FAIL, 0, (LPARAM)pReason))
  326. {
  327. delete pReason; // 投递失败就地释放,避免泄漏
  328. }
  329. }
  330. // ============================================================================
  331. // 消息与事件
  332. // ============================================================================
  333. void CUpdateWnd::Notify(TNotifyUI& msg)
  334. {
  335. if(msg.sType == _T("click"))
  336. {
  337. CDuiString sName = msg.pSender->GetName();
  338. if(sName == _T("closebtn"))
  339. {
  340. PostQuitMessage(0);
  341. return;
  342. }
  343. else if(sName == _T("update_retry_btn"))
  344. {
  345. // 重新下载:Run() 会读本地已下文件的长度作为续传起点
  346. ShowDownloading();
  347. StartDownload();
  348. return;
  349. }
  350. }
  351. }
  352. LRESULT CUpdateWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  353. {
  354. LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
  355. styleValue &= ~WS_CAPTION;
  356. ::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
  357. // 把自己的窗口句柄与窗口绘制管理器挂接在一起
  358. m_pm.Init(m_hWnd);
  359. m_pm.AddPreMessageFilter(this);
  360. CDialogBuilder builder;
  361. CControlUI* pRoot = builder.Create(_T("update.xml"), (UINT)0, NULL, &m_pm);
  362. ASSERT(pRoot && "Failed to parse XML");
  363. // 把这些控件绘制到本窗口上
  364. m_pm.AttachDialog(pRoot);
  365. // 把自己加入到CPaintManagerUI的m_aNotifiers数组中,用于处理Notify函数
  366. m_pm.AddNotifier(this);
  367. Init();
  368. return 0;
  369. }
  370. LRESULT CUpdateWnd::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  371. {
  372. bHandled = FALSE;
  373. return 0;
  374. }
  375. /*
  376. *这个是窗口被销毁的时候调用的
  377. **/
  378. LRESULT CUpdateWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  379. {
  380. // 断开回投通道:子线程下一次回调会发现窗口没了,随即中止下载
  381. g_hUpdateWnd.store(NULL);
  382. bHandled = FALSE;
  383. return 0;
  384. }
  385. LRESULT CUpdateWnd::OnNcActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  386. {
  387. if(::IsIconic(*this))
  388. {
  389. bHandled = FALSE;
  390. }
  391. return (wParam == 0) ? TRUE : FALSE;
  392. }
  393. LRESULT CUpdateWnd::OnNcCalcSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  394. {
  395. return 0;
  396. }
  397. LRESULT CUpdateWnd::OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  398. {
  399. // 更新页没有输入焦点,无需设置焦点(旧代码残留下来的 accountedit 已随旧版 XML 一起删除)
  400. return 0;
  401. }
  402. LRESULT CUpdateWnd::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  403. {
  404. POINT pt;
  405. pt.x = GET_X_LPARAM(lParam);
  406. pt.y = GET_Y_LPARAM(lParam);
  407. ::ScreenToClient(*this, &pt);
  408. RECT rcClient;
  409. ::GetClientRect(*this, &rcClient);
  410. RECT rcCaption = m_pm.GetCaptionRect();
  411. if(pt.x >= rcClient.left + rcCaption.left && pt.x < rcClient.right - rcCaption.right \
  412. && pt.y >= rcCaption.top && pt.y < rcCaption.bottom)
  413. {
  414. CControlUI* pControl = static_cast<CControlUI*>(m_pm.FindControl(pt));
  415. if(pControl && _tcscmp(pControl->GetClass(), L"ButtonUI") != 0)
  416. {
  417. return HTCAPTION;
  418. }
  419. }
  420. return HTCLIENT;
  421. }
  422. LRESULT CUpdateWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  423. {
  424. SIZE szRoundCorner = m_pm.GetRoundCorner();
  425. if(!::IsIconic(*this) && (szRoundCorner.cx != 0 || szRoundCorner.cy != 0))
  426. {
  427. CDuiRect rcWnd;
  428. ::GetWindowRect(*this, &rcWnd);
  429. rcWnd.Offset(-rcWnd.left, -rcWnd.top);
  430. rcWnd.right++;
  431. rcWnd.bottom++;
  432. HRGN hRgn = ::CreateRoundRectRgn(rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy);
  433. ::SetWindowRgn(*this, hRgn, TRUE);
  434. ::DeleteObject(hRgn);
  435. }
  436. bHandled = FALSE;
  437. return 0;
  438. }
  439. LRESULT CUpdateWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
  440. {
  441. LRESULT lRes = 0;
  442. BOOL bHandled = TRUE;
  443. switch(uMsg)
  444. {
  445. case WM_CREATE:
  446. lRes = OnCreate(uMsg, wParam, lParam, bHandled);
  447. break;
  448. case WM_CLOSE:
  449. lRes = OnClose(uMsg, wParam, lParam, bHandled);
  450. break;
  451. case WM_DESTROY:
  452. lRes = OnDestroy(uMsg, wParam, lParam, bHandled);
  453. break;
  454. case WM_NCACTIVATE:
  455. lRes = OnNcActivate(uMsg, wParam, lParam, bHandled);
  456. break;
  457. case WM_NCCALCSIZE:
  458. lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled);
  459. break;
  460. case WM_NCPAINT:
  461. lRes = OnNcPaint(uMsg, wParam, lParam, bHandled);
  462. break;
  463. case WM_NCHITTEST:
  464. lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled);
  465. break;
  466. case WM_SIZE:
  467. lRes = OnSize(uMsg, wParam, lParam, bHandled);
  468. break;
  469. case WM_UPDATEPROGRESS:
  470. lRes = OnRefresh(uMsg, wParam, lParam, bHandled);
  471. break;
  472. case WM_DOWNLOAD_FINISH:
  473. lRes = OnDownloadFinish(uMsg, wParam, lParam, bHandled);
  474. break;
  475. case WM_DOWNLOAD_FAIL:
  476. lRes = OnDownloadFail(uMsg, wParam, lParam, bHandled);
  477. break;
  478. default:
  479. bHandled = FALSE;
  480. }
  481. if(bHandled)
  482. {
  483. return lRes;
  484. }
  485. if(m_pm.MessageHandler(uMsg, wParam, lParam, lRes))
  486. {
  487. return lRes;
  488. }
  489. return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
  490. }
  491. LRESULT CUpdateWnd::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled)
  492. {
  493. return false;
  494. }
  495. double CUpdateWnd::getDownloadFileLength(std::string url)
  496. {
  497. CURL *easy_handle = NULL;
  498. int ret = CURLE_OK;
  499. double size = -1;
  500. do
  501. {
  502. easy_handle = curl_easy_init();
  503. if(!easy_handle)
  504. {
  505. break;
  506. }
  507. // Only get the header data
  508. ret = curl_easy_setopt(easy_handle, CURLOPT_URL, url.c_str());
  509. if (url.find("https://") == 0)
  510. {
  511. ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, false);
  512. ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, false);
  513. }
  514. ret |= curl_easy_setopt(easy_handle, CURLOPT_HEADER, 1L);
  515. ret |= curl_easy_setopt(easy_handle, CURLOPT_NOBODY, 1L);
  516. ret |= curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, nousecb); // libcurl_a.lib will return error code 23 without this sentence on windows
  517. if(ret != CURLE_OK)
  518. {
  519. break;
  520. }
  521. ret = curl_easy_perform(easy_handle);
  522. if(ret != CURLE_OK)
  523. {
  524. char s[100] = { 0 };
  525. sprintf_s(s, sizeof(s), "error:%d:%s", ret, curl_easy_strerror(static_cast<CURLcode>(ret)));
  526. break;
  527. }
  528. // size = -1 if no Content-Length return or Content-Length=0
  529. ret = curl_easy_getinfo(easy_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
  530. if(ret != CURLE_OK)
  531. {
  532. break;
  533. }
  534. }
  535. while(0);
  536. curl_easy_cleanup(easy_handle);
  537. return size;
  538. }
  539. // ---------------------------------------------------------------------------
  540. // 下载线程主体
  541. // 只读写 ctx(shared_ptr 保证生命周期)与文件静态区,绝不涉及窗口成员
  542. // ---------------------------------------------------------------------------
  543. void CUpdateWnd::Run(std::shared_ptr<SUpdateCtx> ctx)
  544. {
  545. ctx->filepath = ctx->dir + CLewaimaiString::UTF8ToUnicode(CLewaimaiString::GetFilenameFromUrl(ctx->url));
  546. // 断点续传:本地已有半成品则从其末尾接着下
  547. std::error_code ecSize;
  548. uintmax_t nLocalSize = std::filesystem::file_size(ctx->filepath, ecSize);
  549. ctx->resumeByte = (ecSize || nLocalSize == 0) ? 0L : (long)nLocalSize;
  550. SetProgressSnapshot((double)ctx->resumeByte, 0.0);
  551. PostProgress();
  552. ctx->curl = curl_easy_init();
  553. if(!ctx->curl)
  554. {
  555. PostFail(L"网络组件初始化失败,请重启程序后重试");
  556. return;
  557. }
  558. // 先探一次总大小,探不到就没必要开文件了
  559. ctx->total = getDownloadFileLength(ctx->url);
  560. if(ctx->total <= 0)
  561. {
  562. curl_easy_cleanup(ctx->curl);
  563. ctx->curl = nullptr;
  564. PostFail(L"无法连接更新服务器,请检查网络后重试");
  565. return;
  566. }
  567. SetProgressSnapshot((double)ctx->resumeByte, ctx->total);
  568. PostProgress();
  569. // 上次已经下完(比如下完没装就退了),直接进完成态
  570. if((double)ctx->resumeByte >= ctx->total)
  571. {
  572. curl_easy_cleanup(ctx->curl);
  573. ctx->curl = nullptr;
  574. SetProgressSnapshot(ctx->total, ctx->total);
  575. PostFinish();
  576. return;
  577. }
  578. errno_t fopenErr = _wfopen_s(&ctx->outfile, ctx->filepath.c_str(), L"ab+");
  579. if(fopenErr != 0 || ctx->outfile == nullptr)
  580. {
  581. curl_easy_cleanup(ctx->curl);
  582. ctx->curl = nullptr;
  583. PostFail(L"无法写入本地文件,请检查磁盘空间与权限");
  584. return;
  585. }
  586. curl_easy_setopt(ctx->curl, CURLOPT_URL, ctx->url.c_str());//在此要注意,此url必须是多字节
  587. if (ctx->url.find("https://") == 0)
  588. {
  589. curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYPEER, false);
  590. curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYHOST, false);
  591. }
  592. curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, 0);
  593. curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, my_write_func);
  594. curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, ctx->outfile);
  595. curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, FALSE);
  596. curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
  597. curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSDATA, ctx.get());//进度回调需要拿文件指针来算真实已下字节
  598. curl_easy_setopt(ctx->curl, CURLOPT_RESUME_FROM, ctx->resumeByte);
  599. CURLcode res = curl_easy_perform(ctx->curl);
  600. fclose(ctx->outfile);
  601. ctx->outfile = nullptr;
  602. curl_easy_cleanup(ctx->curl);
  603. ctx->curl = nullptr;
  604. if(res != CURLE_OK)
  605. {
  606. // 窗口已经关了就别再喊了,用户看不到
  607. if(IsUpdateWndAlive())
  608. {
  609. PostFail(L"网络连接中断,请检查网络后重试");
  610. }
  611. return;
  612. }
  613. // 校验实际落盘大小,防止拿到一个残缺包就去安装
  614. std::error_code ecDone;
  615. uintmax_t nDoneSize = std::filesystem::file_size(ctx->filepath, ecDone);
  616. if(!ecDone)
  617. {
  618. SetProgressSnapshot((double)nDoneSize, ctx->total);
  619. if((double)nDoneSize + 1.0 < ctx->total)
  620. {
  621. if(IsUpdateWndAlive())
  622. {
  623. PostFail(L"下载内容不完整,请重新下载");
  624. }
  625. return;
  626. }
  627. }
  628. PostFinish();
  629. }
  630. size_t CUpdateWnd::my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
  631. {
  632. size_t nWrite = fwrite(ptr, size, nmemb, stream);
  633. if(nWrite == 0)
  634. {
  635. return CURL_WRITEFUNC_PAUSE;
  636. }
  637. else
  638. {
  639. return nWrite;
  640. }
  641. }
  642. int CUpdateWnd::my_progress_func(void *progress_data,
  643. double t, /* dltotal */
  644. double d, /* dlnow */
  645. double ultotal,
  646. double ulnow)
  647. {
  648. (void)t;
  649. (void)d;
  650. (void)ultotal;
  651. (void)ulnow;
  652. // 窗口已经关闭 → 返回非 0 让 curl 中止这次传输,避免留下僵尸下载线程
  653. if(!IsUpdateWndAlive())
  654. {
  655. return 1;
  656. }
  657. const SUpdateCtx* pCtx = static_cast<const SUpdateCtx*>(progress_data);
  658. if(!pCtx)
  659. {
  660. return 0;
  661. }
  662. // 直接读文件当前长度作为已下载量:
  663. // 一是天然包含断点续传已下的部分,不必猜 libcurl 对 dlnow 的口径;
  664. // 二是 CURLOPT_RESUME_FROM 下 dltotal 的语义各版本不一致,统一用自己探到的总大小。
  665. double dlnow = 0.0;
  666. if(pCtx->outfile)
  667. {
  668. __int64 nPos = _ftelli64(pCtx->outfile);
  669. if(nPos > 0)
  670. {
  671. dlnow = (double)nPos;
  672. }
  673. }
  674. SetProgressSnapshot(dlnow, pCtx->total);
  675. PostProgress();
  676. return 0;
  677. }
  678. size_t CUpdateWnd::nousecb(char *buffer, size_t x, size_t y, void *userdata)
  679. {
  680. (void)buffer;
  681. (void)userdata;
  682. return x * y;
  683. }
  684. LRESULT CUpdateWnd::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  685. {
  686. UpdateProgressInfo();
  687. return 0;
  688. }
  689. LRESULT CUpdateWnd::OnDownloadFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  690. {
  691. // curl 完成与「本地文件已完整」都投过 WM_DOWNLOAD_FINISH,这里挡掉重复触发
  692. if(m_bFinished)
  693. {
  694. return 0;
  695. }
  696. m_bFinished = true;
  697. ShowFinish();
  698. if(m_ctx && !m_ctx->filepath.empty())
  699. {
  700. ShellExecute(NULL, L"open", m_ctx->filepath.c_str(), NULL, NULL, SW_SHOW);
  701. }
  702. PostQuitMessage(0);
  703. return 0;
  704. }
  705. LRESULT CUpdateWnd::OnDownloadFail(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
  706. {
  707. // 失败原因由子线程在堆上分配并随消息传来,主线程负责释放
  708. std::wstring* pReason = reinterpret_cast<std::wstring*>(lParam);
  709. std::wstring sReason = pReason ? *pReason : L"";
  710. delete pReason;
  711. pReason = nullptr;
  712. ShowFail(sReason.c_str());
  713. return 0;
  714. }