Fix typos
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
blob20925ee307cfa6504e89ba6aaaa88fe06848c3eb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2024 - 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>
38 #include "DPIAware.h"
40 #pragma comment(lib, "msi.lib")
42 #define SIGNATURE_FILE_ENDING L".rsa.asc"
44 #define WM_USER_DISPLAYSTATUS (WM_USER + 1)
45 #define WM_USER_ENDDOWNLOAD (WM_USER + 2)
46 #define WM_USER_FILLCHANGELOG (WM_USER + 3)
48 IMPLEMENT_DYNAMIC(CCheckForUpdatesDlg, CResizableStandAloneDialog)
49 CCheckForUpdatesDlg::CCheckForUpdatesDlg(CWnd* pParent /*=nullptr*/)
50 : CResizableStandAloneDialog(CCheckForUpdatesDlg::IDD, pParent)
51 , m_sUpdateDownloadLink(L"https://tortoisegit.org/download")
55 CCheckForUpdatesDlg::~CCheckForUpdatesDlg()
59 void CCheckForUpdatesDlg::DoDataExchange(CDataExchange* pDX)
61 CResizableStandAloneDialog::DoDataExchange(pDX);
62 DDX_Control(pDX, IDC_LINK, m_link);
63 DDX_Control(pDX, IDC_PROGRESSBAR, m_progress);
64 DDX_Control(pDX, IDC_LIST_DOWNLOADS, m_ctrlFiles);
65 DDX_Control(pDX, IDC_BUTTON_UPDATE, m_ctrlUpdate);
66 DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);
69 BEGIN_MESSAGE_MAP(CCheckForUpdatesDlg, CResizableStandAloneDialog)
70 ON_WM_TIMER()
71 ON_WM_WINDOWPOSCHANGING()
72 ON_WM_SETCURSOR()
73 ON_WM_DESTROY()
74 ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
75 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
76 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
77 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
78 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
79 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
80 END_MESSAGE_MAP()
82 BOOL CCheckForUpdatesDlg::OnInitDialog()
84 CResizableStandAloneDialog::OnInitDialog();
85 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
87 if (CString hotfix = CPathUtils::GetAppDirectory() + L"hotfix.ini"; PathFileExists(hotfix))
89 CString err;
90 CVersioncheckParser parser;
91 if (parser.Load(hotfix, err))
93 auto version = parser.GetTortoiseGitVersion();
94 if (version.major == TGIT_VERMAJOR && version.minor == TGIT_VERMINOR && version.micro == TGIT_VERMICRO && version.build > TGIT_VERBUILD)
95 m_myVersion = version;
98 if (m_myVersion.version.IsEmpty() || !m_myVersion.major)
99 m_myVersion = { _T(STRFILEVER), L"", L"", TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD };
101 CString temp;
102 temp.Format(IDS_CHECKNEWER_YOURVERSION, m_myVersion.major, m_myVersion.minor, m_myVersion.micro, m_myVersion.build);
103 SetDlgItemText(IDC_YOURVERSION, temp);
105 DialogEnableWindow(IDOK, FALSE);
107 m_pTaskbarList.Release();
108 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
109 m_pTaskbarList = nullptr;
111 // hide changelog and download controls
112 AdjustSize(false, false, false);
114 temp.LoadString(IDS_STATUSLIST_COLFILE);
115 m_ctrlFiles.InsertColumn(0, temp, 0, -1);
116 m_ctrlFiles.InsertColumn(1, temp, 0, -1);
117 m_ctrlFiles.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_CHECKBOXES);
118 m_ctrlFiles.SetColumnWidth(0, CDPIAware::Instance().ScaleX(GetSafeHwnd(), 350));
119 m_ctrlFiles.SetColumnWidth(1, CDPIAware::Instance().ScaleX(GetSafeHwnd(), 200));
121 m_cLogMessage.Init(-1);
122 m_cLogMessage.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());
123 m_cLogMessage.Call(SCI_SETUNDOCOLLECTION, 0);
124 m_cLogMessage.SetReadOnly(true);
126 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_myVersion.version, m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
128 if (!AfxBeginThread(CheckThreadEntry, this))
130 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
133 AddAnchor(IDC_YOURVERSION, TOP_LEFT, TOP_RIGHT);
134 AddAnchor(IDC_CURRENTVERSION, TOP_LEFT, TOP_RIGHT);
135 AddAnchor(IDC_CHECKRESULT, TOP_LEFT, TOP_RIGHT);
136 AddAnchor(IDC_LINK, TOP_LEFT, TOP_RIGHT);
137 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
138 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
139 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
140 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
141 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
142 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
143 AddAnchor(IDOK, BOTTOM_CENTER);
145 SetTimer(100, 1000, nullptr);
146 return TRUE;
149 void CCheckForUpdatesDlg::OnDestroy()
151 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
152 delete reinterpret_cast<CUpdateListCtrl::Entry*>(m_ctrlFiles.GetItemData(i));
154 delete m_updateDownloader;
156 CResizableStandAloneDialog::OnDestroy();
159 void CCheckForUpdatesDlg::OnOK()
161 if (m_bThreadRunning || m_pDownloadThread)
162 return; // Don't exit while downloading
164 CResizableStandAloneDialog::OnOK();
167 void CCheckForUpdatesDlg::OnCancel()
169 if (m_bThreadRunning || m_pDownloadThread)
170 return; // Don't exit while downloading
171 CResizableStandAloneDialog::OnCancel();
174 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
176 return static_cast<CCheckForUpdatesDlg*>(pVoid)->CheckThread();
179 UINT CCheckForUpdatesDlg::CheckThread()
181 m_bThreadRunning = TRUE;
183 CString temp;
184 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
186 bool official = false;
188 CRegString checkurluser = CRegString(L"Software\\TortoiseGit\\UpdateCheckURL", L"");
189 CRegString checkurlmachine = CRegString(L"Software\\TortoiseGit\\UpdateCheckURL", L"", FALSE, HKEY_LOCAL_MACHINE);
190 CString sCheckURL = checkurluser;
191 if (sCheckURL.IsEmpty())
193 sCheckURL = checkurlmachine;
194 if (sCheckURL.IsEmpty())
196 official = true;
197 bool checkPreview = false;
198 #if PREVIEW
199 checkPreview = true;
200 #else
201 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
202 if (DWORD(regCheckPreview))
203 checkPreview = true;
204 #endif
205 if (checkPreview)
207 sCheckURL = L"https://versioncheck.tortoisegit.org/version-preview.txt";
208 SetDlgItemText(IDC_SOURCE, L"Using preview release channel");
210 else
211 sCheckURL = L"https://versioncheck.tortoisegit.org/version.txt";
215 if (!official && CStringUtils::StartsWith(sCheckURL, L"https://versioncheck.tortoisegit.org/"))
216 official = true;
218 if (!official)
219 SetDlgItemText(IDC_SOURCE, L"Using (unofficial) release channel: " + sCheckURL);
221 CString errorText;
222 CVersioncheckParser versioncheck;
223 CVersioncheckParser::Version version;
224 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
225 if (!ret && official)
227 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
228 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
229 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
231 CString error = L"Could not verify digital signature.";
232 if (ret)
233 error += L"\r\nError: " + GetWinINetError(ret) + L" (on " + sCheckURL + SIGNATURE_FILE_ENDING + L")";
234 SetDlgItemText(IDC_CHECKRESULT, error);
235 DeleteUrlCacheEntry(sCheckURL);
236 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
237 goto finish;
240 else if (ret)
242 DeleteUrlCacheEntry(sCheckURL);
243 if (CRegDWORD(L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline", 0))
244 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
245 else
246 errorText.FormatMessage(IDS_CHECKNEWER_NETERROR_FORMAT, static_cast<LPCWSTR>(GetWinINetError(ret) + L" URL: " + sCheckURL), ret);
247 SetDlgItemText(IDC_CHECKRESULT, errorText);
248 goto finish;
251 if (!versioncheck.Load(tempfile, errorText))
253 if (!errorText.IsEmpty())
254 SetDlgItemText(IDC_CHECKRESULT, errorText);
255 else
257 temp.LoadString(IDS_CHECKNEWER_NETERROR);
258 SetDlgItemText(IDC_CHECKRESULT, temp);
260 DeleteUrlCacheEntry(sCheckURL);
261 goto finish;
264 version = versioncheck.GetTortoiseGitVersion();
266 BOOL bNewer = FALSE;
267 if (m_bForce)
268 bNewer = TRUE;
269 else if (version.major > m_myVersion.major)
270 bNewer = TRUE;
271 else if ((version.minor > m_myVersion.minor) && (version.major == m_myVersion.major))
272 bNewer = TRUE;
273 else if ((version.micro > m_myVersion.micro) && (version.minor == m_myVersion.minor) && (version.major == m_myVersion.major))
274 bNewer = TRUE;
275 else if ((version.build > m_myVersion.build) && (version.micro == m_myVersion.micro) && (version.minor == m_myVersion.minor) && (version.major == m_myVersion.major))
276 bNewer = TRUE;
278 m_sNewVersionNumber.Format(L"%u.%u.%u.%u", version.major, version.minor, version.micro, version.build);
279 m_sNewVersionNumberLanguagepacks = version.version_languagepacks;
280 if (m_sNewVersionNumber != version.version_for_filename)
282 CString versionstr = m_sNewVersionNumber + L" (" + version.version_for_filename + L")";
283 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, static_cast<LPCWSTR>(versionstr));
285 else
286 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, static_cast<LPCWSTR>(m_sNewVersionNumber));
287 SetDlgItemText(IDC_CURRENTVERSION, temp);
289 if (bNewer)
291 temp = versioncheck.GetTortoiseGitInfoText();
292 if (!temp.IsEmpty())
294 CString tempLink = versioncheck.GetTortoiseGitInfoTextURL();
295 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
296 m_sUpdateDownloadLink = tempLink;
298 else
299 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
300 SetDlgItemText(IDC_CHECKRESULT, temp);
302 FillChangelog(versioncheck, official);
304 if (versioncheck.GetTortoiseGitIsDirectDownloadable())
305 FillDownloads(versioncheck);
307 AdjustSize(versioncheck.GetTortoiseGitHasChangelog(), versioncheck.GetTortoiseGitIsDirectDownloadable());
308 m_bShowInfo = true;
310 else if (m_bShowInfo)
312 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
313 SetDlgItemText(IDC_CHECKRESULT, temp);
314 FillChangelog(versioncheck, official);
316 // Show changelog controls
317 AdjustSize(versioncheck.GetTortoiseGitHasChangelog(), false);
321 finish:
322 if (!m_sUpdateDownloadLink.IsEmpty())
324 m_link.ShowWindow(SW_SHOW);
325 m_link.SetURL(m_sUpdateDownloadLink);
327 m_bThreadRunning = FALSE;
328 DialogEnableWindow(IDOK, TRUE);
329 return 0;
332 void CCheckForUpdatesDlg::FillDownloads(CVersioncheckParser& versioncheck)
334 m_sFilesURL = versioncheck.GetTortoiseGitBaseURL();
336 bool isHotfix = versioncheck.GetTortoiseGitIsHotfix();
337 if (isHotfix)
338 m_ctrlFiles.InsertItem(0, L"TortoiseGit Hotfix");
339 else
340 m_ctrlFiles.InsertItem(0, L"TortoiseGit");
341 CString filenameMain = versioncheck.GetTortoiseGitMainfilename();
342 m_ctrlFiles.SetItemData(0, reinterpret_cast<DWORD_PTR>(new CUpdateListCtrl::Entry(filenameMain, false, CUpdateListCtrl::STATUS_NONE)));
343 m_ctrlFiles.SetCheck(0 , TRUE);
345 if (isHotfix)
347 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
348 return;
351 struct LangPack
353 CVersioncheckParser::LanguagePack languagepack;
354 bool m_Installed;
356 std::vector<LangPack> availableLangs;
357 std::vector<DWORD> installedLangs;
359 // set up the language selecting combobox
360 CString path = CPathUtils::GetAppParentDirectory();
361 path = path + L"Languages\\";
362 CSimpleFileFind finder(path, L"*.dll");
363 while (finder.FindNextFileNoDirectories())
365 CString file = finder.GetFilePath();
366 CString filename = finder.GetFileName();
367 if (CStringUtils::StartsWithI(filename, L"TortoiseProc"))
369 CString sLoc = filename.Mid(static_cast<int>(wcslen(L"TortoiseProc")));
370 sLoc = sLoc.Left(sLoc.GetLength() - static_cast<int>(wcslen(L".dll"))); // cut off ".dll"
371 if (CStringUtils::StartsWith(sLoc, L"32") && (sLoc.GetLength() > 5))
372 continue;
373 DWORD loc = _wtoi(filename.Mid(static_cast<int>(wcslen(L"TortoiseProc"))));
374 installedLangs.push_back(loc);
379 for (auto& languagepack : versioncheck.GetTortoiseGitLanguagePacks())
381 bool installed = std::find(installedLangs.cbegin(), installedLangs.cend(), languagepack.m_LocaleID) != installedLangs.cend();
382 LangPack pack = { languagepack, installed };
383 availableLangs.push_back(pack);
385 std::stable_sort(availableLangs.begin(), availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
387 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);
389 for (const auto& langs : availableLangs)
391 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.languagepack.m_PackName);
392 m_ctrlFiles.SetItemText(pos, 1, langs.languagepack.m_LangName);
393 m_ctrlFiles.SetItemData(pos, reinterpret_cast<DWORD_PTR>(new CUpdateListCtrl::Entry(langs.languagepack.m_filename, true, CUpdateListCtrl::STATUS_NONE)));
395 if (langs.m_Installed)
396 m_ctrlFiles.SetCheck(pos , TRUE);
398 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
401 void CCheckForUpdatesDlg::FillChangelog(CVersioncheckParser& versioncheck, bool official)
403 ProjectProperties pp;
404 pp.lProjectLanguage = -1;
405 pp.sUrl = versioncheck.GetTortoiseGitIssuesURL();
406 if (!pp.sUrl.IsEmpty())
408 pp.SetCheckRe(L"[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+");
409 pp.SetBugIDRe(L"(\\d+)");
412 if (!versioncheck.GetTortoiseGitHasChangelog())
414 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, reinterpret_cast<WPARAM>(&pp), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(L"No changelog provided.")));
415 return;
418 CString sChangelogURL;
419 sChangelogURL.FormatMessage(versioncheck.GetTortoiseGitChangelogURL(), m_myVersion.major, m_myVersion.minor, m_myVersion.micro, static_cast<LPCWSTR>(m_updateDownloader->m_sWindowsPlatform), static_cast<LPCWSTR>(m_updateDownloader->m_sWindowsVersion), static_cast<LPCWSTR>(m_updateDownloader->m_sWindowsServicePack));
421 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
422 if (DWORD err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false); err != ERROR_SUCCESS)
424 CString msg = L"Could not load changelog.\r\nError: " + GetWinINetError(err) + L" (on " + sChangelogURL + L")";
425 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, reinterpret_cast<WPARAM>(&pp), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(msg)));
426 return;
428 if (official)
430 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
431 if (DWORD err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false); err != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
433 CString error = L"Could not verify digital signature.";
434 if (err)
435 error += L"\r\nError: " + GetWinINetError(err) + L" (on " + sChangelogURL + SIGNATURE_FILE_ENDING + L")";
436 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, reinterpret_cast<WPARAM>(&pp), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(error)));
437 DeleteUrlCacheEntry(sChangelogURL);
438 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
439 return;
443 CString temp;
444 if (CStdioFile file; file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary) || file.GetLength() >= INT_MAX)
446 auto buf = std::make_unique<char[]>(static_cast<UINT>(file.GetLength()));
447 UINT read = file.Read(buf.get(), static_cast<UINT>(file.GetLength()));
448 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
449 CGit::StringAppend(temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
451 else
452 temp = L"Could not open downloaded changelog file.";
453 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, reinterpret_cast<WPARAM>(&pp), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(temp)));
456 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
458 if (nIDEvent == 100)
460 if (m_bThreadRunning == FALSE)
462 KillTimer(100);
463 if (m_bShowInfo)
465 m_bVisible = true;
466 ShowWindow(SW_SHOWNORMAL);
468 else
469 EndDialog(0);
472 CResizableStandAloneDialog::OnTimer(nIDEvent);
475 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
477 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
478 if (!m_bVisible)
479 lpwndpos->flags &= ~SWP_SHOWWINDOW;
482 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
484 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
485 SetCursor(hCur);
486 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
489 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
491 CString title;
492 m_ctrlUpdate.GetWindowText(title);
493 if (!m_pDownloadThread && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
495 bool isOneSelected = false;
496 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
498 if (m_ctrlFiles.GetCheck(i))
500 isOneSelected = true;
501 break;
504 if (!isOneSelected)
505 return;
507 m_eventStop.ResetEvent();
509 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
510 if (m_pDownloadThread)
512 m_pDownloadThread->m_bAutoDelete = FALSE;
513 m_pDownloadThread->ResumeThread();
515 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
517 else
518 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
520 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
522 // Abort
523 m_eventStop.SetEvent();
525 else
527 CString folder = GetDownloadsDirectory();
528 if (m_ctrlUpdate.GetCurrentEntry() == 0)
530 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
532 auto data = reinterpret_cast<CUpdateListCtrl::Entry*>(m_ctrlFiles.GetItemData(i));
533 if (m_ctrlFiles.GetCheck(i) == TRUE)
534 ShellExecute(GetSafeHwnd(), L"open", folder + data->m_filename, nullptr, nullptr, SW_SHOWNORMAL);
536 CResizableStandAloneDialog::OnOK();
538 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
539 ShellExecute(GetSafeHwnd(), L"open", folder, nullptr, nullptr, SW_SHOWNORMAL);
541 m_ctrlUpdate.SetCurrentEntry(0);
545 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
547 return static_cast<CCheckForUpdatesDlg*>(pVoid)->DownloadThread();
550 bool CCheckForUpdatesDlg::VerifyUpdateFile(const CString& filename, const CString& filenameSignature, const CString& reportingFilename, const CString& versionnumber)
552 if (VerifyIntegrity(filename, filenameSignature, m_updateDownloader) != 0)
554 m_sErrors += reportingFilename + SIGNATURE_FILE_ENDING + L": Invalid digital signature.\r\n";
555 return false;
558 MSIHANDLE hSummary;
559 DWORD ret = 0;
560 if ((ret = MsiGetSummaryInformation(NULL, filename, 0, &hSummary)) != 0)
562 CString sFileVer = CPathUtils::GetVersionFromFile(filename).c_str();
563 sFileVer.Trim();
564 if (sFileVer.IsEmpty())
566 m_sErrors.AppendFormat(L"%s: Invalid filetype found (neither executable nor MSI).\r\n", static_cast<LPCWSTR>(reportingFilename));
567 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": MsiGetSummaryInformation reported: %s\n", static_cast<LPCWSTR>(CFormatMessageWrapper(ret)));
568 return false;
570 else if (sFileVer == versionnumber)
571 return true;
573 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"%s\", got: \"%s\").\r\n", static_cast<LPCWSTR>(reportingFilename), static_cast<LPCWSTR>(versionnumber), static_cast<LPCWSTR>(sFileVer));
574 return false;
576 SCOPE_EXIT{ MsiCloseHandle(hSummary); };
578 UINT uiDataType = 0;
579 DWORD cchValue = 4096;
580 CString buffer;
581 int intValue;
582 if (MsiSummaryInfoGetProperty(hSummary, PID_SUBJECT, &uiDataType, &intValue, nullptr, CStrBuf(buffer, cchValue + 1), &cchValue))
584 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (%s).\r\n", static_cast<LPCWSTR>(reportingFilename), static_cast<LPCWSTR>(CFormatMessageWrapper(ret)));
585 return false;
588 if (VT_LPSTR != uiDataType)
590 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (invalid data type returned).\r\n", static_cast<LPCWSTR>(reportingFilename));
591 return false;
594 CString sFileVer = buffer.Right(versionnumber.GetLength() + 1);
595 if (sFileVer != L"v" + versionnumber)
597 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"v%s\", got: \"%s\").\r\n", static_cast<LPCWSTR>(reportingFilename), static_cast<LPCWSTR>(versionnumber), static_cast<LPCWSTR>(sFileVer));
598 return false;
601 return true;
604 bool CCheckForUpdatesDlg::Download(const CString& filename, const CString& versionnumber)
606 CString url = m_sFilesURL + filename;
607 CString destFilename = GetDownloadsDirectory() + filename;
608 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
610 if (VerifyUpdateFile(destFilename, destFilename + SIGNATURE_FILE_ENDING, destFilename, versionnumber))
611 return true;
612 else
614 DeleteFile(destFilename);
615 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
616 DeleteUrlCacheEntry(url);
617 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
621 m_progress.SetRange32(0, 1);
622 m_progress.SetPos(0);
623 m_progress.ShowWindow(SW_SHOW);
624 if (m_pTaskbarList)
626 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
627 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
630 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
631 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
632 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
633 if (!ret)
635 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
636 if (ret)
637 m_sErrors += url + SIGNATURE_FILE_ENDING + L": " + GetWinINetError(ret) + L"\r\n";
638 m_progress.SetPos(m_progress.GetPos() + 1);
639 if (m_pTaskbarList)
641 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
642 int minValue, maxValue;
643 m_progress.GetRange(minValue, maxValue);
644 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
647 else
648 m_sErrors += url + L": " + GetWinINetError(ret) + L"\r\n";
649 if (!ret)
651 if (VerifyUpdateFile(tempfile, signatureTempfile, url, versionnumber))
653 DeleteFile(destFilename);
654 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
655 if (!MoveFile(tempfile, destFilename))
657 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", static_cast<LPCWSTR>(tempfile), static_cast<LPCWSTR>(destFilename));
658 return false;
660 if (!MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING))
662 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", static_cast<LPCWSTR>(signatureTempfile), static_cast<LPCWSTR>(destFilename + SIGNATURE_FILE_ENDING));
663 return false;
665 return true;
667 DeleteUrlCacheEntry(url);
668 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
670 return false;
673 UINT CCheckForUpdatesDlg::DownloadThread()
675 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
676 m_sErrors.Empty();
677 BOOL result = TRUE;
678 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
680 m_ctrlFiles.EnsureVisible(i, FALSE);
681 CRect rect;
682 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
683 auto data = reinterpret_cast<CUpdateListCtrl::Entry*>(m_ctrlFiles.GetItemData(i));
684 if (m_ctrlFiles.GetCheck(i) == TRUE)
686 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
687 m_ctrlFiles.InvalidateRect(rect);
688 if (Download(data->m_filename, data->m_languagepack ? m_sNewVersionNumberLanguagepacks : m_sNewVersionNumber))
689 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
690 else
692 data->m_status = CUpdateListCtrl::STATUS_FAIL;
693 result = FALSE;
696 else
697 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
698 m_ctrlFiles.InvalidateRect(rect);
701 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
703 return result;
706 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
708 ASSERT(m_pDownloadThread);
710 // wait until the thread terminates
711 DWORD dwExitCode;
712 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
713 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
715 // make sure we always have the correct exit code
716 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
718 delete m_pDownloadThread;
719 m_pDownloadThread = nullptr;
721 m_progress.ShowWindow(SW_HIDE);
723 if (dwExitCode == TRUE)
725 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
726 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
727 m_ctrlUpdate.Invalidate();
728 if (m_pTaskbarList)
729 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
731 else
733 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
734 if (m_pTaskbarList)
735 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
736 CString tmp;
737 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
738 if (!m_sErrors.IsEmpty())
739 tmp += L"\r\n\r\nErrors:\r\n" + m_sErrors;
740 CMessageBox::Show(GetSafeHwnd(), tmp, L"TortoiseGit", MB_ICONERROR);
741 if (m_pTaskbarList)
742 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
745 return 0;
748 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM wParam, LPARAM lParam)
750 ASSERT(wParam && lParam);
752 m_cLogMessage.Init(*reinterpret_cast<ProjectProperties*>(wParam));
754 LPCWSTR changelog = reinterpret_cast<LPCWSTR>(lParam);
755 m_cLogMessage.SetText(changelog);
756 m_cLogMessage.Call(SCI_GOTOPOS, 0);
757 m_cLogMessage.Call(SCI_SETWRAPSTARTINDENT, 3);
759 return 0;
762 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
764 if (CComHeapPtr<WCHAR> wcharPtr; SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, nullptr, &wcharPtr)))
766 CString folder { static_cast<LPCWSTR>(wcharPtr) };
767 return folder.TrimRight(L'\\') + L'\\';
770 return {};
773 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
775 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
776 if (pDownloadStatus)
778 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
780 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
781 m_progress.SetPos(pDownloadStatus->ulProgress);
782 if (m_pTaskbarList)
784 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
785 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
789 return 0;
792 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
794 m_pTaskbarList.Release();
795 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
796 return 0;
799 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
801 CString readableError { static_cast<LPCWSTR>(CFormatMessageWrapper(err)) };
802 if (readableError.IsEmpty())
804 for (const CString& module : { L"wininet.dll", L"urlmon.dll" })
806 LPWSTR buffer;
807 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr);
808 readableError = buffer;
809 LocalFree(buffer);
810 if (!readableError.IsEmpty())
811 break;
814 return readableError.Trim();
817 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
819 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
821 CRegDWORD(L"Software\\TortoiseGit\\VersionCheck") = FALSE;
822 OnOK();
826 void CCheckForUpdatesDlg::AdjustSize(bool changelog, bool downloads, bool handleAnchors)
828 if (handleAnchors)
830 RemoveAnchor(IDC_GROUP_CHANGELOG);
831 RemoveAnchor(IDC_LOGMESSAGE);
832 RemoveAnchor(IDC_GROUP_DOWNLOADS);
833 RemoveAnchor(IDC_LIST_DOWNLOADS);
834 RemoveAnchor(IDC_PROGRESSBAR);
835 RemoveAnchor(IDC_DONOTASKAGAIN);
836 RemoveAnchor(IDC_BUTTON_UPDATE);
837 RemoveAnchor(IDOK);
840 m_ctrlFiles.ShowWindow(SW_HIDE);
841 GetDlgItem(IDC_GROUP_CHANGELOG)->ShowWindow(SW_HIDE);
842 GetDlgItem(IDC_LOGMESSAGE)->ShowWindow(SW_HIDE);
843 GetDlgItem(IDC_BUTTON_UPDATE)->ShowWindow(SW_HIDE);
844 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
845 RECT rectWindow, rectOKButton, rectProgress, rectGroupDownloads, rectChangelog;
846 m_progress.GetWindowRect(&rectProgress);
847 GetDlgItem(IDC_GROUP_CHANGELOG)->GetWindowRect(&rectChangelog);
848 GetWindowRect(&rectWindow);
849 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
850 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
851 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
852 if (!changelog && !downloads)
853 OffsetRect(&rectOKButton, 0, rectChangelog.top - rectOKButton.top);
854 else if (changelog && !downloads)
855 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
856 else
857 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
858 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
859 if (changelog || downloads) // download and no changelog currently not supported
861 GetDlgItem(IDC_LOGMESSAGE)->ShowWindow(SW_SHOW);
862 GetDlgItem(IDC_GROUP_CHANGELOG)->ShowWindow(SW_SHOW);
864 if (downloads)
866 m_ctrlFiles.ShowWindow(SW_SHOW);
867 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
868 GetDlgItem(IDC_BUTTON_UPDATE)->ShowWindow(SW_SHOW);
870 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
871 MoveWindow(&rectWindow);
872 ::MapWindowPoints(nullptr, GetSafeHwnd(), reinterpret_cast<LPPOINT>(&rectOKButton), 2);
873 if (downloads)
875 if (CRegDWORD(L"Software\\TortoiseGit\\VersionCheck", TRUE) != FALSE && !m_bForce && !m_bShowInfo)
877 RECT rectDoNotAskAgainButton;
878 GetDlgItem(IDC_DONOTASKAGAIN)->GetWindowRect(&rectDoNotAskAgainButton);
879 ::MapWindowPoints(nullptr, GetSafeHwnd(), reinterpret_cast<LPPOINT>(&rectDoNotAskAgainButton), 2);
880 rectDoNotAskAgainButton.top = rectOKButton.top;
881 rectDoNotAskAgainButton.bottom = rectOKButton.bottom;
882 GetDlgItem(IDC_DONOTASKAGAIN)->MoveWindow(&rectDoNotAskAgainButton);
883 GetDlgItem(IDC_DONOTASKAGAIN)->EnableWindow(TRUE);
884 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
885 rectOKButton.left += CDPIAware::Instance().ScaleX(GetSafeHwnd(), 60);
886 CString temp;
887 temp.LoadString(IDS_REMINDMELATER);
888 GetDlgItem(IDOK)->SetWindowText(temp);
889 rectOKButton.right += CDPIAware::Instance().ScaleX(GetSafeHwnd(), 160);
892 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
894 if (handleAnchors)
896 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
897 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
898 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
899 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
900 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
901 AddAnchor(IDC_DONOTASKAGAIN, BOTTOM_CENTER);
902 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
903 AddAnchor(IDOK, BOTTOM_CENTER);
905 CenterWindow();