Do not shadow variables
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blobee09adb444a74e7a0f74926ea1ce67664785dd9f
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 - 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 "DllVersion.h"
36 #include "CreateProcessHelper.h"
37 #include "gitindex.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 CGitIndexFileMap g_IndexFileMap;
82 volatile LONG nThreadCount = 0;
84 #define PACKVERSION(major,minor) MAKELONG(minor,major)
86 void DebugOutputLastError()
88 LPVOID lpMsgBuf;
89 if (!FormatMessage(
90 FORMAT_MESSAGE_ALLOCATE_BUFFER |
91 FORMAT_MESSAGE_FROM_SYSTEM |
92 FORMAT_MESSAGE_IGNORE_INSERTS,
93 NULL,
94 GetLastError(),
95 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
96 (LPTSTR) &lpMsgBuf,
98 NULL ))
100 return;
103 // Display the string.
104 OutputDebugStringA("TGitCache GetLastError(): ");
105 OutputDebugString((LPCTSTR)lpMsgBuf);
106 OutputDebugStringA("\n");
108 // Free the buffer.
109 LocalFree( lpMsgBuf );
112 void HandleCommandLine(LPSTR lpCmdLine)
114 char *ptr = strstr(lpCmdLine, "/kill:");
115 if (ptr)
117 DWORD pid = (DWORD)atoi(ptr + strlen("/kill:"));
118 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, pid);
119 if (hProcess)
121 if (::WaitForSingleObject(hProcess, 5000) != WAIT_OBJECT_0)
123 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Killing previous TGitCache PID %d\n", pid);
124 if (!::TerminateProcess(hProcess, (UINT)-1))
125 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Kill previous TGitCache PID %d failed\n", pid);
126 ::WaitForSingleObject(hProcess, 5000);
128 ::CloseHandle(hProcess);
129 for (int i = 0; i < 5; i++)
131 HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, GetCacheMutexName());
132 if (!hMutex)
133 break;
134 ::CloseHandle(hMutex);
135 ::Sleep(1000);
141 void HandleRestart()
143 if (bRestart)
145 TCHAR exeName[MAX_PATH] = { 0 };
146 ::GetModuleFileName(nullptr, exeName, _countof(exeName));
147 TCHAR cmdLine[20] = { 0 };
148 _stprintf_s(cmdLine, _T(" /kill:%d"), GetCurrentProcessId());
149 if (!CCreateProcessHelper::CreateProcessDetached(exeName, cmdLine))
150 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
154 int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR lpCmdLine, int /*cmdShow*/)
156 SetDllDirectory(L"");
157 git_libgit2_init();
158 HandleCommandLine(lpCmdLine);
159 CAutoGeneralHandle hReloadProtection = ::CreateMutex(NULL, FALSE, GetCacheMutexName());
161 if ((!hReloadProtection) || (GetLastError() == ERROR_ALREADY_EXISTS))
163 // An instance of TGitCache is already running
164 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": TGitCache ignoring restart\n");
165 return 0;
168 CGitStatusCache::Create();
169 CGitStatusCache::Instance().Init();
171 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
173 DWORD dwThreadId;
174 MSG msg;
175 TCHAR szWindowClass[] = {TGIT_CACHE_WINDOW_NAME};
177 // create a hidden window to receive window messages.
178 WNDCLASSEX wcex;
179 wcex.cbSize = sizeof(WNDCLASSEX);
180 wcex.style = CS_HREDRAW | CS_VREDRAW;
181 wcex.lpfnWndProc = (WNDPROC)WndProc;
182 wcex.cbClsExtra = 0;
183 wcex.cbWndExtra = 0;
184 wcex.hInstance = hInstance;
185 wcex.hIcon = 0;
186 wcex.hCursor = 0;
187 wcex.hbrBackground = 0;
188 wcex.lpszMenuName = NULL;
189 wcex.lpszClassName = szWindowClass;
190 wcex.hIconSm = 0;
191 RegisterClassEx(&wcex);
192 hWndHidden = CreateWindow(TGIT_CACHE_WINDOW_NAME, TGIT_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, NULL, 0, hInstance, 0);
193 hTrayWnd = hWndHidden;
194 if (hWndHidden == NULL)
196 return 0;
198 if (CRegStdDWORD(_T("Software\\TortoiseGit\\CacheTrayIcon"), FALSE)==TRUE)
200 SecureZeroMemory(&niData,sizeof(NOTIFYICONDATA));
202 DWORD dwMajor = 0;
203 DWORD dwMinor = 0;
204 GetShellVersion(&dwMajor, &dwMinor);
205 DWORD dwVersion = PACKVERSION(dwMajor, dwMinor);
206 if (dwVersion >= PACKVERSION(6,0))
207 niData.cbSize = sizeof(NOTIFYICONDATA);
208 else if (dwVersion >= PACKVERSION(5,0))
209 niData.cbSize = NOTIFYICONDATA_V2_SIZE;
210 else
211 niData.cbSize = NOTIFYICONDATA_V1_SIZE;
213 niData.uID = TRAY_ID; // own tray icon ID
214 niData.hWnd = hWndHidden;
215 niData.uFlags = NIF_ICON|NIF_MESSAGE;
217 // load the icon
218 niData.hIcon =
219 (HICON)LoadImage(hInstance,
220 MAKEINTRESOURCE(IDI_TGITCACHE),
221 IMAGE_ICON,
222 GetSystemMetrics(SM_CXSMICON),
223 GetSystemMetrics(SM_CYSMICON),
224 LR_DEFAULTCOLOR);
226 // set the message to send
227 // note: the message value should be in the
228 // range of WM_APP through 0xBFFF
229 niData.uCallbackMessage = TRAY_CALLBACK;
230 Shell_NotifyIcon(NIM_ADD,&niData);
231 // free icon handle
232 if(niData.hIcon && DestroyIcon(niData.hIcon))
233 niData.hIcon = NULL;
236 // Create a thread which waits for incoming pipe connections
237 CAutoGeneralHandle hPipeThread = CreateThread(
238 NULL, // no security attribute
239 0, // default stack size
240 PipeThread,
241 (LPVOID) &bRun, // thread parameter
242 0, // not suspended
243 &dwThreadId); // returns thread ID
245 if (!hPipeThread)
247 return 0;
249 else hPipeThread.CloseHandle();
251 // Create a thread which waits for incoming pipe connections
252 CAutoGeneralHandle hCommandWaitThread = CreateThread(
253 NULL, // no security attribute
254 0, // default stack size
255 CommandWaitThread,
256 (LPVOID) &bRun, // thread parameter
257 0, // not suspended
258 &dwThreadId); // returns thread ID
260 if (!hCommandWaitThread)
262 return 0;
266 // loop to handle window messages.
267 while (bRun)
269 BOOL bLoopRet = GetMessage(&msg, NULL, 0, 0);
270 if ((bLoopRet != -1)&&(bLoopRet != 0))
272 DispatchMessage(&msg);
276 bRun = false;
278 CGitStatusCache::Destroy();
279 HandleRestart();
280 return 0;
283 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
285 switch (message)
287 case TRAY_CALLBACK:
289 switch(lParam)
291 case WM_LBUTTONDBLCLK:
292 if (IsWindowVisible(hWnd))
293 ShowWindow(hWnd, SW_HIDE);
294 else
295 ShowWindow(hWnd, SW_RESTORE);
296 break;
297 case WM_MOUSEMOVE:
299 CString sInfoTip;
300 NOTIFYICONDATA SystemTray;
301 sInfoTip.Format(_T("TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d"),
302 CGitStatusCache::Instance().GetCacheSize(),
303 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
305 SystemTray.cbSize = sizeof(NOTIFYICONDATA);
306 SystemTray.hWnd = hTrayWnd;
307 SystemTray.uID = TRAY_ID;
308 SystemTray.uFlags = NIF_TIP;
309 _tcscpy_s(SystemTray.szTip, sInfoTip);
310 Shell_NotifyIcon(NIM_MODIFY, &SystemTray);
312 break;
313 case WM_RBUTTONUP:
314 case WM_CONTEXTMENU:
316 POINT pt;
317 GetCursorPos(&pt);
318 HMENU hMenu = CreatePopupMenu();
319 if(hMenu)
321 bool enabled = (DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) != ShellCache::none;
322 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_ENABLE, enabled ? _T("Disable Status Cache") : _T("Enable Status Cache"));
323 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_EXIT, _T("Exit"));
324 SetForegroundWindow(hWnd);
325 TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, NULL);
326 DestroyMenu(hMenu);
329 break;
332 break;
333 case WM_PAINT:
335 PAINTSTRUCT ps;
336 HDC hdc = BeginPaint(hWnd, &ps);
337 RECT rect;
338 GetClientRect(hWnd, &rect);
339 // clear the background
340 HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
341 HGDIOBJ oldbrush = SelectObject(hdc, background);
342 FillRect(hdc, &rect, background);
344 int line = 0;
345 SIZE fontsize = {0};
346 AutoLocker print(critSec);
347 GetTextExtentPoint32( hdc, szCurrentCrawledPath[0], (int)_tcslen(szCurrentCrawledPath[0]), &fontsize );
348 for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)
350 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
351 ++line;
353 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
355 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
356 ++line;
359 SelectObject(hdc,oldbrush);
360 EndPaint(hWnd, &ps);
361 DeleteObject(background);
362 return 0L;
364 break;
365 case WM_COMMAND:
367 WORD wmId = LOWORD(wParam);
369 switch (wmId)
371 case TRAYPOP_ENABLE:
373 CRegStdDWORD reg = CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe);
374 bool enabled = (DWORD)reg != ShellCache::none;
375 reg = enabled ? ShellCache::none : ShellCache::exe;
376 if (enabled)
378 bRestart = true;
379 DestroyWindow(hWnd);
381 break;
383 case TRAYPOP_EXIT:
384 DestroyWindow(hWnd);
385 break;
387 return 1;
389 case WM_QUERYENDSESSION:
391 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
392 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
393 CGitStatusCache::Instance().Stop();
394 return TRUE;
396 break;
397 case WM_CLOSE:
398 case WM_ENDSESSION:
399 case WM_DESTROY:
400 case WM_QUIT:
402 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
403 if (niData.hWnd)
405 niData.hIcon = (HICON)LoadImage(GetModuleHandle(NULL),
406 MAKEINTRESOURCE(IDI_TGITCACHE_STOPPING),
407 IMAGE_ICON,
408 GetSystemMetrics(SM_CXSMICON),
409 GetSystemMetrics(SM_CYSMICON),
410 LR_DEFAULTCOLOR);
411 Shell_NotifyIcon(NIM_MODIFY, &niData);
413 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
414 CGitStatusCache::Instance().Stop();
415 CGitStatusCache::Instance().SaveCache();
416 if (message != WM_QUIT)
417 PostQuitMessage(0);
418 bRun = false;
419 return 1;
421 break;
422 case WM_DEVICECHANGE:
424 DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;
425 switch (wParam)
427 case DBT_CUSTOMEVENT:
429 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
430 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
432 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
433 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))
435 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device to be dismounted\n");
436 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
437 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
439 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))
441 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device lock event\n");
442 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
443 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
447 break;
448 case DBT_DEVICEREMOVEPENDING:
449 case DBT_DEVICEQUERYREMOVE:
450 case DBT_DEVICEREMOVECOMPLETE:
451 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
452 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
454 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
455 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
456 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
458 else if (phdr->dbch_devicetype == DBT_DEVTYP_VOLUME)
460 DEV_BROADCAST_VOLUME * pVolume = (DEV_BROADCAST_VOLUME*)lParam;
461 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
462 for (BYTE i = 0; i < 26; ++i)
464 if (pVolume->dbcv_unitmask & (1 << i))
466 TCHAR driveletter = 'A' + i;
467 CString drive = CString(driveletter);
468 drive += L":\\";
469 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
473 else
475 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
476 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
478 break;
481 break;
482 default:
483 break;
485 return DefWindowProc(hWnd, message, wParam, lParam);
488 //////////////////////////////////////////////////////////////////////////
490 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
492 CTGitPath path;
493 *pResponseLength = 0;
494 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
496 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
498 else
500 path.SetFromWin(pRequest->path);
503 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
504 if (readLock.IsAcquired())
506 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags, false).BuildCacheResponse(*pReply, *pResponseLength);
508 else
510 CStatusCacheEntry entry;
511 entry.BuildCacheResponse(*pReply, *pResponseLength);
515 DWORD WINAPI PipeThread(LPVOID lpvParam)
517 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": PipeThread started\n");
518 bool* bThreadRun = (bool*)lpvParam;
519 // The main loop creates an instance of the named pipe and
520 // then waits for a client to connect to it. When the client
521 // connects, a thread is created to handle communications
522 // with that client, and the loop is repeated.
523 DWORD dwThreadId;
524 BOOL fConnected;
525 CAutoFile hPipe;
527 while (*bThreadRun)
529 hPipe = CreateNamedPipe(
530 GetCachePipeName(),
531 PIPE_ACCESS_DUPLEX, // read/write access
532 PIPE_TYPE_MESSAGE | // message type pipe
533 PIPE_READMODE_MESSAGE | // message-read mode
534 PIPE_WAIT, // blocking mode
535 PIPE_UNLIMITED_INSTANCES, // max. instances
536 BUFSIZE, // output buffer size
537 BUFSIZE, // input buffer size
538 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
539 NULL); // NULL DACL
541 if (!hPipe)
543 if (*bThreadRun)
544 Sleep(200);
545 continue; // never leave the thread!
548 // Wait for the client to connect; if it succeeds,
549 // the function returns a nonzero value. If the function returns
550 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
551 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
552 if (fConnected)
554 // Create a thread for this client.
555 CAutoGeneralHandle hInstanceThread = CreateThread(
556 NULL, // no security attribute
557 0, // default stack size
558 InstanceThread,
559 (HANDLE) hPipe, // thread parameter
560 0, // not suspended
561 &dwThreadId); // returns thread ID
563 if (!hInstanceThread)
565 DisconnectNamedPipe(hPipe);
566 // since we're now closing this thread, we also have to close the whole application!
567 // otherwise the thread is dead, but the app is still running, refusing new instances
568 // but no pipe will be available anymore.
569 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
570 return 1;
572 // detach the handle, since we passed it to the thread
573 hPipe.Detach();
575 else
577 // The client could not connect, so close the pipe.
578 hPipe.CloseHandle();
579 if (*bThreadRun)
580 Sleep(200);
581 continue; // don't end the thread!
584 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
585 return 0;
588 DWORD WINAPI CommandWaitThread(LPVOID lpvParam)
590 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWaitThread started\n");
591 bool* bThreadRun = (bool*)lpvParam;
592 // The main loop creates an instance of the named pipe and
593 // then waits for a client to connect to it. When the client
594 // connects, a thread is created to handle communications
595 // with that client, and the loop is repeated.
596 DWORD dwThreadId;
597 BOOL fConnected;
598 CAutoFile hPipe;
600 while (*bThreadRun)
602 hPipe = CreateNamedPipe(
603 GetCacheCommandPipeName(),
604 PIPE_ACCESS_DUPLEX, // read/write access
605 PIPE_TYPE_MESSAGE | // message type pipe
606 PIPE_READMODE_MESSAGE | // message-read mode
607 PIPE_WAIT, // blocking mode
608 PIPE_UNLIMITED_INSTANCES, // max. instances
609 BUFSIZE, // output buffer size
610 BUFSIZE, // input buffer size
611 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
612 NULL); // NULL DACL
614 if (!hPipe)
616 if (*bThreadRun)
617 Sleep(200);
618 continue; // never leave the thread!
621 // Wait for the client to connect; if it succeeds,
622 // the function returns a nonzero value. If the function returns
623 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
624 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
625 if (fConnected)
627 // Create a thread for this client.
628 CAutoGeneralHandle hCommandThread = CreateThread(
629 NULL, // no security attribute
630 0, // default stack size
631 CommandThread,
632 (HANDLE) hPipe, // thread parameter
633 0, // not suspended
634 &dwThreadId); // returns thread ID
636 if (!hCommandThread)
638 DisconnectNamedPipe(hPipe);
639 hPipe.CloseHandle();
640 // since we're now closing this thread, we also have to close the whole application!
641 // otherwise the thread is dead, but the app is still running, refusing new instances
642 // but no pipe will be available anymore.
643 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
644 return 1;
646 // detach the handle, since we passed it to the thread
647 hPipe.Detach();
649 else
651 // The client could not connect, so close the pipe.
652 hPipe.CloseHandle();
653 if (*bThreadRun)
654 Sleep(200);
655 continue; // don't end the thread!
658 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
659 return 0;
662 DWORD WINAPI InstanceThread(LPVOID lpvParam)
664 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": InstanceThread started\n");
665 TGITCacheResponse response;
666 DWORD cbBytesRead, cbWritten;
667 CAutoFile hPipe;
669 // The thread's parameter is a handle to a pipe instance.
671 hPipe = lpvParam;
672 InterlockedIncrement(&nThreadCount);
673 while (bRun)
675 // Read client requests from the pipe.
676 TGITCacheRequest request;
677 BOOL fSuccess = ReadFile(
678 hPipe, // handle to pipe
679 &request, // buffer to receive data
680 sizeof(request), // size of buffer
681 &cbBytesRead, // number of bytes read
682 NULL); // not overlapped I/O
684 if (! fSuccess || cbBytesRead == 0)
686 DisconnectNamedPipe(hPipe);
687 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
688 InterlockedDecrement(&nThreadCount);
689 if (nThreadCount == 0)
690 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
691 return 1;
694 DWORD responseLength;
695 GetAnswerToRequest(&request, &response, &responseLength);
697 // Write the reply to the pipe.
698 fSuccess = WriteFile(
699 hPipe, // handle to pipe
700 &response, // buffer to write from
701 responseLength, // number of bytes to write
702 &cbWritten, // number of bytes written
703 NULL); // not overlapped I/O
705 if (! fSuccess || responseLength != cbWritten)
707 DisconnectNamedPipe(hPipe);
708 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
709 InterlockedDecrement(&nThreadCount);
710 if (nThreadCount == 0)
711 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
712 return 1;
716 // Flush the pipe to allow the client to read the pipe's contents
717 // before disconnecting. Then disconnect the pipe, and close the
718 // handle to this pipe instance.
720 FlushFileBuffers(hPipe);
721 DisconnectNamedPipe(hPipe);
722 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
723 InterlockedDecrement(&nThreadCount);
724 if (nThreadCount == 0)
725 PostMessage(hWndHidden, WM_CLOSE, 0, 0);
726 return 0;
729 DWORD WINAPI CommandThread(LPVOID lpvParam)
731 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
732 DWORD cbBytesRead;
733 CAutoFile hPipe;
735 // The thread's parameter is a handle to a pipe instance.
737 hPipe = lpvParam;
739 while (bRun)
741 // Read client requests from the pipe.
742 TGITCacheCommand command;
743 BOOL fSuccess = ReadFile(
744 hPipe, // handle to pipe
745 &command, // buffer to receive data
746 sizeof(command), // size of buffer
747 &cbBytesRead, // number of bytes read
748 NULL); // not overlapped I/O
750 if (! fSuccess || cbBytesRead == 0)
752 DisconnectNamedPipe(hPipe);
753 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
754 return 1;
757 switch (command.command)
759 case TGITCACHECOMMAND_END:
760 FlushFileBuffers(hPipe);
761 DisconnectNamedPipe(hPipe);
762 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
763 return 0;
764 case TGITCACHECOMMAND_CRAWL:
766 CTGitPath changedpath;
767 changedpath.SetFromWin(command.path, true);
768 // remove the path from our cache - that will 'invalidate' it.
770 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
771 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
773 CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());
775 break;
776 case TGITCACHECOMMAND_REFRESHALL:
778 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
779 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": refresh all\n"));
780 CGitStatusCache::Instance().Refresh();
782 break;
783 case TGITCACHECOMMAND_RELEASE:
785 CTGitPath changedpath;
786 changedpath.SetFromWin(command.path, true);
787 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": release handle for path %s\n"), changedpath.GetWinPath());
788 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
789 CGitStatusCache::Instance().CloseWatcherHandles(changedpath);
790 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
792 break;
793 case TGITCACHECOMMAND_BLOCK:
795 CTGitPath changedpath;
796 changedpath.SetFromWin(command.path);
797 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": block path %s\n"), changedpath.GetWinPath());
798 CGitStatusCache::Instance().BlockPath(changedpath);
800 break;
801 case TGITCACHECOMMAND_UNBLOCK:
803 CTGitPath changedpath;
804 changedpath.SetFromWin(command.path);
805 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": unblock path %s\n"), changedpath.GetWinPath());
806 CGitStatusCache::Instance().UnBlockPath(changedpath);
808 break;
812 // Flush the pipe to allow the client to read the pipe's contents
813 // before disconnecting. Then disconnect the pipe, and close the
814 // handle to this pipe instance.
816 FlushFileBuffers(hPipe);
817 DisconnectNamedPipe(hPipe);
818 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
819 return 0;