Use CUnicodeUtils::GetUnicode instead of CGit::StringAppend if possible
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
blob5ee3a333c1f4c227739947b0de6d6ef88a1b3858
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2015 - 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 "LoglistCommonResource.h"
23 #include "..\version.h"
24 #include "MessageBox.h"
25 #include "CheckForUpdatesDlg.h"
26 #include "registry.h"
27 #include "AppUtils.h"
28 #include "TempFile.h"
29 #include "SmartHandle.h"
30 #include "SysInfo.h"
31 #include "PathUtils.h"
32 #include "DirFileEnum.h"
33 #include "UnicodeUtils.h"
34 #include "UpdateCrypto.h"
35 #include "Win7.h"
37 #define SIGNATURE_FILE_ENDING _T(".rsa.asc")
39 #define WM_USER_DISPLAYSTATUS (WM_USER + 1)
40 #define WM_USER_ENDDOWNLOAD (WM_USER + 2)
41 #define WM_USER_FILLCHANGELOG (WM_USER + 3)
43 IMPLEMENT_DYNAMIC(CCheckForUpdatesDlg, CStandAloneDialog)
44 CCheckForUpdatesDlg::CCheckForUpdatesDlg(CWnd* pParent /*=NULL*/)
45 : CStandAloneDialog(CCheckForUpdatesDlg::IDD, pParent)
46 , m_bShowInfo(FALSE)
47 , m_bForce(FALSE)
48 , m_bVisible(FALSE)
49 , m_pDownloadThread(NULL)
50 , m_bThreadRunning(FALSE)
51 , m_updateDownloader(nullptr)
53 m_sUpdateDownloadLink = _T("http://redir.tortoisegit.org/download");
56 CCheckForUpdatesDlg::~CCheckForUpdatesDlg()
60 void CCheckForUpdatesDlg::DoDataExchange(CDataExchange* pDX)
62 CStandAloneDialog::DoDataExchange(pDX);
63 DDX_Control(pDX, IDC_LINK, m_link);
64 DDX_Control(pDX, IDC_PROGRESSBAR, m_progress);
65 DDX_Control(pDX, IDC_LIST_DOWNLOADS, m_ctrlFiles);
66 DDX_Control(pDX, IDC_BUTTON_UPDATE, m_ctrlUpdate);
67 DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);
70 BEGIN_MESSAGE_MAP(CCheckForUpdatesDlg, CStandAloneDialog)
71 ON_WM_TIMER()
72 ON_WM_WINDOWPOSCHANGING()
73 ON_WM_SETCURSOR()
74 ON_WM_DESTROY()
75 ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
76 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
77 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
78 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
79 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
80 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
81 END_MESSAGE_MAP()
83 BOOL CCheckForUpdatesDlg::OnInitDialog()
85 CStandAloneDialog::OnInitDialog();
86 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
88 CString temp;
89 temp.Format(IDS_CHECKNEWER_YOURVERSION, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD);
90 SetDlgItemText(IDC_YOURVERSION, temp);
92 DialogEnableWindow(IDOK, FALSE);
94 m_pTaskbarList.Release();
95 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
96 m_pTaskbarList = nullptr;
98 // hide download controls
99 m_ctrlFiles.ShowWindow(SW_HIDE);
100 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
101 RECT rectWindow, rectGroupDownloads, rectOKButton;
102 GetWindowRect(&rectWindow);
103 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
104 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
105 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
106 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
107 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
108 MoveWindow(&rectWindow);
109 ::MapWindowPoints(NULL, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
110 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
112 temp.LoadString(IDS_STATUSLIST_COLFILE);
113 m_ctrlFiles.InsertColumn(0, temp, 0, -1);
114 m_ctrlFiles.InsertColumn(1, temp, 0, -1);
115 m_ctrlFiles.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_CHECKBOXES);
116 m_ctrlFiles.SetColumnWidth(0, 350);
117 m_ctrlFiles.SetColumnWidth(1, 200);
119 m_cLogMessage.Init(-1);
120 m_cLogMessage.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
121 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
123 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
125 if (AfxBeginThread(CheckThreadEntry, this)==NULL)
127 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
130 SetTimer(100, 1000, NULL);
131 return TRUE;
134 void CCheckForUpdatesDlg::OnDestroy()
136 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
137 delete (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
139 if (m_updateDownloader)
140 delete m_updateDownloader;
142 CStandAloneDialog::OnDestroy();
145 void CCheckForUpdatesDlg::OnOK()
147 if (m_bThreadRunning || m_pDownloadThread != NULL)
148 return; // Don't exit while downloading
150 CStandAloneDialog::OnOK();
153 void CCheckForUpdatesDlg::OnCancel()
155 if (m_bThreadRunning || m_pDownloadThread != NULL)
156 return; // Don't exit while downloading
157 CStandAloneDialog::OnCancel();
160 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
162 return ((CCheckForUpdatesDlg*)pVoid)->CheckThread();
165 UINT CCheckForUpdatesDlg::CheckThread()
167 m_bThreadRunning = TRUE;
169 CString temp;
170 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
172 bool official = false;
174 CRegString checkurluser = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""));
175 CRegString checkurlmachine = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""), FALSE, HKEY_LOCAL_MACHINE);
176 CString sCheckURL = checkurluser;
177 if (sCheckURL.IsEmpty())
179 sCheckURL = checkurlmachine;
180 if (sCheckURL.IsEmpty())
182 official = true;
183 bool checkPreview = false;
184 #if PREVIEW
185 checkPreview = true;
186 #else
187 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
188 if (DWORD(regCheckPreview))
189 checkPreview = true;
190 #endif
191 if (checkPreview)
193 sCheckURL = _T("://versioncheck.tortoisegit.org/version-preview.txt");
194 SetDlgItemText(IDC_SOURCE, _T("Using preview release channel"));
196 else
197 sCheckURL = _T("://versioncheck.tortoisegit.org/version.txt");
198 if (SysInfo::Instance().IsVistaOrLater()) // we need SNI support
199 sCheckURL = _T("https") + sCheckURL;
200 else
201 sCheckURL = _T("http") + sCheckURL;
205 if (!official && sCheckURL.Find(_T("://versioncheck.tortoisegit.org/")) > 0)
206 official = true;
208 if (!official)
209 SetDlgItemText(IDC_SOURCE, _T("Using (unofficial) release channel: ") + sCheckURL);
211 CString ver;
212 CAutoConfig versioncheck(true);
213 CString errorText;
214 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
215 if (!ret && official)
217 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
218 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
219 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
221 CString error = _T("Could not verify digital signature.");
222 if (ret)
223 error += _T("\r\nError: ") + GetWinINetError(ret) + _T(" (on ") + sCheckURL + SIGNATURE_FILE_ENDING + _T(")");
224 SetDlgItemText(IDC_CHECKRESULT, error);
225 DeleteUrlCacheEntry(sCheckURL);
226 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
227 goto finish;
230 else if (ret)
232 DeleteUrlCacheEntry(sCheckURL);
233 if (CRegDWORD(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline"), 0))
234 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
235 else
236 errorText.Format(IDS_CHECKNEWER_NETERROR_FORMAT, GetWinINetError(ret) + _T(" URL: ") + sCheckURL, ret);
237 SetDlgItemText(IDC_CHECKRESULT, errorText);
238 goto finish;
241 git_config_add_file_ondisk(versioncheck, CUnicodeUtils::GetUTF8(tempfile), GIT_CONFIG_LEVEL_GLOBAL, 0);
243 unsigned int major, minor, micro, build;
244 major = minor = micro = build = 0;
245 unsigned __int64 version = 0;
247 if (!versioncheck.GetString(_T("tortoisegit.version"), ver))
249 CString vertemp = ver;
250 major = _ttoi(vertemp);
251 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
252 minor = _ttoi(vertemp);
253 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
254 micro = _ttoi(vertemp);
255 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
256 build = _ttoi(vertemp);
257 version = major;
258 version <<= 16;
259 version += minor;
260 version <<= 16;
261 version += micro;
262 version <<= 16;
263 version += build;
265 if (version == 0)
267 temp.LoadString(IDS_CHECKNEWER_NETERROR);
268 SetDlgItemText(IDC_CHECKRESULT, temp);
269 goto finish;
272 // another versionstring for the filename can be provided
273 // this is needed for preview releases
274 vertemp.Empty();
275 versioncheck.GetString(_T("tortoisegit.versionstring"), vertemp);
276 if (!vertemp.IsEmpty())
277 ver = vertemp;
279 else
281 errorText = _T("Could not parse version check file: ") + g_Git.GetLibGit2LastErr();
282 SetDlgItemText(IDC_CHECKRESULT, errorText);
283 DeleteUrlCacheEntry(sCheckURL);
284 goto finish;
288 BOOL bNewer = FALSE;
289 if (m_bForce)
290 bNewer = TRUE;
291 else if (major > TGIT_VERMAJOR)
292 bNewer = TRUE;
293 else if ((minor > TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
294 bNewer = TRUE;
295 else if ((micro > TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
296 bNewer = TRUE;
297 else if ((build > TGIT_VERBUILD) && (micro == TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
298 bNewer = TRUE;
300 CString versionstr;
301 versionstr.Format(_T("%u.%u.%u.%u"), major, minor, micro, build);
302 if (versionstr != ver)
303 versionstr += _T(" (") + ver + _T(")");
304 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)versionstr);
305 SetDlgItemText(IDC_CURRENTVERSION, temp);
307 if (bNewer)
309 versioncheck.GetString(_T("tortoisegit.infotext"), temp);
310 if (!temp.IsEmpty())
312 CString tempLink;
313 versioncheck.GetString(_T("tortoisegit.infotexturl"), tempLink);
314 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
315 m_sUpdateDownloadLink = tempLink;
317 else
318 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
319 SetDlgItemText(IDC_CHECKRESULT, temp);
321 FillChangelog(versioncheck, official);
322 FillDownloads(versioncheck, ver);
324 // Show download controls
325 RECT rectWindow, rectProgress, rectGroupDownloads, rectOKButton;
326 GetWindowRect(&rectWindow);
327 m_progress.GetWindowRect(&rectProgress);
328 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
329 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
330 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
331 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
332 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
333 MoveWindow(&rectWindow);
334 ::MapWindowPoints(NULL, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
335 if (CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck"), TRUE) != FALSE && !m_bForce && !m_bShowInfo)
337 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
338 rectOKButton.left += 60;
339 CString temp;
340 temp.LoadString(IDS_REMINDMELATER);
341 GetDlgItem(IDOK)->SetWindowText(temp);
342 rectOKButton.right += 160;
344 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
345 m_ctrlFiles.ShowWindow(SW_SHOW);
346 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
347 CenterWindow();
348 m_bShowInfo = TRUE;
350 else if (m_bShowInfo)
352 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
353 SetDlgItemText(IDC_CHECKRESULT, temp);
354 FillChangelog(versioncheck, official);
358 finish:
359 if (!m_sUpdateDownloadLink.IsEmpty())
361 m_link.ShowWindow(SW_SHOW);
362 m_link.SetURL(m_sUpdateDownloadLink);
364 m_bThreadRunning = FALSE;
365 DialogEnableWindow(IDOK, TRUE);
366 return 0;
369 void CCheckForUpdatesDlg::FillDownloads(CAutoConfig& versioncheck, const CString version)
371 #if WIN64
372 const CString x86x64 = _T("64");
373 #else
374 const CString x86x64 = _T("32");
375 #endif
377 versioncheck.GetString(_T("tortoisegit.baseurl"), m_sFilesURL);
378 if (m_sFilesURL.IsEmpty())
379 m_sFilesURL.Format(_T("http://updater.download.tortoisegit.org/tgit/%s/"), version);
381 bool isHotfix = false;
382 versioncheck.GetBool(_T("tortoisegit.hotfix"), isHotfix);
384 if (isHotfix)
385 m_ctrlFiles.InsertItem(0, _T("TortoiseGit Hotfix"));
386 else
387 m_ctrlFiles.InsertItem(0, _T("TortoiseGit"));
388 CString filenameMain, filenamePattern;
389 versioncheck.GetString(_T("tortoisegit.mainfilename"), filenamePattern);
390 if (filenamePattern.IsEmpty())
391 filenamePattern = _T("TortoiseGit-%1!s!-%2!s!bit.msi");
392 filenameMain.FormatMessage(filenamePattern, version, x86x64);
393 m_ctrlFiles.SetItemData(0, (DWORD_PTR)(new CUpdateListCtrl::Entry(filenameMain, CUpdateListCtrl::STATUS_NONE)));
394 m_ctrlFiles.SetCheck(0 , TRUE);
396 if (isHotfix)
398 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
399 return;
402 struct LangPack
404 CString m_PackName;
405 CString m_LangName;
406 DWORD m_LocaleID;
407 CString m_LangCode;
408 bool m_Installed;
410 struct LanguagePacks
412 std::vector<LangPack> availableLangs;
413 std::vector<DWORD> installedLangs;
414 } languagePacks;
416 // set up the language selecting combobox
417 CString path = CPathUtils::GetAppParentDirectory();
418 path = path + _T("Languages\\");
419 CSimpleFileFind finder(path, _T("*.dll"));
420 while (finder.FindNextFileNoDirectories())
422 CString file = finder.GetFilePath();
423 CString filename = finder.GetFileName();
424 if (filename.Left(12).CompareNoCase(_T("TortoiseProc")) == 0)
426 CString sVer = _T(STRPRODUCTVER);
427 sVer = sVer.Left(sVer.ReverseFind('.'));
428 CString sFileVer = CPathUtils::GetVersionFromFile(file);
429 sFileVer = sFileVer.Left(sFileVer.ReverseFind('.'));
430 CString sLoc = filename.Mid(12);
431 sLoc = sLoc.Left(sLoc.GetLength() - 4); // cut off ".dll"
432 if ((sLoc.Left(2) == L"32") && (sLoc.GetLength() > 5))
433 continue;
434 DWORD loc = _tstoi(filename.Mid(12));
435 languagePacks.installedLangs.push_back(loc);
440 git_config_get_multivar_foreach(versioncheck, "tortoisegit.langs", nullptr, [](const git_config_entry* configentry, void* payload) -> int
442 LanguagePacks* languagePacks = (LanguagePacks*)payload;
443 CString langs = CUnicodeUtils::GetUnicode(configentry->value);
445 int nextTokenPos = langs.Find(_T(";"), 5); // be extensible for the future
446 if (nextTokenPos > 0)
447 langs = langs.Left(nextTokenPos);
448 CString sLang = _T("TortoiseGit Language Pack ") + langs.Mid(5);
450 DWORD loc = _tstoi(langs.Mid(0, 4));
451 TCHAR buf[MAX_PATH] = { 0 };
452 GetLocaleInfo(loc, LOCALE_SNATIVELANGNAME, buf, _countof(buf));
453 CString sLang2(buf);
454 GetLocaleInfo(loc, LOCALE_SNATIVECTRYNAME, buf, _countof(buf));
455 if (buf[0])
457 sLang2 += _T(" (");
458 sLang2 += buf;
459 sLang2 += _T(")");
462 bool installed = std::find(languagePacks->installedLangs.begin(), languagePacks->installedLangs.end(), loc) != languagePacks->installedLangs.end();
463 LangPack pack = { sLang, sLang2, loc, langs.Mid(5), installed };
464 languagePacks->availableLangs.push_back(pack);
466 return 0;
467 }, &languagePacks);
468 std::stable_sort(languagePacks.availableLangs.begin(), languagePacks.availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
470 return (a.m_Installed && !b.m_Installed) ? 1 : (!a.m_Installed && b.m_Installed) ? 0 : (a.m_PackName.Compare(b.m_PackName) < 0);
472 filenamePattern.Empty();
473 versioncheck.GetString(_T("tortoisegit.languagepackfilename"), filenamePattern);
474 if (filenamePattern.IsEmpty())
475 filenamePattern = _T("TortoiseGit-LanguagePack-%1!s!-%2!s!bit-%3!s!.msi");
476 for (auto langs : languagePacks.availableLangs)
478 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.m_PackName);
479 m_ctrlFiles.SetItemText(pos, 1, langs.m_LangName);
481 CString filename;
482 filename.FormatMessage(filenamePattern, version, x86x64, langs.m_LangCode, langs.m_LocaleID);
483 m_ctrlFiles.SetItemData(pos, (DWORD_PTR)(new CUpdateListCtrl::Entry(filename, CUpdateListCtrl::STATUS_NONE)));
485 if (langs.m_Installed)
486 m_ctrlFiles.SetCheck(pos , TRUE);
488 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
491 void CCheckForUpdatesDlg::FillChangelog(CAutoConfig& versioncheck, bool official)
493 ProjectProperties pp;
494 pp.lProjectLanguage = -1;
495 if (versioncheck.GetString(_T("tortoisegit.issuesurl"), pp.sUrl))
496 pp.sUrl = _T("https://code.google.com/p/tortoisegit/issues/detail?id=%BUGID%");
497 if (!pp.sUrl.IsEmpty())
499 pp.SetCheckRe(_T("[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+"));
500 pp.SetBugIDRe(_T("(\\d+)"));
502 m_cLogMessage.Init(pp);
504 CString sChangelogURL;
505 versioncheck.GetString(_T("TortoiseGit.changelogurl"), sChangelogURL);
506 if (sChangelogURL.IsEmpty())
507 sChangelogURL = _T("https://versioncheck.tortoisegit.org/changelog.txt");
508 else
510 CString tmp(sChangelogURL);
511 sChangelogURL.FormatMessage(tmp, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, m_updateDownloader->m_sWindowsPlatform, m_updateDownloader->m_sWindowsVersion, m_updateDownloader->m_sWindowsServicePack);
514 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
515 DWORD err;
516 if ((err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false)) != ERROR_SUCCESS)
518 CString msg = _T("Could not load changelog.\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + _T(")");
519 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)msg));
520 return;
522 if (official)
524 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
525 if ((err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false)) != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
527 CString error = _T("Could not verify digital signature.");
528 if (err)
529 error += _T("\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + SIGNATURE_FILE_ENDING + _T(")");
530 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)error));
531 DeleteUrlCacheEntry(sChangelogURL);
532 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
533 return;
537 CString temp;
538 CStdioFile file;
539 if (file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary))
541 std::unique_ptr<BYTE[]> buf(new BYTE[(UINT)file.GetLength()]);
542 UINT read = file.Read(buf.get(), (UINT)file.GetLength());
543 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
544 CGit::StringAppend(&temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
546 else
547 temp = _T("Could not open downloaded changelog file.");
548 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)temp));
551 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
553 if (nIDEvent == 100)
555 if (m_bThreadRunning == FALSE)
557 if (m_bShowInfo)
559 m_bVisible = TRUE;
560 ShowWindow(SW_SHOWNORMAL);
562 else
564 EndDialog(0);
568 CStandAloneDialog::OnTimer(nIDEvent);
571 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
573 CStandAloneDialog::OnWindowPosChanging(lpwndpos);
574 if (m_bVisible == FALSE)
575 lpwndpos->flags &= ~SWP_SHOWWINDOW;
578 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
580 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
581 SetCursor(hCur);
582 return CStandAloneDialogTmpl<CDialog>::OnSetCursor(pWnd, nHitTest, message);
585 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
587 CString title;
588 m_ctrlUpdate.GetWindowText(title);
589 if (m_pDownloadThread == NULL && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
591 bool isOneSelected = false;
592 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
594 if (m_ctrlFiles.GetCheck(i))
596 isOneSelected = true;
597 break;
600 if (!isOneSelected)
601 return;
603 m_eventStop.ResetEvent();
605 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
606 if (m_pDownloadThread != NULL)
608 m_pDownloadThread->m_bAutoDelete = FALSE;
609 m_pDownloadThread->ResumeThread();
611 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
613 else
615 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
618 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
620 // Abort
621 m_eventStop.SetEvent();
623 else
625 CString folder = GetDownloadsDirectory();
626 if (m_ctrlUpdate.GetCurrentEntry() == 0)
628 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
630 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
631 if (m_ctrlFiles.GetCheck(i) == TRUE)
632 ShellExecute(NULL, _T("open"), folder + data->m_filename, NULL, NULL, SW_SHOWNORMAL);
634 CStandAloneDialog::OnOK();
636 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
638 ShellExecute(NULL, _T("open"), folder, NULL, NULL, SW_SHOWNORMAL);
641 m_ctrlUpdate.SetCurrentEntry(0);
645 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
647 return ((CCheckForUpdatesDlg*)pVoid)->DownloadThread();
650 bool CCheckForUpdatesDlg::Download(CString filename)
652 CString url = m_sFilesURL + filename;
653 CString destFilename = GetDownloadsDirectory() + filename;
654 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
656 if (VerifyIntegrity(destFilename, destFilename + SIGNATURE_FILE_ENDING, m_updateDownloader) == 0)
657 return true;
658 else
660 DeleteFile(destFilename);
661 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
662 DeleteUrlCacheEntry(url);
663 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
667 m_progress.SetRange32(0, 1);
668 m_progress.SetPos(0);
669 m_progress.ShowWindow(SW_SHOW);
670 if (m_pTaskbarList)
672 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
673 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
676 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
677 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
678 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
679 if (!ret)
681 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
682 if (ret)
683 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": ") + GetWinINetError(ret) + _T("\r\n");
684 m_progress.SetPos(m_progress.GetPos() + 1);
685 if (m_pTaskbarList)
687 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
688 int minValue, maxValue;
689 m_progress.GetRange(minValue, maxValue);
690 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
693 else
694 m_sErrors += url + _T(": ") + GetWinINetError(ret) + _T("\r\n");
695 if (!ret)
697 if (VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader) == 0)
699 DeleteFile(destFilename);
700 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
701 MoveFile(tempfile, destFilename);
702 MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING);
703 return true;
705 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": Broken digital signature.\r\n");
706 DeleteUrlCacheEntry(url);
707 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
709 return false;
712 UINT CCheckForUpdatesDlg::DownloadThread()
714 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
715 m_sErrors.Empty();
716 BOOL result = TRUE;
717 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
719 m_ctrlFiles.EnsureVisible(i, FALSE);
720 CRect rect;
721 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
722 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
723 if (m_ctrlFiles.GetCheck(i) == TRUE)
725 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
726 m_ctrlFiles.InvalidateRect(rect);
727 if (Download(data->m_filename))
728 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
729 else
731 data->m_status = CUpdateListCtrl::STATUS_FAIL;
732 result = FALSE;
735 else
736 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
737 m_ctrlFiles.InvalidateRect(rect);
740 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
742 return result;
745 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
747 ASSERT(m_pDownloadThread != NULL);
749 // wait until the thread terminates
750 DWORD dwExitCode;
751 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
752 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
754 // make sure we always have the correct exit code
755 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
757 delete m_pDownloadThread;
758 m_pDownloadThread = NULL;
760 m_progress.ShowWindow(SW_HIDE);
762 if (dwExitCode == TRUE)
764 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
765 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
766 m_ctrlUpdate.Invalidate();
767 if (m_pTaskbarList)
768 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
770 else
772 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
773 if (m_pTaskbarList)
774 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
775 CString tmp;
776 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
777 if (!m_sErrors.IsEmpty())
778 tmp += _T("\r\n\r\nErrors:\r\n") + m_sErrors;
779 CMessageBox::Show(NULL, tmp, _T("TortoiseGit"), MB_ICONERROR);
780 if (m_pTaskbarList)
781 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
784 return 0;
787 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM, LPARAM lParam)
789 ASSERT(lParam != NULL);
791 LPCTSTR changelog = reinterpret_cast<LPCTSTR>(lParam);
792 m_cLogMessage.Call(SCI_SETREADONLY, FALSE);
793 m_cLogMessage.SetText(changelog);
794 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
795 m_cLogMessage.Call(SCI_GOTOPOS, 0);
797 return 0;
800 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
802 CString folder;
804 if (SysInfo::Instance().IsVistaOrLater())
806 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(_T("shell32.dll"));
807 if (hShell)
809 typedef HRESULT STDAPICALLTYPE SHGetKnownFolderPathFN(__in REFKNOWNFOLDERID rfid, __in DWORD dwFlags, __in_opt HANDLE hToken, __deref_out PWSTR *ppszPath);
810 SHGetKnownFolderPathFN *pfnSHGetKnownFolderPath = (SHGetKnownFolderPathFN*)GetProcAddress(hShell, "SHGetKnownFolderPath");
811 if (pfnSHGetKnownFolderPath)
813 wchar_t * wcharPtr = 0;
814 HRESULT hr = pfnSHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, NULL, &wcharPtr);
815 if (SUCCEEDED(hr))
817 folder = wcharPtr;
818 CoTaskMemFree(static_cast<void*>(wcharPtr));
819 return folder.TrimRight(_T("\\")) + _T("\\");
825 TCHAR szPath[MAX_PATH] = {0};
826 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, szPath)))
827 folder = szPath;
828 CString downloads = folder.TrimRight(_T("\\")) + _T("\\Downloads\\");
829 if ((PathFileExists(downloads) && PathIsDirectory(downloads)) || (!PathFileExists(downloads) && CreateDirectory(downloads, NULL)))
830 return downloads;
832 return folder;
835 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
837 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
838 if (pDownloadStatus != NULL)
840 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
842 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
843 m_progress.SetPos(pDownloadStatus->ulProgress);
844 if (m_pTaskbarList)
846 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
847 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
851 return 0;
854 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
856 m_pTaskbarList.Release();
857 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
858 return 0;
861 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
863 CString readableError = CFormatMessageWrapper(err);
864 if (readableError.IsEmpty())
866 CString modules[] = { _T("wininet.dll"), _T("urlmon.dll") };
867 for (auto module : modules)
869 LPTSTR buffer;
870 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, (LPTSTR)&buffer, 0, NULL);
871 readableError = buffer;
872 LocalFree(buffer);
873 if (!readableError.IsEmpty())
874 break;
877 return readableError.Trim();
880 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
882 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
884 CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck")) = FALSE;
885 OnOK();