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.
23 #include "TGITCache.h"
24 #include "GitStatusCache.h"
25 #include "CacheInterface.h"
28 #include "CrashReport.h"
29 #include "GitAdminDir.h"
33 #include <wrl/client.h>
35 #include "../version.h"
36 #include "SmartHandle.h"
37 #include "CreateProcessHelper.h"
39 #include "LoadIconEx.h"
40 #include "../Git/Git.h"
43 #define GET_X_LPARAM(lp) static_cast<int>(static_cast<short>(LOWORD(lp)))
46 #define GET_Y_LPARAM(lp) static_cast<int>(static_cast<short>(HIWORD(lp)))
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
);
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
);
63 bool bRestart
= false;
64 NOTIFYICONDATA niData
;
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
79 git_libgit2_shutdown();
81 Shell_NotifyIcon(NIM_DELETE
, &niData
);
85 volatile LONG nThreadCount
= 0;
87 #define PACKVERSION(major,minor) MAKELONG(minor,major)
89 void HandleCommandLine(LPSTR lpCmdLine
)
91 char *ptr
= strstr(lpCmdLine
, "/kill:");
94 DWORD pid
= static_cast<DWORD
>(atoi(ptr
+ strlen("/kill:")));
95 HANDLE hProcess
= ::OpenProcess(PROCESS_TERMINATE
, FALSE
, pid
);
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());
111 ::CloseHandle(hMutex
);
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
)
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
;
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
);
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
"");
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");
170 CGitStatusCache::Create();
171 CGitStatusCache::Instance().Init();
173 SecureZeroMemory(szCurrentCrawledPath
, sizeof(szCurrentCrawledPath
));
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
;
192 // Create a thread which waits for incoming pipe connections
193 CAutoGeneralHandle hPipeThread
= CreateThread(
194 nullptr, // no security attribute
195 0, // default stack size
197 &bRun
, // thread parameter
199 &dwThreadId
); // returns thread ID
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
210 &bRun
, // thread parameter
212 &dwThreadId
); // returns thread ID
214 if (!hCommandWaitThread
)
217 // create a thread that monitors explorer windows
218 CAutoGeneralHandle hExplorerMonitorThread
= CreateThread(nullptr, 0, ExplorerMonitorThread
, &bRun
, 0, &dwThreadId
);
219 if (!hExplorerMonitorThread
)
224 // loop to handle window messages.
227 BOOL bLoopRet
= GetMessage(&msg
, nullptr, 0, 0);
228 if ((bLoopRet
!= -1)&&(bLoopRet
!= 0))
229 DispatchMessage(&msg
);
234 CGitStatusCache::Destroy();
239 LRESULT CALLBACK
WndProc(HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
241 static UINT s_uTaskbarRestart
= RegisterWindowMessage(L
"TaskbarCreated");
248 case WM_LBUTTONDBLCLK
:
249 if (IsWindowVisible(hWnd
))
250 ShowWindow(hWnd
, SW_HIDE
);
252 ShowWindow(hWnd
, SW_RESTORE
);
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
);
275 HMENU hMenu
= CreatePopupMenu();
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);
293 HDC hdc
= BeginPaint(hWnd
, &ps
);
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
);
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
])));
310 for (int i
=0; i
<nCurrentCrawledpathIndex
; ++i
)
312 TextOut(hdc
, 0, line
*fontsize
.cy
, szCurrentCrawledPath
[i
], static_cast<int>(wcslen(szCurrentCrawledPath
[i
])));
316 SelectObject(hdc
,oldbrush
);
318 DeleteObject(background
);
324 WORD wmId
= LOWORD(wParam
);
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
;
346 case WM_QUERYENDSESSION
:
348 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_QUERYENDSESSION\n");
349 CAutoWriteWeakLock
writeLock(CGitStatusCache::Instance().GetGuard(), 200);
350 CGitStatusCache::Instance().Stop();
359 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
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
)
374 case WM_DEVICECHANGE
:
376 auto phdr
= reinterpret_cast<DEV_BROADCAST_HDR
*>(lParam
);
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
);
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
);
421 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive
));
427 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
428 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE
);
435 if (message
== s_uTaskbarRestart
)
439 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
442 //////////////////////////////////////////////////////////////////////////
444 VOID
GetAnswerToRequest(const TGITCacheRequest
* pRequest
, TGITCacheResponse
* pReply
, DWORD
* pResponseLength
)
447 *pResponseLength
= 0;
448 if(pRequest
->flags
& TGITCACHE_FLAGS_FOLDERISKNOWN
)
449 path
.SetFromWin(pRequest
->path
, !!(pRequest
->flags
& TGITCACHE_FLAGS_ISFOLDER
));
451 path
.SetFromWin(pRequest
->path
);
455 CStatusCacheEntry entry
;
456 entry
.BuildCacheResponse(*pReply
, *pResponseLength
);
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
);
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()))))
483 auto titleTextBuf
= std::make_unique
<wchar_t[]>(MAX_PATH
);
484 int titleTextBufLength
= MAX_PATH
;
489 if (CRegStdDWORD(L
"Software\\TortoiseGit\\ModifyExplorerTitle", TRUE
) == FALSE
)
498 if (shellWindows
->get_Count(&shellCount
) != S_OK
)
501 for (long i
= 0; i
< shellCount
; ++i
)
504 Microsoft::WRL::ComPtr
<IDispatch
> disp
;
505 shellWindows
->Item(v
, disp
.GetAddressOf());
508 Microsoft::WRL::ComPtr
<IWebBrowserApp
> webBrowserApp
;
509 if (FAILED(disp
->QueryInterface(webBrowserApp
.GetAddressOf())))
511 HWND hwndWba
= nullptr;
512 if (FAILED(webBrowserApp
->get_HWND(reinterpret_cast<LONG_PTR
*>(&hwndWba
))))
514 Microsoft::WRL::ComPtr
<IServiceProvider
> serviceProvider
;
515 if (FAILED(webBrowserApp
->QueryInterface(serviceProvider
.GetAddressOf())))
517 Microsoft::WRL::ComPtr
<IShellBrowser
> shellBrowser
;
518 if (FAILED(serviceProvider
->QueryService(SID_STopLevelBrowser
, shellBrowser
.GetAddressOf())))
520 Microsoft::WRL::ComPtr
<IShellView
> psv
;
521 if (FAILED(shellBrowser
->QueryActiveShellView(psv
.GetAddressOf())))
523 Microsoft::WRL::ComPtr
<IFolderView
> pfv
;
524 if (FAILED(psv
->QueryInterface(pfv
.GetAddressOf())))
526 Microsoft::WRL::ComPtr
<IPersistFolder2
> ppf2
;
527 if (FAILED(pfv
->GetFolder(IID_IPersistFolder2
, &ppf2
)))
529 CComHeapPtr
<ITEMIDLIST
> pidlFolder
;
530 if (FAILED(ppf2
->GetCurFolder(&pidlFolder
)))
532 wchar_t szPath
[MAX_PATH
]{};
533 if (!SHGetPathFromIDList(pidlFolder
, szPath
))
537 path
.SetFromWin(szPath
);
539 CAutoReadLock
readLock(CGitStatusCache::Instance().GetGuard());
540 auto pCachedDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
544 auto gitDir
= pCachedDir
->GetProjectRoot();
545 if (gitDir
.IsEmpty())
548 auto sharedIndex
= g_IndexFileMap
.SafeGet(gitDir
);
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
572 inOut
.Format(L
" \u2193%lld \u2191%lld", sharedIndex
->m_incoming
, sharedIndex
->m_outgoing
);
573 sWindowText
+= inOut
;
576 sWindowText
+= L
" \u2302";
577 if (sharedIndex
->m_stashCount
)
580 sStash
.Format(L
" \u205E%lld", sharedIndex
->m_stashCount
);
581 sWindowText
+= sStash
;
583 sWindowText
+= L
" ]";
585 SetWindowText(hwndWba
, sWindowText
.c_str());
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.
605 hPipe
= CreateNamedPipe(
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
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
);
630 // Create a thread for this client.
631 CAutoGeneralHandle hInstanceThread
= CreateThread(
632 nullptr, // no security attribute
633 0, // default stack size
635 hPipe
, // thread parameter
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);
648 // detach the handle, since we passed it to the thread
653 // The client could not connect, so close the pipe.
657 continue; // don't end the thread!
660 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Pipe thread exited\n");
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.
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
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
);
703 // Create a thread for this client.
704 CAutoGeneralHandle hCommandThread
= CreateThread(
705 nullptr, // no security attribute
706 0, // default stack size
708 hPipe
, // thread parameter
710 &dwThreadId
); // returns thread ID
714 DisconnectNamedPipe(hPipe
);
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);
722 // detach the handle, since we passed it to the thread
727 // The client could not connect, so close the pipe.
731 continue; // don't end the thread!
734 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CommandWait thread exited\n");
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
);
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);
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);
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);
805 DWORD WINAPI
CommandThread(LPVOID lpvParam
)
807 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CommandThread started\n");
810 // The thread's parameter is a handle to a pipe instance.
811 CAutoFile
hPipe(std::move(lpvParam
));
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");
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");
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());
851 case TGITCACHECOMMAND_REFRESHALL
:
853 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
854 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": refresh all\n");
855 CGitStatusCache::Instance().Refresh();
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());
868 cachedDir
->Invalidate();
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
);
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
);
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");