Fixed issue #2271: "Check For Updates" dialog should be resizable
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
blob103205f3ff6fc41abd58d11ece62bbf393c17977
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 "Win7.h"
38 #define SIGNATURE_FILE_ENDING _T(".rsa.asc")
40 #define WM_USER_DISPLAYSTATUS (WM_USER + 1)
41 #define WM_USER_ENDDOWNLOAD (WM_USER + 2)
42 #define WM_USER_FILLCHANGELOG (WM_USER + 3)
44 IMPLEMENT_DYNAMIC(CCheckForUpdatesDlg, CResizableStandAloneDialog)
45 CCheckForUpdatesDlg::CCheckForUpdatesDlg(CWnd* pParent /*=nullptr*/)
46 : CResizableStandAloneDialog(CCheckForUpdatesDlg::IDD, pParent)
47 , m_bShowInfo(FALSE)
48 , m_bForce(FALSE)
49 , m_bVisible(FALSE)
50 , m_pDownloadThread(nullptr)
51 , m_bThreadRunning(FALSE)
52 , m_updateDownloader(nullptr)
54 m_sUpdateDownloadLink = _T("https://tortoisegit.org/download");
57 CCheckForUpdatesDlg::~CCheckForUpdatesDlg()
61 void CCheckForUpdatesDlg::DoDataExchange(CDataExchange* pDX)
63 CResizableStandAloneDialog::DoDataExchange(pDX);
64 DDX_Control(pDX, IDC_LINK, m_link);
65 DDX_Control(pDX, IDC_PROGRESSBAR, m_progress);
66 DDX_Control(pDX, IDC_LIST_DOWNLOADS, m_ctrlFiles);
67 DDX_Control(pDX, IDC_BUTTON_UPDATE, m_ctrlUpdate);
68 DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);
71 BEGIN_MESSAGE_MAP(CCheckForUpdatesDlg, CResizableStandAloneDialog)
72 ON_WM_TIMER()
73 ON_WM_WINDOWPOSCHANGING()
74 ON_WM_SETCURSOR()
75 ON_WM_DESTROY()
76 ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
77 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
78 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
79 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
80 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
81 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
82 END_MESSAGE_MAP()
84 BOOL CCheckForUpdatesDlg::OnInitDialog()
86 CResizableStandAloneDialog::OnInitDialog();
87 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
89 CString temp;
90 temp.Format(IDS_CHECKNEWER_YOURVERSION, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD);
91 SetDlgItemText(IDC_YOURVERSION, temp);
93 DialogEnableWindow(IDOK, FALSE);
95 m_pTaskbarList.Release();
96 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
97 m_pTaskbarList = nullptr;
99 // hide download controls
100 m_ctrlFiles.ShowWindow(SW_HIDE);
101 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
102 RECT rectWindow, rectGroupDownloads, rectOKButton;
103 GetWindowRect(&rectWindow);
104 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
105 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
106 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
107 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
108 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
109 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
110 MoveWindow(&rectWindow);
111 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
112 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
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, 350);
119 m_ctrlFiles.SetColumnWidth(1, 200);
121 m_cLogMessage.Init(-1);
122 m_cLogMessage.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
123 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
125 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
127 if (!AfxBeginThread(CheckThreadEntry, this))
129 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
132 AddAnchor(IDC_YOURVERSION, TOP_LEFT, TOP_RIGHT);
133 AddAnchor(IDC_CURRENTVERSION, TOP_LEFT, TOP_RIGHT);
134 AddAnchor(IDC_CHECKRESULT, TOP_LEFT, TOP_RIGHT);
135 AddAnchor(IDC_LINK, TOP_LEFT, TOP_RIGHT);
136 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
137 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
138 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
139 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
140 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
141 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
142 AddAnchor(IDOK, BOTTOM_CENTER);
144 SetTimer(100, 1000, nullptr);
145 return TRUE;
148 void CCheckForUpdatesDlg::OnDestroy()
150 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
151 delete (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
153 delete m_updateDownloader;
155 CResizableStandAloneDialog::OnDestroy();
158 void CCheckForUpdatesDlg::OnOK()
160 if (m_bThreadRunning || m_pDownloadThread)
161 return; // Don't exit while downloading
163 CResizableStandAloneDialog::OnOK();
166 void CCheckForUpdatesDlg::OnCancel()
168 if (m_bThreadRunning || m_pDownloadThread)
169 return; // Don't exit while downloading
170 CResizableStandAloneDialog::OnCancel();
173 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
175 return ((CCheckForUpdatesDlg*)pVoid)->CheckThread();
178 UINT CCheckForUpdatesDlg::CheckThread()
180 m_bThreadRunning = TRUE;
182 CString temp;
183 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
185 bool official = false;
187 CRegString checkurluser = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""));
188 CRegString checkurlmachine = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""), FALSE, HKEY_LOCAL_MACHINE);
189 CString sCheckURL = checkurluser;
190 if (sCheckURL.IsEmpty())
192 sCheckURL = checkurlmachine;
193 if (sCheckURL.IsEmpty())
195 official = true;
196 bool checkPreview = false;
197 #if PREVIEW
198 checkPreview = true;
199 #else
200 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
201 if (DWORD(regCheckPreview))
202 checkPreview = true;
203 #endif
204 if (checkPreview)
206 sCheckURL = _T("https://versioncheck.tortoisegit.org/version-preview.txt");
207 SetDlgItemText(IDC_SOURCE, _T("Using preview release channel"));
209 else
210 sCheckURL = _T("https://versioncheck.tortoisegit.org/version.txt");
214 if (!official && sCheckURL.Find(_T("://versioncheck.tortoisegit.org/")) > 0)
215 official = true;
217 if (!official)
218 SetDlgItemText(IDC_SOURCE, _T("Using (unofficial) release channel: ") + sCheckURL);
220 CString ver;
221 CAutoConfig versioncheck(true);
222 CString errorText;
223 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
224 if (!ret && official)
226 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
227 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
228 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
230 CString error = _T("Could not verify digital signature.");
231 if (ret)
232 error += _T("\r\nError: ") + GetWinINetError(ret) + _T(" (on ") + sCheckURL + SIGNATURE_FILE_ENDING + _T(")");
233 SetDlgItemText(IDC_CHECKRESULT, error);
234 DeleteUrlCacheEntry(sCheckURL);
235 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
236 goto finish;
239 else if (ret)
241 DeleteUrlCacheEntry(sCheckURL);
242 if (CRegDWORD(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline"), 0))
243 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
244 else
245 errorText.Format(IDS_CHECKNEWER_NETERROR_FORMAT, (LPCTSTR)(GetWinINetError(ret) + _T(" URL: ") + sCheckURL), ret);
246 SetDlgItemText(IDC_CHECKRESULT, errorText);
247 goto finish;
250 git_config_add_file_ondisk(versioncheck, CUnicodeUtils::GetUTF8(tempfile), GIT_CONFIG_LEVEL_GLOBAL, 0);
252 unsigned int major, minor, micro, build;
253 major = minor = micro = build = 0;
254 unsigned __int64 version = 0;
256 if (!versioncheck.GetString(_T("tortoisegit.version"), ver))
258 CString vertemp = ver;
259 major = _ttoi(vertemp);
260 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
261 minor = _ttoi(vertemp);
262 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
263 micro = _ttoi(vertemp);
264 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
265 build = _ttoi(vertemp);
266 version = major;
267 version <<= 16;
268 version += minor;
269 version <<= 16;
270 version += micro;
271 version <<= 16;
272 version += build;
274 if (version == 0)
276 temp.LoadString(IDS_CHECKNEWER_NETERROR);
277 SetDlgItemText(IDC_CHECKRESULT, temp);
278 goto finish;
281 // another versionstring for the filename can be provided
282 // this is needed for preview releases
283 vertemp.Empty();
284 versioncheck.GetString(_T("tortoisegit.versionstring"), vertemp);
285 if (!vertemp.IsEmpty())
286 ver = vertemp;
288 else
290 errorText = _T("Could not parse version check file: ") + g_Git.GetLibGit2LastErr();
291 SetDlgItemText(IDC_CHECKRESULT, errorText);
292 DeleteUrlCacheEntry(sCheckURL);
293 goto finish;
297 BOOL bNewer = FALSE;
298 if (m_bForce)
299 bNewer = TRUE;
300 else if (major > TGIT_VERMAJOR)
301 bNewer = TRUE;
302 else if ((minor > TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
303 bNewer = TRUE;
304 else if ((micro > TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
305 bNewer = TRUE;
306 else if ((build > TGIT_VERBUILD) && (micro == TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
307 bNewer = TRUE;
309 CString versionstr;
310 versionstr.Format(_T("%u.%u.%u.%u"), major, minor, micro, build);
311 if (versionstr != ver)
312 versionstr += _T(" (") + ver + _T(")");
313 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)versionstr);
314 SetDlgItemText(IDC_CURRENTVERSION, temp);
316 if (bNewer)
318 versioncheck.GetString(_T("tortoisegit.infotext"), temp);
319 if (!temp.IsEmpty())
321 CString tempLink;
322 versioncheck.GetString(_T("tortoisegit.infotexturl"), tempLink);
323 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
324 m_sUpdateDownloadLink = tempLink;
326 else
327 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
328 SetDlgItemText(IDC_CHECKRESULT, temp);
330 FillChangelog(versioncheck, official);
331 FillDownloads(versioncheck, ver);
333 RemoveAnchor(IDC_GROUP_CHANGELOG);
334 RemoveAnchor(IDC_LOGMESSAGE);
335 RemoveAnchor(IDC_GROUP_DOWNLOADS);
336 RemoveAnchor(IDC_LIST_DOWNLOADS);
337 RemoveAnchor(IDC_PROGRESSBAR);
338 RemoveAnchor(IDC_BUTTON_UPDATE);
339 RemoveAnchor(IDOK);
341 // Show download controls
342 RECT rectWindow, rectProgress, rectGroupDownloads, rectOKButton;
343 GetWindowRect(&rectWindow);
344 m_progress.GetWindowRect(&rectProgress);
345 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
346 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
347 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
348 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
349 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
350 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
351 MoveWindow(&rectWindow);
352 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
353 if (CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck"), TRUE) != FALSE && !m_bForce && !m_bShowInfo)
355 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
356 rectOKButton.left += 60;
357 temp.LoadString(IDS_REMINDMELATER);
358 GetDlgItem(IDOK)->SetWindowText(temp);
359 rectOKButton.right += 160;
361 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
362 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
363 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
364 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
365 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
366 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
367 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
368 AddAnchor(IDOK, BOTTOM_CENTER);
369 m_ctrlFiles.ShowWindow(SW_SHOW);
370 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
371 CenterWindow();
372 m_bShowInfo = TRUE;
374 else if (m_bShowInfo)
376 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
377 SetDlgItemText(IDC_CHECKRESULT, temp);
378 FillChangelog(versioncheck, official);
382 finish:
383 if (!m_sUpdateDownloadLink.IsEmpty())
385 m_link.ShowWindow(SW_SHOW);
386 m_link.SetURL(m_sUpdateDownloadLink);
388 m_bThreadRunning = FALSE;
389 DialogEnableWindow(IDOK, TRUE);
390 return 0;
393 void CCheckForUpdatesDlg::FillDownloads(CAutoConfig& versioncheck, const CString version)
395 #if WIN64
396 const CString x86x64 = _T("64");
397 #else
398 const CString x86x64 = _T("32");
399 #endif
401 versioncheck.GetString(_T("tortoisegit.baseurl"), m_sFilesURL);
402 if (m_sFilesURL.IsEmpty())
403 m_sFilesURL.Format(_T("http://updater.download.tortoisegit.org/tgit/%s/"), (LPCTSTR)version);
405 bool isHotfix = false;
406 versioncheck.GetBool(_T("tortoisegit.hotfix"), isHotfix);
408 if (isHotfix)
409 m_ctrlFiles.InsertItem(0, _T("TortoiseGit Hotfix"));
410 else
411 m_ctrlFiles.InsertItem(0, _T("TortoiseGit"));
412 CString filenameMain, filenamePattern;
413 versioncheck.GetString(_T("tortoisegit.mainfilename"), filenamePattern);
414 if (filenamePattern.IsEmpty())
415 filenamePattern = _T("TortoiseGit-%1!s!-%2!s!bit.msi");
416 filenameMain.FormatMessage(filenamePattern, version, x86x64);
417 m_ctrlFiles.SetItemData(0, (DWORD_PTR)(new CUpdateListCtrl::Entry(filenameMain, CUpdateListCtrl::STATUS_NONE)));
418 m_ctrlFiles.SetCheck(0 , TRUE);
420 if (isHotfix)
422 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
423 return;
426 struct LangPack
428 CString m_PackName;
429 CString m_LangName;
430 DWORD m_LocaleID;
431 CString m_LangCode;
432 bool m_Installed;
434 struct LanguagePacks
436 std::vector<LangPack> availableLangs;
437 std::vector<DWORD> installedLangs;
438 } languagePacks;
440 // set up the language selecting combobox
441 CString path = CPathUtils::GetAppParentDirectory();
442 path = path + _T("Languages\\");
443 CSimpleFileFind finder(path, _T("*.dll"));
444 while (finder.FindNextFileNoDirectories())
446 CString file = finder.GetFilePath();
447 CString filename = finder.GetFileName();
448 if (filename.Left(12).CompareNoCase(_T("TortoiseProc")) == 0)
450 CString sVer = _T(STRPRODUCTVER);
451 sVer = sVer.Left(sVer.ReverseFind('.'));
452 CString sFileVer = CPathUtils::GetVersionFromFile(file);
453 sFileVer = sFileVer.Left(sFileVer.ReverseFind('.'));
454 CString sLoc = filename.Mid(12);
455 sLoc = sLoc.Left(sLoc.GetLength() - 4); // cut off ".dll"
456 if ((sLoc.Left(2) == L"32") && (sLoc.GetLength() > 5))
457 continue;
458 DWORD loc = _tstoi(filename.Mid(12));
459 languagePacks.installedLangs.push_back(loc);
464 git_config_get_multivar_foreach(versioncheck, "tortoisegit.langs", nullptr, [](const git_config_entry* configentry, void* payload) -> int
466 LanguagePacks* languagePacks = (LanguagePacks*)payload;
467 CString langs = CUnicodeUtils::GetUnicode(configentry->value);
469 int nextTokenPos = langs.Find(_T(";"), 5); // be extensible for the future
470 if (nextTokenPos > 0)
471 langs = langs.Left(nextTokenPos);
472 CString sLang = _T("TortoiseGit Language Pack ") + langs.Mid(5);
474 DWORD loc = _tstoi(langs.Mid(0, 4));
475 TCHAR buf[MAX_PATH] = { 0 };
476 GetLocaleInfo(loc, LOCALE_SNATIVELANGNAME, buf, _countof(buf));
477 CString sLang2(buf);
478 GetLocaleInfo(loc, LOCALE_SNATIVECTRYNAME, buf, _countof(buf));
479 if (buf[0])
481 sLang2 += _T(" (");
482 sLang2 += buf;
483 sLang2 += _T(")");
486 bool installed = std::find(languagePacks->installedLangs.cbegin(), languagePacks->installedLangs.cend(), loc) != languagePacks->installedLangs.cend();
487 LangPack pack = { sLang, sLang2, loc, langs.Mid(5), installed };
488 languagePacks->availableLangs.push_back(pack);
490 return 0;
491 }, &languagePacks);
492 std::stable_sort(languagePacks.availableLangs.begin(), languagePacks.availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
494 return (a.m_Installed && !b.m_Installed) ? 1 : (!a.m_Installed && b.m_Installed) ? 0 : (a.m_PackName.Compare(b.m_PackName) < 0);
496 filenamePattern.Empty();
497 versioncheck.GetString(_T("tortoisegit.languagepackfilename"), filenamePattern);
498 if (filenamePattern.IsEmpty())
499 filenamePattern = _T("TortoiseGit-LanguagePack-%1!s!-%2!s!bit-%3!s!.msi");
500 for (const auto& langs : languagePacks.availableLangs)
502 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.m_PackName);
503 m_ctrlFiles.SetItemText(pos, 1, langs.m_LangName);
505 CString filename;
506 filename.FormatMessage(filenamePattern, version, x86x64, langs.m_LangCode, langs.m_LocaleID);
507 m_ctrlFiles.SetItemData(pos, (DWORD_PTR)(new CUpdateListCtrl::Entry(filename, CUpdateListCtrl::STATUS_NONE)));
509 if (langs.m_Installed)
510 m_ctrlFiles.SetCheck(pos , TRUE);
512 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
515 void CCheckForUpdatesDlg::FillChangelog(CAutoConfig& versioncheck, bool official)
517 ProjectProperties pp;
518 pp.lProjectLanguage = -1;
519 if (versioncheck.GetString(_T("tortoisegit.issuesurl"), pp.sUrl))
520 pp.sUrl = _T("https://tortoisegit.org/issue/%BUGID%");
521 if (!pp.sUrl.IsEmpty())
523 pp.SetCheckRe(_T("[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+"));
524 pp.SetBugIDRe(_T("(\\d+)"));
526 m_cLogMessage.Init(pp);
528 CString sChangelogURL;
529 versioncheck.GetString(_T("TortoiseGit.changelogurl"), sChangelogURL);
530 if (sChangelogURL.IsEmpty())
531 sChangelogURL = _T("https://versioncheck.tortoisegit.org/changelog.txt");
532 else
534 CString tmp(sChangelogURL);
535 sChangelogURL.FormatMessage(tmp, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, m_updateDownloader->m_sWindowsPlatform, m_updateDownloader->m_sWindowsVersion, m_updateDownloader->m_sWindowsServicePack);
538 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
539 DWORD err;
540 if ((err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false)) != ERROR_SUCCESS)
542 CString msg = _T("Could not load changelog.\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + _T(")");
543 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)msg));
544 return;
546 if (official)
548 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
549 if ((err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false)) != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
551 CString error = _T("Could not verify digital signature.");
552 if (err)
553 error += _T("\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + SIGNATURE_FILE_ENDING + _T(")");
554 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)error));
555 DeleteUrlCacheEntry(sChangelogURL);
556 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
557 return;
561 CString temp;
562 CStdioFile file;
563 if (file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary))
565 auto buf = std::make_unique<BYTE[]>((UINT)file.GetLength());
566 UINT read = file.Read(buf.get(), (UINT)file.GetLength());
567 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
568 CGit::StringAppend(&temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
570 else
571 temp = _T("Could not open downloaded changelog file.");
572 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)temp));
575 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
577 if (nIDEvent == 100)
579 if (m_bThreadRunning == FALSE)
581 KillTimer(100);
582 if (m_bShowInfo)
584 m_bVisible = TRUE;
585 ShowWindow(SW_SHOWNORMAL);
587 else
588 EndDialog(0);
591 CResizableStandAloneDialog::OnTimer(nIDEvent);
594 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
596 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
597 if (m_bVisible == FALSE)
598 lpwndpos->flags &= ~SWP_SHOWWINDOW;
601 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
603 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
604 SetCursor(hCur);
605 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
608 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
610 CString title;
611 m_ctrlUpdate.GetWindowText(title);
612 if (!m_pDownloadThread && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
614 bool isOneSelected = false;
615 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
617 if (m_ctrlFiles.GetCheck(i))
619 isOneSelected = true;
620 break;
623 if (!isOneSelected)
624 return;
626 m_eventStop.ResetEvent();
628 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
629 if (m_pDownloadThread)
631 m_pDownloadThread->m_bAutoDelete = FALSE;
632 m_pDownloadThread->ResumeThread();
634 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
636 else
637 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
639 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
641 // Abort
642 m_eventStop.SetEvent();
644 else
646 CString folder = GetDownloadsDirectory();
647 if (m_ctrlUpdate.GetCurrentEntry() == 0)
649 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
651 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
652 if (m_ctrlFiles.GetCheck(i) == TRUE)
653 ShellExecute(GetSafeHwnd(), _T("open"), folder + data->m_filename, nullptr, nullptr, SW_SHOWNORMAL);
655 CResizableStandAloneDialog::OnOK();
657 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
658 ShellExecute(GetSafeHwnd(), _T("open"), folder, nullptr, nullptr, SW_SHOWNORMAL);
660 m_ctrlUpdate.SetCurrentEntry(0);
664 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
666 return ((CCheckForUpdatesDlg*)pVoid)->DownloadThread();
669 bool CCheckForUpdatesDlg::Download(CString filename)
671 CString url = m_sFilesURL + filename;
672 CString destFilename = GetDownloadsDirectory() + filename;
673 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
675 if (VerifyIntegrity(destFilename, destFilename + SIGNATURE_FILE_ENDING, m_updateDownloader) == 0)
676 return true;
677 else
679 DeleteFile(destFilename);
680 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
681 DeleteUrlCacheEntry(url);
682 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
686 m_progress.SetRange32(0, 1);
687 m_progress.SetPos(0);
688 m_progress.ShowWindow(SW_SHOW);
689 if (m_pTaskbarList)
691 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
692 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
695 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
696 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
697 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
698 if (!ret)
700 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
701 if (ret)
702 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": ") + GetWinINetError(ret) + _T("\r\n");
703 m_progress.SetPos(m_progress.GetPos() + 1);
704 if (m_pTaskbarList)
706 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
707 int minValue, maxValue;
708 m_progress.GetRange(minValue, maxValue);
709 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
712 else
713 m_sErrors += url + _T(": ") + GetWinINetError(ret) + _T("\r\n");
714 if (!ret)
716 if (VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader) == 0)
718 DeleteFile(destFilename);
719 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
720 MoveFile(tempfile, destFilename);
721 MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING);
722 return true;
724 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": Broken digital signature.\r\n");
725 DeleteUrlCacheEntry(url);
726 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
728 return false;
731 UINT CCheckForUpdatesDlg::DownloadThread()
733 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
734 m_sErrors.Empty();
735 BOOL result = TRUE;
736 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
738 m_ctrlFiles.EnsureVisible(i, FALSE);
739 CRect rect;
740 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
741 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
742 if (m_ctrlFiles.GetCheck(i) == TRUE)
744 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
745 m_ctrlFiles.InvalidateRect(rect);
746 if (Download(data->m_filename))
747 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
748 else
750 data->m_status = CUpdateListCtrl::STATUS_FAIL;
751 result = FALSE;
754 else
755 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
756 m_ctrlFiles.InvalidateRect(rect);
759 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
761 return result;
764 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
766 ASSERT(m_pDownloadThread);
768 // wait until the thread terminates
769 DWORD dwExitCode;
770 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
771 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
773 // make sure we always have the correct exit code
774 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
776 delete m_pDownloadThread;
777 m_pDownloadThread = nullptr;
779 m_progress.ShowWindow(SW_HIDE);
781 if (dwExitCode == TRUE)
783 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
784 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
785 m_ctrlUpdate.Invalidate();
786 if (m_pTaskbarList)
787 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
789 else
791 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
792 if (m_pTaskbarList)
793 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
794 CString tmp;
795 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
796 if (!m_sErrors.IsEmpty())
797 tmp += _T("\r\n\r\nErrors:\r\n") + m_sErrors;
798 CMessageBox::Show(GetSafeHwnd(), tmp, L"TortoiseGit", MB_ICONERROR);
799 if (m_pTaskbarList)
800 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
803 return 0;
806 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM, LPARAM lParam)
808 ASSERT(lParam);
810 LPCTSTR changelog = reinterpret_cast<LPCTSTR>(lParam);
811 m_cLogMessage.Call(SCI_SETREADONLY, FALSE);
812 m_cLogMessage.SetText(changelog);
813 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
814 m_cLogMessage.Call(SCI_GOTOPOS, 0);
816 return 0;
819 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
821 CString folder;
823 PWSTR wcharPtr = nullptr;
824 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, nullptr, &wcharPtr)))
826 folder = wcharPtr;
827 CoTaskMemFree(wcharPtr);
828 return folder.TrimRight(_T("\\")) + _T("\\");
831 return folder;
834 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
836 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
837 if (pDownloadStatus)
839 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
841 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
842 m_progress.SetPos(pDownloadStatus->ulProgress);
843 if (m_pTaskbarList)
845 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
846 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
850 return 0;
853 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
855 m_pTaskbarList.Release();
856 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
857 return 0;
860 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
862 CString readableError = CFormatMessageWrapper(err);
863 if (readableError.IsEmpty())
865 for (const CString& module : { _T("wininet.dll"), _T("urlmon.dll") })
867 LPTSTR buffer;
868 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, (LPTSTR)&buffer, 0, nullptr);
869 readableError = buffer;
870 LocalFree(buffer);
871 if (!readableError.IsEmpty())
872 break;
875 return readableError.Trim();
878 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
880 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
882 CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck")) = FALSE;
883 OnOK();