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-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.
23 #include "TGITCache.h"
24 #include "GitStatusCache.h"
25 #include "CacheInterface.h"
28 #include "CrashReport.h"
29 #include "GitAdminDir.h"
33 #include "..\version.h"
34 #include "SmartHandle.h"
35 #include "CreateProcessHelper.h"
39 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
42 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
46 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
48 #if ENABLE_CRASHHANLDER
49 CCrashReportTGit
crasher(L
"TGitCache " _T(APP_X64_STRING
), TGIT_VERMAJOR
, TGIT_VERMINOR
, TGIT_VERMICRO
, TGIT_VERBUILD
, TGIT_VERDATE
);
52 DWORD WINAPI
InstanceThread(LPVOID
);
53 DWORD WINAPI
PipeThread(LPVOID
);
54 DWORD WINAPI
CommandWaitThread(LPVOID
);
55 DWORD WINAPI
CommandThread(LPVOID
);
56 LRESULT CALLBACK
WndProc(HWND
, UINT
, WPARAM
, LPARAM
);
58 bool bRestart
= false;
59 NOTIFYICONDATA niData
;
62 TCHAR szCurrentCrawledPath
[MAX_CRAWLEDPATHS
][MAX_CRAWLEDPATHSLEN
];
63 int nCurrentCrawledpathIndex
= 0;
64 CComAutoCriticalSection critSec
;
66 // must put this before any global variables that auto free git objects,
67 // so this destructor is called after freeing git objects
73 git_libgit2_shutdown();
75 Shell_NotifyIcon(NIM_DELETE
, &niData
);
79 CGitIndexFileMap g_IndexFileMap
;
81 volatile LONG nThreadCount
= 0;
83 #define PACKVERSION(major,minor) MAKELONG(minor,major)
85 void DebugOutputLastError()
89 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
90 FORMAT_MESSAGE_FROM_SYSTEM
|
91 FORMAT_MESSAGE_IGNORE_INSERTS
,
94 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
102 // Display the string.
103 OutputDebugStringA("TGitCache GetLastError(): ");
104 OutputDebugString((LPCTSTR
)lpMsgBuf
);
105 OutputDebugStringA("\n");
108 LocalFree( lpMsgBuf
);
111 void HandleCommandLine(LPSTR lpCmdLine
)
113 char *ptr
= strstr(lpCmdLine
, "/kill:");
116 DWORD pid
= (DWORD
)atoi(ptr
+ strlen("/kill:"));
117 HANDLE hProcess
= ::OpenProcess(PROCESS_TERMINATE
, FALSE
, pid
);
120 if (::WaitForSingleObject(hProcess
, 5000) != WAIT_OBJECT_0
)
122 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Killing previous TGitCache PID %d\n", pid
);
123 if (!::TerminateProcess(hProcess
, (UINT
)-1))
124 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Kill previous TGitCache PID %d failed\n", pid
);
125 ::WaitForSingleObject(hProcess
, 5000);
127 ::CloseHandle(hProcess
);
128 for (int i
= 0; i
< 5; i
++)
130 HANDLE hMutex
= ::OpenMutex(MUTEX_ALL_ACCESS
, FALSE
, GetCacheMutexName());
133 ::CloseHandle(hMutex
);
144 TCHAR exeName
[MAX_PATH
] = { 0 };
145 ::GetModuleFileName(nullptr, exeName
, _countof(exeName
));
146 TCHAR cmdLine
[20] = { 0 };
147 swprintf_s(cmdLine
, L
" /kill:%d", GetCurrentProcessId());
148 if (!CCreateProcessHelper::CreateProcessDetached(exeName
, cmdLine
))
149 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Failed to start cache\n");
153 static void AddSystrayIcon()
155 if (CRegStdDWORD(L
"Software\\TortoiseGit\\CacheTrayIcon", FALSE
) != TRUE
)
158 SecureZeroMemory(&niData
, sizeof(NOTIFYICONDATA
));
159 niData
.cbSize
= sizeof(NOTIFYICONDATA
);
160 niData
.uID
= TRAY_ID
; // own tray icon ID
161 niData
.hWnd
= hWndHidden
;
162 niData
.uFlags
= NIF_ICON
| NIF_MESSAGE
;
166 (HICON
)LoadImage(GetModuleHandle(nullptr),
167 MAKEINTRESOURCE(IDI_TGITCACHE
),
169 GetSystemMetrics(SM_CXSMICON
),
170 GetSystemMetrics(SM_CYSMICON
),
173 // set the message to send
174 // note: the message value should be in the
175 // range of WM_APP through 0xBFFF
176 niData
.uCallbackMessage
= TRAY_CALLBACK
;
177 Shell_NotifyIcon(NIM_ADD
, &niData
);
179 if (niData
.hIcon
&& DestroyIcon(niData
.hIcon
))
180 niData
.hIcon
= nullptr;
183 int __stdcall
WinMain(HINSTANCE hInstance
, HINSTANCE
/*hPrevInstance*/, LPSTR lpCmdLine
, int /*cmdShow*/)
185 SetDllDirectory(L
"");
187 git_libgit2_opts(GIT_OPT_SET_WINDOWS_SHAREMODE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
);
188 HandleCommandLine(lpCmdLine
);
189 CAutoGeneralHandle hReloadProtection
= ::CreateMutex(nullptr, FALSE
, GetCacheMutexName());
191 if ((!hReloadProtection
) || (GetLastError() == ERROR_ALREADY_EXISTS
))
193 // An instance of TGitCache is already running
194 CTraceToOutputDebugString::Instance()(__FUNCTION__
": TGitCache ignoring restart\n");
198 CGitStatusCache::Create();
199 CGitStatusCache::Instance().Init();
201 SecureZeroMemory(szCurrentCrawledPath
, sizeof(szCurrentCrawledPath
));
205 TCHAR szWindowClass
[] = {TGIT_CACHE_WINDOW_NAME
};
207 // create a hidden window to receive window messages.
208 WNDCLASSEX wcex
= { 0 };
209 wcex
.cbSize
= sizeof(WNDCLASSEX
);
210 wcex
.style
= CS_HREDRAW
| CS_VREDRAW
;
211 wcex
.lpfnWndProc
= (WNDPROC
)WndProc
;
212 wcex
.hInstance
= hInstance
;
213 wcex
.lpszClassName
= szWindowClass
;
214 RegisterClassEx(&wcex
);
215 hWndHidden
= CreateWindow(TGIT_CACHE_WINDOW_NAME
, TGIT_CACHE_WINDOW_NAME
, WS_CAPTION
, 0, 0, 800, 300, nullptr, 0, hInstance
, 0);
216 hTrayWnd
= hWndHidden
;
220 // Create a thread which waits for incoming pipe connections
221 CAutoGeneralHandle hPipeThread
= CreateThread(
222 nullptr, // no security attribute
223 0, // default stack size
225 (LPVOID
) &bRun
, // thread parameter
227 &dwThreadId
); // returns thread ID
231 else hPipeThread
.CloseHandle();
233 // Create a thread which waits for incoming pipe connections
234 CAutoGeneralHandle hCommandWaitThread
= CreateThread(
235 nullptr, // no security attribute
236 0, // default stack size
238 (LPVOID
) &bRun
, // thread parameter
240 &dwThreadId
); // returns thread ID
242 if (!hCommandWaitThread
)
247 // loop to handle window messages.
250 BOOL bLoopRet
= GetMessage(&msg
, nullptr, 0, 0);
251 if ((bLoopRet
!= -1)&&(bLoopRet
!= 0))
252 DispatchMessage(&msg
);
257 CGitStatusCache::Destroy();
262 LRESULT CALLBACK
WndProc(HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
264 static UINT s_uTaskbarRestart
= RegisterWindowMessage(L
"TaskbarCreated");
271 case WM_LBUTTONDBLCLK
:
272 if (IsWindowVisible(hWnd
))
273 ShowWindow(hWnd
, SW_HIDE
);
275 ShowWindow(hWnd
, SW_RESTORE
);
280 NOTIFYICONDATA SystemTray
;
281 sInfoTip
.Format(L
"TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d",
282 CGitStatusCache::Instance().GetCacheSize(),
283 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
285 SystemTray
.cbSize
= sizeof(NOTIFYICONDATA
);
286 SystemTray
.hWnd
= hTrayWnd
;
287 SystemTray
.uID
= TRAY_ID
;
288 SystemTray
.uFlags
= NIF_TIP
;
289 wcscpy_s(SystemTray
.szTip
, sInfoTip
);
290 Shell_NotifyIcon(NIM_MODIFY
, &SystemTray
);
298 HMENU hMenu
= CreatePopupMenu();
301 bool enabled
= (DWORD
)CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) != ShellCache::none
;
302 InsertMenu(hMenu
, (UINT
)-1, MF_BYPOSITION
, TRAYPOP_ENABLE
, enabled
? L
"Disable Status Cache" : L
"Enable Status Cache");
303 InsertMenu(hMenu
, (UINT
)-1, MF_BYPOSITION
, TRAYPOP_EXIT
, L
"Exit");
304 SetForegroundWindow(hWnd
);
305 TrackPopupMenu(hMenu
, TPM_BOTTOMALIGN
, pt
.x
, pt
.y
, 0, hWnd
, nullptr);
316 HDC hdc
= BeginPaint(hWnd
, &ps
);
318 GetClientRect(hWnd
, &rect
);
319 // clear the background
320 HBRUSH background
= CreateSolidBrush(::GetSysColor(COLOR_WINDOW
));
321 HGDIOBJ oldbrush
= SelectObject(hdc
, background
);
322 FillRect(hdc
, &rect
, background
);
326 AutoLocker
print(critSec
);
327 GetTextExtentPoint32(hdc
, szCurrentCrawledPath
[0], (int)wcslen(szCurrentCrawledPath
[0]), &fontsize
);
328 for (int i
=nCurrentCrawledpathIndex
; i
<MAX_CRAWLEDPATHS
; ++i
)
330 TextOut(hdc
, 0, line
*fontsize
.cy
, szCurrentCrawledPath
[i
], (int)wcslen(szCurrentCrawledPath
[i
]));
333 for (int i
=0; i
<nCurrentCrawledpathIndex
; ++i
)
335 TextOut(hdc
, 0, line
*fontsize
.cy
, szCurrentCrawledPath
[i
], (int)wcslen(szCurrentCrawledPath
[i
]));
339 SelectObject(hdc
,oldbrush
);
341 DeleteObject(background
);
347 WORD wmId
= LOWORD(wParam
);
353 CRegStdDWORD reg
= CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
);
354 bool enabled
= (DWORD
)reg
!= ShellCache::none
;
355 reg
= enabled
? ShellCache::none
: ShellCache::exe
;
369 case WM_QUERYENDSESSION
:
371 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_QUERYENDSESSION\n");
372 CAutoWriteWeakLock
writeLock(CGitStatusCache::Instance().GetGuard(), 200);
373 CGitStatusCache::Instance().Stop();
382 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
385 niData
.hIcon
= (HICON
)LoadImage(GetModuleHandle(nullptr),
386 MAKEINTRESOURCE(IDI_TGITCACHE_STOPPING
),
388 GetSystemMetrics(SM_CXSMICON
),
389 GetSystemMetrics(SM_CYSMICON
),
391 Shell_NotifyIcon(NIM_MODIFY
, &niData
);
393 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
394 CGitStatusCache::Instance().Stop();
395 CGitStatusCache::Instance().SaveCache();
396 if (message
!= WM_QUIT
)
402 case WM_DEVICECHANGE
:
404 DEV_BROADCAST_HDR
* phdr
= (DEV_BROADCAST_HDR
*)lParam
;
407 case DBT_CUSTOMEVENT
:
409 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
410 if (phdr
->dbch_devicetype
== DBT_DEVTYP_HANDLE
)
412 DEV_BROADCAST_HANDLE
* phandle
= (DEV_BROADCAST_HANDLE
*)lParam
;
413 if (IsEqualGUID(phandle
->dbch_eventguid
, GUID_IO_VOLUME_DISMOUNT
))
415 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Device to be dismounted\n");
416 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
417 CGitStatusCache::Instance().CloseWatcherHandles(phandle
->dbch_handle
);
419 if (IsEqualGUID(phandle
->dbch_eventguid
, GUID_IO_VOLUME_LOCK
))
421 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Device lock event\n");
422 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
423 CGitStatusCache::Instance().CloseWatcherHandles(phandle
->dbch_handle
);
428 case DBT_DEVICEREMOVEPENDING
:
429 case DBT_DEVICEQUERYREMOVE
:
430 case DBT_DEVICEREMOVECOMPLETE
:
431 CTraceToOutputDebugString::Instance()(__FUNCTION__
": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
432 if (phdr
->dbch_devicetype
== DBT_DEVTYP_HANDLE
)
434 DEV_BROADCAST_HANDLE
* phandle
= (DEV_BROADCAST_HANDLE
*)lParam
;
435 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
436 CGitStatusCache::Instance().CloseWatcherHandles(phandle
->dbch_handle
);
438 else if (phdr
->dbch_devicetype
== DBT_DEVTYP_VOLUME
)
440 DEV_BROADCAST_VOLUME
* pVolume
= (DEV_BROADCAST_VOLUME
*)lParam
;
441 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
442 for (BYTE i
= 0; i
< 26; ++i
)
444 if (pVolume
->dbcv_unitmask
& (1 << i
))
446 TCHAR driveletter
= 'A' + i
;
447 CString drive
= CString(driveletter
);
449 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive
));
455 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
456 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE
);
463 if (message
== s_uTaskbarRestart
)
467 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
470 //////////////////////////////////////////////////////////////////////////
472 VOID
GetAnswerToRequest(const TGITCacheRequest
* pRequest
, TGITCacheResponse
* pReply
, DWORD
* pResponseLength
)
475 *pResponseLength
= 0;
476 if(pRequest
->flags
& TGITCACHE_FLAGS_FOLDERISKNOWN
)
477 path
.SetFromWin(pRequest
->path
, !!(pRequest
->flags
& TGITCACHE_FLAGS_ISFOLDER
));
479 path
.SetFromWin(pRequest
->path
);
483 CStatusCacheEntry entry
;
484 entry
.BuildCacheResponse(*pReply
, *pResponseLength
);
488 CAutoReadWeakLock
readLock(CGitStatusCache::Instance().GetGuard(), 2000);
489 if (readLock
.IsAcquired())
490 CGitStatusCache::Instance().GetStatusForPath(path
, pRequest
->flags
, false).BuildCacheResponse(*pReply
, *pResponseLength
);
493 CStatusCacheEntry entry
;
494 entry
.BuildCacheResponse(*pReply
, *pResponseLength
);
498 DWORD WINAPI
PipeThread(LPVOID lpvParam
)
500 CTraceToOutputDebugString::Instance()(__FUNCTION__
": PipeThread started\n");
501 bool* bThreadRun
= (bool*)lpvParam
;
502 // The main loop creates an instance of the named pipe and
503 // then waits for a client to connect to it. When the client
504 // connects, a thread is created to handle communications
505 // with that client, and the loop is repeated.
512 hPipe
= CreateNamedPipe(
514 PIPE_ACCESS_DUPLEX
, // read/write access
515 PIPE_TYPE_MESSAGE
| // message type pipe
516 PIPE_READMODE_MESSAGE
| // message-read mode
517 PIPE_WAIT
, // blocking mode
518 PIPE_UNLIMITED_INSTANCES
, // max. instances
519 BUFSIZE
, // output buffer size
520 BUFSIZE
, // input buffer size
521 NMPWAIT_USE_DEFAULT_WAIT
, // client time-out
522 nullptr); // nullptr DACL
528 continue; // never leave the thread!
531 // Wait for the client to connect; if it succeeds,
532 // the function returns a nonzero value. If the function returns
533 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
534 fConnected
= ConnectNamedPipe(hPipe
, nullptr) ? TRUE
: (GetLastError() == ERROR_PIPE_CONNECTED
);
537 // Create a thread for this client.
538 CAutoGeneralHandle hInstanceThread
= CreateThread(
539 nullptr, // no security attribute
540 0, // default stack size
542 (HANDLE
) hPipe
, // thread parameter
544 &dwThreadId
); // returns thread ID
546 if (!hInstanceThread
)
548 DisconnectNamedPipe(hPipe
);
549 // since we're now closing this thread, we also have to close the whole application!
550 // otherwise the thread is dead, but the app is still running, refusing new instances
551 // but no pipe will be available anymore.
552 PostMessage(hWndHidden
, WM_CLOSE
, 0, 0);
555 // detach the handle, since we passed it to the thread
560 // The client could not connect, so close the pipe.
564 continue; // don't end the thread!
567 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Pipe thread exited\n");
571 DWORD WINAPI
CommandWaitThread(LPVOID lpvParam
)
573 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CommandWaitThread started\n");
574 bool* bThreadRun
= (bool*)lpvParam
;
575 // The main loop creates an instance of the named pipe and
576 // then waits for a client to connect to it. When the client
577 // connects, a thread is created to handle communications
578 // with that client, and the loop is repeated.
585 hPipe
= CreateNamedPipe(
586 GetCacheCommandPipeName(),
587 PIPE_ACCESS_DUPLEX
, // read/write access
588 PIPE_TYPE_MESSAGE
| // message type pipe
589 PIPE_READMODE_MESSAGE
| // message-read mode
590 PIPE_WAIT
, // blocking mode
591 PIPE_UNLIMITED_INSTANCES
, // max. instances
592 BUFSIZE
, // output buffer size
593 BUFSIZE
, // input buffer size
594 NMPWAIT_USE_DEFAULT_WAIT
, // client time-out
595 nullptr); // nullptr DACL
601 continue; // never leave the thread!
604 // Wait for the client to connect; if it succeeds,
605 // the function returns a nonzero value. If the function returns
606 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
607 fConnected
= ConnectNamedPipe(hPipe
, nullptr) ? TRUE
: (GetLastError() == ERROR_PIPE_CONNECTED
);
610 // Create a thread for this client.
611 CAutoGeneralHandle hCommandThread
= CreateThread(
612 nullptr, // no security attribute
613 0, // default stack size
615 (HANDLE
) hPipe
, // thread parameter
617 &dwThreadId
); // returns thread ID
621 DisconnectNamedPipe(hPipe
);
623 // since we're now closing this thread, we also have to close the whole application!
624 // otherwise the thread is dead, but the app is still running, refusing new instances
625 // but no pipe will be available anymore.
626 PostMessage(hWndHidden
, WM_CLOSE
, 0, 0);
629 // detach the handle, since we passed it to the thread
634 // The client could not connect, so close the pipe.
638 continue; // don't end the thread!
641 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CommandWait thread exited\n");
645 DWORD WINAPI
InstanceThread(LPVOID lpvParam
)
647 CTraceToOutputDebugString::Instance()(__FUNCTION__
": InstanceThread started\n");
648 TGITCacheResponse response
;
649 DWORD cbBytesRead
, cbWritten
;
651 // The thread's parameter is a handle to a pipe instance.
652 CAutoFile
hPipe(std::move(lpvParam
));
654 InterlockedIncrement(&nThreadCount
);
657 // Read client requests from the pipe.
658 TGITCacheRequest request
;
659 BOOL fSuccess
= ReadFile(
660 hPipe
, // handle to pipe
661 &request
, // buffer to receive data
662 sizeof(request
), // size of buffer
663 &cbBytesRead
, // number of bytes read
664 nullptr); // not overlapped I/O
666 if (! fSuccess
|| cbBytesRead
== 0)
668 DisconnectNamedPipe(hPipe
);
669 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Instance thread exited\n");
670 InterlockedDecrement(&nThreadCount
);
671 if (nThreadCount
== 0)
672 PostMessage(hWndHidden
, WM_CLOSE
, 0, 0);
676 DWORD responseLength
;
677 GetAnswerToRequest(&request
, &response
, &responseLength
);
679 // Write the reply to the pipe.
680 fSuccess
= WriteFile(
681 hPipe
, // handle to pipe
682 &response
, // buffer to write from
683 responseLength
, // number of bytes to write
684 &cbWritten
, // number of bytes written
685 nullptr); // not overlapped I/O
687 if (! fSuccess
|| responseLength
!= cbWritten
)
689 DisconnectNamedPipe(hPipe
);
690 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Instance thread exited\n");
691 InterlockedDecrement(&nThreadCount
);
692 if (nThreadCount
== 0)
693 PostMessage(hWndHidden
, WM_CLOSE
, 0, 0);
698 // Flush the pipe to allow the client to read the pipe's contents
699 // before disconnecting. Then disconnect the pipe, and close the
700 // handle to this pipe instance.
702 FlushFileBuffers(hPipe
);
703 DisconnectNamedPipe(hPipe
);
704 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Instance thread exited\n");
705 InterlockedDecrement(&nThreadCount
);
706 if (nThreadCount
== 0)
707 PostMessage(hWndHidden
, WM_CLOSE
, 0, 0);
711 DWORD WINAPI
CommandThread(LPVOID lpvParam
)
713 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CommandThread started\n");
716 // The thread's parameter is a handle to a pipe instance.
717 CAutoFile
hPipe(std::move(lpvParam
));
721 // Read client requests from the pipe.
722 TGITCacheCommand command
;
723 BOOL fSuccess
= ReadFile(
724 hPipe
, // handle to pipe
725 &command
, // buffer to receive data
726 sizeof(command
), // size of buffer
727 &cbBytesRead
, // number of bytes read
728 nullptr); // not overlapped I/O
730 if (! fSuccess
|| cbBytesRead
== 0)
732 DisconnectNamedPipe(hPipe
);
733 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Command thread exited\n");
737 switch (command
.command
)
739 case TGITCACHECOMMAND_END
:
740 FlushFileBuffers(hPipe
);
741 DisconnectNamedPipe(hPipe
);
742 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Command thread exited\n");
744 case TGITCACHECOMMAND_CRAWL
:
746 CTGitPath changedpath
;
747 changedpath
.SetFromWin(command
.path
, true);
748 // remove the path from our cache - that will 'invalidate' it.
750 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
751 CGitStatusCache::Instance().RemoveCacheForPath(changedpath
);
753 CGitStatusCache::Instance().AddFolderForCrawling(changedpath
.GetDirectory());
756 case TGITCACHECOMMAND_REFRESHALL
:
758 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
759 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": refresh all\n");
760 CGitStatusCache::Instance().Refresh();
763 case TGITCACHECOMMAND_RELEASE
:
765 CTGitPath changedpath
;
766 changedpath
.SetFromWin(command
.path
, true);
767 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": release handle for path %s\n", changedpath
.GetWinPath());
768 CAutoWriteLock
writeLock(CGitStatusCache::Instance().GetGuard());
769 CGitStatusCache::Instance().CloseWatcherHandles(changedpath
);
770 CGitStatusCache::Instance().RemoveCacheForPath(changedpath
);
773 case TGITCACHECOMMAND_BLOCK
:
775 CTGitPath changedpath
;
776 changedpath
.SetFromWin(command
.path
);
777 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": block path %s\n", changedpath
.GetWinPath());
778 CGitStatusCache::Instance().BlockPath(changedpath
);
781 case TGITCACHECOMMAND_UNBLOCK
:
783 CTGitPath changedpath
;
784 changedpath
.SetFromWin(command
.path
);
785 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": unblock path %s\n", changedpath
.GetWinPath());
786 CGitStatusCache::Instance().UnBlockPath(changedpath
);
792 // Flush the pipe to allow the client to read the pipe's contents
793 // before disconnecting. Then disconnect the pipe, and close the
794 // handle to this pipe instance.
796 FlushFileBuffers(hPipe
);
797 DisconnectNamedPipe(hPipe
);
798 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Command thread exited\n");