Apply backgroundcolors.patch
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blob0dc7c4c74b62fd0ad6f2ed39b62c3d6c0f97e299
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005 - 2006,2010 - Will Dean, Stefan Kueng
4 // Copyright (C) 2008-2014, 2016-2022 - 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 "stdafx.h"
22 #include <ShellAPI.h>
23 #include "TGITCache.h"
24 #include "GitStatusCache.h"
25 #include "CacheInterface.h"
26 #include "Resource.h"
27 #include "registry.h"
28 #include "CrashReport.h"
29 #include "GitAdminDir.h"
30 #include <Dbt.h>
31 #include <InitGuid.h>
32 #include <Ioevent.h>
33 #include <wrl/client.h>
35 #include "../version.h"
36 #include "SmartHandle.h"
37 #include "CreateProcessHelper.h"
38 #include "gitindex.h"
39 #include "LoadIconEx.h"
40 #include "../Git/Git.h"
42 #ifndef GET_X_LPARAM
43 #define GET_X_LPARAM(lp) static_cast<int>(static_cast<short>(LOWORD(lp)))
44 #endif
45 #ifndef GET_Y_LPARAM
46 #define GET_Y_LPARAM(lp) static_cast<int>(static_cast<short>(HIWORD(lp)))
47 #endif
50 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
52 #if ENABLE_CRASHHANLDER && !_M_ARM64
53 CCrashReportTGit crasher(L"TGitCache " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);
54 #endif
56 DWORD WINAPI ExplorerMonitorThread(LPVOID);
57 DWORD WINAPI InstanceThread(LPVOID);
58 DWORD WINAPI PipeThread(LPVOID);
59 DWORD WINAPI CommandWaitThread(LPVOID);
60 DWORD WINAPI CommandThread(LPVOID);
61 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
62 bool bRun = true;
63 bool bRestart = false;
64 NOTIFYICONDATA niData;
65 HWND hWndHidden;
66 HWND hTrayWnd;
67 wchar_t szCurrentCrawledPath[MAX_CRAWLEDPATHS][MAX_CRAWLEDPATHSLEN];
68 int nCurrentCrawledpathIndex = 0;
69 CComAutoCriticalSection critSec;
70 extern CGitIndexFileMap g_IndexFileMap;
72 // must put this before any global variables that auto free git objects,
73 // so this destructor is called after freeing git objects
74 class CGit2InitClass
76 public:
77 ~CGit2InitClass()
79 git_libgit2_shutdown();
80 if (niData.hWnd)
81 Shell_NotifyIcon(NIM_DELETE, &niData);
83 } git2init;
85 volatile LONG nThreadCount = 0;
87 #define PACKVERSION(major,minor) MAKELONG(minor,major)
89 void HandleCommandLine(LPSTR lpCmdLine)
91 char *ptr = strstr(lpCmdLine, "/kill:");
92 if (ptr)
94 DWORD pid = static_cast<DWORD>(atoi(ptr + strlen("/kill:")));
95 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, pid);
96 if (hProcess)
98 if (::WaitForSingleObject(hProcess, 5000) != WAIT_OBJECT_0)
100 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Killing previous TGitCache PID %d\n", pid);
101 if (!::TerminateProcess(hProcess, static_cast<UINT>(-1)))
102 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Kill previous TGitCache PID %d failed\n", pid);
103 ::WaitForSingleObject(hProcess, 5000);
105 ::CloseHandle(hProcess);
106 for (int i = 0; i < 5; i++)
108 HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, GetCacheMutexName());
109 if (!hMutex)
110 break;
111 ::CloseHandle(hMutex);
112 ::Sleep(1000);
118 void HandleRestart()
120 if (bRestart)
122 wchar_t exeName[MAX_PATH] = { 0 };
123 ::GetModuleFileName(nullptr, exeName, _countof(exeName));
124 wchar_t cmdLine[20] = { 0 };
125 swprintf_s(cmdLine, L" /kill:%d", GetCurrentProcessId());
126 if (!CCreateProcessHelper::CreateProcessDetached(exeName, cmdLine))
127 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
131 static void AddSystrayIcon()
133 if (CRegStdDWORD(L"Software\\TortoiseGit\\CacheTrayIcon", FALSE) != TRUE)
134 return;
136 SecureZeroMemory(&niData, sizeof(NOTIFYICONDATA));
137 niData.cbSize = sizeof(NOTIFYICONDATA);
138 niData.uID = TRAY_ID; // own tray icon ID
139 niData.hWnd = hWndHidden;
140 niData.uFlags = NIF_ICON | NIF_MESSAGE;
142 // load the icon
143 niData.hIcon = LoadIconEx(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_TGITCACHE));
145 // set the message to send
146 // note: the message value should be in the
147 // range of WM_APP through 0xBFFF
148 niData.uCallbackMessage = TRAY_CALLBACK;
149 Shell_NotifyIcon(NIM_ADD, &niData);
150 // free icon handle
151 if (niData.hIcon && DestroyIcon(niData.hIcon))
152 niData.hIcon = nullptr;
155 int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR lpCmdLine, int /*cmdShow*/)
157 SetDllDirectory(L"");
158 git_libgit2_init();
159 git_libgit2_opts(GIT_OPT_SET_WINDOWS_SHAREMODE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
160 HandleCommandLine(lpCmdLine);
161 CAutoGeneralHandle hReloadProtection = ::CreateMutex(nullptr, FALSE, GetCacheMutexName());
163 if ((!hReloadProtection) || (GetLastError() == ERROR_ALREADY_EXISTS))
165 // An instance of TGitCache is already running
166 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": TGitCache ignoring restart\n");
167 return 0;
170 CGitStatusCache::Create();
171 CGitStatusCache::Instance().Init();
173 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
175 DWORD dwThreadId;
176 MSG msg;
177 wchar_t szWindowClass[] = {TGIT_CACHE_WINDOW_NAME};
179 // create a hidden window to receive window messages.
180 WNDCLASSEX wcex = { 0 };
181 wcex.cbSize = sizeof(WNDCLASSEX);
182 wcex.style = CS_HREDRAW | CS_VREDRAW;
183 wcex.lpfnWndProc = reinterpret_cast<WNDPROC>(WndProc);
184 wcex.hInstance = hInstance;
185 wcex.lpszClassName = szWindowClass;
186 RegisterClassEx(&wcex);
187 hWndHidden = CreateWindow(TGIT_CACHE_WINDOW_NAME, TGIT_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, nullptr, 0, hInstance, 0);
188 hTrayWnd = hWndHidden;
189 if (!hWndHidden)
190 return 0;
192 // Create a thread which waits for incoming pipe connections
193 CAutoGeneralHandle hPipeThread = CreateThread(
194 nullptr, // no security attribute
195 0, // default stack size
196 PipeThread,
197 &bRun, // thread parameter
198 0, // not suspended
199 &dwThreadId); // returns thread ID
201 if (!hPipeThread)
202 return 0;
203 else hPipeThread.CloseHandle();
205 // Create a thread which waits for incoming pipe connections
206 CAutoGeneralHandle hCommandWaitThread = CreateThread(
207 nullptr, // no security attribute
208 0, // default stack size
209 CommandWaitThread,
210 &bRun, // thread parameter
211 0, // not suspended
212 &dwThreadId); // returns thread ID
214 if (!hCommandWaitThread)
215 return 0;
217 // create a thread that monitors explorer windows
218 CAutoGeneralHandle hExplorerMonitorThread = CreateThread(nullptr, 0, ExplorerMonitorThread, &bRun, 0, &dwThreadId);
219 if (!hExplorerMonitorThread)
220 return 0;
222 AddSystrayIcon();
224 // loop to handle window messages.
225 while (bRun)
227 BOOL bLoopRet = GetMessage(&msg, nullptr, 0, 0);
228 if ((bLoopRet != -1)&&(bLoopRet != 0))
229 DispatchMessage(&msg);
232 bRun = false;
234 CGitStatusCache::Destroy();
235 HandleRestart();
236 return 0;
239 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
241 static UINT s_uTaskbarRestart = RegisterWindowMessage(L"TaskbarCreated");
242 switch (message)
244 case TRAY_CALLBACK:
246 switch(lParam)
248 case WM_LBUTTONDBLCLK:
249 if (IsWindowVisible(hWnd))
250 ShowWindow(hWnd, SW_HIDE);
251 else
252 ShowWindow(hWnd, SW_RESTORE);
253 break;
254 case WM_MOUSEMOVE:
256 CString sInfoTip;
257 NOTIFYICONDATA SystemTray;
258 sInfoTip.Format(L"TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d",
259 CGitStatusCache::Instance().GetCacheSize(),
260 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
262 SystemTray.cbSize = sizeof(NOTIFYICONDATA);
263 SystemTray.hWnd = hTrayWnd;
264 SystemTray.uID = TRAY_ID;
265 SystemTray.uFlags = NIF_TIP;
266 wcscpy_s(SystemTray.szTip, sInfoTip);
267 Shell_NotifyIcon(NIM_MODIFY, &SystemTray);
269 break;
270 case WM_RBUTTONUP:
271 case WM_CONTEXTMENU:
273 POINT pt;
274 GetCursorPos(&pt);
275 HMENU hMenu = CreatePopupMenu();
276 if(hMenu)
278 bool enabled = static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe)) != ShellCache::none;
279 InsertMenu(hMenu, static_cast<UINT>(-1), MF_BYPOSITION, TRAYPOP_ENABLE, enabled ? L"Disable Status Cache" : L"Enable Status Cache");
280 InsertMenu(hMenu, static_cast<UINT>(-1), MF_BYPOSITION, TRAYPOP_EXIT, L"Exit");
281 SetForegroundWindow(hWnd);
282 TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, nullptr);
283 DestroyMenu(hMenu);
286 break;
289 break;
290 case WM_PAINT:
292 PAINTSTRUCT ps;
293 HDC hdc = BeginPaint(hWnd, &ps);
294 RECT rect;
295 GetClientRect(hWnd, &rect);
296 // clear the background
297 HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
298 HGDIOBJ oldbrush = SelectObject(hdc, background);
299 FillRect(hdc, &rect, background);
301 int line = 0;
302 SIZE fontsize = {0};
303 AutoLocker print(critSec);
304 GetTextExtentPoint32(hdc, szCurrentCrawledPath[0], static_cast<int>(wcslen(szCurrentCrawledPath[0])), &fontsize);
305 for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)
307 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], static_cast<int>(wcslen(szCurrentCrawledPath[i])));
308 ++line;
310 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
312 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], static_cast<int>(wcslen(szCurrentCrawledPath[i])));
313 ++line;
316 SelectObject(hdc,oldbrush);
317 EndPaint(hWnd, &ps);
318 DeleteObject(background);
319 return 0L;
321 break;
322 case WM_COMMAND:
324 WORD wmId = LOWORD(wParam);
326 switch (wmId)
328 case TRAYPOP_ENABLE:
330 CRegStdDWORD reg = CRegStdDWORD(L"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe);
331 bool enabled = static_cast<DWORD>(reg) != ShellCache::none;
332 reg = enabled ? ShellCache::none : ShellCache::exe;
333 if (enabled)
335 bRestart = true;
336 DestroyWindow(hWnd);
338 break;
340 case TRAYPOP_EXIT:
341 DestroyWindow(hWnd);
342 break;
344 return 1;
346 case WM_QUERYENDSESSION:
348 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
349 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
350 CGitStatusCache::Instance().Stop();
351 return TRUE;
353 break;
354 case WM_CLOSE:
355 case WM_ENDSESSION:
356 case WM_DESTROY:
357 case WM_QUIT:
359 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
360 if (niData.hWnd)
362 niData.hIcon = LoadIconEx(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_TGITCACHE_STOPPING));
363 Shell_NotifyIcon(NIM_MODIFY, &niData);
365 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
366 CGitStatusCache::Instance().Stop();
367 CGitStatusCache::Instance().SaveCache();
368 if (message != WM_QUIT)
369 PostQuitMessage(0);
370 bRun = false;
371 return 1;
373 break;
374 case WM_DEVICECHANGE:
376 auto phdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
377 switch (wParam)
379 case DBT_CUSTOMEVENT:
381 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
382 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
384 auto phandle = reinterpret_cast<DEV_BROADCAST_HANDLE*>(lParam);
385 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))
387 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device to be dismounted\n");
388 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
389 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
391 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))
393 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device lock event\n");
394 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
395 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
399 break;
400 case DBT_DEVICEREMOVEPENDING:
401 case DBT_DEVICEQUERYREMOVE:
402 case DBT_DEVICEREMOVECOMPLETE:
403 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
404 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
406 auto phandle = reinterpret_cast<DEV_BROADCAST_HANDLE*>(lParam);
407 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
408 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
410 else if (phdr->dbch_devicetype == DBT_DEVTYP_VOLUME)
412 auto pVolume = reinterpret_cast<DEV_BROADCAST_VOLUME*>(lParam);
413 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
414 for (BYTE i = 0; i < 26; ++i)
416 if (pVolume->dbcv_unitmask & (1 << i))
418 wchar_t driveletter = 'A' + i;
419 CString drive = CString(driveletter);
420 drive += L":\\";
421 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
425 else
427 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
428 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
430 break;
433 break;
434 default:
435 if (message == s_uTaskbarRestart)
436 AddSystrayIcon();
437 break;
439 return DefWindowProc(hWnd, message, wParam, lParam);
442 //////////////////////////////////////////////////////////////////////////
444 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
446 CTGitPath path;
447 *pResponseLength = 0;
448 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
449 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
450 else
451 path.SetFromWin(pRequest->path);
453 if (!bRun)
455 CStatusCacheEntry entry;
456 entry.BuildCacheResponse(*pReply, *pResponseLength);
457 return;
460 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
461 if (readLock.IsAcquired())
463 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": app asked for status of %s\n", pRequest->path);
464 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags).BuildCacheResponse(*pReply, *pResponseLength);
466 else
468 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": timeout for asked status of %s\n", pRequest->path);
469 CStatusCacheEntry entry;
470 entry.BuildCacheResponse(*pReply, *pResponseLength);
474 DWORD WINAPI ExplorerMonitorThread(LPVOID lpvParam)
476 auto bThreadRun = static_cast<bool*>(lpvParam);
477 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
479 Microsoft::WRL::ComPtr<IShellWindows> shellWindows;
480 if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, reinterpret_cast<void**>(shellWindows.GetAddressOf()))))
481 return 1;
483 auto titleTextBuf = std::make_unique<wchar_t[]>(MAX_PATH);
484 int titleTextBufLength = MAX_PATH;
485 while (*bThreadRun)
487 Sleep(500);
489 if (CRegStdDWORD(L"Software\\TortoiseGit\\ModifyExplorerTitle", TRUE) == FALSE)
491 Sleep(60000);
492 continue;
495 VARIANT v{};
496 V_VT(&v) = VT_I4;
497 long shellCount = 0;
498 if (shellWindows->get_Count(&shellCount) != S_OK)
499 continue;
501 for (long i = 0; i < shellCount; ++i)
503 v.lVal = i;
504 Microsoft::WRL::ComPtr<IDispatch> disp;
505 shellWindows->Item(v, disp.GetAddressOf());
506 if (!disp)
507 continue;
508 Microsoft::WRL::ComPtr<IWebBrowserApp> webBrowserApp;
509 if (FAILED(disp->QueryInterface(webBrowserApp.GetAddressOf())))
510 continue;
511 HWND hwndWba = nullptr;
512 if (FAILED(webBrowserApp->get_HWND(reinterpret_cast<LONG_PTR*>(&hwndWba))))
513 continue;
514 Microsoft::WRL::ComPtr<IServiceProvider> serviceProvider;
515 if (FAILED(webBrowserApp->QueryInterface(serviceProvider.GetAddressOf())))
516 continue;
517 Microsoft::WRL::ComPtr<IShellBrowser> shellBrowser;
518 if (FAILED(serviceProvider->QueryService(SID_STopLevelBrowser, shellBrowser.GetAddressOf())))
519 continue;
520 Microsoft::WRL::ComPtr<IShellView> psv;
521 if (FAILED(shellBrowser->QueryActiveShellView(psv.GetAddressOf())))
522 continue;
523 Microsoft::WRL::ComPtr<IFolderView> pfv;
524 if (FAILED(psv->QueryInterface(pfv.GetAddressOf())))
525 continue;
526 Microsoft::WRL::ComPtr<IPersistFolder2> ppf2;
527 if (FAILED(pfv->GetFolder(IID_IPersistFolder2, &ppf2)))
528 continue;
529 CComHeapPtr<ITEMIDLIST> pidlFolder;
530 if (FAILED(ppf2->GetCurFolder(&pidlFolder)))
531 continue;
532 wchar_t szPath[MAX_PATH]{};
533 if (!SHGetPathFromIDList(pidlFolder, szPath))
534 continue;
536 CTGitPath path;
537 path.SetFromWin(szPath);
539 CAutoReadLock readLock(CGitStatusCache::Instance().GetGuard());
540 auto pCachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
541 if (!pCachedDir)
542 continue;
544 auto gitDir = pCachedDir->GetProjectRoot();
545 if (gitDir.IsEmpty())
546 continue;
548 auto sharedIndex = g_IndexFileMap.SafeGet(gitDir);
549 if (!sharedIndex)
550 continue;
552 auto textLen = GetWindowTextLength(hwndWba);
553 textLen += sharedIndex->m_branch.GetLength() + 40;
554 if (titleTextBufLength < textLen)
556 titleTextBufLength = textLen;
557 titleTextBuf = std::make_unique<wchar_t[]>(titleTextBufLength);
559 GetWindowText(hwndWba, titleTextBuf.get(), titleTextBufLength);
560 std::wstring sWindowText = titleTextBuf.get();
561 if (auto foundPos = sWindowText.find(L" ["); foundPos != std::wstring::npos)
562 sWindowText = sWindowText.substr(0, foundPos);
564 if (!sharedIndex->m_branch.IsEmpty())
566 sWindowText += L" [ ";
567 sWindowText += sharedIndex->m_branch;
568 if (sharedIndex->m_outgoing != static_cast<size_t>(-1))
570 // upstream branch available
571 CString inOut;
572 inOut.Format(L" \u2193%lld \u2191%lld", sharedIndex->m_incoming, sharedIndex->m_outgoing);
573 sWindowText += inOut;
575 else
576 sWindowText += L" \u2302";
577 if (sharedIndex->m_stashCount)
579 CString sStash;
580 sStash.Format(L" \u205E%lld", sharedIndex->m_stashCount);
581 sWindowText += sStash;
583 sWindowText += L" ]";
585 SetWindowText(hwndWba, sWindowText.c_str());
588 return 0;
591 DWORD WINAPI PipeThread(LPVOID lpvParam)
593 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": PipeThread started\n");
594 auto bThreadRun = reinterpret_cast<bool*>(lpvParam);
595 // The main loop creates an instance of the named pipe and
596 // then waits for a client to connect to it. When the client
597 // connects, a thread is created to handle communications
598 // with that client, and the loop is repeated.
599 DWORD dwThreadId;
600 BOOL fConnected;
601 CAutoFile hPipe;
603 while (*bThreadRun)
605 hPipe = CreateNamedPipe(
606 GetCachePipeName(),
607 PIPE_ACCESS_DUPLEX, // read/write access
608 PIPE_TYPE_MESSAGE | // message type pipe
609 PIPE_READMODE_MESSAGE | // message-read mode
610 PIPE_WAIT, // blocking mode
611 PIPE_UNLIMITED_INSTANCES, // max. instances
612 BUFSIZE, // output buffer size
613 BUFSIZE, // input buffer size
614 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
615 nullptr); // nullptr DACL
617 if (!hPipe)
619 if (*bThreadRun)
620 Sleep(200);
621 continue; // never leave the thread!
624 // Wait for the client to connect; if it succeeds,
625 // the function returns a nonzero value. If the function returns
626 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
627 fConnected = ConnectNamedPipe(hPipe, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
628 if (fConnected)
630 // Create a thread for this client.
631 CAutoGeneralHandle hInstanceThread = CreateThread(
632 nullptr, // no security attribute
633 0, // default stack size
634 InstanceThread,
635 hPipe, // thread parameter
636 0, // not suspended
637 &dwThreadId); // returns thread ID
639 if (!hInstanceThread)
641 DisconnectNamedPipe(hPipe);
642 // since we're now closing this thread, we also have to close the whole application!
643 // otherwise the thread is dead, but the app is still running, refusing new instances
644 // but no pipe will be available anymore.
645 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
646 return 1;
648 // detach the handle, since we passed it to the thread
649 hPipe.Detach();
651 else
653 // The client could not connect, so close the pipe.
654 hPipe.CloseHandle();
655 if (*bThreadRun)
656 Sleep(200);
657 continue; // don't end the thread!
660 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
661 return 0;
664 DWORD WINAPI CommandWaitThread(LPVOID lpvParam)
666 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWaitThread started\n");
667 auto bThreadRun = reinterpret_cast<bool*>(lpvParam);
668 // The main loop creates an instance of the named pipe and
669 // then waits for a client to connect to it. When the client
670 // connects, a thread is created to handle communications
671 // with that client, and the loop is repeated.
672 DWORD dwThreadId;
673 BOOL fConnected;
674 CAutoFile hPipe;
676 while (*bThreadRun)
678 hPipe = CreateNamedPipe(
679 GetCacheCommandPipeName(),
680 PIPE_ACCESS_DUPLEX, // read/write access
681 PIPE_TYPE_MESSAGE | // message type pipe
682 PIPE_READMODE_MESSAGE | // message-read mode
683 PIPE_WAIT, // blocking mode
684 PIPE_UNLIMITED_INSTANCES, // max. instances
685 BUFSIZE, // output buffer size
686 BUFSIZE, // input buffer size
687 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
688 nullptr); // nullptr DACL
690 if (!hPipe)
692 if (*bThreadRun)
693 Sleep(200);
694 continue; // never leave the thread!
697 // Wait for the client to connect; if it succeeds,
698 // the function returns a nonzero value. If the function returns
699 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
700 fConnected = ConnectNamedPipe(hPipe, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
701 if (fConnected)
703 // Create a thread for this client.
704 CAutoGeneralHandle hCommandThread = CreateThread(
705 nullptr, // no security attribute
706 0, // default stack size
707 CommandThread,
708 hPipe, // thread parameter
709 0, // not suspended
710 &dwThreadId); // returns thread ID
712 if (!hCommandThread)
714 DisconnectNamedPipe(hPipe);
715 hPipe.CloseHandle();
716 // since we're now closing this thread, we also have to close the whole application!
717 // otherwise the thread is dead, but the app is still running, refusing new instances
718 // but no pipe will be available anymore.
719 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
720 return 1;
722 // detach the handle, since we passed it to the thread
723 hPipe.Detach();
725 else
727 // The client could not connect, so close the pipe.
728 hPipe.CloseHandle();
729 if (*bThreadRun)
730 Sleep(200);
731 continue; // don't end the thread!
734 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
735 return 0;
738 DWORD WINAPI InstanceThread(LPVOID lpvParam)
740 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": InstanceThread started\n");
741 TGITCacheResponse response;
742 DWORD cbBytesRead, cbWritten;
744 // The thread's parameter is a handle to a pipe instance.
745 CAutoFile hPipe(std::move(lpvParam));
747 InterlockedIncrement(&nThreadCount);
748 while (bRun)
750 // Read client requests from the pipe.
751 TGITCacheRequest request;
752 BOOL fSuccess = ReadFile(
753 hPipe, // handle to pipe
754 &request, // buffer to receive data
755 sizeof(request), // size of buffer
756 &cbBytesRead, // number of bytes read
757 nullptr); // not overlapped I/O
759 if (! fSuccess || cbBytesRead == 0)
761 DisconnectNamedPipe(hPipe);
762 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
763 if (InterlockedDecrement(&nThreadCount) == 0)
764 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
765 return 1;
768 // sanitize request
769 request.path[_countof(request.path) - 1] = L'\0';
770 request.flags &= TGITCACHE_FLAGS_MASK;
772 DWORD responseLength;
773 GetAnswerToRequest(&request, &response, &responseLength);
775 // Write the reply to the pipe.
776 fSuccess = WriteFile(
777 hPipe, // handle to pipe
778 &response, // buffer to write from
779 responseLength, // number of bytes to write
780 &cbWritten, // number of bytes written
781 nullptr); // not overlapped I/O
783 if (! fSuccess || responseLength != cbWritten)
785 DisconnectNamedPipe(hPipe);
786 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
787 if (InterlockedDecrement(&nThreadCount) == 0)
788 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
789 return 1;
793 // Flush the pipe to allow the client to read the pipe's contents
794 // before disconnecting. Then disconnect the pipe, and close the
795 // handle to this pipe instance.
797 FlushFileBuffers(hPipe);
798 DisconnectNamedPipe(hPipe);
799 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
800 if (InterlockedDecrement(&nThreadCount) == 0)
801 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
802 return 0;
805 DWORD WINAPI CommandThread(LPVOID lpvParam)
807 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
808 DWORD cbBytesRead;
810 // The thread's parameter is a handle to a pipe instance.
811 CAutoFile hPipe(std::move(lpvParam));
813 while (bRun)
815 // Read client requests from the pipe.
816 TGITCacheCommand command;
817 BOOL fSuccess = ReadFile(
818 hPipe, // handle to pipe
819 &command, // buffer to receive data
820 sizeof(command), // size of buffer
821 &cbBytesRead, // number of bytes read
822 nullptr); // not overlapped I/O
824 if (! fSuccess || cbBytesRead == 0)
826 DisconnectNamedPipe(hPipe);
827 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
828 return 1;
831 command.path[_countof(command.path) - 1] = L'\0';
832 switch (command.command)
834 case TGITCACHECOMMAND_END:
835 FlushFileBuffers(hPipe);
836 DisconnectNamedPipe(hPipe);
837 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
838 return 0;
839 case TGITCACHECOMMAND_CRAWL:
841 CTGitPath changedpath;
842 changedpath.SetFromWin(command.path, true);
843 // remove the path from our cache - that will 'invalidate' it.
845 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
846 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
848 CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());
850 break;
851 case TGITCACHECOMMAND_REFRESHALL:
853 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
854 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": refresh all\n");
855 CGitStatusCache::Instance().Refresh();
857 break;
858 case TGITCACHECOMMAND_RELEASE:
860 CTGitPath changedpath;
861 changedpath.SetFromWin(command.path, true);
862 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": release handle for path %s\n", changedpath.GetWinPath());
863 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
864 CGitStatusCache::Instance().CloseWatcherHandles(changedpath);
865 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
866 auto cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(changedpath.GetContainingDirectory());
867 if (cachedDir)
868 cachedDir->Invalidate();
870 break;
871 case TGITCACHECOMMAND_BLOCK:
873 CTGitPath changedpath;
874 changedpath.SetFromWin(command.path);
875 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": block path %s\n", changedpath.GetWinPath());
876 CGitStatusCache::Instance().BlockPath(changedpath);
878 break;
879 case TGITCACHECOMMAND_UNBLOCK:
881 CTGitPath changedpath;
882 changedpath.SetFromWin(command.path);
883 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": unblock path %s\n", changedpath.GetWinPath());
884 CGitStatusCache::Instance().UnBlockPath(changedpath);
886 break;
890 // Flush the pipe to allow the client to read the pipe's contents
891 // before disconnecting. Then disconnect the pipe, and close the
892 // handle to this pipe instance.
894 FlushFileBuffers(hPipe);
895 DisconnectNamedPipe(hPipe);
896 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
897 return 0;