Use libgit2 with threads
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blobad048e78b9a0f62cd0f2a4c04ad61b67b1dc4cb6
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"
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 hWnd;
61 HWND hTrayWnd;
62 TCHAR szCurrentCrawledPath[MAX_CRAWLEDPATHS][MAX_CRAWLEDPATHSLEN];
63 int nCurrentCrawledpathIndex = 0;
64 CComAutoCriticalSection critSec;
66 volatile LONG nThreadCount = 0;
68 #define PACKVERSION(major,minor) MAKELONG(minor,major)
70 void DebugOutputLastError()
72 LPVOID lpMsgBuf;
73 if (!FormatMessage(
74 FORMAT_MESSAGE_ALLOCATE_BUFFER |
75 FORMAT_MESSAGE_FROM_SYSTEM |
76 FORMAT_MESSAGE_IGNORE_INSERTS,
77 NULL,
78 GetLastError(),
79 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
80 (LPTSTR) &lpMsgBuf,
82 NULL ))
84 return;
87 // Display the string.
88 OutputDebugStringA("TGitCache GetLastError(): ");
89 OutputDebugString((LPCTSTR)lpMsgBuf);
90 OutputDebugStringA("\n");
92 // Free the buffer.
93 LocalFree( lpMsgBuf );
96 void HandleCommandLine(LPSTR lpCmdLine)
98 char *ptr = strstr(lpCmdLine, "/kill:");
99 if (ptr)
101 DWORD pid = (DWORD)atoi(ptr + strlen("/kill:"));
102 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, pid);
103 if (hProcess)
105 if (::WaitForSingleObject(hProcess, 5000) != WAIT_OBJECT_0)
107 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Killing previous TGitCache PID %d\n", pid);
108 if (!::TerminateProcess(hProcess, (UINT)-1))
109 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Kill previous TGitCache PID %d failed\n", pid);
110 ::WaitForSingleObject(hProcess, 5000);
112 ::CloseHandle(hProcess);
113 for (int i = 0; i < 5; i++)
115 HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, GetCacheMutexName());
116 if (!hMutex)
117 break;
118 ::CloseHandle(hMutex);
119 ::Sleep(1000);
125 void HandleRestart()
127 if (bRestart)
129 TCHAR exeName[MAX_PATH] = { 0 };
130 ::GetModuleFileName(nullptr, exeName, _countof(exeName));
131 TCHAR cmdLine[20] = { 0 };
132 _stprintf_s(cmdLine, _T(" /kill:%d"), GetCurrentProcessId());
133 if (!CCreateProcessHelper::CreateProcessDetached(exeName, cmdLine))
134 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
138 int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR lpCmdLine, int /*cmdShow*/)
140 SetDllDirectory(L"");
141 git_threads_init();
142 HandleCommandLine(lpCmdLine);
143 CAutoGeneralHandle hReloadProtection = ::CreateMutex(NULL, FALSE, GetCacheMutexName());
145 if ((!hReloadProtection) || (GetLastError() == ERROR_ALREADY_EXISTS))
147 // An instance of TGitCache is already running
148 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": TGitCache ignoring restart\n");
149 return 0;
152 CGitStatusCache::Create();
153 CGitStatusCache::Instance().Init();
155 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
157 DWORD dwThreadId;
158 MSG msg;
159 TCHAR szWindowClass[] = {TGIT_CACHE_WINDOW_NAME};
161 // create a hidden window to receive window messages.
162 WNDCLASSEX wcex;
163 wcex.cbSize = sizeof(WNDCLASSEX);
164 wcex.style = CS_HREDRAW | CS_VREDRAW;
165 wcex.lpfnWndProc = (WNDPROC)WndProc;
166 wcex.cbClsExtra = 0;
167 wcex.cbWndExtra = 0;
168 wcex.hInstance = hInstance;
169 wcex.hIcon = 0;
170 wcex.hCursor = 0;
171 wcex.hbrBackground = 0;
172 wcex.lpszMenuName = NULL;
173 wcex.lpszClassName = szWindowClass;
174 wcex.hIconSm = 0;
175 RegisterClassEx(&wcex);
176 hWnd = CreateWindow(TGIT_CACHE_WINDOW_NAME, TGIT_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, NULL, 0, hInstance, 0);
177 hTrayWnd = hWnd;
178 if (hWnd == NULL)
180 return 0;
182 if (CRegStdDWORD(_T("Software\\TortoiseGit\\CacheTrayIcon"), FALSE)==TRUE)
184 SecureZeroMemory(&niData,sizeof(NOTIFYICONDATA));
186 DWORD dwMajor = 0;
187 DWORD dwMinor = 0;
188 GetShellVersion(&dwMajor, &dwMinor);
189 DWORD dwVersion = PACKVERSION(dwMajor, dwMinor);
190 if (dwVersion >= PACKVERSION(6,0))
191 niData.cbSize = sizeof(NOTIFYICONDATA);
192 else if (dwVersion >= PACKVERSION(5,0))
193 niData.cbSize = NOTIFYICONDATA_V2_SIZE;
194 else
195 niData.cbSize = NOTIFYICONDATA_V1_SIZE;
197 niData.uID = TRAY_ID; // own tray icon ID
198 niData.hWnd = hWnd;
199 niData.uFlags = NIF_ICON|NIF_MESSAGE;
201 // load the icon
202 niData.hIcon =
203 (HICON)LoadImage(hInstance,
204 MAKEINTRESOURCE(IDI_TGITCACHE),
205 IMAGE_ICON,
206 GetSystemMetrics(SM_CXSMICON),
207 GetSystemMetrics(SM_CYSMICON),
208 LR_DEFAULTCOLOR);
210 // set the message to send
211 // note: the message value should be in the
212 // range of WM_APP through 0xBFFF
213 niData.uCallbackMessage = TRAY_CALLBACK;
214 Shell_NotifyIcon(NIM_ADD,&niData);
215 // free icon handle
216 if(niData.hIcon && DestroyIcon(niData.hIcon))
217 niData.hIcon = NULL;
220 // Create a thread which waits for incoming pipe connections
221 CAutoGeneralHandle hPipeThread = CreateThread(
222 NULL, // 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)
231 return 0;
233 else hPipeThread.CloseHandle();
235 // Create a thread which waits for incoming pipe connections
236 CAutoGeneralHandle hCommandWaitThread = CreateThread(
237 NULL, // no security attribute
238 0, // default stack size
239 CommandWaitThread,
240 (LPVOID) &bRun, // thread parameter
241 0, // not suspended
242 &dwThreadId); // returns thread ID
244 if (!hCommandWaitThread)
246 return 0;
250 // loop to handle window messages.
251 while (bRun)
253 BOOL bLoopRet = GetMessage(&msg, NULL, 0, 0);
254 if ((bLoopRet != -1)&&(bLoopRet != 0))
256 DispatchMessage(&msg);
260 bRun = false;
262 Shell_NotifyIcon(NIM_DELETE,&niData);
263 CGitStatusCache::Destroy();
264 HandleRestart();
265 git_threads_shutdown();
266 return 0;
269 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
271 switch (message)
273 case TRAY_CALLBACK:
275 switch(lParam)
277 case WM_LBUTTONDBLCLK:
278 if (IsWindowVisible(hWnd))
279 ShowWindow(hWnd, SW_HIDE);
280 else
281 ShowWindow(hWnd, SW_RESTORE);
282 break;
283 case WM_MOUSEMOVE:
285 CString sInfoTip;
286 NOTIFYICONDATA SystemTray;
287 sInfoTip.Format(_T("TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d"),
288 CGitStatusCache::Instance().GetCacheSize(),
289 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
291 SystemTray.cbSize = sizeof(NOTIFYICONDATA);
292 SystemTray.hWnd = hTrayWnd;
293 SystemTray.uID = TRAY_ID;
294 SystemTray.uFlags = NIF_TIP;
295 _tcscpy_s(SystemTray.szTip, sInfoTip);
296 Shell_NotifyIcon(NIM_MODIFY, &SystemTray);
298 break;
299 case WM_RBUTTONUP:
300 case WM_CONTEXTMENU:
302 POINT pt;
303 GetCursorPos(&pt);
304 HMENU hMenu = CreatePopupMenu();
305 if(hMenu)
307 bool enabled = (DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) != ShellCache::none;
308 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_ENABLE, enabled ? _T("Disable Status Cache") : _T("Enable Status Cache"));
309 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_EXIT, _T("Exit"));
310 SetForegroundWindow(hWnd);
311 TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, NULL);
312 DestroyMenu(hMenu);
315 break;
318 break;
319 case WM_PAINT:
321 PAINTSTRUCT ps;
322 HDC hdc = BeginPaint(hWnd, &ps);
323 RECT rect;
324 GetClientRect(hWnd, &rect);
325 // clear the background
326 HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
327 HGDIOBJ oldbrush = SelectObject(hdc, background);
328 FillRect(hdc, &rect, background);
330 int line = 0;
331 SIZE fontsize = {0};
332 AutoLocker print(critSec);
333 GetTextExtentPoint32( hdc, szCurrentCrawledPath[0], (int)_tcslen(szCurrentCrawledPath[0]), &fontsize );
334 for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)
336 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
337 ++line;
339 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
341 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
342 ++line;
345 SelectObject(hdc,oldbrush);
346 EndPaint(hWnd, &ps);
347 DeleteObject(background);
348 return 0L;
350 break;
351 case WM_COMMAND:
353 WORD wmId = LOWORD(wParam);
355 switch (wmId)
357 case TRAYPOP_ENABLE:
359 CRegStdDWORD reg = CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe);
360 bool enabled = (DWORD)reg != ShellCache::none;
361 reg = enabled ? ShellCache::none : ShellCache::exe;
362 if (enabled)
364 bRestart = true;
365 DestroyWindow(hWnd);
367 break;
369 case TRAYPOP_EXIT:
370 DestroyWindow(hWnd);
371 break;
373 return 1;
375 case WM_QUERYENDSESSION:
377 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
378 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
379 CGitStatusCache::Instance().Stop();
380 return TRUE;
382 break;
383 case WM_CLOSE:
384 case WM_ENDSESSION:
385 case WM_DESTROY:
386 case WM_QUIT:
388 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
389 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
390 CGitStatusCache::Instance().Stop();
391 CGitStatusCache::Instance().SaveCache();
392 if (message != WM_QUIT)
393 PostQuitMessage(0);
394 bRun = false;
395 return 1;
397 break;
398 case WM_DEVICECHANGE:
400 DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;
401 switch (wParam)
403 case DBT_CUSTOMEVENT:
405 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
406 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
408 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
409 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))
411 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device to be dismounted\n");
412 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
413 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
415 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))
417 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device lock event\n");
418 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
419 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
423 break;
424 case DBT_DEVICEREMOVEPENDING:
425 case DBT_DEVICEQUERYREMOVE:
426 case DBT_DEVICEREMOVECOMPLETE:
427 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
428 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
430 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
431 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
432 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
434 else if (phdr->dbch_devicetype == DBT_DEVTYP_VOLUME)
436 DEV_BROADCAST_VOLUME * pVolume = (DEV_BROADCAST_VOLUME*)lParam;
437 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
438 for (BYTE i = 0; i < 26; ++i)
440 if (pVolume->dbcv_unitmask & (1 << i))
442 TCHAR driveletter = 'A' + i;
443 CString drive = CString(driveletter);
444 drive += L":\\";
445 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
449 else
451 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
452 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
454 break;
457 break;
458 default:
459 break;
461 return DefWindowProc(hWnd, message, wParam, lParam);
464 //////////////////////////////////////////////////////////////////////////
466 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
468 CTGitPath path;
469 *pResponseLength = 0;
470 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
472 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
474 else
476 path.SetFromWin(pRequest->path);
479 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
480 if (readLock.IsAcquired())
482 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags, false).BuildCacheResponse(*pReply, *pResponseLength);
484 else
486 CStatusCacheEntry entry;
487 entry.BuildCacheResponse(*pReply, *pResponseLength);
491 DWORD WINAPI PipeThread(LPVOID lpvParam)
493 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": PipeThread started\n");
494 bool * bRun = (bool *)lpvParam;
495 // The main loop creates an instance of the named pipe and
496 // then waits for a client to connect to it. When the client
497 // connects, a thread is created to handle communications
498 // with that client, and the loop is repeated.
499 DWORD dwThreadId;
500 BOOL fConnected;
501 CAutoFile hPipe;
503 while (*bRun)
505 hPipe = CreateNamedPipe(
506 GetCachePipeName(),
507 PIPE_ACCESS_DUPLEX, // read/write access
508 PIPE_TYPE_MESSAGE | // message type pipe
509 PIPE_READMODE_MESSAGE | // message-read mode
510 PIPE_WAIT, // blocking mode
511 PIPE_UNLIMITED_INSTANCES, // max. instances
512 BUFSIZE, // output buffer size
513 BUFSIZE, // input buffer size
514 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
515 NULL); // NULL DACL
517 if (!hPipe)
519 if (*bRun)
520 Sleep(200);
521 continue; // never leave the thread!
524 // Wait for the client to connect; if it succeeds,
525 // the function returns a nonzero value. If the function returns
526 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
527 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
528 if (fConnected)
530 // Create a thread for this client.
531 CAutoGeneralHandle hInstanceThread = CreateThread(
532 NULL, // no security attribute
533 0, // default stack size
534 InstanceThread,
535 (HANDLE) hPipe, // thread parameter
536 0, // not suspended
537 &dwThreadId); // returns thread ID
539 if (!hInstanceThread)
541 DisconnectNamedPipe(hPipe);
542 // since we're now closing this thread, we also have to close the whole application!
543 // otherwise the thread is dead, but the app is still running, refusing new instances
544 // but no pipe will be available anymore.
545 PostMessage(hWnd, WM_CLOSE, 0, 0);
546 return 1;
548 // detach the handle, since we passed it to the thread
549 hPipe.Detach();
551 else
553 // The client could not connect, so close the pipe.
554 hPipe.CloseHandle();
555 if (*bRun)
556 Sleep(200);
557 continue; // don't end the thread!
560 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
561 return 0;
564 DWORD WINAPI CommandWaitThread(LPVOID lpvParam)
566 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWaitThread started\n");
567 bool * bRun = (bool *)lpvParam;
568 // The main loop creates an instance of the named pipe and
569 // then waits for a client to connect to it. When the client
570 // connects, a thread is created to handle communications
571 // with that client, and the loop is repeated.
572 DWORD dwThreadId;
573 BOOL fConnected;
574 CAutoFile hPipe;
576 while (*bRun)
578 hPipe = CreateNamedPipe(
579 GetCacheCommandPipeName(),
580 PIPE_ACCESS_DUPLEX, // read/write access
581 PIPE_TYPE_MESSAGE | // message type pipe
582 PIPE_READMODE_MESSAGE | // message-read mode
583 PIPE_WAIT, // blocking mode
584 PIPE_UNLIMITED_INSTANCES, // max. instances
585 BUFSIZE, // output buffer size
586 BUFSIZE, // input buffer size
587 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
588 NULL); // NULL DACL
590 if (!hPipe)
592 if (*bRun)
593 Sleep(200);
594 continue; // never leave the thread!
597 // Wait for the client to connect; if it succeeds,
598 // the function returns a nonzero value. If the function returns
599 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
600 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
601 if (fConnected)
603 // Create a thread for this client.
604 CAutoGeneralHandle hCommandThread = CreateThread(
605 NULL, // no security attribute
606 0, // default stack size
607 CommandThread,
608 (HANDLE) hPipe, // thread parameter
609 0, // not suspended
610 &dwThreadId); // returns thread ID
612 if (!hCommandThread)
614 DisconnectNamedPipe(hPipe);
615 hPipe.CloseHandle();
616 // since we're now closing this thread, we also have to close the whole application!
617 // otherwise the thread is dead, but the app is still running, refusing new instances
618 // but no pipe will be available anymore.
619 PostMessage(hWnd, WM_CLOSE, 0, 0);
620 return 1;
622 // detach the handle, since we passed it to the thread
623 hPipe.Detach();
625 else
627 // The client could not connect, so close the pipe.
628 hPipe.CloseHandle();
629 if (*bRun)
630 Sleep(200);
631 continue; // don't end the thread!
634 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
635 return 0;
638 DWORD WINAPI InstanceThread(LPVOID lpvParam)
640 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": InstanceThread started\n");
641 TGITCacheResponse response;
642 DWORD cbBytesRead, cbWritten;
643 CAutoFile hPipe;
645 // The thread's parameter is a handle to a pipe instance.
647 hPipe = lpvParam;
648 InterlockedIncrement(&nThreadCount);
649 while (bRun)
651 // Read client requests from the pipe.
652 TGITCacheRequest request;
653 BOOL fSuccess = ReadFile(
654 hPipe, // handle to pipe
655 &request, // buffer to receive data
656 sizeof(request), // size of buffer
657 &cbBytesRead, // number of bytes read
658 NULL); // not overlapped I/O
660 if (! fSuccess || cbBytesRead == 0)
662 DisconnectNamedPipe(hPipe);
663 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
664 InterlockedDecrement(&nThreadCount);
665 if (nThreadCount == 0)
666 PostMessage(hWnd, WM_CLOSE, 0, 0);
667 return 1;
670 DWORD responseLength;
671 GetAnswerToRequest(&request, &response, &responseLength);
673 // Write the reply to the pipe.
674 fSuccess = WriteFile(
675 hPipe, // handle to pipe
676 &response, // buffer to write from
677 responseLength, // number of bytes to write
678 &cbWritten, // number of bytes written
679 NULL); // not overlapped I/O
681 if (! fSuccess || responseLength != cbWritten)
683 DisconnectNamedPipe(hPipe);
684 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
685 InterlockedDecrement(&nThreadCount);
686 if (nThreadCount == 0)
687 PostMessage(hWnd, WM_CLOSE, 0, 0);
688 return 1;
692 // Flush the pipe to allow the client to read the pipe's contents
693 // before disconnecting. Then disconnect the pipe, and close the
694 // handle to this pipe instance.
696 FlushFileBuffers(hPipe);
697 DisconnectNamedPipe(hPipe);
698 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
699 InterlockedDecrement(&nThreadCount);
700 if (nThreadCount == 0)
701 PostMessage(hWnd, WM_CLOSE, 0, 0);
702 return 0;
705 DWORD WINAPI CommandThread(LPVOID lpvParam)
707 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
708 DWORD cbBytesRead;
709 CAutoFile hPipe;
711 // The thread's parameter is a handle to a pipe instance.
713 hPipe = lpvParam;
715 while (bRun)
717 // Read client requests from the pipe.
718 TGITCacheCommand command;
719 BOOL fSuccess = ReadFile(
720 hPipe, // handle to pipe
721 &command, // buffer to receive data
722 sizeof(command), // size of buffer
723 &cbBytesRead, // number of bytes read
724 NULL); // not overlapped I/O
726 if (! fSuccess || cbBytesRead == 0)
728 DisconnectNamedPipe(hPipe);
729 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
730 return 1;
733 switch (command.command)
735 case TGITCACHECOMMAND_END:
736 FlushFileBuffers(hPipe);
737 DisconnectNamedPipe(hPipe);
738 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
739 return 0;
740 case TGITCACHECOMMAND_CRAWL:
742 CTGitPath changedpath;
743 changedpath.SetFromWin(command.path, true);
744 // remove the path from our cache - that will 'invalidate' it.
746 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
747 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
749 CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());
751 break;
752 case TGITCACHECOMMAND_REFRESHALL:
754 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
755 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": refresh all\n"));
756 CGitStatusCache::Instance().Refresh();
758 break;
759 case TGITCACHECOMMAND_RELEASE:
761 CTGitPath changedpath;
762 changedpath.SetFromWin(command.path, true);
763 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": release handle for path %s\n"), changedpath.GetWinPath());
764 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
765 CGitStatusCache::Instance().CloseWatcherHandles(changedpath);
766 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
768 break;
769 case TGITCACHECOMMAND_BLOCK:
771 CTGitPath changedpath;
772 changedpath.SetFromWin(command.path);
773 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": block path %s\n"), changedpath.GetWinPath());
774 CGitStatusCache::Instance().BlockPath(changedpath);
776 break;
777 case TGITCACHECOMMAND_UNBLOCK:
779 CTGitPath changedpath;
780 changedpath.SetFromWin(command.path);
781 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": unblock path %s\n"), changedpath.GetWinPath());
782 CGitStatusCache::Instance().UnBlockPath(changedpath);
784 break;
788 // Flush the pipe to allow the client to read the pipe's contents
789 // before disconnecting. Then disconnect the pipe, and close the
790 // handle to this pipe instance.
792 FlushFileBuffers(hPipe);
793 DisconnectNamedPipe(hPipe);
794 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
795 return 0;