1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2017 - 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 L".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)
56 , m_sUpdateDownloadLink(L
"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_WM_SYSCOLORCHANGE()
80 ON_BN_CLICKED(IDC_BUTTON_UPDATE
, OnBnClickedButtonUpdate
)
81 ON_MESSAGE(WM_USER_DISPLAYSTATUS
, OnDisplayStatus
)
82 ON_MESSAGE(WM_USER_ENDDOWNLOAD
, OnEndDownload
)
83 ON_MESSAGE(WM_USER_FILLCHANGELOG
, OnFillChangelog
)
84 ON_REGISTERED_MESSAGE(TaskBarButtonCreated
, OnTaskbarBtnCreated
)
85 ON_BN_CLICKED(IDC_DONOTASKAGAIN
, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain
)
88 BOOL
CCheckForUpdatesDlg::OnInitDialog()
90 CResizableStandAloneDialog::OnInitDialog();
91 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
94 temp
.Format(IDS_CHECKNEWER_YOURVERSION
, TGIT_VERMAJOR
, TGIT_VERMINOR
, TGIT_VERMICRO
, TGIT_VERBUILD
);
95 SetDlgItemText(IDC_YOURVERSION
, temp
);
97 DialogEnableWindow(IDOK
, FALSE
);
99 m_pTaskbarList
.Release();
100 if (FAILED(m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
)))
101 m_pTaskbarList
= nullptr;
103 // hide download controls
104 m_ctrlFiles
.ShowWindow(SW_HIDE
);
105 GetDlgItem(IDC_GROUP_DOWNLOADS
)->ShowWindow(SW_HIDE
);
106 RECT rectWindow
, rectGroupDownloads
, rectOKButton
;
107 GetWindowRect(&rectWindow
);
108 GetDlgItem(IDC_GROUP_DOWNLOADS
)->GetWindowRect(&rectGroupDownloads
);
109 GetDlgItem(IDOK
)->GetWindowRect(&rectOKButton
);
110 LONG bottomDistance
= rectWindow
.bottom
- rectOKButton
.bottom
;
111 OffsetRect(&rectOKButton
, 0, rectGroupDownloads
.top
- rectOKButton
.top
);
112 rectWindow
.bottom
= rectOKButton
.bottom
+ bottomDistance
;
113 SetMinTrackSize(CSize(rectWindow
.right
- rectWindow
.left
, rectWindow
.bottom
- rectWindow
.top
));
114 MoveWindow(&rectWindow
);
115 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT
)&rectOKButton
, 2);
116 GetDlgItem(IDOK
)->MoveWindow(&rectOKButton
);
118 temp
.LoadString(IDS_STATUSLIST_COLFILE
);
119 m_ctrlFiles
.InsertColumn(0, temp
, 0, -1);
120 m_ctrlFiles
.InsertColumn(1, temp
, 0, -1);
121 m_ctrlFiles
.SetExtendedStyle(LVS_EX_DOUBLEBUFFER
| LVS_EX_CHECKBOXES
);
122 m_ctrlFiles
.SetColumnWidth(0, 350);
123 m_ctrlFiles
.SetColumnWidth(1, 200);
125 m_cLogMessage
.Init(-1);
126 m_cLogMessage
.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());
127 m_cLogMessage
.Call(SCI_SETREADONLY
, TRUE
);
129 m_updateDownloader
= new CUpdateDownloader(GetSafeHwnd(), m_bForce
== TRUE
, WM_USER_DISPLAYSTATUS
, &m_eventStop
);
131 if (!AfxBeginThread(CheckThreadEntry
, this))
133 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
136 AddAnchor(IDC_YOURVERSION
, TOP_LEFT
, TOP_RIGHT
);
137 AddAnchor(IDC_CURRENTVERSION
, TOP_LEFT
, TOP_RIGHT
);
138 AddAnchor(IDC_CHECKRESULT
, TOP_LEFT
, TOP_RIGHT
);
139 AddAnchor(IDC_LINK
, TOP_LEFT
, TOP_RIGHT
);
140 AddAnchor(IDC_GROUP_CHANGELOG
, TOP_LEFT
, BOTTOM_RIGHT
);
141 AddAnchor(IDC_LOGMESSAGE
, TOP_LEFT
, BOTTOM_RIGHT
);
142 AddAnchor(IDC_GROUP_DOWNLOADS
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
143 AddAnchor(IDC_LIST_DOWNLOADS
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
144 AddAnchor(IDC_PROGRESSBAR
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
145 AddAnchor(IDC_BUTTON_UPDATE
, BOTTOM_RIGHT
);
146 AddAnchor(IDOK
, BOTTOM_CENTER
);
148 SetTimer(100, 1000, nullptr);
152 void CCheckForUpdatesDlg::OnDestroy()
154 for (int i
= 0; i
< m_ctrlFiles
.GetItemCount(); ++i
)
155 delete (CUpdateListCtrl::Entry
*)m_ctrlFiles
.GetItemData(i
);
157 delete m_updateDownloader
;
159 CResizableStandAloneDialog::OnDestroy();
162 void CCheckForUpdatesDlg::OnOK()
164 if (m_bThreadRunning
|| m_pDownloadThread
)
165 return; // Don't exit while downloading
167 CResizableStandAloneDialog::OnOK();
170 void CCheckForUpdatesDlg::OnCancel()
172 if (m_bThreadRunning
|| m_pDownloadThread
)
173 return; // Don't exit while downloading
174 CResizableStandAloneDialog::OnCancel();
177 UINT
CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid
)
179 return reinterpret_cast<CCheckForUpdatesDlg
*>(pVoid
)->CheckThread();
182 UINT
CCheckForUpdatesDlg::CheckThread()
184 m_bThreadRunning
= TRUE
;
187 CString tempfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
189 bool official
= false;
191 CRegString checkurluser
= CRegString(L
"Software\\TortoiseGit\\UpdateCheckURL", L
"");
192 CRegString checkurlmachine
= CRegString(L
"Software\\TortoiseGit\\UpdateCheckURL", L
"", FALSE
, HKEY_LOCAL_MACHINE
);
193 CString sCheckURL
= checkurluser
;
194 if (sCheckURL
.IsEmpty())
196 sCheckURL
= checkurlmachine
;
197 if (sCheckURL
.IsEmpty())
200 bool checkPreview
= false;
204 CRegStdDWORD
regCheckPreview(L
"Software\\TortoiseGit\\VersionCheckPreview", FALSE
);
205 if (DWORD(regCheckPreview
))
210 sCheckURL
= L
"https://versioncheck.tortoisegit.org/version-preview.txt";
211 SetDlgItemText(IDC_SOURCE
, L
"Using preview release channel");
214 sCheckURL
= L
"https://versioncheck.tortoisegit.org/version.txt";
218 if (!official
&& CStringUtils::StartsWith(sCheckURL
, L
"https://versioncheck.tortoisegit.org/"))
222 SetDlgItemText(IDC_SOURCE
, L
"Using (unofficial) release channel: " + sCheckURL
);
225 CVersioncheckParser versioncheck
;
226 CVersioncheckParser::Version version
;
227 DWORD ret
= m_updateDownloader
->DownloadFile(sCheckURL
, tempfile
, false);
228 if (!ret
&& official
)
230 CString signatureTempfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
231 ret
= m_updateDownloader
->DownloadFile(sCheckURL
+ SIGNATURE_FILE_ENDING
, signatureTempfile
, false);
232 if (ret
|| VerifyIntegrity(tempfile
, signatureTempfile
, m_updateDownloader
))
234 CString error
= L
"Could not verify digital signature.";
236 error
+= L
"\r\nError: " + GetWinINetError(ret
) + L
" (on " + sCheckURL
+ SIGNATURE_FILE_ENDING
+ L
")";
237 SetDlgItemText(IDC_CHECKRESULT
, error
);
238 DeleteUrlCacheEntry(sCheckURL
);
239 DeleteUrlCacheEntry(sCheckURL
+ SIGNATURE_FILE_ENDING
);
245 DeleteUrlCacheEntry(sCheckURL
);
246 if (CRegDWORD(L
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline", 0))
247 errorText
.LoadString(IDS_OFFLINEMODE
); // offline mode enabled
249 errorText
.Format(IDS_CHECKNEWER_NETERROR_FORMAT
, (LPCTSTR
)(GetWinINetError(ret
) + L
" URL: " + sCheckURL
), ret
);
250 SetDlgItemText(IDC_CHECKRESULT
, errorText
);
254 if (!versioncheck
.Load(tempfile
, errorText
))
256 if (!errorText
.IsEmpty())
257 SetDlgItemText(IDC_CHECKRESULT
, errorText
);
260 temp
.LoadString(IDS_CHECKNEWER_NETERROR
);
261 SetDlgItemText(IDC_CHECKRESULT
, temp
);
263 DeleteUrlCacheEntry(sCheckURL
);
267 version
= versioncheck
.GetTortoiseGitVersion();
272 else if (version
.major
> TGIT_VERMAJOR
)
274 else if ((version
.minor
> TGIT_VERMINOR
) && (version
.major
== TGIT_VERMAJOR
))
276 else if ((version
.micro
> TGIT_VERMICRO
) && (version
.minor
== TGIT_VERMINOR
) && (version
.major
== TGIT_VERMAJOR
))
278 else if ((version
.build
> TGIT_VERBUILD
) && (version
.micro
== TGIT_VERMICRO
) && (version
.minor
== TGIT_VERMINOR
) && (version
.major
== TGIT_VERMAJOR
))
281 m_sNewVersionNumber
.Format(L
"%u.%u.%u.%u", version
.major
, version
.minor
, version
.micro
, version
.build
);
282 if (m_sNewVersionNumber
!= version
.version_for_filename
)
284 CString versionstr
= m_sNewVersionNumber
+ L
" (" + version
.version_for_filename
+ L
")";
285 temp
.Format(IDS_CHECKNEWER_CURRENTVERSION
, (LPCTSTR
)versionstr
);
288 temp
.Format(IDS_CHECKNEWER_CURRENTVERSION
, (LPCTSTR
)m_sNewVersionNumber
);
289 SetDlgItemText(IDC_CURRENTVERSION
, temp
);
293 temp
= versioncheck
.GetTortoiseGitInfoText();
296 CString tempLink
= versioncheck
.GetTortoiseGitInfoTextURL();
297 if (!tempLink
.IsEmpty()) // find out the download link-URL, if any
298 m_sUpdateDownloadLink
= tempLink
;
301 temp
.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE
);
302 SetDlgItemText(IDC_CHECKRESULT
, temp
);
304 FillChangelog(versioncheck
, official
);
305 FillDownloads(versioncheck
);
307 RemoveAnchor(IDC_GROUP_CHANGELOG
);
308 RemoveAnchor(IDC_LOGMESSAGE
);
309 RemoveAnchor(IDC_GROUP_DOWNLOADS
);
310 RemoveAnchor(IDC_LIST_DOWNLOADS
);
311 RemoveAnchor(IDC_PROGRESSBAR
);
312 RemoveAnchor(IDC_DONOTASKAGAIN
);
313 RemoveAnchor(IDC_BUTTON_UPDATE
);
316 // Show download controls
317 RECT rectWindow
, rectProgress
, rectGroupDownloads
, rectOKButton
;
318 GetWindowRect(&rectWindow
);
319 m_progress
.GetWindowRect(&rectProgress
);
320 GetDlgItem(IDC_GROUP_DOWNLOADS
)->GetWindowRect(&rectGroupDownloads
);
321 GetDlgItem(IDOK
)->GetWindowRect(&rectOKButton
);
322 LONG bottomDistance
= rectWindow
.bottom
- rectOKButton
.bottom
;
323 OffsetRect(&rectOKButton
, 0, (rectGroupDownloads
.bottom
+ (rectGroupDownloads
.bottom
- rectProgress
.bottom
)) - rectOKButton
.top
);
324 rectWindow
.bottom
= rectOKButton
.bottom
+ bottomDistance
;
325 SetMinTrackSize(CSize(rectWindow
.right
- rectWindow
.left
, rectWindow
.bottom
- rectWindow
.top
));
326 MoveWindow(&rectWindow
);
327 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT
)&rectOKButton
, 2);
328 if (CRegDWORD(L
"Software\\TortoiseGit\\VersionCheck", TRUE
) != FALSE
&& !m_bForce
&& !m_bShowInfo
)
330 GetDlgItem(IDC_DONOTASKAGAIN
)->ShowWindow(SW_SHOW
);
331 rectOKButton
.left
+= 60;
332 temp
.LoadString(IDS_REMINDMELATER
);
333 GetDlgItem(IDOK
)->SetWindowText(temp
);
334 rectOKButton
.right
+= 160;
336 GetDlgItem(IDOK
)->MoveWindow(&rectOKButton
);
337 AddAnchor(IDC_GROUP_CHANGELOG
, TOP_LEFT
, BOTTOM_RIGHT
);
338 AddAnchor(IDC_LOGMESSAGE
, TOP_LEFT
, BOTTOM_RIGHT
);
339 AddAnchor(IDC_GROUP_DOWNLOADS
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
340 AddAnchor(IDC_LIST_DOWNLOADS
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
341 AddAnchor(IDC_PROGRESSBAR
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
342 AddAnchor(IDC_DONOTASKAGAIN
, BOTTOM_CENTER
);
343 AddAnchor(IDC_BUTTON_UPDATE
, BOTTOM_RIGHT
);
344 AddAnchor(IDOK
, BOTTOM_CENTER
);
345 m_ctrlFiles
.ShowWindow(SW_SHOW
);
346 GetDlgItem(IDC_GROUP_DOWNLOADS
)->ShowWindow(SW_SHOW
);
350 else if (m_bShowInfo
)
352 temp
.LoadString(IDS_CHECKNEWER_YOURUPTODATE
);
353 SetDlgItemText(IDC_CHECKRESULT
, temp
);
354 FillChangelog(versioncheck
, official
);
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
);
369 void CCheckForUpdatesDlg::FillDownloads(CVersioncheckParser
& versioncheck
)
371 m_sFilesURL
= versioncheck
.GetTortoiseGitBaseURL();
373 bool isHotfix
= versioncheck
.GetTortoiseGitIsHotfix();
375 m_ctrlFiles
.InsertItem(0, L
"TortoiseGit Hotfix");
377 m_ctrlFiles
.InsertItem(0, L
"TortoiseGit");
378 CString filenameMain
= versioncheck
.GetTortoiseGitMainfilename();
379 m_ctrlFiles
.SetItemData(0, (DWORD_PTR
)(new CUpdateListCtrl::Entry(filenameMain
, CUpdateListCtrl::STATUS_NONE
)));
380 m_ctrlFiles
.SetCheck(0 , TRUE
);
384 DialogEnableWindow(IDC_BUTTON_UPDATE
, TRUE
);
390 CVersioncheckParser::LanguagePack languagepack
;
393 std::vector
<LangPack
> availableLangs
;
394 std::vector
<DWORD
> installedLangs
;
396 // set up the language selecting combobox
397 CString path
= CPathUtils::GetAppParentDirectory();
398 path
= path
+ L
"Languages\\";
399 CSimpleFileFind
finder(path
, L
"*.dll");
400 while (finder
.FindNextFileNoDirectories())
402 CString file
= finder
.GetFilePath();
403 CString filename
= finder
.GetFileName();
404 if (CStringUtils::StartsWithI(filename
, L
"TortoiseProc"))
406 CString sVer
= _T(STRPRODUCTVER
);
407 sVer
= sVer
.Left(sVer
.ReverseFind('.'));
408 CString sFileVer
= CPathUtils::GetVersionFromFile(file
);
409 sFileVer
= sFileVer
.Left(sFileVer
.ReverseFind('.'));
410 CString sLoc
= filename
.Mid(12);
411 sLoc
= sLoc
.Left(sLoc
.GetLength() - 4); // cut off ".dll"
412 if (CStringUtils::StartsWith(sLoc
, L
"32") && (sLoc
.GetLength() > 5))
414 DWORD loc
= _wtoi(filename
.Mid(12));
415 installedLangs
.push_back(loc
);
420 for (auto& languagepack
: versioncheck
.GetTortoiseGitLanguagePacks())
422 bool installed
= std::find(installedLangs
.cbegin(), installedLangs
.cend(), languagepack
.m_LocaleID
) != installedLangs
.cend();
423 LangPack pack
= { languagepack
, installed
};
424 availableLangs
.push_back(pack
);
426 std::stable_sort(availableLangs
.begin(), availableLangs
.end(), [&](const LangPack
& a
, const LangPack
& b
) -> int
428 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);
430 for (const auto& langs
: availableLangs
)
432 int pos
= m_ctrlFiles
.InsertItem(m_ctrlFiles
.GetItemCount(), langs
.languagepack
.m_PackName
);
433 m_ctrlFiles
.SetItemText(pos
, 1, langs
.languagepack
.m_LangName
);
434 m_ctrlFiles
.SetItemData(pos
, (DWORD_PTR
)(new CUpdateListCtrl::Entry(langs
.languagepack
.m_filename
, CUpdateListCtrl::STATUS_NONE
)));
436 if (langs
.m_Installed
)
437 m_ctrlFiles
.SetCheck(pos
, TRUE
);
439 DialogEnableWindow(IDC_BUTTON_UPDATE
, TRUE
);
442 void CCheckForUpdatesDlg::FillChangelog(CVersioncheckParser
& versioncheck
, bool official
)
444 ProjectProperties pp
;
445 pp
.lProjectLanguage
= -1;
446 pp
.sUrl
= versioncheck
.GetTortoiseGitIssuesURL();
447 if (!pp
.sUrl
.IsEmpty())
449 pp
.SetCheckRe(L
"[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+");
450 pp
.SetBugIDRe(L
"(\\d+)");
452 m_cLogMessage
.Init(pp
);
454 CString sChangelogURL
;
455 sChangelogURL
.FormatMessage(versioncheck
.GetTortoiseGitChangelogURL(), TGIT_VERMAJOR
, TGIT_VERMINOR
, TGIT_VERMICRO
, (LPCTSTR
)m_updateDownloader
->m_sWindowsPlatform
, (LPCTSTR
)m_updateDownloader
->m_sWindowsVersion
, (LPCTSTR
)m_updateDownloader
->m_sWindowsServicePack
);
457 CString tempchangelogfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
459 if ((err
= m_updateDownloader
->DownloadFile(sChangelogURL
, tempchangelogfile
, false)) != ERROR_SUCCESS
)
461 CString msg
= L
"Could not load changelog.\r\nError: " + GetWinINetError(err
) + L
" (on " + sChangelogURL
+ L
")";
462 ::SendMessage(m_hWnd
, WM_USER_FILLCHANGELOG
, 0, reinterpret_cast<LPARAM
>((LPCTSTR
)msg
));
467 CString signatureTempfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
468 if ((err
= m_updateDownloader
->DownloadFile(sChangelogURL
+ SIGNATURE_FILE_ENDING
, signatureTempfile
, false)) != ERROR_SUCCESS
|| VerifyIntegrity(tempchangelogfile
, signatureTempfile
, m_updateDownloader
))
470 CString error
= L
"Could not verify digital signature.";
472 error
+= L
"\r\nError: " + GetWinINetError(err
) + L
" (on " + sChangelogURL
+ SIGNATURE_FILE_ENDING
+ L
")";
473 ::SendMessage(m_hWnd
, WM_USER_FILLCHANGELOG
, 0, reinterpret_cast<LPARAM
>((LPCTSTR
)error
));
474 DeleteUrlCacheEntry(sChangelogURL
);
475 DeleteUrlCacheEntry(sChangelogURL
+ SIGNATURE_FILE_ENDING
);
482 if (file
.Open(tempchangelogfile
, CFile::modeRead
| CFile::typeBinary
))
484 auto buf
= std::make_unique
<BYTE
[]>((UINT
)file
.GetLength());
485 UINT read
= file
.Read(buf
.get(), (UINT
)file
.GetLength());
486 bool skipBom
= read
>= 3 && buf
[0] == 0xEF && buf
[1] == 0xBB && buf
[2] == 0xBF;
487 CGit::StringAppend(&temp
, buf
.get() + (skipBom
? 3 : 0), CP_UTF8
, read
- (skipBom
? 3 : 0));
490 temp
= L
"Could not open downloaded changelog file.";
491 ::SendMessage(m_hWnd
, WM_USER_FILLCHANGELOG
, 0, reinterpret_cast<LPARAM
>((LPCTSTR
)temp
));
494 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent
)
498 if (m_bThreadRunning
== FALSE
)
504 ShowWindow(SW_SHOWNORMAL
);
510 CResizableStandAloneDialog::OnTimer(nIDEvent
);
513 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS
* lpwndpos
)
515 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos
);
516 if (m_bVisible
== FALSE
)
517 lpwndpos
->flags
&= ~SWP_SHOWWINDOW
;
520 BOOL
CCheckForUpdatesDlg::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
522 HCURSOR hCur
= LoadCursor(nullptr, IDC_ARROW
);
524 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
527 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
530 m_ctrlUpdate
.GetWindowText(title
);
531 if (!m_pDownloadThread
&& title
== CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD
)))
533 bool isOneSelected
= false;
534 for (int i
= 0; i
< (int)m_ctrlFiles
.GetItemCount(); ++i
)
536 if (m_ctrlFiles
.GetCheck(i
))
538 isOneSelected
= true;
545 m_eventStop
.ResetEvent();
547 m_pDownloadThread
= ::AfxBeginThread(DownloadThreadEntry
, this, THREAD_PRIORITY_NORMAL
, 0, CREATE_SUSPENDED
);
548 if (m_pDownloadThread
)
550 m_pDownloadThread
->m_bAutoDelete
= FALSE
;
551 m_pDownloadThread
->ResumeThread();
553 GetDlgItem(IDC_BUTTON_UPDATE
)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)));
556 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
558 else if (title
== CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)))
561 m_eventStop
.SetEvent();
565 CString folder
= GetDownloadsDirectory();
566 if (m_ctrlUpdate
.GetCurrentEntry() == 0)
568 for (int i
= 0; i
< (int)m_ctrlFiles
.GetItemCount(); ++i
)
570 CUpdateListCtrl::Entry
*data
= (CUpdateListCtrl::Entry
*)m_ctrlFiles
.GetItemData(i
);
571 if (m_ctrlFiles
.GetCheck(i
) == TRUE
)
572 ShellExecute(GetSafeHwnd(), L
"open", folder
+ data
->m_filename
, nullptr, nullptr, SW_SHOWNORMAL
);
574 CResizableStandAloneDialog::OnOK();
576 else if (m_ctrlUpdate
.GetCurrentEntry() == 1)
577 ShellExecute(GetSafeHwnd(), L
"open", folder
, nullptr, nullptr, SW_SHOWNORMAL
);
579 m_ctrlUpdate
.SetCurrentEntry(0);
583 UINT
CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid
)
585 return reinterpret_cast<CCheckForUpdatesDlg
*>(pVoid
)->DownloadThread();
588 bool CCheckForUpdatesDlg::VerifyUpdateFile(const CString
& filename
, const CString
& filenameSignature
, const CString
& reportingFilename
)
590 if (VerifyIntegrity(filename
, filenameSignature
, m_updateDownloader
) != 0)
592 m_sErrors
+= reportingFilename
+ SIGNATURE_FILE_ENDING
+ L
": Invalid digital signature.\r\n";
598 if ((ret
= MsiGetSummaryInformation(NULL
, filename
, 0, &hSummary
)) != 0)
600 CString sFileVer
= CPathUtils::GetVersionFromFile(filename
);
602 if (sFileVer
.IsEmpty())
604 m_sErrors
.AppendFormat(L
"%s: Invalid filetype found (neither executable nor MSI).\r\n", (LPCTSTR
)reportingFilename
);
605 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": MsiGetSummaryInformation reported: %s\n", (LPCTSTR
)CFormatMessageWrapper(ret
));
608 else if (sFileVer
== m_sNewVersionNumber
)
611 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
);
614 SCOPE_EXIT
{ MsiCloseHandle(hSummary
); };
617 DWORD cchValue
= 4096;
620 if (MsiSummaryInfoGetProperty(hSummary
, PID_SUBJECT
, &uiDataType
, &intValue
, nullptr, CStrBuf(buffer
, cchValue
+ 1), &cchValue
))
622 m_sErrors
.AppendFormat(L
"%s: Error obtaining version of MSI file (%s).\r\n", (LPCTSTR
)reportingFilename
, (LPCTSTR
)CFormatMessageWrapper(ret
));
626 if (VT_LPSTR
!= uiDataType
)
628 m_sErrors
.AppendFormat(L
"%s: Error obtaining version of MSI file (invalid data type returned).\r\n", (LPCTSTR
)reportingFilename
);
632 CString sFileVer
= buffer
.Right(m_sNewVersionNumber
.GetLength() + 1);
633 if (sFileVer
!= L
"v" + m_sNewVersionNumber
)
635 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
);
642 bool CCheckForUpdatesDlg::Download(CString filename
)
644 CString url
= m_sFilesURL
+ filename
;
645 CString destFilename
= GetDownloadsDirectory() + filename
;
646 if (PathFileExists(destFilename
) && PathFileExists(destFilename
+ SIGNATURE_FILE_ENDING
))
648 if (VerifyUpdateFile(destFilename
, destFilename
+ SIGNATURE_FILE_ENDING
, destFilename
))
652 DeleteFile(destFilename
);
653 DeleteFile(destFilename
+ SIGNATURE_FILE_ENDING
);
654 DeleteUrlCacheEntry(url
);
655 DeleteUrlCacheEntry(url
+ SIGNATURE_FILE_ENDING
);
659 m_progress
.SetRange32(0, 1);
660 m_progress
.SetPos(0);
661 m_progress
.ShowWindow(SW_SHOW
);
664 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
665 m_pTaskbarList
->SetProgressValue(m_hWnd
, 0, 1);
668 CString tempfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
669 CString signatureTempfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
670 DWORD ret
= m_updateDownloader
->DownloadFile(url
, tempfile
, true);
673 ret
= m_updateDownloader
->DownloadFile(url
+ SIGNATURE_FILE_ENDING
, signatureTempfile
, true);
675 m_sErrors
+= url
+ SIGNATURE_FILE_ENDING
+ L
": " + GetWinINetError(ret
) + L
"\r\n";
676 m_progress
.SetPos(m_progress
.GetPos() + 1);
679 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
680 int minValue
, maxValue
;
681 m_progress
.GetRange(minValue
, maxValue
);
682 m_pTaskbarList
->SetProgressValue(m_hWnd
, m_progress
.GetPos(), maxValue
);
686 m_sErrors
+= url
+ L
": " + GetWinINetError(ret
) + L
"\r\n";
689 if (VerifyUpdateFile(tempfile
, signatureTempfile
, url
))
691 DeleteFile(destFilename
);
692 DeleteFile(destFilename
+ SIGNATURE_FILE_ENDING
);
693 if (!MoveFile(tempfile
, destFilename
))
695 m_sErrors
.AppendFormat(L
"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR
)tempfile
, (LPCTSTR
)destFilename
);
698 if (!MoveFile(signatureTempfile
, destFilename
+ SIGNATURE_FILE_ENDING
))
700 m_sErrors
.AppendFormat(L
"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR
)signatureTempfile
, (LPCTSTR
)(destFilename
+ SIGNATURE_FILE_ENDING
));
705 DeleteUrlCacheEntry(url
);
706 DeleteUrlCacheEntry(url
+ SIGNATURE_FILE_ENDING
);
711 UINT
CCheckForUpdatesDlg::DownloadThread()
713 m_ctrlFiles
.SetExtendedStyle(m_ctrlFiles
.GetExtendedStyle() & ~LVS_EX_CHECKBOXES
);
716 for (int i
= 0; i
< (int)m_ctrlFiles
.GetItemCount(); ++i
)
718 m_ctrlFiles
.EnsureVisible(i
, FALSE
);
720 m_ctrlFiles
.GetItemRect(i
, &rect
, LVIR_BOUNDS
);
721 CUpdateListCtrl::Entry
*data
= (CUpdateListCtrl::Entry
*)m_ctrlFiles
.GetItemData(i
);
722 if (m_ctrlFiles
.GetCheck(i
) == TRUE
)
724 data
->m_status
= CUpdateListCtrl::STATUS_DOWNLOADING
;
725 m_ctrlFiles
.InvalidateRect(rect
);
726 if (Download(data
->m_filename
))
727 data
->m_status
= CUpdateListCtrl::STATUS_SUCCESS
;
730 data
->m_status
= CUpdateListCtrl::STATUS_FAIL
;
735 data
->m_status
= CUpdateListCtrl::STATUS_IGNORE
;
736 m_ctrlFiles
.InvalidateRect(rect
);
739 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD
, 0, 0);
744 LRESULT
CCheckForUpdatesDlg::OnEndDownload(WPARAM
, LPARAM
)
746 ASSERT(m_pDownloadThread
);
748 // wait until the thread terminates
750 if (::GetExitCodeThread(m_pDownloadThread
->m_hThread
, &dwExitCode
) && dwExitCode
== STILL_ACTIVE
)
751 ::WaitForSingleObject(m_pDownloadThread
->m_hThread
, INFINITE
);
753 // make sure we always have the correct exit code
754 ::GetExitCodeThread(m_pDownloadThread
->m_hThread
, &dwExitCode
);
756 delete m_pDownloadThread
;
757 m_pDownloadThread
= nullptr;
759 m_progress
.ShowWindow(SW_HIDE
);
761 if (dwExitCode
== TRUE
)
763 m_ctrlUpdate
.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL
)));
764 m_ctrlUpdate
.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER
)));
765 m_ctrlUpdate
.Invalidate();
767 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
771 m_ctrlUpdate
.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD
)));
773 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_ERROR
);
775 tmp
.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD
);
776 if (!m_sErrors
.IsEmpty())
777 tmp
+= L
"\r\n\r\nErrors:\r\n" + m_sErrors
;
778 CMessageBox::Show(GetSafeHwnd(), tmp
, L
"TortoiseGit", MB_ICONERROR
);
780 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
786 LRESULT
CCheckForUpdatesDlg::OnFillChangelog(WPARAM
, LPARAM lParam
)
790 LPCTSTR changelog
= reinterpret_cast<LPCTSTR
>(lParam
);
791 m_cLogMessage
.Call(SCI_SETREADONLY
, FALSE
);
792 m_cLogMessage
.SetText(changelog
);
793 m_cLogMessage
.Call(SCI_SETREADONLY
, TRUE
);
794 m_cLogMessage
.Call(SCI_GOTOPOS
, 0);
795 m_cLogMessage
.Call(SCI_SETWRAPSTARTINDENT
, 3);
800 CString
CCheckForUpdatesDlg::GetDownloadsDirectory()
804 PWSTR wcharPtr
= nullptr;
805 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads
, KF_FLAG_CREATE
, nullptr, &wcharPtr
)))
808 CoTaskMemFree(wcharPtr
);
809 return folder
.TrimRight(L
'\\') + L
'\\';
815 LRESULT
CCheckForUpdatesDlg::OnDisplayStatus(WPARAM
, LPARAM lParam
)
817 const CUpdateDownloader::DOWNLOADSTATUS
*const pDownloadStatus
= reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS
*>(lParam
);
820 ASSERT(::AfxIsValidAddress(pDownloadStatus
, sizeof(CUpdateDownloader::DOWNLOADSTATUS
)));
822 m_progress
.SetRange32(0, pDownloadStatus
->ulProgressMax
);
823 m_progress
.SetPos(pDownloadStatus
->ulProgress
);
826 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
827 m_pTaskbarList
->SetProgressValue(m_hWnd
, pDownloadStatus
->ulProgress
, pDownloadStatus
->ulProgressMax
);
834 LRESULT
CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
836 m_pTaskbarList
.Release();
837 m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
);
841 CString
CCheckForUpdatesDlg::GetWinINetError(DWORD err
)
843 CString readableError
= CFormatMessageWrapper(err
);
844 if (readableError
.IsEmpty())
846 for (const CString
& module
: { L
"wininet.dll", L
"urlmon.dll" })
849 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_FROM_HMODULE
, GetModuleHandle(module
), err
, 0, (LPTSTR
)&buffer
, 0, nullptr);
850 readableError
= buffer
;
852 if (!readableError
.IsEmpty())
856 return readableError
.Trim();
859 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
861 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS
, IDS_APPNAME
, 2, IDI_QUESTION
, IDS_DISABLEUPDATECHECKSBUTTON
, IDS_ABORTBUTTON
) == 1)
863 CRegDWORD(L
"Software\\TortoiseGit\\VersionCheck") = FALSE
;
868 void CCheckForUpdatesDlg::OnSysColorChange()
870 __super::OnSysColorChange();
871 m_cLogMessage
.SetColors(true);
872 m_cLogMessage
.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());