Allow to clear filters with escape
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
blob0bed450e61da5446baec62f75fd555dfbed18a21
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2016 - 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 _T(".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)
57 m_sUpdateDownloadLink = _T("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_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
80 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
81 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
82 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
83 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
84 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
85 END_MESSAGE_MAP()
87 BOOL CCheckForUpdatesDlg::OnInitDialog()
89 CResizableStandAloneDialog::OnInitDialog();
90 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
92 CString temp;
93 temp.Format(IDS_CHECKNEWER_YOURVERSION, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD);
94 SetDlgItemText(IDC_YOURVERSION, temp);
96 DialogEnableWindow(IDOK, FALSE);
98 m_pTaskbarList.Release();
99 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
100 m_pTaskbarList = nullptr;
102 // hide download controls
103 m_ctrlFiles.ShowWindow(SW_HIDE);
104 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
105 RECT rectWindow, rectGroupDownloads, rectOKButton;
106 GetWindowRect(&rectWindow);
107 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
108 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
109 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
110 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
111 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
112 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
113 MoveWindow(&rectWindow);
114 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
115 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
117 temp.LoadString(IDS_STATUSLIST_COLFILE);
118 m_ctrlFiles.InsertColumn(0, temp, 0, -1);
119 m_ctrlFiles.InsertColumn(1, temp, 0, -1);
120 m_ctrlFiles.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_CHECKBOXES);
121 m_ctrlFiles.SetColumnWidth(0, 350);
122 m_ctrlFiles.SetColumnWidth(1, 200);
124 m_cLogMessage.Init(-1);
125 m_cLogMessage.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
126 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
128 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
130 if (!AfxBeginThread(CheckThreadEntry, this))
132 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
135 AddAnchor(IDC_YOURVERSION, TOP_LEFT, TOP_RIGHT);
136 AddAnchor(IDC_CURRENTVERSION, TOP_LEFT, TOP_RIGHT);
137 AddAnchor(IDC_CHECKRESULT, TOP_LEFT, TOP_RIGHT);
138 AddAnchor(IDC_LINK, TOP_LEFT, TOP_RIGHT);
139 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
140 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
141 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
142 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
143 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
144 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
145 AddAnchor(IDOK, BOTTOM_CENTER);
147 SetTimer(100, 1000, nullptr);
148 return TRUE;
151 void CCheckForUpdatesDlg::OnDestroy()
153 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
154 delete (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
156 delete m_updateDownloader;
158 CResizableStandAloneDialog::OnDestroy();
161 void CCheckForUpdatesDlg::OnOK()
163 if (m_bThreadRunning || m_pDownloadThread)
164 return; // Don't exit while downloading
166 CResizableStandAloneDialog::OnOK();
169 void CCheckForUpdatesDlg::OnCancel()
171 if (m_bThreadRunning || m_pDownloadThread)
172 return; // Don't exit while downloading
173 CResizableStandAloneDialog::OnCancel();
176 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
178 return ((CCheckForUpdatesDlg*)pVoid)->CheckThread();
181 UINT CCheckForUpdatesDlg::CheckThread()
183 m_bThreadRunning = TRUE;
185 CString temp;
186 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
188 bool official = false;
190 CRegString checkurluser = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""));
191 CRegString checkurlmachine = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""), FALSE, HKEY_LOCAL_MACHINE);
192 CString sCheckURL = checkurluser;
193 if (sCheckURL.IsEmpty())
195 sCheckURL = checkurlmachine;
196 if (sCheckURL.IsEmpty())
198 official = true;
199 bool checkPreview = false;
200 #if PREVIEW
201 checkPreview = true;
202 #else
203 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
204 if (DWORD(regCheckPreview))
205 checkPreview = true;
206 #endif
207 if (checkPreview)
209 sCheckURL = _T("https://versioncheck.tortoisegit.org/version-preview.txt");
210 SetDlgItemText(IDC_SOURCE, _T("Using preview release channel"));
212 else
213 sCheckURL = _T("https://versioncheck.tortoisegit.org/version.txt");
217 if (!official && sCheckURL.Find(_T("://versioncheck.tortoisegit.org/")) > 0)
218 official = true;
220 if (!official)
221 SetDlgItemText(IDC_SOURCE, _T("Using (unofficial) release channel: ") + sCheckURL);
223 CString ver;
224 CAutoConfig versioncheck(true);
225 CString errorText;
226 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
227 if (!ret && official)
229 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
230 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
231 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
233 CString error = _T("Could not verify digital signature.");
234 if (ret)
235 error += _T("\r\nError: ") + GetWinINetError(ret) + _T(" (on ") + sCheckURL + SIGNATURE_FILE_ENDING + _T(")");
236 SetDlgItemText(IDC_CHECKRESULT, error);
237 DeleteUrlCacheEntry(sCheckURL);
238 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
239 goto finish;
242 else if (ret)
244 DeleteUrlCacheEntry(sCheckURL);
245 if (CRegDWORD(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline"), 0))
246 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
247 else
248 errorText.Format(IDS_CHECKNEWER_NETERROR_FORMAT, (LPCTSTR)(GetWinINetError(ret) + _T(" URL: ") + sCheckURL), ret);
249 SetDlgItemText(IDC_CHECKRESULT, errorText);
250 goto finish;
253 git_config_add_file_ondisk(versioncheck, CUnicodeUtils::GetUTF8(tempfile), GIT_CONFIG_LEVEL_GLOBAL, 0);
255 unsigned int major, minor, micro, build;
256 major = minor = micro = build = 0;
257 unsigned __int64 version = 0;
259 if (!versioncheck.GetString(_T("tortoisegit.version"), ver))
261 CString vertemp = ver;
262 major = _ttoi(vertemp);
263 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
264 minor = _ttoi(vertemp);
265 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
266 micro = _ttoi(vertemp);
267 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
268 build = _ttoi(vertemp);
269 version = major;
270 version <<= 16;
271 version += minor;
272 version <<= 16;
273 version += micro;
274 version <<= 16;
275 version += build;
277 if (version == 0)
279 temp.LoadString(IDS_CHECKNEWER_NETERROR);
280 SetDlgItemText(IDC_CHECKRESULT, temp);
281 goto finish;
284 // another versionstring for the filename can be provided
285 // this is needed for preview releases
286 vertemp.Empty();
287 versioncheck.GetString(_T("tortoisegit.versionstring"), vertemp);
288 if (!vertemp.IsEmpty())
289 ver = vertemp;
291 else
293 errorText = _T("Could not parse version check file: ") + g_Git.GetLibGit2LastErr();
294 SetDlgItemText(IDC_CHECKRESULT, errorText);
295 DeleteUrlCacheEntry(sCheckURL);
296 goto finish;
300 BOOL bNewer = FALSE;
301 if (m_bForce)
302 bNewer = TRUE;
303 else if (major > TGIT_VERMAJOR)
304 bNewer = TRUE;
305 else if ((minor > TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
306 bNewer = TRUE;
307 else if ((micro > TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
308 bNewer = TRUE;
309 else if ((build > TGIT_VERBUILD) && (micro == TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
310 bNewer = TRUE;
312 m_sNewVersionNumber.Format(L"%u.%u.%u.%u", major, minor, micro, build);
313 if (m_sNewVersionNumber != ver)
315 CString versionstr = m_sNewVersionNumber + L" (" + ver + ")";
316 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)versionstr);
318 else
319 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)m_sNewVersionNumber);
320 SetDlgItemText(IDC_CURRENTVERSION, temp);
322 if (bNewer)
324 versioncheck.GetString(_T("tortoisegit.infotext"), temp);
325 if (!temp.IsEmpty())
327 CString tempLink;
328 versioncheck.GetString(_T("tortoisegit.infotexturl"), tempLink);
329 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
330 m_sUpdateDownloadLink = tempLink;
332 else
333 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
334 SetDlgItemText(IDC_CHECKRESULT, temp);
336 FillChangelog(versioncheck, official);
337 FillDownloads(versioncheck, ver);
339 RemoveAnchor(IDC_GROUP_CHANGELOG);
340 RemoveAnchor(IDC_LOGMESSAGE);
341 RemoveAnchor(IDC_GROUP_DOWNLOADS);
342 RemoveAnchor(IDC_LIST_DOWNLOADS);
343 RemoveAnchor(IDC_PROGRESSBAR);
344 RemoveAnchor(IDC_BUTTON_UPDATE);
345 RemoveAnchor(IDOK);
347 // Show download controls
348 RECT rectWindow, rectProgress, rectGroupDownloads, rectOKButton;
349 GetWindowRect(&rectWindow);
350 m_progress.GetWindowRect(&rectProgress);
351 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
352 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
353 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
354 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
355 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
356 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
357 MoveWindow(&rectWindow);
358 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
359 if (CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck"), TRUE) != FALSE && !m_bForce && !m_bShowInfo)
361 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
362 rectOKButton.left += 60;
363 temp.LoadString(IDS_REMINDMELATER);
364 GetDlgItem(IDOK)->SetWindowText(temp);
365 rectOKButton.right += 160;
367 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
368 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
369 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
370 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
371 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
372 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
373 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
374 AddAnchor(IDOK, BOTTOM_CENTER);
375 m_ctrlFiles.ShowWindow(SW_SHOW);
376 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
377 CenterWindow();
378 m_bShowInfo = TRUE;
380 else if (m_bShowInfo)
382 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
383 SetDlgItemText(IDC_CHECKRESULT, temp);
384 FillChangelog(versioncheck, official);
388 finish:
389 if (!m_sUpdateDownloadLink.IsEmpty())
391 m_link.ShowWindow(SW_SHOW);
392 m_link.SetURL(m_sUpdateDownloadLink);
394 m_bThreadRunning = FALSE;
395 DialogEnableWindow(IDOK, TRUE);
396 return 0;
399 void CCheckForUpdatesDlg::FillDownloads(CAutoConfig& versioncheck, const CString version)
401 #if WIN64
402 const CString x86x64 = _T("64");
403 #else
404 const CString x86x64 = _T("32");
405 #endif
407 versioncheck.GetString(_T("tortoisegit.baseurl"), m_sFilesURL);
408 if (m_sFilesURL.IsEmpty())
409 m_sFilesURL.Format(_T("http://updater.download.tortoisegit.org/tgit/%s/"), (LPCTSTR)version);
411 bool isHotfix = false;
412 versioncheck.GetBool(_T("tortoisegit.hotfix"), isHotfix);
414 if (isHotfix)
415 m_ctrlFiles.InsertItem(0, _T("TortoiseGit Hotfix"));
416 else
417 m_ctrlFiles.InsertItem(0, _T("TortoiseGit"));
418 CString filenameMain, filenamePattern;
419 versioncheck.GetString(_T("tortoisegit.mainfilename"), filenamePattern);
420 if (filenamePattern.IsEmpty())
421 filenamePattern = _T("TortoiseGit-%1!s!-%2!s!bit.msi");
422 filenameMain.FormatMessage(filenamePattern, version, x86x64);
423 m_ctrlFiles.SetItemData(0, (DWORD_PTR)(new CUpdateListCtrl::Entry(filenameMain, CUpdateListCtrl::STATUS_NONE)));
424 m_ctrlFiles.SetCheck(0 , TRUE);
426 if (isHotfix)
428 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
429 return;
432 struct LangPack
434 CString m_PackName;
435 CString m_LangName;
436 DWORD m_LocaleID;
437 CString m_LangCode;
438 bool m_Installed;
440 struct LanguagePacks
442 std::vector<LangPack> availableLangs;
443 std::vector<DWORD> installedLangs;
444 } languagePacks;
446 // set up the language selecting combobox
447 CString path = CPathUtils::GetAppParentDirectory();
448 path = path + _T("Languages\\");
449 CSimpleFileFind finder(path, _T("*.dll"));
450 while (finder.FindNextFileNoDirectories())
452 CString file = finder.GetFilePath();
453 CString filename = finder.GetFileName();
454 if (filename.Left(12).CompareNoCase(_T("TortoiseProc")) == 0)
456 CString sVer = _T(STRPRODUCTVER);
457 sVer = sVer.Left(sVer.ReverseFind('.'));
458 CString sFileVer = CPathUtils::GetVersionFromFile(file);
459 sFileVer = sFileVer.Left(sFileVer.ReverseFind('.'));
460 CString sLoc = filename.Mid(12);
461 sLoc = sLoc.Left(sLoc.GetLength() - 4); // cut off ".dll"
462 if ((sLoc.Left(2) == L"32") && (sLoc.GetLength() > 5))
463 continue;
464 DWORD loc = _tstoi(filename.Mid(12));
465 languagePacks.installedLangs.push_back(loc);
470 git_config_get_multivar_foreach(versioncheck, "tortoisegit.langs", nullptr, [](const git_config_entry* configentry, void* payload) -> int
472 LanguagePacks* languagePacks = (LanguagePacks*)payload;
473 CString langs = CUnicodeUtils::GetUnicode(configentry->value);
475 int nextTokenPos = langs.Find(_T(";"), 5); // be extensible for the future
476 if (nextTokenPos > 0)
477 langs = langs.Left(nextTokenPos);
478 CString sLang = _T("TortoiseGit Language Pack ") + langs.Mid(5);
480 DWORD loc = _tstoi(langs.Mid(0, 4));
481 TCHAR buf[MAX_PATH] = { 0 };
482 GetLocaleInfo(loc, LOCALE_SNATIVELANGNAME, buf, _countof(buf));
483 CString sLang2(buf);
484 GetLocaleInfo(loc, LOCALE_SNATIVECTRYNAME, buf, _countof(buf));
485 if (buf[0])
487 sLang2 += _T(" (");
488 sLang2 += buf;
489 sLang2 += _T(")");
492 bool installed = std::find(languagePacks->installedLangs.cbegin(), languagePacks->installedLangs.cend(), loc) != languagePacks->installedLangs.cend();
493 LangPack pack = { sLang, sLang2, loc, langs.Mid(5), installed };
494 languagePacks->availableLangs.push_back(pack);
496 return 0;
497 }, &languagePacks);
498 std::stable_sort(languagePacks.availableLangs.begin(), languagePacks.availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
500 return (a.m_Installed && !b.m_Installed) ? 1 : (!a.m_Installed && b.m_Installed) ? 0 : (a.m_PackName.Compare(b.m_PackName) < 0);
502 filenamePattern.Empty();
503 versioncheck.GetString(_T("tortoisegit.languagepackfilename"), filenamePattern);
504 if (filenamePattern.IsEmpty())
505 filenamePattern = _T("TortoiseGit-LanguagePack-%1!s!-%2!s!bit-%3!s!.msi");
506 for (const auto& langs : languagePacks.availableLangs)
508 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.m_PackName);
509 m_ctrlFiles.SetItemText(pos, 1, langs.m_LangName);
511 CString filename;
512 filename.FormatMessage(filenamePattern, version, x86x64, langs.m_LangCode, langs.m_LocaleID);
513 m_ctrlFiles.SetItemData(pos, (DWORD_PTR)(new CUpdateListCtrl::Entry(filename, CUpdateListCtrl::STATUS_NONE)));
515 if (langs.m_Installed)
516 m_ctrlFiles.SetCheck(pos , TRUE);
518 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
521 void CCheckForUpdatesDlg::FillChangelog(CAutoConfig& versioncheck, bool official)
523 ProjectProperties pp;
524 pp.lProjectLanguage = -1;
525 if (versioncheck.GetString(_T("tortoisegit.issuesurl"), pp.sUrl))
526 pp.sUrl = _T("https://tortoisegit.org/issue/%BUGID%");
527 if (!pp.sUrl.IsEmpty())
529 pp.SetCheckRe(_T("[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+"));
530 pp.SetBugIDRe(_T("(\\d+)"));
532 m_cLogMessage.Init(pp);
534 CString sChangelogURL;
535 versioncheck.GetString(_T("TortoiseGit.changelogurl"), sChangelogURL);
536 if (sChangelogURL.IsEmpty())
537 sChangelogURL = _T("https://versioncheck.tortoisegit.org/changelog.txt");
538 else
540 CString tmp(sChangelogURL);
541 sChangelogURL.FormatMessage(tmp, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, m_updateDownloader->m_sWindowsPlatform, m_updateDownloader->m_sWindowsVersion, m_updateDownloader->m_sWindowsServicePack);
544 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
545 DWORD err;
546 if ((err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false)) != ERROR_SUCCESS)
548 CString msg = _T("Could not load changelog.\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + _T(")");
549 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)msg));
550 return;
552 if (official)
554 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
555 if ((err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false)) != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
557 CString error = _T("Could not verify digital signature.");
558 if (err)
559 error += _T("\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + SIGNATURE_FILE_ENDING + _T(")");
560 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)error));
561 DeleteUrlCacheEntry(sChangelogURL);
562 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
563 return;
567 CString temp;
568 CStdioFile file;
569 if (file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary))
571 auto buf = std::make_unique<BYTE[]>((UINT)file.GetLength());
572 UINT read = file.Read(buf.get(), (UINT)file.GetLength());
573 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
574 CGit::StringAppend(&temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
576 else
577 temp = _T("Could not open downloaded changelog file.");
578 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)temp));
581 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
583 if (nIDEvent == 100)
585 if (m_bThreadRunning == FALSE)
587 KillTimer(100);
588 if (m_bShowInfo)
590 m_bVisible = TRUE;
591 ShowWindow(SW_SHOWNORMAL);
593 else
594 EndDialog(0);
597 CResizableStandAloneDialog::OnTimer(nIDEvent);
600 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
602 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
603 if (m_bVisible == FALSE)
604 lpwndpos->flags &= ~SWP_SHOWWINDOW;
607 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
609 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
610 SetCursor(hCur);
611 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
614 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
616 CString title;
617 m_ctrlUpdate.GetWindowText(title);
618 if (!m_pDownloadThread && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
620 bool isOneSelected = false;
621 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
623 if (m_ctrlFiles.GetCheck(i))
625 isOneSelected = true;
626 break;
629 if (!isOneSelected)
630 return;
632 m_eventStop.ResetEvent();
634 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
635 if (m_pDownloadThread)
637 m_pDownloadThread->m_bAutoDelete = FALSE;
638 m_pDownloadThread->ResumeThread();
640 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
642 else
643 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
645 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
647 // Abort
648 m_eventStop.SetEvent();
650 else
652 CString folder = GetDownloadsDirectory();
653 if (m_ctrlUpdate.GetCurrentEntry() == 0)
655 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
657 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
658 if (m_ctrlFiles.GetCheck(i) == TRUE)
659 ShellExecute(GetSafeHwnd(), _T("open"), folder + data->m_filename, nullptr, nullptr, SW_SHOWNORMAL);
661 CResizableStandAloneDialog::OnOK();
663 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
664 ShellExecute(GetSafeHwnd(), _T("open"), folder, nullptr, nullptr, SW_SHOWNORMAL);
666 m_ctrlUpdate.SetCurrentEntry(0);
670 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
672 return ((CCheckForUpdatesDlg*)pVoid)->DownloadThread();
675 bool CCheckForUpdatesDlg::VerifyUpdateFile(const CString& filename, const CString& filenameSignature, const CString& reportingFilename)
677 if (VerifyIntegrity(filename, filenameSignature, m_updateDownloader) != 0)
679 m_sErrors += reportingFilename + SIGNATURE_FILE_ENDING + L": Invalid digital signature.\r\n";
680 return false;
683 MSIHANDLE hSummary;
684 DWORD ret = 0;
685 if ((ret = MsiGetSummaryInformation(NULL, filename, 0, &hSummary)) != 0)
687 CString sFileVer = CPathUtils::GetVersionFromFile(filename);
688 sFileVer.Trim();
689 if (sFileVer.IsEmpty())
691 m_sErrors.AppendFormat(L"%s: Invalid filetype found (neither executable nor MSI).\r\n", (LPCTSTR)reportingFilename);
692 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": MsiGetSummaryInformation reported: %s\n"), (LPCTSTR)CFormatMessageWrapper(ret));
693 return false;
695 else if (sFileVer == m_sNewVersionNumber)
696 return true;
698 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);
699 return false;
701 SCOPE_EXIT{ MsiCloseHandle(hSummary); };
703 UINT uiDataType = 0;
704 DWORD cchValue = 4096;
705 CString buffer;
706 if (MsiSummaryInfoGetProperty(hSummary, PID_SUBJECT, &uiDataType, nullptr, nullptr, CStrBuf(buffer, cchValue + 1), &cchValue))
708 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (%s).\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)CFormatMessageWrapper(ret));
709 return false;
712 if (VT_LPSTR != uiDataType)
714 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (invalid data type returned).\r\n", (LPCTSTR)reportingFilename);
715 return false;
718 CString sFileVer = buffer.Right(m_sNewVersionNumber.GetLength() + 1);
719 if (sFileVer != L"v" + m_sNewVersionNumber)
721 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);
722 return false;
725 return true;
728 bool CCheckForUpdatesDlg::Download(CString filename)
730 CString url = m_sFilesURL + filename;
731 CString destFilename = GetDownloadsDirectory() + filename;
732 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
734 if (VerifyUpdateFile(destFilename, destFilename + SIGNATURE_FILE_ENDING, destFilename))
735 return true;
736 else
738 DeleteFile(destFilename);
739 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
740 DeleteUrlCacheEntry(url);
741 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
745 m_progress.SetRange32(0, 1);
746 m_progress.SetPos(0);
747 m_progress.ShowWindow(SW_SHOW);
748 if (m_pTaskbarList)
750 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
751 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
754 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
755 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
756 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
757 if (!ret)
759 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
760 if (ret)
761 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": ") + GetWinINetError(ret) + _T("\r\n");
762 m_progress.SetPos(m_progress.GetPos() + 1);
763 if (m_pTaskbarList)
765 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
766 int minValue, maxValue;
767 m_progress.GetRange(minValue, maxValue);
768 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
771 else
772 m_sErrors += url + _T(": ") + GetWinINetError(ret) + _T("\r\n");
773 if (!ret)
775 if (VerifyUpdateFile(tempfile, signatureTempfile, url))
777 DeleteFile(destFilename);
778 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
779 if (!MoveFile(tempfile, destFilename))
781 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)filename, (LPCTSTR)tempfile, (LPCTSTR)destFilename);
782 return false;
784 if (!MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING))
786 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)filename, (LPCTSTR)tempfile, (LPCTSTR)destFilename);
787 return false;
789 return true;
791 DeleteUrlCacheEntry(url);
792 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
794 return false;
797 UINT CCheckForUpdatesDlg::DownloadThread()
799 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
800 m_sErrors.Empty();
801 BOOL result = TRUE;
802 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
804 m_ctrlFiles.EnsureVisible(i, FALSE);
805 CRect rect;
806 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
807 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
808 if (m_ctrlFiles.GetCheck(i) == TRUE)
810 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
811 m_ctrlFiles.InvalidateRect(rect);
812 if (Download(data->m_filename))
813 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
814 else
816 data->m_status = CUpdateListCtrl::STATUS_FAIL;
817 result = FALSE;
820 else
821 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
822 m_ctrlFiles.InvalidateRect(rect);
825 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
827 return result;
830 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
832 ASSERT(m_pDownloadThread);
834 // wait until the thread terminates
835 DWORD dwExitCode;
836 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
837 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
839 // make sure we always have the correct exit code
840 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
842 delete m_pDownloadThread;
843 m_pDownloadThread = nullptr;
845 m_progress.ShowWindow(SW_HIDE);
847 if (dwExitCode == TRUE)
849 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
850 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
851 m_ctrlUpdate.Invalidate();
852 if (m_pTaskbarList)
853 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
855 else
857 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
858 if (m_pTaskbarList)
859 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
860 CString tmp;
861 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
862 if (!m_sErrors.IsEmpty())
863 tmp += _T("\r\n\r\nErrors:\r\n") + m_sErrors;
864 CMessageBox::Show(GetSafeHwnd(), tmp, L"TortoiseGit", MB_ICONERROR);
865 if (m_pTaskbarList)
866 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
869 return 0;
872 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM, LPARAM lParam)
874 ASSERT(lParam);
876 LPCTSTR changelog = reinterpret_cast<LPCTSTR>(lParam);
877 m_cLogMessage.Call(SCI_SETREADONLY, FALSE);
878 m_cLogMessage.SetText(changelog);
879 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
880 m_cLogMessage.Call(SCI_GOTOPOS, 0);
882 return 0;
885 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
887 CString folder;
889 PWSTR wcharPtr = nullptr;
890 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, nullptr, &wcharPtr)))
892 folder = wcharPtr;
893 CoTaskMemFree(wcharPtr);
894 return folder.TrimRight(_T("\\")) + _T("\\");
897 return folder;
900 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
902 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
903 if (pDownloadStatus)
905 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
907 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
908 m_progress.SetPos(pDownloadStatus->ulProgress);
909 if (m_pTaskbarList)
911 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
912 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
916 return 0;
919 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
921 m_pTaskbarList.Release();
922 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
923 return 0;
926 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
928 CString readableError = CFormatMessageWrapper(err);
929 if (readableError.IsEmpty())
931 for (const CString& module : { _T("wininet.dll"), _T("urlmon.dll") })
933 LPTSTR buffer;
934 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, (LPTSTR)&buffer, 0, nullptr);
935 readableError = buffer;
936 LocalFree(buffer);
937 if (!readableError.IsEmpty())
938 break;
941 return readableError.Trim();
944 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
946 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
948 CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck")) = FALSE;
949 OnOK();