Terminate thread if it still runs after one second
[TortoiseGit.git] / src / TortoiseProc / GravatarPictureBox.cpp
blobca863acb0217d42fb45c5f8838b8861c6081a37e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2017, 2019 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "GravatarPictureBox.h"
21 #include "Picture.h"
22 #include "MessageBox.h"
23 #include "UnicodeUtils.h"
24 #include "Git.h"
25 #include "LoglistCommonResource.h"
26 #include "resource.h"
27 #include <WinCrypt.h>
28 #include "SmartHandle.h"
30 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
32 static CString CalcMD5(CString text)
34 HCRYPTPROV hProv = 0;
35 if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
36 return L"";
37 SCOPE_EXIT { CryptReleaseContext(hProv, 0); };
39 HCRYPTHASH hHash = 0;
40 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
41 return L"";
42 SCOPE_EXIT { CryptDestroyHash(hHash); };
44 CStringA textA = CUnicodeUtils::GetUTF8(text);
45 if (!CryptHashData(hHash, (LPBYTE)(LPCSTR)textA, textA.GetLength(), 0))
46 return L"";
48 CString hash;
49 BYTE rgbHash[16];
50 DWORD cbHash = _countof(rgbHash);
51 if (!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
52 return L"";
54 for (DWORD i = 0; i < cbHash; i++)
56 BYTE hi = rgbHash[i] >> 4;
57 BYTE lo = rgbHash[i] & 0xf;
58 hash.AppendChar(hi + (hi > 9 ? 87 : 48));
59 hash.AppendChar(lo + (lo > 9 ? 87 : 48));
62 return hash;
65 BEGIN_MESSAGE_MAP(CGravatar, CStatic)
66 ON_WM_PAINT()
67 END_MESSAGE_MAP()
69 CGravatar::CGravatar()
70 : CStatic()
71 , m_gravatarEvent(INVALID_HANDLE_VALUE)
72 , m_gravatarThread(nullptr)
73 , m_gravatarExit(nullptr)
74 , m_bEnableGravatar(false)
78 CGravatar::~CGravatar()
80 SafeTerminateGravatarThread();
81 if (m_gravatarEvent != INVALID_HANDLE_VALUE)
82 CloseHandle(m_gravatarEvent);
85 void CGravatar::Init()
87 if (m_bEnableGravatar)
89 if (m_gravatarEvent == INVALID_HANDLE_VALUE)
90 m_gravatarEvent = ::CreateEvent(nullptr, FALSE, TRUE, nullptr);
91 if (m_gravatarThread == nullptr)
93 m_gravatarExit = new bool(false);
94 m_gravatarThread = AfxBeginThread([](LPVOID lpVoid) -> UINT { reinterpret_cast<CGravatar*>(lpVoid)->GravatarThread(); return 0; }, this, THREAD_PRIORITY_BELOW_NORMAL, 0, CREATE_SUSPENDED);
95 if (m_gravatarThread == nullptr)
97 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
98 delete m_gravatarExit;
99 m_gravatarExit = nullptr;
100 return;
102 m_gravatarThread->m_bAutoDelete = FALSE;
103 m_gravatarThread->ResumeThread();
108 void CGravatar::LoadGravatar(CString email)
110 if (m_gravatarThread == nullptr)
111 return;
113 if (email.IsEmpty())
115 m_gravatarLock.Lock();
116 m_email.Empty();
117 if (!m_filename.IsEmpty())
119 m_filename.Empty();
120 m_gravatarLock.Unlock();
121 Invalidate();
123 else
124 m_gravatarLock.Unlock();
125 return;
128 email.Trim();
129 email.MakeLower();
130 m_gravatarLock.Lock();
131 bool diff = m_email != email;
132 m_email = email;
133 m_gravatarLock.Unlock();
134 if (diff)
135 ::SetEvent(m_gravatarEvent);
138 void CGravatar::GravatarThread()
140 bool *gravatarExit = m_gravatarExit;
141 CString gravatarBaseUrl = CRegString(L"Software\\TortoiseGit\\GravatarUrl", L"http://www.gravatar.com/avatar/%HASH%?d=identicon");
143 CString hostname;
144 CString baseUrlPath;
145 URL_COMPONENTS urlComponents = {0};
146 urlComponents.dwStructSize = sizeof(urlComponents);
147 urlComponents.lpszHostName = hostname.GetBuffer(INTERNET_MAX_HOST_NAME_LENGTH);
148 urlComponents.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
149 urlComponents.lpszUrlPath = baseUrlPath.GetBuffer(INTERNET_MAX_PATH_LENGTH);
150 urlComponents.dwUrlPathLength = INTERNET_MAX_PATH_LENGTH;
151 if (!InternetCrackUrl(gravatarBaseUrl, gravatarBaseUrl.GetLength(), 0, &urlComponents))
153 m_filename.Empty();
154 return;
156 hostname.ReleaseBuffer();
157 baseUrlPath.ReleaseBuffer();
159 HINTERNET hOpenHandle = InternetOpen(L"TortoiseGit", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
160 SCOPE_EXIT { InternetCloseHandle(hOpenHandle); };
161 bool isHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
162 HINTERNET hConnectHandle = InternetConnect(hOpenHandle, hostname, urlComponents.nPort, nullptr, nullptr, isHttps ? INTERNET_SCHEME_HTTP : urlComponents.nScheme, 0, 0);
163 if (!hConnectHandle)
165 m_filename.Empty();
166 return;
168 SCOPE_EXIT { InternetCloseHandle(hConnectHandle); };
170 while (!*gravatarExit)
172 ::WaitForSingleObject(m_gravatarEvent, INFINITE);
173 while (!*gravatarExit)
175 m_gravatarLock.Lock();
176 CString email = m_email;
177 m_gravatarLock.Unlock();
178 if (email.IsEmpty())
179 break;
181 Sleep(500);
182 if (*gravatarExit)
183 break;
184 m_gravatarLock.Lock();
185 bool diff = email != m_email;
186 m_gravatarLock.Unlock();
187 if (diff)
188 continue;
190 CString md5 = CalcMD5(email);
191 if (md5.IsEmpty())
192 continue;
194 CString gravatarUrl = baseUrlPath;
195 gravatarUrl.Replace(L"%HASH%", md5);
196 CString tempFile;
197 GetTempPath(tempFile);
198 tempFile += md5;
199 if (PathFileExists(tempFile))
201 CAutoLocker lock(m_gravatarLock);
202 if (m_email == email)
204 m_filename = tempFile;
205 m_email.Empty();
207 else
208 m_filename.Empty();
210 else
212 BOOL ret = DownloadToFile(gravatarExit, hConnectHandle, isHttps, gravatarUrl, tempFile);
213 if (ret)
215 DeleteFile(tempFile);
216 if (*gravatarExit)
217 break;
218 CAutoLocker lock(m_gravatarLock);
219 m_filename.Empty();
221 if (*gravatarExit)
222 break;
223 CAutoLocker lock(m_gravatarLock);
224 if (m_email == email && !ret)
226 CAutoFile hFile = CreateFile(tempFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
227 FILETIME creationTime = {};
228 GetFileTime(hFile, &creationTime, nullptr, nullptr);
229 uint64_t delta = 7 * 24 * 60 * 60 * 10000000LL;
230 DWORD low = creationTime.dwLowDateTime;
231 creationTime.dwLowDateTime += delta & 0xffffffff;
232 creationTime.dwHighDateTime += delta >> 32;
233 if (creationTime.dwLowDateTime < low)
234 creationTime.dwHighDateTime++;
235 SetFileTime(hFile, &creationTime, nullptr, nullptr);
236 m_filename = tempFile;
237 m_email.Empty();
239 else
240 m_filename.Empty();
242 if (*gravatarExit)
243 break;
244 Invalidate();
249 int CGravatar::DownloadToFile(bool* gravatarExit, const HINTERNET hConnectHandle, bool isHttps, const CString& urlpath, const CString& dest)
251 HINTERNET hResourceHandle = HttpOpenRequest(hConnectHandle, nullptr, urlpath, nullptr, nullptr, nullptr, INTERNET_FLAG_KEEP_CONNECTION | (isHttps ? INTERNET_FLAG_SECURE : 0), 0);
252 if (!hResourceHandle)
253 return -1;
255 SCOPE_EXIT { InternetCloseHandle(hResourceHandle); };
256 resend:
257 if (*gravatarExit)
258 return (int)INET_E_DOWNLOAD_FAILURE;
260 BOOL httpsendrequest = HttpSendRequest(hResourceHandle, nullptr, 0, nullptr, 0);
262 DWORD dwError = InternetErrorDlg(GetSafeHwnd(), hResourceHandle, ERROR_SUCCESS, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, nullptr);
264 if (dwError == ERROR_INTERNET_FORCE_RETRY)
265 goto resend;
266 else if (!httpsendrequest || *gravatarExit)
267 return (int)INET_E_DOWNLOAD_FAILURE;
269 DWORD statusCode = 0;
270 DWORD length = sizeof(statusCode);
271 if (!HttpQueryInfo(hResourceHandle, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, (LPVOID)&statusCode, &length, nullptr) || statusCode != 200)
273 if (statusCode == 404)
274 return ERROR_FILE_NOT_FOUND;
275 else if (statusCode == 403)
276 return ERROR_ACCESS_DENIED;
277 return (int)INET_E_DOWNLOAD_FAILURE;
280 CFile destinationFile;
281 if (!destinationFile.Open(dest, CFile::modeCreate | CFile::modeWrite))
282 return ERROR_ACCESS_DENIED;
284 DWORD downloadedSum = 0; // sum of bytes downloaded so far
285 while (!*gravatarExit)
287 DWORD size; // size of the data available
288 if (!InternetQueryDataAvailable(hResourceHandle, &size, 0, 0))
289 return (int)INET_E_DOWNLOAD_FAILURE;
291 DWORD downloaded; // size of the downloaded data
292 auto buff = std::make_unique<TCHAR[]>(size + 1);
293 if (!InternetReadFile(hResourceHandle, (LPVOID)buff.get(), size, &downloaded))
294 return (int)INET_E_DOWNLOAD_FAILURE;
296 if (downloaded == 0)
297 break;
299 buff[downloaded] = '\0';
300 destinationFile.Write(buff.get(), downloaded);
302 downloadedSum += downloaded;
304 destinationFile.Close();
305 if (downloadedSum == 0)
306 return (int)INET_E_DOWNLOAD_FAILURE;
308 return 0;
311 void CGravatar::SafeTerminateGravatarThread()
313 if (m_gravatarExit != nullptr)
314 *m_gravatarExit = true;
315 if (m_gravatarThread)
317 ::SetEvent(m_gravatarEvent);
318 if (::WaitForSingleObject(m_gravatarThread->m_hThread, 1000) == WAIT_TIMEOUT)
319 ::TerminateThread(m_gravatarThread, 0);
320 delete m_gravatarThread;
321 m_gravatarThread = nullptr;
323 delete m_gravatarExit;
324 m_gravatarExit = nullptr;
327 void CGravatar::OnPaint()
329 CPaintDC dc(this);
330 RECT rect;
331 GetClientRect(&rect);
332 dc.FillSolidRect(&rect, GetSysColor(COLOR_BTNFACE));
333 m_gravatarLock.Lock();
334 CString filename = m_filename;
335 m_gravatarLock.Unlock();
336 if (filename.IsEmpty()) return;
337 CPicture picture;
338 picture.Load(filename.GetString());
339 picture.Show(dc, rect);