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.
21 #include "TortoiseProc.h"
23 #include "LoglistCommonResource.h"
24 #include "..\version.h"
25 #include "MessageBox.h"
26 #include "CheckForUpdatesDlg.h"
30 #include "SmartHandle.h"
32 #include "PathUtils.h"
33 #include "DirFileEnum.h"
34 #include "UnicodeUtils.h"
35 #include "UpdateCrypto.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
)
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
)
76 ON_WM_WINDOWPOSCHANGING()
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
)
87 BOOL
CCheckForUpdatesDlg::OnInitDialog()
89 CResizableStandAloneDialog::OnInitDialog();
90 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
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);
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
;
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())
199 bool checkPreview
= false;
203 CRegStdDWORD
regCheckPreview(L
"Software\\TortoiseGit\\VersionCheckPreview", FALSE
);
204 if (DWORD(regCheckPreview
))
209 sCheckURL
= _T("https://versioncheck.tortoisegit.org/version-preview.txt");
210 SetDlgItemText(IDC_SOURCE
, _T("Using preview release channel"));
213 sCheckURL
= _T("https://versioncheck.tortoisegit.org/version.txt");
217 if (!official
&& sCheckURL
.Find(_T("://versioncheck.tortoisegit.org/")) > 0)
221 SetDlgItemText(IDC_SOURCE
, _T("Using (unofficial) release channel: ") + sCheckURL
);
224 CAutoConfig
versioncheck(true);
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.");
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
);
244 DeleteUrlCacheEntry(sCheckURL
);
245 if (CRegDWORD(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline"), 0))
246 errorText
.LoadString(IDS_OFFLINEMODE
); // offline mode enabled
248 errorText
.Format(IDS_CHECKNEWER_NETERROR_FORMAT
, (LPCTSTR
)(GetWinINetError(ret
) + _T(" URL: ") + sCheckURL
), ret
);
249 SetDlgItemText(IDC_CHECKRESULT
, errorText
);
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
);
279 temp
.LoadString(IDS_CHECKNEWER_NETERROR
);
280 SetDlgItemText(IDC_CHECKRESULT
, temp
);
284 // another versionstring for the filename can be provided
285 // this is needed for preview releases
287 versioncheck
.GetString(_T("tortoisegit.versionstring"), vertemp
);
288 if (!vertemp
.IsEmpty())
293 errorText
= _T("Could not parse version check file: ") + g_Git
.GetLibGit2LastErr();
294 SetDlgItemText(IDC_CHECKRESULT
, errorText
);
295 DeleteUrlCacheEntry(sCheckURL
);
303 else if (major
> TGIT_VERMAJOR
)
305 else if ((minor
> TGIT_VERMINOR
) && (major
== TGIT_VERMAJOR
))
307 else if ((micro
> TGIT_VERMICRO
) && (minor
== TGIT_VERMINOR
) && (major
== TGIT_VERMAJOR
))
309 else if ((build
> TGIT_VERBUILD
) && (micro
== TGIT_VERMICRO
) && (minor
== TGIT_VERMINOR
) && (major
== TGIT_VERMAJOR
))
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
);
319 temp
.Format(IDS_CHECKNEWER_CURRENTVERSION
, (LPCTSTR
)m_sNewVersionNumber
);
320 SetDlgItemText(IDC_CURRENTVERSION
, temp
);
324 versioncheck
.GetString(_T("tortoisegit.infotext"), temp
);
328 versioncheck
.GetString(_T("tortoisegit.infotexturl"), tempLink
);
329 if (!tempLink
.IsEmpty()) // find out the download link-URL, if any
330 m_sUpdateDownloadLink
= tempLink
;
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
);
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
);
380 else if (m_bShowInfo
)
382 temp
.LoadString(IDS_CHECKNEWER_YOURUPTODATE
);
383 SetDlgItemText(IDC_CHECKRESULT
, temp
);
384 FillChangelog(versioncheck
, official
);
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
);
399 void CCheckForUpdatesDlg::FillDownloads(CAutoConfig
& versioncheck
, const CString version
)
402 const CString x86x64
= _T("64");
404 const CString x86x64
= _T("32");
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
);
415 m_ctrlFiles
.InsertItem(0, _T("TortoiseGit Hotfix"));
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
);
428 DialogEnableWindow(IDC_BUTTON_UPDATE
, TRUE
);
442 std::vector
<LangPack
> availableLangs
;
443 std::vector
<DWORD
> installedLangs
;
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))
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
));
484 GetLocaleInfo(loc
, LOCALE_SNATIVECTRYNAME
, buf
, _countof(buf
));
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
);
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
);
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");
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();
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
));
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.");
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
);
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));
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
)
585 if (m_bThreadRunning
== FALSE
)
591 ShowWindow(SW_SHOWNORMAL
);
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
);
611 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
614 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
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;
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
)));
643 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
645 else if (title
== CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)))
648 m_eventStop
.SetEvent();
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";
685 if ((ret
= MsiGetSummaryInformation(NULL
, filename
, 0, &hSummary
)) != 0)
687 CString sFileVer
= CPathUtils::GetVersionFromFile(filename
);
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
));
695 else if (sFileVer
== m_sNewVersionNumber
)
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
);
701 SCOPE_EXIT
{ MsiCloseHandle(hSummary
); };
704 DWORD cchValue
= 4096;
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
));
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
);
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
);
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
))
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
);
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);
759 ret
= m_updateDownloader
->DownloadFile(url
+ SIGNATURE_FILE_ENDING
, signatureTempfile
, true);
761 m_sErrors
+= url
+ SIGNATURE_FILE_ENDING
+ _T(": ") + GetWinINetError(ret
) + _T("\r\n");
762 m_progress
.SetPos(m_progress
.GetPos() + 1);
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
);
772 m_sErrors
+= url
+ _T(": ") + GetWinINetError(ret
) + _T("\r\n");
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
);
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
);
791 DeleteUrlCacheEntry(url
);
792 DeleteUrlCacheEntry(url
+ SIGNATURE_FILE_ENDING
);
797 UINT
CCheckForUpdatesDlg::DownloadThread()
799 m_ctrlFiles
.SetExtendedStyle(m_ctrlFiles
.GetExtendedStyle() & ~LVS_EX_CHECKBOXES
);
802 for (int i
= 0; i
< (int)m_ctrlFiles
.GetItemCount(); ++i
)
804 m_ctrlFiles
.EnsureVisible(i
, FALSE
);
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
;
816 data
->m_status
= CUpdateListCtrl::STATUS_FAIL
;
821 data
->m_status
= CUpdateListCtrl::STATUS_IGNORE
;
822 m_ctrlFiles
.InvalidateRect(rect
);
825 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD
, 0, 0);
830 LRESULT
CCheckForUpdatesDlg::OnEndDownload(WPARAM
, LPARAM
)
832 ASSERT(m_pDownloadThread
);
834 // wait until the thread terminates
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();
853 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
857 m_ctrlUpdate
.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD
)));
859 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_ERROR
);
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
);
866 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
872 LRESULT
CCheckForUpdatesDlg::OnFillChangelog(WPARAM
, LPARAM 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);
885 CString
CCheckForUpdatesDlg::GetDownloadsDirectory()
889 PWSTR wcharPtr
= nullptr;
890 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads
, KF_FLAG_CREATE
, nullptr, &wcharPtr
)))
893 CoTaskMemFree(wcharPtr
);
894 return folder
.TrimRight(_T("\\")) + _T("\\");
900 LRESULT
CCheckForUpdatesDlg::OnDisplayStatus(WPARAM
, LPARAM lParam
)
902 const CUpdateDownloader::DOWNLOADSTATUS
*const pDownloadStatus
= reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS
*>(lParam
);
905 ASSERT(::AfxIsValidAddress(pDownloadStatus
, sizeof(CUpdateDownloader::DOWNLOADSTATUS
)));
907 m_progress
.SetRange32(0, pDownloadStatus
->ulProgressMax
);
908 m_progress
.SetPos(pDownloadStatus
->ulProgress
);
911 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
912 m_pTaskbarList
->SetProgressValue(m_hWnd
, pDownloadStatus
->ulProgress
, pDownloadStatus
->ulProgressMax
);
919 LRESULT
CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
921 m_pTaskbarList
.Release();
922 m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
);
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") })
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
;
937 if (!readableError
.IsEmpty())
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
;