Drop unused code
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blob5c6e86aef1633585d31cc22380e113b66eb9b3a9
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-2018 - 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 "../version.h"
34 #include "SmartHandle.h"
35 #include "CreateProcessHelper.h"
36 #include "gitindex.h"
37 #include "LoadIconEx.h"
39 #ifndef GET_X_LPARAM
40 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
41 #endif
42 #ifndef GET_Y_LPARAM
43 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
44 #endif
47 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
49 #if ENABLE_CRASHHANLDER
50 CCrashReportTGit crasher(L"TGitCache " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);
51 #endif
53 DWORD WINAPI InstanceThread(LPVOID);
54 DWORD WINAPI PipeThread(LPVOID);
55 DWORD WINAPI CommandWaitThread(LPVOID);
56 DWORD WINAPI CommandThread(LPVOID);
57 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
58 bool bRun = true;
59 bool bRestart = false;
60 NOTIFYICONDATA niData;
61 HWND hWndHidden;
62 HWND hTrayWnd;
63 TCHAR szCurrentCrawledPath[MAX_CRAWLEDPATHS][MAX_CRAWLEDPATHSLEN];
64 int nCurrentCrawledpathIndex = 0;
65 CComAutoCriticalSection critSec;
67 // must put this before any global variables that auto free git objects,
68 // so this destructor is called after freeing git objects
69 class CGit2InitClass
71 public:
72 ~CGit2InitClass()
74 git_libgit2_shutdown();
75 if (niData.hWnd)
76 Shell_NotifyIcon(NIM_DELETE, &niData);
78 } git2init;
80 volatile LONG nThreadCount = 0;
82 #define PACKVERSION(major,minor) MAKELONG(minor,major)
84 void HandleCommandLine(LPSTR lpCmdLine)
86 char *ptr = strstr(lpCmdLine, "/kill:");
87 if (ptr)
89 DWORD pid = (DWORD)atoi(ptr + strlen("/kill:"));
90 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, pid);
91 if (hProcess)
93 if (::WaitForSingleObject(hProcess, 5000) != WAIT_OBJECT_0)
95 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Killing previous TGitCache PID %d\n", pid);
96 if (!::TerminateProcess(hProcess, (UINT)-1))
97 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Kill previous TGitCache PID %d failed\n", pid);
98 ::WaitForSingleObject(hProcess, 5000);
100 ::CloseHandle(hProcess);
101 for (int i = 0; i < 5; i++)
103 HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, GetCacheMutexName());
104 if (!hMutex)
105 break;
106 ::CloseHandle(hMutex);
107 ::Sleep(1000);
113 void HandleRestart()
115 if (bRestart)
117 TCHAR exeName[MAX_PATH] = { 0 };
118 ::GetModuleFileName(nullptr, exeName, _countof(exeName));
119 TCHAR cmdLine[20] = { 0 };
120 swprintf_s(cmdLine, L" /kill:%d", GetCurrentProcessId());
121 if (!CCreateProcessHelper::CreateProcessDetached(exeName, cmdLine))
122 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
126 static void AddSystrayIcon()
128 if (CRegStdDWORD(L"Software\\TortoiseGit\\CacheTrayIcon", FALSE) != TRUE)
129 return;
131 SecureZeroMemory(&niData, sizeof(NOTIFYICONDATA));
132 niData.cbSize = sizeof(NOTIFYICONDATA);
133 niData.uID = TRAY_ID; // own tray icon ID
134 niData.hWnd = hWndHidden;
135 niData.uFlags = NIF_ICON | NIF_MESSAGE;
137 // load the icon
138 niData.hIcon = LoadIconEx(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_TGITCACHE));
140 // set the message to send
141 // note: the message value should be in the
142 // range of WM_APP through 0xBFFF
143 niData.uCallbackMessage = TRAY_CALLBACK;
144 Shell_NotifyIcon(NIM_ADD, &niData);
145 // free icon handle
146 if (niData.hIcon && DestroyIcon(niData.hIcon))
147 niData.hIcon = nullptr;
150 int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR lpCmdLine, int /*cmdShow*/)
152 SetDllDirectory(L"");
153 git_libgit2_init();
154 git_libgit2_opts(GIT_OPT_SET_WINDOWS_SHAREMODE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
155 HandleCommandLine(lpCmdLine);
156 CAutoGeneralHandle hReloadProtection = ::CreateMutex(nullptr, FALSE, GetCacheMutexName());
158 if ((!hReloadProtection) || (GetLastError() == ERROR_ALREADY_EXISTS))
160 // An instance of TGitCache is already running
161 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": TGitCache ignoring restart\n");
162 return 0;
165 CGitStatusCache::Create();
166 CGitStatusCache::Instance().Init();
168 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
170 DWORD dwThreadId;
171 MSG msg;
172 TCHAR szWindowClass[] = {TGIT_CACHE_WINDOW_NAME};
174 // create a hidden window to receive window messages.
175 WNDCLASSEX wcex = { 0 };
176 wcex.cbSize = sizeof(WNDCLASSEX);
177 wcex.style = CS_HREDRAW | CS_VREDRAW;
178 wcex.lpfnWndProc = (WNDPROC)WndProc;
179 wcex.hInstance = hInstance;
180 wcex.lpszClassName = szWindowClass;
181 RegisterClassEx(&wcex);
182 hWndHidden = CreateWindow(TGIT_CACHE_WINDOW_NAME, TGIT_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, nullptr, 0, hInstance, 0);
183 hTrayWnd = hWndHidden;
184 if (!hWndHidden)
185 return 0;
187 // Create a thread which waits for incoming pipe connections
188 CAutoGeneralHandle hPipeThread = CreateThread(
189 nullptr, // no security attribute
190 0, // default stack size
191 PipeThread,
192 (LPVOID) &bRun, // thread parameter
193 0, // not suspended
194 &dwThreadId); // returns thread ID
196 if (!hPipeThread)
197 return 0;
198 else hPipeThread.CloseHandle();
200 // Create a thread which waits for incoming pipe connections
201 CAutoGeneralHandle hCommandWaitThread = CreateThread(
202 nullptr, // no security attribute
203 0, // default stack size
204 CommandWaitThread,
205 (LPVOID) &bRun, // thread parameter
206 0, // not suspended
207 &dwThreadId); // returns thread ID
209 if (!hCommandWaitThread)
210 return 0;
212 AddSystrayIcon();
214 // loop to handle window messages.
215 while (bRun)
217 BOOL bLoopRet = GetMessage(&msg, nullptr, 0, 0);
218 if ((bLoopRet != -1)&&(bLoopRet != 0))
219 DispatchMessage(&msg);
222 bRun = false;
224 CGitStatusCache::Destroy();
225 HandleRestart();
226 return 0;
229 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
231 static UINT s_uTaskbarRestart = RegisterWindowMessage(L"TaskbarCreated");
232 switch (message)
234 case TRAY_CALLBACK:
236 switch(lParam)
238 case WM_LBUTTONDBLCLK:
239 if (IsWindowVisible(hWnd))
240 ShowWindow(hWnd, SW_HIDE);
241 else
242 ShowWindow(hWnd, SW_RESTORE);
243 break;
244 case WM_MOUSEMOVE:
246 CString sInfoTip;
247 NOTIFYICONDATA SystemTray;
248 sInfoTip.Format(L"TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d",
249 CGitStatusCache::Instance().GetCacheSize(),
250 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
252 SystemTray.cbSize = sizeof(NOTIFYICONDATA);
253 SystemTray.hWnd = hTrayWnd;
254 SystemTray.uID = TRAY_ID;
255 SystemTray.uFlags = NIF_TIP;
256 wcscpy_s(SystemTray.szTip, sInfoTip);
257 Shell_NotifyIcon(NIM_MODIFY, &SystemTray);
259 break;
260 case WM_RBUTTONUP:
261 case WM_CONTEXTMENU:
263 POINT pt;
264 GetCursorPos(&pt);
265 HMENU hMenu = CreatePopupMenu();
266 if(hMenu)
268 bool enabled = (DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) != ShellCache::none;
269 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_ENABLE, enabled ? L"Disable Status Cache" : L"Enable Status Cache");
270 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_EXIT, L"Exit");
271 SetForegroundWindow(hWnd);
272 TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, nullptr);
273 DestroyMenu(hMenu);
276 break;
279 break;
280 case WM_PAINT:
282 PAINTSTRUCT ps;
283 HDC hdc = BeginPaint(hWnd, &ps);
284 RECT rect;
285 GetClientRect(hWnd, &rect);
286 // clear the background
287 HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
288 HGDIOBJ oldbrush = SelectObject(hdc, background);
289 FillRect(hdc, &rect, background);
291 int line = 0;
292 SIZE fontsize = {0};
293 AutoLocker print(critSec);
294 GetTextExtentPoint32(hdc, szCurrentCrawledPath[0], (int)wcslen(szCurrentCrawledPath[0]), &fontsize);
295 for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)
297 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)wcslen(szCurrentCrawledPath[i]));
298 ++line;
300 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
302 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)wcslen(szCurrentCrawledPath[i]));
303 ++line;
306 SelectObject(hdc,oldbrush);
307 EndPaint(hWnd, &ps);
308 DeleteObject(background);
309 return 0L;
311 break;
312 case WM_COMMAND:
314 WORD wmId = LOWORD(wParam);
316 switch (wmId)
318 case TRAYPOP_ENABLE:
320 CRegStdDWORD reg = CRegStdDWORD(L"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe);
321 bool enabled = (DWORD)reg != ShellCache::none;
322 reg = enabled ? ShellCache::none : ShellCache::exe;
323 if (enabled)
325 bRestart = true;
326 DestroyWindow(hWnd);
328 break;
330 case TRAYPOP_EXIT:
331 DestroyWindow(hWnd);
332 break;
334 return 1;
336 case WM_QUERYENDSESSION:
338 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
339 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
340 CGitStatusCache::Instance().Stop();
341 return TRUE;
343 break;
344 case WM_CLOSE:
345 case WM_ENDSESSION:
346 case WM_DESTROY:
347 case WM_QUIT:
349 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
350 if (niData.hWnd)
352 niData.hIcon = LoadIconEx(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_TGITCACHE_STOPPING));
353 Shell_NotifyIcon(NIM_MODIFY, &niData);
355 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
356 CGitStatusCache::Instance().Stop();
357 CGitStatusCache::Instance().SaveCache();
358 if (message != WM_QUIT)
359 PostQuitMessage(0);
360 bRun = false;
361 return 1;
363 break;
364 case WM_DEVICECHANGE:
366 DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;
367 switch (wParam)
369 case DBT_CUSTOMEVENT:
371 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
372 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
374 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
375 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))
377 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device to be dismounted\n");
378 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
379 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
381 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))
383 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device lock event\n");
384 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
385 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
389 break;
390 case DBT_DEVICEREMOVEPENDING:
391 case DBT_DEVICEQUERYREMOVE:
392 case DBT_DEVICEREMOVECOMPLETE:
393 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
394 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
396 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
397 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
398 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
400 else if (phdr->dbch_devicetype == DBT_DEVTYP_VOLUME)
402 DEV_BROADCAST_VOLUME * pVolume = (DEV_BROADCAST_VOLUME*)lParam;
403 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
404 for (BYTE i = 0; i < 26; ++i)
406 if (pVolume->dbcv_unitmask & (1 << i))
408 TCHAR driveletter = 'A' + i;
409 CString drive = CString(driveletter);
410 drive += L":\\";
411 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
415 else
417 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
418 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
420 break;
423 break;
424 default:
425 if (message == s_uTaskbarRestart)
426 AddSystrayIcon();
427 break;
429 return DefWindowProc(hWnd, message, wParam, lParam);
432 //////////////////////////////////////////////////////////////////////////
434 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
436 CTGitPath path;
437 *pResponseLength = 0;
438 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
439 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
440 else
441 path.SetFromWin(pRequest->path);
443 if (!bRun)
445 CStatusCacheEntry entry;
446 entry.BuildCacheResponse(*pReply, *pResponseLength);
447 return;
450 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
451 if (readLock.IsAcquired())
453 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": app asked for status of %s\n", pRequest->path);
454 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags).BuildCacheResponse(*pReply, *pResponseLength);
456 else
458 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": timeout for asked status of %s\n", pRequest->path);
459 CStatusCacheEntry entry;
460 entry.BuildCacheResponse(*pReply, *pResponseLength);
464 DWORD WINAPI PipeThread(LPVOID lpvParam)
466 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": PipeThread started\n");
467 bool* bThreadRun = (bool*)lpvParam;
468 // The main loop creates an instance of the named pipe and
469 // then waits for a client to connect to it. When the client
470 // connects, a thread is created to handle communications
471 // with that client, and the loop is repeated.
472 DWORD dwThreadId;
473 BOOL fConnected;
474 CAutoFile hPipe;
476 while (*bThreadRun)
478 hPipe = CreateNamedPipe(
479 GetCachePipeName(),
480 PIPE_ACCESS_DUPLEX, // read/write access
481 PIPE_TYPE_MESSAGE | // message type pipe
482 PIPE_READMODE_MESSAGE | // message-read mode
483 PIPE_WAIT, // blocking mode
484 PIPE_UNLIMITED_INSTANCES, // max. instances
485 BUFSIZE, // output buffer size
486 BUFSIZE, // input buffer size
487 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
488 nullptr); // nullptr DACL
490 if (!hPipe)
492 if (*bThreadRun)
493 Sleep(200);
494 continue; // never leave the thread!
497 // Wait for the client to connect; if it succeeds,
498 // the function returns a nonzero value. If the function returns
499 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
500 fConnected = ConnectNamedPipe(hPipe, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
501 if (fConnected)
503 // Create a thread for this client.
504 CAutoGeneralHandle hInstanceThread = CreateThread(
505 nullptr, // no security attribute
506 0, // default stack size
507 InstanceThread,
508 (HANDLE) hPipe, // thread parameter
509 0, // not suspended
510 &dwThreadId); // returns thread ID
512 if (!hInstanceThread)
514 DisconnectNamedPipe(hPipe);
515 // since we're now closing this thread, we also have to close the whole application!
516 // otherwise the thread is dead, but the app is still running, refusing new instances
517 // but no pipe will be available anymore.
518 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
519 return 1;
521 // detach the handle, since we passed it to the thread
522 hPipe.Detach();
524 else
526 // The client could not connect, so close the pipe.
527 hPipe.CloseHandle();
528 if (*bThreadRun)
529 Sleep(200);
530 continue; // don't end the thread!
533 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
534 return 0;
537 DWORD WINAPI CommandWaitThread(LPVOID lpvParam)
539 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWaitThread started\n");
540 bool* bThreadRun = (bool*)lpvParam;
541 // The main loop creates an instance of the named pipe and
542 // then waits for a client to connect to it. When the client
543 // connects, a thread is created to handle communications
544 // with that client, and the loop is repeated.
545 DWORD dwThreadId;
546 BOOL fConnected;
547 CAutoFile hPipe;
549 while (*bThreadRun)
551 hPipe = CreateNamedPipe(
552 GetCacheCommandPipeName(),
553 PIPE_ACCESS_DUPLEX, // read/write access
554 PIPE_TYPE_MESSAGE | // message type pipe
555 PIPE_READMODE_MESSAGE | // message-read mode
556 PIPE_WAIT, // blocking mode
557 PIPE_UNLIMITED_INSTANCES, // max. instances
558 BUFSIZE, // output buffer size
559 BUFSIZE, // input buffer size
560 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
561 nullptr); // nullptr DACL
563 if (!hPipe)
565 if (*bThreadRun)
566 Sleep(200);
567 continue; // never leave the thread!
570 // Wait for the client to connect; if it succeeds,
571 // the function returns a nonzero value. If the function returns
572 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
573 fConnected = ConnectNamedPipe(hPipe, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
574 if (fConnected)
576 // Create a thread for this client.
577 CAutoGeneralHandle hCommandThread = CreateThread(
578 nullptr, // no security attribute
579 0, // default stack size
580 CommandThread,
581 (HANDLE) hPipe, // thread parameter
582 0, // not suspended
583 &dwThreadId); // returns thread ID
585 if (!hCommandThread)
587 DisconnectNamedPipe(hPipe);
588 hPipe.CloseHandle();
589 // since we're now closing this thread, we also have to close the whole application!
590 // otherwise the thread is dead, but the app is still running, refusing new instances
591 // but no pipe will be available anymore.
592 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
593 return 1;
595 // detach the handle, since we passed it to the thread
596 hPipe.Detach();
598 else
600 // The client could not connect, so close the pipe.
601 hPipe.CloseHandle();
602 if (*bThreadRun)
603 Sleep(200);
604 continue; // don't end the thread!
607 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
608 return 0;
611 DWORD WINAPI InstanceThread(LPVOID lpvParam)
613 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": InstanceThread started\n");
614 TGITCacheResponse response;
615 DWORD cbBytesRead, cbWritten;
617 // The thread's parameter is a handle to a pipe instance.
618 CAutoFile hPipe(std::move(lpvParam));
620 InterlockedIncrement(&nThreadCount);
621 while (bRun)
623 // Read client requests from the pipe.
624 TGITCacheRequest request;
625 BOOL fSuccess = ReadFile(
626 hPipe, // handle to pipe
627 &request, // buffer to receive data
628 sizeof(request), // size of buffer
629 &cbBytesRead, // number of bytes read
630 nullptr); // not overlapped I/O
632 if (! fSuccess || cbBytesRead == 0)
634 DisconnectNamedPipe(hPipe);
635 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
636 if (InterlockedDecrement(&nThreadCount) == 0)
637 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
638 return 1;
641 DWORD responseLength;
642 GetAnswerToRequest(&request, &response, &responseLength);
644 // Write the reply to the pipe.
645 fSuccess = WriteFile(
646 hPipe, // handle to pipe
647 &response, // buffer to write from
648 responseLength, // number of bytes to write
649 &cbWritten, // number of bytes written
650 nullptr); // not overlapped I/O
652 if (! fSuccess || responseLength != cbWritten)
654 DisconnectNamedPipe(hPipe);
655 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
656 if (InterlockedDecrement(&nThreadCount) == 0)
657 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
658 return 1;
662 // Flush the pipe to allow the client to read the pipe's contents
663 // before disconnecting. Then disconnect the pipe, and close the
664 // handle to this pipe instance.
666 FlushFileBuffers(hPipe);
667 DisconnectNamedPipe(hPipe);
668 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
669 if (InterlockedDecrement(&nThreadCount) == 0)
670 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
671 return 0;
674 DWORD WINAPI CommandThread(LPVOID lpvParam)
676 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
677 DWORD cbBytesRead;
679 // The thread's parameter is a handle to a pipe instance.
680 CAutoFile hPipe(std::move(lpvParam));
682 while (bRun)
684 // Read client requests from the pipe.
685 TGITCacheCommand command;
686 BOOL fSuccess = ReadFile(
687 hPipe, // handle to pipe
688 &command, // buffer to receive data
689 sizeof(command), // size of buffer
690 &cbBytesRead, // number of bytes read
691 nullptr); // not overlapped I/O
693 if (! fSuccess || cbBytesRead == 0)
695 DisconnectNamedPipe(hPipe);
696 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
697 return 1;
700 switch (command.command)
702 case TGITCACHECOMMAND_END:
703 FlushFileBuffers(hPipe);
704 DisconnectNamedPipe(hPipe);
705 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
706 return 0;
707 case TGITCACHECOMMAND_CRAWL:
709 CTGitPath changedpath;
710 changedpath.SetFromWin(command.path, true);
711 // remove the path from our cache - that will 'invalidate' it.
713 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
714 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
716 CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());
718 break;
719 case TGITCACHECOMMAND_REFRESHALL:
721 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
722 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": refresh all\n");
723 CGitStatusCache::Instance().Refresh();
725 break;
726 case TGITCACHECOMMAND_RELEASE:
728 CTGitPath changedpath;
729 changedpath.SetFromWin(command.path, true);
730 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": release handle for path %s\n", changedpath.GetWinPath());
731 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
732 CGitStatusCache::Instance().CloseWatcherHandles(changedpath);
733 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
734 auto cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(changedpath.GetContainingDirectory());
735 if (cachedDir)
736 cachedDir->Invalidate();
738 break;
739 case TGITCACHECOMMAND_BLOCK:
741 CTGitPath changedpath;
742 changedpath.SetFromWin(command.path);
743 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": block path %s\n", changedpath.GetWinPath());
744 CGitStatusCache::Instance().BlockPath(changedpath);
746 break;
747 case TGITCACHECOMMAND_UNBLOCK:
749 CTGitPath changedpath;
750 changedpath.SetFromWin(command.path);
751 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": unblock path %s\n", changedpath.GetWinPath());
752 CGitStatusCache::Instance().UnBlockPath(changedpath);
754 break;
758 // Flush the pipe to allow the client to read the pipe's contents
759 // before disconnecting. Then disconnect the pipe, and close the
760 // handle to this pipe instance.
762 FlushFileBuffers(hPipe);
763 DisconnectNamedPipe(hPipe);
764 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
765 return 0;