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