Prevent possible crash: Check whether process wants to exit before continuing processing
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blobe56ae916dd1058900f8bf6c9b0b6367b91e3f646
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.
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"
38 #ifndef GET_X_LPARAM
39 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
40 #endif
41 #ifndef GET_Y_LPARAM
42 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
43 #endif
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);
50 #endif
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);
57 bool bRun = true;
58 bool bRestart = false;
59 NOTIFYICONDATA niData;
60 HWND hWndHidden;
61 HWND hTrayWnd;
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
68 class CGit2InitClass
70 public:
71 ~CGit2InitClass()
73 git_libgit2_shutdown();
74 if (niData.hWnd)
75 Shell_NotifyIcon(NIM_DELETE, &niData);
77 } git2init;
79 CGitIndexFileMap g_IndexFileMap;
81 volatile LONG nThreadCount = 0;
83 #define PACKVERSION(major,minor) MAKELONG(minor,major)
85 void DebugOutputLastError()
87 LPVOID lpMsgBuf;
88 if (!FormatMessage(
89 FORMAT_MESSAGE_ALLOCATE_BUFFER |
90 FORMAT_MESSAGE_FROM_SYSTEM |
91 FORMAT_MESSAGE_IGNORE_INSERTS,
92 nullptr,
93 GetLastError(),
94 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
95 (LPTSTR) &lpMsgBuf,
97 nullptr))
99 return;
102 // Display the string.
103 OutputDebugStringA("TGitCache GetLastError(): ");
104 OutputDebugString((LPCTSTR)lpMsgBuf);
105 OutputDebugStringA("\n");
107 // Free the buffer.
108 LocalFree( lpMsgBuf );
111 void HandleCommandLine(LPSTR lpCmdLine)
113 char *ptr = strstr(lpCmdLine, "/kill:");
114 if (ptr)
116 DWORD pid = (DWORD)atoi(ptr + strlen("/kill:"));
117 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, pid);
118 if (hProcess)
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());
131 if (!hMutex)
132 break;
133 ::CloseHandle(hMutex);
134 ::Sleep(1000);
140 void HandleRestart()
142 if (bRestart)
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)
156 return;
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;
164 // load the icon
165 niData.hIcon =
166 (HICON)LoadImage(GetModuleHandle(nullptr),
167 MAKEINTRESOURCE(IDI_TGITCACHE),
168 IMAGE_ICON,
169 GetSystemMetrics(SM_CXSMICON),
170 GetSystemMetrics(SM_CYSMICON),
171 LR_DEFAULTCOLOR);
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);
178 // free icon handle
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"");
186 git_libgit2_init();
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");
195 return 0;
198 CGitStatusCache::Create();
199 CGitStatusCache::Instance().Init();
201 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
203 DWORD dwThreadId;
204 MSG msg;
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;
217 if (!hWndHidden)
218 return 0;
220 // Create a thread which waits for incoming pipe connections
221 CAutoGeneralHandle hPipeThread = CreateThread(
222 nullptr, // no security attribute
223 0, // default stack size
224 PipeThread,
225 (LPVOID) &bRun, // thread parameter
226 0, // not suspended
227 &dwThreadId); // returns thread ID
229 if (!hPipeThread)
230 return 0;
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
237 CommandWaitThread,
238 (LPVOID) &bRun, // thread parameter
239 0, // not suspended
240 &dwThreadId); // returns thread ID
242 if (!hCommandWaitThread)
243 return 0;
245 AddSystrayIcon();
247 // loop to handle window messages.
248 while (bRun)
250 BOOL bLoopRet = GetMessage(&msg, nullptr, 0, 0);
251 if ((bLoopRet != -1)&&(bLoopRet != 0))
252 DispatchMessage(&msg);
255 bRun = false;
257 CGitStatusCache::Destroy();
258 HandleRestart();
259 return 0;
262 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
264 static UINT s_uTaskbarRestart = RegisterWindowMessage(L"TaskbarCreated");
265 switch (message)
267 case TRAY_CALLBACK:
269 switch(lParam)
271 case WM_LBUTTONDBLCLK:
272 if (IsWindowVisible(hWnd))
273 ShowWindow(hWnd, SW_HIDE);
274 else
275 ShowWindow(hWnd, SW_RESTORE);
276 break;
277 case WM_MOUSEMOVE:
279 CString sInfoTip;
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);
292 break;
293 case WM_RBUTTONUP:
294 case WM_CONTEXTMENU:
296 POINT pt;
297 GetCursorPos(&pt);
298 HMENU hMenu = CreatePopupMenu();
299 if(hMenu)
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);
306 DestroyMenu(hMenu);
309 break;
312 break;
313 case WM_PAINT:
315 PAINTSTRUCT ps;
316 HDC hdc = BeginPaint(hWnd, &ps);
317 RECT rect;
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);
324 int line = 0;
325 SIZE fontsize = {0};
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]));
331 ++line;
333 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
335 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)wcslen(szCurrentCrawledPath[i]));
336 ++line;
339 SelectObject(hdc,oldbrush);
340 EndPaint(hWnd, &ps);
341 DeleteObject(background);
342 return 0L;
344 break;
345 case WM_COMMAND:
347 WORD wmId = LOWORD(wParam);
349 switch (wmId)
351 case TRAYPOP_ENABLE:
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;
356 if (enabled)
358 bRestart = true;
359 DestroyWindow(hWnd);
361 break;
363 case TRAYPOP_EXIT:
364 DestroyWindow(hWnd);
365 break;
367 return 1;
369 case WM_QUERYENDSESSION:
371 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
372 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
373 CGitStatusCache::Instance().Stop();
374 return TRUE;
376 break;
377 case WM_CLOSE:
378 case WM_ENDSESSION:
379 case WM_DESTROY:
380 case WM_QUIT:
382 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
383 if (niData.hWnd)
385 niData.hIcon = (HICON)LoadImage(GetModuleHandle(nullptr),
386 MAKEINTRESOURCE(IDI_TGITCACHE_STOPPING),
387 IMAGE_ICON,
388 GetSystemMetrics(SM_CXSMICON),
389 GetSystemMetrics(SM_CYSMICON),
390 LR_DEFAULTCOLOR);
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)
397 PostQuitMessage(0);
398 bRun = false;
399 return 1;
401 break;
402 case WM_DEVICECHANGE:
404 DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;
405 switch (wParam)
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);
427 break;
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);
448 drive += L":\\";
449 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
453 else
455 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
456 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
458 break;
461 break;
462 default:
463 if (message == s_uTaskbarRestart)
464 AddSystrayIcon();
465 break;
467 return DefWindowProc(hWnd, message, wParam, lParam);
470 //////////////////////////////////////////////////////////////////////////
472 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
474 CTGitPath path;
475 *pResponseLength = 0;
476 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
477 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
478 else
479 path.SetFromWin(pRequest->path);
481 if (!bRun)
483 CStatusCacheEntry entry;
484 entry.BuildCacheResponse(*pReply, *pResponseLength);
485 return;
488 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
489 if (readLock.IsAcquired())
490 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags, false).BuildCacheResponse(*pReply, *pResponseLength);
491 else
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.
506 DWORD dwThreadId;
507 BOOL fConnected;
508 CAutoFile hPipe;
510 while (*bThreadRun)
512 hPipe = CreateNamedPipe(
513 GetCachePipeName(),
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
524 if (!hPipe)
526 if (*bThreadRun)
527 Sleep(200);
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);
535 if (fConnected)
537 // Create a thread for this client.
538 CAutoGeneralHandle hInstanceThread = CreateThread(
539 nullptr, // no security attribute
540 0, // default stack size
541 InstanceThread,
542 (HANDLE) hPipe, // thread parameter
543 0, // not suspended
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);
553 return 1;
555 // detach the handle, since we passed it to the thread
556 hPipe.Detach();
558 else
560 // The client could not connect, so close the pipe.
561 hPipe.CloseHandle();
562 if (*bThreadRun)
563 Sleep(200);
564 continue; // don't end the thread!
567 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
568 return 0;
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.
579 DWORD dwThreadId;
580 BOOL fConnected;
581 CAutoFile hPipe;
583 while (*bThreadRun)
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
597 if (!hPipe)
599 if (*bThreadRun)
600 Sleep(200);
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);
608 if (fConnected)
610 // Create a thread for this client.
611 CAutoGeneralHandle hCommandThread = CreateThread(
612 nullptr, // no security attribute
613 0, // default stack size
614 CommandThread,
615 (HANDLE) hPipe, // thread parameter
616 0, // not suspended
617 &dwThreadId); // returns thread ID
619 if (!hCommandThread)
621 DisconnectNamedPipe(hPipe);
622 hPipe.CloseHandle();
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);
627 return 1;
629 // detach the handle, since we passed it to the thread
630 hPipe.Detach();
632 else
634 // The client could not connect, so close the pipe.
635 hPipe.CloseHandle();
636 if (*bThreadRun)
637 Sleep(200);
638 continue; // don't end the thread!
641 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
642 return 0;
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);
655 while (bRun)
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);
673 return 1;
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);
694 return 1;
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);
708 return 0;
711 DWORD WINAPI CommandThread(LPVOID lpvParam)
713 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
714 DWORD cbBytesRead;
716 // The thread's parameter is a handle to a pipe instance.
717 CAutoFile hPipe(std::move(lpvParam));
719 while (bRun)
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");
734 return 1;
737 switch (command.command)
739 case TGITCACHECOMMAND_END:
740 FlushFileBuffers(hPipe);
741 DisconnectNamedPipe(hPipe);
742 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
743 return 0;
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());
755 break;
756 case TGITCACHECOMMAND_REFRESHALL:
758 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
759 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": refresh all\n");
760 CGitStatusCache::Instance().Refresh();
762 break;
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);
772 break;
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);
780 break;
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);
788 break;
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");
799 return 0;