Fix typo in doc
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
blob0aa9efa7a803546038a0885c9687a99987e55d8c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2017 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "Git.h"
23 #include "LoglistCommonResource.h"
24 #include "..\version.h"
25 #include "MessageBox.h"
26 #include "CheckForUpdatesDlg.h"
27 #include "registry.h"
28 #include "AppUtils.h"
29 #include "TempFile.h"
30 #include "SmartHandle.h"
31 #include "SysInfo.h"
32 #include "PathUtils.h"
33 #include "DirFileEnum.h"
34 #include "UnicodeUtils.h"
35 #include "UpdateCrypto.h"
36 #include <MsiDefs.h>
37 #include <MsiQuery.h>
39 #pragma comment(lib, "msi.lib")
41 #define SIGNATURE_FILE_ENDING L".rsa.asc"
43 #define WM_USER_DISPLAYSTATUS (WM_USER + 1)
44 #define WM_USER_ENDDOWNLOAD (WM_USER + 2)
45 #define WM_USER_FILLCHANGELOG (WM_USER + 3)
47 IMPLEMENT_DYNAMIC(CCheckForUpdatesDlg, CResizableStandAloneDialog)
48 CCheckForUpdatesDlg::CCheckForUpdatesDlg(CWnd* pParent /*=nullptr*/)
49 : CResizableStandAloneDialog(CCheckForUpdatesDlg::IDD, pParent)
50 , m_bShowInfo(FALSE)
51 , m_bForce(FALSE)
52 , m_bVisible(FALSE)
53 , m_pDownloadThread(nullptr)
54 , m_bThreadRunning(FALSE)
55 , m_updateDownloader(nullptr)
56 , m_sUpdateDownloadLink(L"https://tortoisegit.org/download")
60 CCheckForUpdatesDlg::~CCheckForUpdatesDlg()
64 void CCheckForUpdatesDlg::DoDataExchange(CDataExchange* pDX)
66 CResizableStandAloneDialog::DoDataExchange(pDX);
67 DDX_Control(pDX, IDC_LINK, m_link);
68 DDX_Control(pDX, IDC_PROGRESSBAR, m_progress);
69 DDX_Control(pDX, IDC_LIST_DOWNLOADS, m_ctrlFiles);
70 DDX_Control(pDX, IDC_BUTTON_UPDATE, m_ctrlUpdate);
71 DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);
74 BEGIN_MESSAGE_MAP(CCheckForUpdatesDlg, CResizableStandAloneDialog)
75 ON_WM_TIMER()
76 ON_WM_WINDOWPOSCHANGING()
77 ON_WM_SETCURSOR()
78 ON_WM_DESTROY()
79 ON_WM_SYSCOLORCHANGE()
80 ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
81 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
82 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
83 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
84 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
85 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
86 END_MESSAGE_MAP()
88 BOOL CCheckForUpdatesDlg::OnInitDialog()
90 CResizableStandAloneDialog::OnInitDialog();
91 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
93 CString temp;
94 temp.Format(IDS_CHECKNEWER_YOURVERSION, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD);
95 SetDlgItemText(IDC_YOURVERSION, temp);
97 DialogEnableWindow(IDOK, FALSE);
99 m_pTaskbarList.Release();
100 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
101 m_pTaskbarList = nullptr;
103 // hide download controls
104 m_ctrlFiles.ShowWindow(SW_HIDE);
105 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
106 RECT rectWindow, rectGroupDownloads, rectOKButton;
107 GetWindowRect(&rectWindow);
108 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
109 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
110 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
111 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
112 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
113 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
114 MoveWindow(&rectWindow);
115 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
116 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
118 temp.LoadString(IDS_STATUSLIST_COLFILE);
119 m_ctrlFiles.InsertColumn(0, temp, 0, -1);
120 m_ctrlFiles.InsertColumn(1, temp, 0, -1);
121 m_ctrlFiles.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_CHECKBOXES);
122 m_ctrlFiles.SetColumnWidth(0, 350);
123 m_ctrlFiles.SetColumnWidth(1, 200);
125 m_cLogMessage.Init(-1);
126 m_cLogMessage.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());
127 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
129 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
131 if (!AfxBeginThread(CheckThreadEntry, this))
133 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
136 AddAnchor(IDC_YOURVERSION, TOP_LEFT, TOP_RIGHT);
137 AddAnchor(IDC_CURRENTVERSION, TOP_LEFT, TOP_RIGHT);
138 AddAnchor(IDC_CHECKRESULT, TOP_LEFT, TOP_RIGHT);
139 AddAnchor(IDC_LINK, TOP_LEFT, TOP_RIGHT);
140 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
141 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
142 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
143 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
144 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
145 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
146 AddAnchor(IDOK, BOTTOM_CENTER);
148 SetTimer(100, 1000, nullptr);
149 return TRUE;
152 void CCheckForUpdatesDlg::OnDestroy()
154 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
155 delete (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
157 delete m_updateDownloader;
159 CResizableStandAloneDialog::OnDestroy();
162 void CCheckForUpdatesDlg::OnOK()
164 if (m_bThreadRunning || m_pDownloadThread)
165 return; // Don't exit while downloading
167 CResizableStandAloneDialog::OnOK();
170 void CCheckForUpdatesDlg::OnCancel()
172 if (m_bThreadRunning || m_pDownloadThread)
173 return; // Don't exit while downloading
174 CResizableStandAloneDialog::OnCancel();
177 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
179 return reinterpret_cast<CCheckForUpdatesDlg*>(pVoid)->CheckThread();
182 UINT CCheckForUpdatesDlg::CheckThread()
184 m_bThreadRunning = TRUE;
186 CString temp;
187 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
189 bool official = false;
191 CRegString checkurluser = CRegString(L"Software\\TortoiseGit\\UpdateCheckURL", L"");
192 CRegString checkurlmachine = CRegString(L"Software\\TortoiseGit\\UpdateCheckURL", L"", FALSE, HKEY_LOCAL_MACHINE);
193 CString sCheckURL = checkurluser;
194 if (sCheckURL.IsEmpty())
196 sCheckURL = checkurlmachine;
197 if (sCheckURL.IsEmpty())
199 official = true;
200 bool checkPreview = false;
201 #if PREVIEW
202 checkPreview = true;
203 #else
204 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
205 if (DWORD(regCheckPreview))
206 checkPreview = true;
207 #endif
208 if (checkPreview)
210 sCheckURL = L"https://versioncheck.tortoisegit.org/version-preview.txt";
211 SetDlgItemText(IDC_SOURCE, L"Using preview release channel");
213 else
214 sCheckURL = L"https://versioncheck.tortoisegit.org/version.txt";
218 if (!official && CStringUtils::StartsWith(sCheckURL, L"https://versioncheck.tortoisegit.org/"))
219 official = true;
221 if (!official)
222 SetDlgItemText(IDC_SOURCE, L"Using (unofficial) release channel: " + sCheckURL);
224 CString errorText;
225 CVersioncheckParser versioncheck;
226 CVersioncheckParser::Version version;
227 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
228 if (!ret && official)
230 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
231 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
232 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
234 CString error = L"Could not verify digital signature.";
235 if (ret)
236 error += L"\r\nError: " + GetWinINetError(ret) + L" (on " + sCheckURL + SIGNATURE_FILE_ENDING + L")";
237 SetDlgItemText(IDC_CHECKRESULT, error);
238 DeleteUrlCacheEntry(sCheckURL);
239 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
240 goto finish;
243 else if (ret)
245 DeleteUrlCacheEntry(sCheckURL);
246 if (CRegDWORD(L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline", 0))
247 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
248 else
249 errorText.Format(IDS_CHECKNEWER_NETERROR_FORMAT, (LPCTSTR)(GetWinINetError(ret) + L" URL: " + sCheckURL), ret);
250 SetDlgItemText(IDC_CHECKRESULT, errorText);
251 goto finish;
254 if (!versioncheck.Load(tempfile, errorText))
256 if (!errorText.IsEmpty())
257 SetDlgItemText(IDC_CHECKRESULT, errorText);
258 else
260 temp.LoadString(IDS_CHECKNEWER_NETERROR);
261 SetDlgItemText(IDC_CHECKRESULT, temp);
263 DeleteUrlCacheEntry(sCheckURL);
264 goto finish;
267 version = versioncheck.GetTortoiseGitVersion();
269 BOOL bNewer = FALSE;
270 if (m_bForce)
271 bNewer = TRUE;
272 else if (version.major > TGIT_VERMAJOR)
273 bNewer = TRUE;
274 else if ((version.minor > TGIT_VERMINOR) && (version.major == TGIT_VERMAJOR))
275 bNewer = TRUE;
276 else if ((version.micro > TGIT_VERMICRO) && (version.minor == TGIT_VERMINOR) && (version.major == TGIT_VERMAJOR))
277 bNewer = TRUE;
278 else if ((version.build > TGIT_VERBUILD) && (version.micro == TGIT_VERMICRO) && (version.minor == TGIT_VERMINOR) && (version.major == TGIT_VERMAJOR))
279 bNewer = TRUE;
281 m_sNewVersionNumber.Format(L"%u.%u.%u.%u", version.major, version.minor, version.micro, version.build);
282 if (m_sNewVersionNumber != version.version_for_filename)
284 CString versionstr = m_sNewVersionNumber + L" (" + version.version_for_filename + L")";
285 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)versionstr);
287 else
288 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)m_sNewVersionNumber);
289 SetDlgItemText(IDC_CURRENTVERSION, temp);
291 if (bNewer)
293 temp = versioncheck.GetTortoiseGitInfoText();
294 if (!temp.IsEmpty())
296 CString tempLink = versioncheck.GetTortoiseGitInfoTextURL();
297 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
298 m_sUpdateDownloadLink = tempLink;
300 else
301 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
302 SetDlgItemText(IDC_CHECKRESULT, temp);
304 FillChangelog(versioncheck, official);
305 FillDownloads(versioncheck);
307 RemoveAnchor(IDC_GROUP_CHANGELOG);
308 RemoveAnchor(IDC_LOGMESSAGE);
309 RemoveAnchor(IDC_GROUP_DOWNLOADS);
310 RemoveAnchor(IDC_LIST_DOWNLOADS);
311 RemoveAnchor(IDC_PROGRESSBAR);
312 RemoveAnchor(IDC_DONOTASKAGAIN);
313 RemoveAnchor(IDC_BUTTON_UPDATE);
314 RemoveAnchor(IDOK);
316 // Show download controls
317 RECT rectWindow, rectProgress, rectGroupDownloads, rectOKButton;
318 GetWindowRect(&rectWindow);
319 m_progress.GetWindowRect(&rectProgress);
320 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
321 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
322 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
323 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
324 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
325 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
326 MoveWindow(&rectWindow);
327 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
328 if (CRegDWORD(L"Software\\TortoiseGit\\VersionCheck", TRUE) != FALSE && !m_bForce && !m_bShowInfo)
330 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
331 rectOKButton.left += 60;
332 temp.LoadString(IDS_REMINDMELATER);
333 GetDlgItem(IDOK)->SetWindowText(temp);
334 rectOKButton.right += 160;
336 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
337 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
338 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
339 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
340 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
341 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
342 AddAnchor(IDC_DONOTASKAGAIN, BOTTOM_CENTER);
343 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
344 AddAnchor(IDOK, BOTTOM_CENTER);
345 m_ctrlFiles.ShowWindow(SW_SHOW);
346 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
347 CenterWindow();
348 m_bShowInfo = TRUE;
350 else if (m_bShowInfo)
352 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
353 SetDlgItemText(IDC_CHECKRESULT, temp);
354 FillChangelog(versioncheck, official);
358 finish:
359 if (!m_sUpdateDownloadLink.IsEmpty())
361 m_link.ShowWindow(SW_SHOW);
362 m_link.SetURL(m_sUpdateDownloadLink);
364 m_bThreadRunning = FALSE;
365 DialogEnableWindow(IDOK, TRUE);
366 return 0;
369 void CCheckForUpdatesDlg::FillDownloads(CVersioncheckParser& versioncheck)
371 m_sFilesURL = versioncheck.GetTortoiseGitBaseURL();
373 bool isHotfix = versioncheck.GetTortoiseGitIsHotfix();
374 if (isHotfix)
375 m_ctrlFiles.InsertItem(0, L"TortoiseGit Hotfix");
376 else
377 m_ctrlFiles.InsertItem(0, L"TortoiseGit");
378 CString filenameMain = versioncheck.GetTortoiseGitMainfilename();
379 m_ctrlFiles.SetItemData(0, (DWORD_PTR)(new CUpdateListCtrl::Entry(filenameMain, CUpdateListCtrl::STATUS_NONE)));
380 m_ctrlFiles.SetCheck(0 , TRUE);
382 if (isHotfix)
384 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
385 return;
388 struct LangPack
390 CVersioncheckParser::LanguagePack languagepack;
391 bool m_Installed;
393 std::vector<LangPack> availableLangs;
394 std::vector<DWORD> installedLangs;
396 // set up the language selecting combobox
397 CString path = CPathUtils::GetAppParentDirectory();
398 path = path + L"Languages\\";
399 CSimpleFileFind finder(path, L"*.dll");
400 while (finder.FindNextFileNoDirectories())
402 CString file = finder.GetFilePath();
403 CString filename = finder.GetFileName();
404 if (CStringUtils::StartsWithI(filename, L"TortoiseProc"))
406 CString sVer = _T(STRPRODUCTVER);
407 sVer = sVer.Left(sVer.ReverseFind('.'));
408 CString sFileVer = CPathUtils::GetVersionFromFile(file);
409 sFileVer = sFileVer.Left(sFileVer.ReverseFind('.'));
410 CString sLoc = filename.Mid(12);
411 sLoc = sLoc.Left(sLoc.GetLength() - 4); // cut off ".dll"
412 if (CStringUtils::StartsWith(sLoc, L"32") && (sLoc.GetLength() > 5))
413 continue;
414 DWORD loc = _wtoi(filename.Mid(12));
415 installedLangs.push_back(loc);
420 for (auto& languagepack : versioncheck.GetTortoiseGitLanguagePacks())
422 bool installed = std::find(installedLangs.cbegin(), installedLangs.cend(), languagepack.m_LocaleID) != installedLangs.cend();
423 LangPack pack = { languagepack, installed };
424 availableLangs.push_back(pack);
426 std::stable_sort(availableLangs.begin(), availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
428 return (a.m_Installed && !b.m_Installed) ? 1 : (!a.m_Installed && b.m_Installed) ? 0 : (a.languagepack.m_PackName.Compare(b.languagepack.m_PackName) < 0);
430 for (const auto& langs : availableLangs)
432 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.languagepack.m_PackName);
433 m_ctrlFiles.SetItemText(pos, 1, langs.languagepack.m_LangName);
434 m_ctrlFiles.SetItemData(pos, (DWORD_PTR)(new CUpdateListCtrl::Entry(langs.languagepack.m_filename, CUpdateListCtrl::STATUS_NONE)));
436 if (langs.m_Installed)
437 m_ctrlFiles.SetCheck(pos , TRUE);
439 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
442 void CCheckForUpdatesDlg::FillChangelog(CVersioncheckParser& versioncheck, bool official)
444 ProjectProperties pp;
445 pp.lProjectLanguage = -1;
446 pp.sUrl = versioncheck.GetTortoiseGitIssuesURL();
447 if (!pp.sUrl.IsEmpty())
449 pp.SetCheckRe(L"[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+");
450 pp.SetBugIDRe(L"(\\d+)");
452 m_cLogMessage.Init(pp);
454 CString sChangelogURL;
455 sChangelogURL.FormatMessage(versioncheck.GetTortoiseGitChangelogURL(), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, (LPCTSTR)m_updateDownloader->m_sWindowsPlatform, (LPCTSTR)m_updateDownloader->m_sWindowsVersion, (LPCTSTR)m_updateDownloader->m_sWindowsServicePack);
457 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
458 DWORD err;
459 if ((err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false)) != ERROR_SUCCESS)
461 CString msg = L"Could not load changelog.\r\nError: " + GetWinINetError(err) + L" (on " + sChangelogURL + L")";
462 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)msg));
463 return;
465 if (official)
467 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
468 if ((err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false)) != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
470 CString error = L"Could not verify digital signature.";
471 if (err)
472 error += L"\r\nError: " + GetWinINetError(err) + L" (on " + sChangelogURL + SIGNATURE_FILE_ENDING + L")";
473 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)error));
474 DeleteUrlCacheEntry(sChangelogURL);
475 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
476 return;
480 CString temp;
481 CStdioFile file;
482 if (file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary))
484 auto buf = std::make_unique<BYTE[]>((UINT)file.GetLength());
485 UINT read = file.Read(buf.get(), (UINT)file.GetLength());
486 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
487 CGit::StringAppend(&temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
489 else
490 temp = L"Could not open downloaded changelog file.";
491 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)temp));
494 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
496 if (nIDEvent == 100)
498 if (m_bThreadRunning == FALSE)
500 KillTimer(100);
501 if (m_bShowInfo)
503 m_bVisible = TRUE;
504 ShowWindow(SW_SHOWNORMAL);
506 else
507 EndDialog(0);
510 CResizableStandAloneDialog::OnTimer(nIDEvent);
513 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
515 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
516 if (m_bVisible == FALSE)
517 lpwndpos->flags &= ~SWP_SHOWWINDOW;
520 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
522 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
523 SetCursor(hCur);
524 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
527 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
529 CString title;
530 m_ctrlUpdate.GetWindowText(title);
531 if (!m_pDownloadThread && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
533 bool isOneSelected = false;
534 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
536 if (m_ctrlFiles.GetCheck(i))
538 isOneSelected = true;
539 break;
542 if (!isOneSelected)
543 return;
545 m_eventStop.ResetEvent();
547 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
548 if (m_pDownloadThread)
550 m_pDownloadThread->m_bAutoDelete = FALSE;
551 m_pDownloadThread->ResumeThread();
553 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
555 else
556 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
558 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
560 // Abort
561 m_eventStop.SetEvent();
563 else
565 CString folder = GetDownloadsDirectory();
566 if (m_ctrlUpdate.GetCurrentEntry() == 0)
568 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
570 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
571 if (m_ctrlFiles.GetCheck(i) == TRUE)
572 ShellExecute(GetSafeHwnd(), L"open", folder + data->m_filename, nullptr, nullptr, SW_SHOWNORMAL);
574 CResizableStandAloneDialog::OnOK();
576 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
577 ShellExecute(GetSafeHwnd(), L"open", folder, nullptr, nullptr, SW_SHOWNORMAL);
579 m_ctrlUpdate.SetCurrentEntry(0);
583 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
585 return reinterpret_cast<CCheckForUpdatesDlg*>(pVoid)->DownloadThread();
588 bool CCheckForUpdatesDlg::VerifyUpdateFile(const CString& filename, const CString& filenameSignature, const CString& reportingFilename)
590 if (VerifyIntegrity(filename, filenameSignature, m_updateDownloader) != 0)
592 m_sErrors += reportingFilename + SIGNATURE_FILE_ENDING + L": Invalid digital signature.\r\n";
593 return false;
596 MSIHANDLE hSummary;
597 DWORD ret = 0;
598 if ((ret = MsiGetSummaryInformation(NULL, filename, 0, &hSummary)) != 0)
600 CString sFileVer = CPathUtils::GetVersionFromFile(filename);
601 sFileVer.Trim();
602 if (sFileVer.IsEmpty())
604 m_sErrors.AppendFormat(L"%s: Invalid filetype found (neither executable nor MSI).\r\n", (LPCTSTR)reportingFilename);
605 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": MsiGetSummaryInformation reported: %s\n", (LPCTSTR)CFormatMessageWrapper(ret));
606 return false;
608 else if (sFileVer == m_sNewVersionNumber)
609 return true;
611 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"%s\", got: \"%s\").\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)m_sNewVersionNumber, (LPCTSTR)sFileVer);
612 return false;
614 SCOPE_EXIT{ MsiCloseHandle(hSummary); };
616 UINT uiDataType = 0;
617 DWORD cchValue = 4096;
618 CString buffer;
619 int intValue;
620 if (MsiSummaryInfoGetProperty(hSummary, PID_SUBJECT, &uiDataType, &intValue, nullptr, CStrBuf(buffer, cchValue + 1), &cchValue))
622 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (%s).\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)CFormatMessageWrapper(ret));
623 return false;
626 if (VT_LPSTR != uiDataType)
628 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (invalid data type returned).\r\n", (LPCTSTR)reportingFilename);
629 return false;
632 CString sFileVer = buffer.Right(m_sNewVersionNumber.GetLength() + 1);
633 if (sFileVer != L"v" + m_sNewVersionNumber)
635 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"v%s\", got: \"%s\").\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)m_sNewVersionNumber, (LPCTSTR)sFileVer);
636 return false;
639 return true;
642 bool CCheckForUpdatesDlg::Download(CString filename)
644 CString url = m_sFilesURL + filename;
645 CString destFilename = GetDownloadsDirectory() + filename;
646 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
648 if (VerifyUpdateFile(destFilename, destFilename + SIGNATURE_FILE_ENDING, destFilename))
649 return true;
650 else
652 DeleteFile(destFilename);
653 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
654 DeleteUrlCacheEntry(url);
655 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
659 m_progress.SetRange32(0, 1);
660 m_progress.SetPos(0);
661 m_progress.ShowWindow(SW_SHOW);
662 if (m_pTaskbarList)
664 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
665 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
668 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
669 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
670 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
671 if (!ret)
673 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
674 if (ret)
675 m_sErrors += url + SIGNATURE_FILE_ENDING + L": " + GetWinINetError(ret) + L"\r\n";
676 m_progress.SetPos(m_progress.GetPos() + 1);
677 if (m_pTaskbarList)
679 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
680 int minValue, maxValue;
681 m_progress.GetRange(minValue, maxValue);
682 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
685 else
686 m_sErrors += url + L": " + GetWinINetError(ret) + L"\r\n";
687 if (!ret)
689 if (VerifyUpdateFile(tempfile, signatureTempfile, url))
691 DeleteFile(destFilename);
692 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
693 if (!MoveFile(tempfile, destFilename))
695 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)tempfile, (LPCTSTR)destFilename);
696 return false;
698 if (!MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING))
700 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)signatureTempfile, (LPCTSTR)(destFilename + SIGNATURE_FILE_ENDING));
701 return false;
703 return true;
705 DeleteUrlCacheEntry(url);
706 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
708 return false;
711 UINT CCheckForUpdatesDlg::DownloadThread()
713 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
714 m_sErrors.Empty();
715 BOOL result = TRUE;
716 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
718 m_ctrlFiles.EnsureVisible(i, FALSE);
719 CRect rect;
720 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
721 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
722 if (m_ctrlFiles.GetCheck(i) == TRUE)
724 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
725 m_ctrlFiles.InvalidateRect(rect);
726 if (Download(data->m_filename))
727 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
728 else
730 data->m_status = CUpdateListCtrl::STATUS_FAIL;
731 result = FALSE;
734 else
735 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
736 m_ctrlFiles.InvalidateRect(rect);
739 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
741 return result;
744 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
746 ASSERT(m_pDownloadThread);
748 // wait until the thread terminates
749 DWORD dwExitCode;
750 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
751 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
753 // make sure we always have the correct exit code
754 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
756 delete m_pDownloadThread;
757 m_pDownloadThread = nullptr;
759 m_progress.ShowWindow(SW_HIDE);
761 if (dwExitCode == TRUE)
763 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
764 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
765 m_ctrlUpdate.Invalidate();
766 if (m_pTaskbarList)
767 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
769 else
771 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
772 if (m_pTaskbarList)
773 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
774 CString tmp;
775 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
776 if (!m_sErrors.IsEmpty())
777 tmp += L"\r\n\r\nErrors:\r\n" + m_sErrors;
778 CMessageBox::Show(GetSafeHwnd(), tmp, L"TortoiseGit", MB_ICONERROR);
779 if (m_pTaskbarList)
780 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
783 return 0;
786 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM, LPARAM lParam)
788 ASSERT(lParam);
790 LPCTSTR changelog = reinterpret_cast<LPCTSTR>(lParam);
791 m_cLogMessage.Call(SCI_SETREADONLY, FALSE);
792 m_cLogMessage.SetText(changelog);
793 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
794 m_cLogMessage.Call(SCI_GOTOPOS, 0);
795 m_cLogMessage.Call(SCI_SETWRAPSTARTINDENT, 3);
797 return 0;
800 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
802 CString folder;
804 PWSTR wcharPtr = nullptr;
805 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, nullptr, &wcharPtr)))
807 folder = wcharPtr;
808 CoTaskMemFree(wcharPtr);
809 return folder.TrimRight(L'\\') + L'\\';
812 return folder;
815 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
817 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
818 if (pDownloadStatus)
820 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
822 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
823 m_progress.SetPos(pDownloadStatus->ulProgress);
824 if (m_pTaskbarList)
826 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
827 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
831 return 0;
834 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
836 m_pTaskbarList.Release();
837 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
838 return 0;
841 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
843 CString readableError = CFormatMessageWrapper(err);
844 if (readableError.IsEmpty())
846 for (const CString& module : { L"wininet.dll", L"urlmon.dll" })
848 LPTSTR buffer;
849 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, (LPTSTR)&buffer, 0, nullptr);
850 readableError = buffer;
851 LocalFree(buffer);
852 if (!readableError.IsEmpty())
853 break;
856 return readableError.Trim();
859 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
861 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
863 CRegDWORD(L"Software\\TortoiseGit\\VersionCheck") = FALSE;
864 OnOK();
868 void CCheckForUpdatesDlg::OnSysColorChange()
870 __super::OnSysColorChange();
871 m_cLogMessage.SetColors(true);
872 m_cLogMessage.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());