Don't move one char to the right before searching for the next target
[TortoiseGit.git] / src / TGitCache / TGITCache.cpp
blob6ae59c1caf997faff9b952c160b2961888c54d2c
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 HandleCommandLine(lpCmdLine);
142 CAutoGeneralHandle hReloadProtection = ::CreateMutex(NULL, FALSE, GetCacheMutexName());
144 if ((!hReloadProtection) || (GetLastError() == ERROR_ALREADY_EXISTS))
146 // An instance of TGitCache is already running
147 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": TGitCache ignoring restart\n");
148 return 0;
151 CGitStatusCache::Create();
152 CGitStatusCache::Instance().Init();
154 SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));
156 DWORD dwThreadId;
157 MSG msg;
158 TCHAR szWindowClass[] = {TGIT_CACHE_WINDOW_NAME};
160 // create a hidden window to receive window messages.
161 WNDCLASSEX wcex;
162 wcex.cbSize = sizeof(WNDCLASSEX);
163 wcex.style = CS_HREDRAW | CS_VREDRAW;
164 wcex.lpfnWndProc = (WNDPROC)WndProc;
165 wcex.cbClsExtra = 0;
166 wcex.cbWndExtra = 0;
167 wcex.hInstance = hInstance;
168 wcex.hIcon = 0;
169 wcex.hCursor = 0;
170 wcex.hbrBackground = 0;
171 wcex.lpszMenuName = NULL;
172 wcex.lpszClassName = szWindowClass;
173 wcex.hIconSm = 0;
174 RegisterClassEx(&wcex);
175 hWnd = CreateWindow(TGIT_CACHE_WINDOW_NAME, TGIT_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, NULL, 0, hInstance, 0);
176 hTrayWnd = hWnd;
177 if (hWnd == NULL)
179 return 0;
181 if (CRegStdDWORD(_T("Software\\TortoiseGit\\CacheTrayIcon"), FALSE)==TRUE)
183 SecureZeroMemory(&niData,sizeof(NOTIFYICONDATA));
185 DWORD dwMajor = 0;
186 DWORD dwMinor = 0;
187 GetShellVersion(&dwMajor, &dwMinor);
188 DWORD dwVersion = PACKVERSION(dwMajor, dwMinor);
189 if (dwVersion >= PACKVERSION(6,0))
190 niData.cbSize = sizeof(NOTIFYICONDATA);
191 else if (dwVersion >= PACKVERSION(5,0))
192 niData.cbSize = NOTIFYICONDATA_V2_SIZE;
193 else
194 niData.cbSize = NOTIFYICONDATA_V1_SIZE;
196 niData.uID = TRAY_ID; // own tray icon ID
197 niData.hWnd = hWnd;
198 niData.uFlags = NIF_ICON|NIF_MESSAGE;
200 // load the icon
201 niData.hIcon =
202 (HICON)LoadImage(hInstance,
203 MAKEINTRESOURCE(IDI_TGITCACHE),
204 IMAGE_ICON,
205 GetSystemMetrics(SM_CXSMICON),
206 GetSystemMetrics(SM_CYSMICON),
207 LR_DEFAULTCOLOR);
209 // set the message to send
210 // note: the message value should be in the
211 // range of WM_APP through 0xBFFF
212 niData.uCallbackMessage = TRAY_CALLBACK;
213 Shell_NotifyIcon(NIM_ADD,&niData);
214 // free icon handle
215 if(niData.hIcon && DestroyIcon(niData.hIcon))
216 niData.hIcon = NULL;
219 // Create a thread which waits for incoming pipe connections
220 CAutoGeneralHandle hPipeThread = CreateThread(
221 NULL, // no security attribute
222 0, // default stack size
223 PipeThread,
224 (LPVOID) &bRun, // thread parameter
225 0, // not suspended
226 &dwThreadId); // returns thread ID
228 if (!hPipeThread)
230 //OutputDebugStringA("TGitCache: Could not create pipe thread\n");
231 //DebugOutputLastError();
232 return 0;
234 else hPipeThread.CloseHandle();
236 // Create a thread which waits for incoming pipe connections
237 CAutoGeneralHandle hCommandWaitThread = CreateThread(
238 NULL, // no security attribute
239 0, // default stack size
240 CommandWaitThread,
241 (LPVOID) &bRun, // thread parameter
242 0, // not suspended
243 &dwThreadId); // returns thread ID
245 if (!hCommandWaitThread)
247 //OutputDebugStringA("TGitCache: Could not create command wait thread\n");
248 //DebugOutputLastError();
249 return 0;
253 // loop to handle window messages.
254 while (bRun)
256 BOOL bLoopRet = GetMessage(&msg, NULL, 0, 0);
257 if ((bLoopRet != -1)&&(bLoopRet != 0))
259 DispatchMessage(&msg);
263 bRun = false;
265 Shell_NotifyIcon(NIM_DELETE,&niData);
266 CGitStatusCache::Destroy();
267 HandleRestart();
269 return 0;
272 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
274 switch (message)
276 case TRAY_CALLBACK:
278 switch(lParam)
280 case WM_LBUTTONDBLCLK:
281 if (IsWindowVisible(hWnd))
282 ShowWindow(hWnd, SW_HIDE);
283 else
284 ShowWindow(hWnd, SW_RESTORE);
285 break;
286 case WM_MOUSEMOVE:
288 CString sInfoTip;
289 NOTIFYICONDATA SystemTray;
290 sInfoTip.Format(_T("TortoiseGit Overlay Icon Server\nCached Directories: %Id\nWatched paths: %d"),
291 CGitStatusCache::Instance().GetCacheSize(),
292 CGitStatusCache::Instance().GetNumberOfWatchedPaths());
294 SystemTray.cbSize = sizeof(NOTIFYICONDATA);
295 SystemTray.hWnd = hTrayWnd;
296 SystemTray.uID = TRAY_ID;
297 SystemTray.uFlags = NIF_TIP;
298 _tcscpy_s(SystemTray.szTip, sInfoTip);
299 Shell_NotifyIcon(NIM_MODIFY, &SystemTray);
301 break;
302 case WM_RBUTTONUP:
303 case WM_CONTEXTMENU:
305 POINT pt;
306 GetCursorPos(&pt);
307 HMENU hMenu = CreatePopupMenu();
308 if(hMenu)
310 bool enabled = (DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) != ShellCache::none;
311 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_ENABLE, enabled ? _T("Disable Status Cache") : _T("Enable Status Cache"));
312 InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_EXIT, _T("Exit"));
313 SetForegroundWindow(hWnd);
314 TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, NULL);
315 DestroyMenu(hMenu);
318 break;
321 break;
322 case WM_PAINT:
324 PAINTSTRUCT ps;
325 HDC hdc = BeginPaint(hWnd, &ps);
326 RECT rect;
327 GetClientRect(hWnd, &rect);
328 // clear the background
329 HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
330 HGDIOBJ oldbrush = SelectObject(hdc, background);
331 FillRect(hdc, &rect, background);
333 int line = 0;
334 SIZE fontsize = {0};
335 AutoLocker print(critSec);
336 GetTextExtentPoint32( hdc, szCurrentCrawledPath[0], (int)_tcslen(szCurrentCrawledPath[0]), &fontsize );
337 for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)
339 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
340 ++line;
342 for (int i=0; i<nCurrentCrawledpathIndex; ++i)
344 TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));
345 ++line;
348 SelectObject(hdc,oldbrush);
349 EndPaint(hWnd, &ps);
350 DeleteObject(background);
351 return 0L;
353 break;
354 case WM_COMMAND:
356 WORD wmId = LOWORD(wParam);
358 switch (wmId)
360 case TRAYPOP_ENABLE:
362 CRegStdDWORD reg = CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"), GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe);
363 bool enabled = (DWORD)reg != ShellCache::none;
364 reg = enabled ? ShellCache::none : ShellCache::exe;
365 if (enabled)
367 bRestart = true;
368 DestroyWindow(hWnd);
370 break;
372 case TRAYPOP_EXIT:
373 DestroyWindow(hWnd);
374 break;
376 return 1;
378 case WM_QUERYENDSESSION:
380 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_QUERYENDSESSION\n");
381 CAutoWriteWeakLock writeLock(CGitStatusCache::Instance().GetGuard(), 200);
382 CGitStatusCache::Instance().Stop();
383 return TRUE;
385 break;
386 case WM_CLOSE:
387 case WM_ENDSESSION:
388 case WM_DESTROY:
389 case WM_QUIT:
391 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");
392 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
393 CGitStatusCache::Instance().Stop();
394 CGitStatusCache::Instance().SaveCache();
395 if (message != WM_QUIT)
396 PostQuitMessage(0);
397 bRun = false;
398 return 1;
400 break;
401 case WM_DEVICECHANGE:
403 DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;
404 switch (wParam)
406 case DBT_CUSTOMEVENT:
408 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");
409 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
411 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
412 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))
414 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device to be dismounted\n");
415 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
416 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
418 if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))
420 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Device lock event\n");
421 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
422 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
426 break;
427 case DBT_DEVICEREMOVEPENDING:
428 case DBT_DEVICEQUERYREMOVE:
429 case DBT_DEVICEREMOVECOMPLETE:
430 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING/DBT_DEVICEQUERYREMOVE/DBT_DEVICEREMOVECOMPLETE\n");
431 if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
433 DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;
434 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
435 CGitStatusCache::Instance().CloseWatcherHandles(phandle->dbch_handle);
437 else if (phdr->dbch_devicetype == DBT_DEVTYP_VOLUME)
439 DEV_BROADCAST_VOLUME * pVolume = (DEV_BROADCAST_VOLUME*)lParam;
440 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
441 for (BYTE i = 0; i < 26; ++i)
443 if (pVolume->dbcv_unitmask & (1 << i))
445 TCHAR driveletter = 'A' + i;
446 CString drive = CString(driveletter);
447 drive += L":\\";
448 CGitStatusCache::Instance().CloseWatcherHandles(CTGitPath(drive));
452 else
454 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
455 CGitStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);
457 break;
460 break;
461 default:
462 break;
464 return DefWindowProc(hWnd, message, wParam, lParam);
467 //////////////////////////////////////////////////////////////////////////
469 VOID GetAnswerToRequest(const TGITCacheRequest* pRequest, TGITCacheResponse* pReply, DWORD* pResponseLength)
471 CTGitPath path;
472 *pResponseLength = 0;
473 if(pRequest->flags & TGITCACHE_FLAGS_FOLDERISKNOWN)
475 path.SetFromWin(pRequest->path, !!(pRequest->flags & TGITCACHE_FLAGS_ISFOLDER));
477 else
479 path.SetFromWin(pRequest->path);
482 CAutoReadWeakLock readLock(CGitStatusCache::Instance().GetGuard(), 2000);
483 if (readLock.IsAcquired())
485 CGitStatusCache::Instance().GetStatusForPath(path, pRequest->flags, false).BuildCacheResponse(*pReply, *pResponseLength);
487 else
489 CStatusCacheEntry entry;
490 entry.BuildCacheResponse(*pReply, *pResponseLength);
494 DWORD WINAPI PipeThread(LPVOID lpvParam)
496 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": PipeThread started\n");
497 bool * bRun = (bool *)lpvParam;
498 // The main loop creates an instance of the named pipe and
499 // then waits for a client to connect to it. When the client
500 // connects, a thread is created to handle communications
501 // with that client, and the loop is repeated.
502 DWORD dwThreadId;
503 BOOL fConnected;
504 CAutoFile hPipe;
506 while (*bRun)
508 hPipe = CreateNamedPipe(
509 GetCachePipeName(),
510 PIPE_ACCESS_DUPLEX, // read/write access
511 PIPE_TYPE_MESSAGE | // message type pipe
512 PIPE_READMODE_MESSAGE | // message-read mode
513 PIPE_WAIT, // blocking mode
514 PIPE_UNLIMITED_INSTANCES, // max. instances
515 BUFSIZE, // output buffer size
516 BUFSIZE, // input buffer size
517 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
518 NULL); // NULL DACL
520 if (!hPipe)
522 //OutputDebugStringA("TGitCache: CreatePipe failed\n");
523 //DebugOutputLastError();
524 if (*bRun)
525 Sleep(200);
526 continue; // never leave the thread!
529 // Wait for the client to connect; if it succeeds,
530 // the function returns a nonzero value. If the function returns
531 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
532 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
533 if (fConnected)
535 // Create a thread for this client.
536 CAutoGeneralHandle hInstanceThread = CreateThread(
537 NULL, // no security attribute
538 0, // default stack size
539 InstanceThread,
540 (HANDLE) hPipe, // thread parameter
541 0, // not suspended
542 &dwThreadId); // returns thread ID
544 if (!hInstanceThread)
546 //OutputDebugStringA("TGitCache: Could not create Instance thread\n");
547 //DebugOutputLastError();
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(hWnd, 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 //OutputDebugStringA("TGitCache: ConnectNamedPipe failed\n");
562 //DebugOutputLastError();
563 hPipe.CloseHandle();
564 if (*bRun)
565 Sleep(200);
566 continue; // don't end the thread!
569 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Pipe thread exited\n");
570 return 0;
573 DWORD WINAPI CommandWaitThread(LPVOID lpvParam)
575 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWaitThread started\n");
576 bool * bRun = (bool *)lpvParam;
577 // The main loop creates an instance of the named pipe and
578 // then waits for a client to connect to it. When the client
579 // connects, a thread is created to handle communications
580 // with that client, and the loop is repeated.
581 DWORD dwThreadId;
582 BOOL fConnected;
583 CAutoFile hPipe;
585 while (*bRun)
587 hPipe = CreateNamedPipe(
588 GetCacheCommandPipeName(),
589 PIPE_ACCESS_DUPLEX, // read/write access
590 PIPE_TYPE_MESSAGE | // message type pipe
591 PIPE_READMODE_MESSAGE | // message-read mode
592 PIPE_WAIT, // blocking mode
593 PIPE_UNLIMITED_INSTANCES, // max. instances
594 BUFSIZE, // output buffer size
595 BUFSIZE, // input buffer size
596 NMPWAIT_USE_DEFAULT_WAIT, // client time-out
597 NULL); // NULL DACL
599 if (!hPipe)
601 //OutputDebugStringA("TGitCache: CreatePipe failed\n");
602 //DebugOutputLastError();
603 if (*bRun)
604 Sleep(200);
605 continue; // never leave the thread!
608 // Wait for the client to connect; if it succeeds,
609 // the function returns a nonzero value. If the function returns
610 // zero, GetLastError returns ERROR_PIPE_CONNECTED.
611 fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
612 if (fConnected)
614 // Create a thread for this client.
615 CAutoGeneralHandle hCommandThread = CreateThread(
616 NULL, // no security attribute
617 0, // default stack size
618 CommandThread,
619 (HANDLE) hPipe, // thread parameter
620 0, // not suspended
621 &dwThreadId); // returns thread ID
623 if (!hCommandThread)
625 //OutputDebugStringA("TGitCache: Could not create Command thread\n");
626 //DebugOutputLastError();
627 DisconnectNamedPipe(hPipe);
628 hPipe.CloseHandle();
629 // since we're now closing this thread, we also have to close the whole application!
630 // otherwise the thread is dead, but the app is still running, refusing new instances
631 // but no pipe will be available anymore.
632 PostMessage(hWnd, WM_CLOSE, 0, 0);
633 return 1;
635 // detach the handle, since we passed it to the thread
636 hPipe.Detach();
638 else
640 // The client could not connect, so close the pipe.
641 //OutputDebugStringA("TGitCache: ConnectNamedPipe failed\n");
642 //DebugOutputLastError();
643 hPipe.CloseHandle();
644 if (*bRun)
645 Sleep(200);
646 continue; // don't end the thread!
649 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandWait thread exited\n");
650 return 0;
653 DWORD WINAPI InstanceThread(LPVOID lpvParam)
655 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": InstanceThread started\n");
656 TGITCacheResponse response;
657 DWORD cbBytesRead, cbWritten;
658 CAutoFile hPipe;
660 // The thread's parameter is a handle to a pipe instance.
662 hPipe = lpvParam;
663 InterlockedIncrement(&nThreadCount);
664 while (bRun)
666 // Read client requests from the pipe.
667 TGITCacheRequest request;
668 BOOL fSuccess = ReadFile(
669 hPipe, // handle to pipe
670 &request, // buffer to receive data
671 sizeof(request), // size of buffer
672 &cbBytesRead, // number of bytes read
673 NULL); // not overlapped I/O
675 if (! fSuccess || cbBytesRead == 0)
677 DisconnectNamedPipe(hPipe);
678 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
679 InterlockedDecrement(&nThreadCount);
680 if (nThreadCount == 0)
681 PostMessage(hWnd, WM_CLOSE, 0, 0);
682 return 1;
685 DWORD responseLength;
686 GetAnswerToRequest(&request, &response, &responseLength);
688 // Write the reply to the pipe.
689 fSuccess = WriteFile(
690 hPipe, // handle to pipe
691 &response, // buffer to write from
692 responseLength, // number of bytes to write
693 &cbWritten, // number of bytes written
694 NULL); // not overlapped I/O
696 if (! fSuccess || responseLength != cbWritten)
698 DisconnectNamedPipe(hPipe);
699 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
700 InterlockedDecrement(&nThreadCount);
701 if (nThreadCount == 0)
702 PostMessage(hWnd, WM_CLOSE, 0, 0);
703 return 1;
707 // Flush the pipe to allow the client to read the pipe's contents
708 // before disconnecting. Then disconnect the pipe, and close the
709 // handle to this pipe instance.
711 FlushFileBuffers(hPipe);
712 DisconnectNamedPipe(hPipe);
713 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Instance thread exited\n");
714 InterlockedDecrement(&nThreadCount);
715 if (nThreadCount == 0)
716 PostMessage(hWnd, WM_CLOSE, 0, 0);
717 return 0;
720 DWORD WINAPI CommandThread(LPVOID lpvParam)
722 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CommandThread started\n");
723 DWORD cbBytesRead;
724 CAutoFile hPipe;
726 // The thread's parameter is a handle to a pipe instance.
728 hPipe = lpvParam;
730 while (bRun)
732 // Read client requests from the pipe.
733 TGITCacheCommand command;
734 BOOL fSuccess = ReadFile(
735 hPipe, // handle to pipe
736 &command, // buffer to receive data
737 sizeof(command), // size of buffer
738 &cbBytesRead, // number of bytes read
739 NULL); // not overlapped I/O
741 if (! fSuccess || cbBytesRead == 0)
743 DisconnectNamedPipe(hPipe);
744 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
745 return 1;
748 switch (command.command)
750 case TGITCACHECOMMAND_END:
751 FlushFileBuffers(hPipe);
752 DisconnectNamedPipe(hPipe);
753 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
754 return 0;
755 case TGITCACHECOMMAND_CRAWL:
757 CTGitPath changedpath;
758 changedpath.SetFromWin(command.path, true);
759 // remove the path from our cache - that will 'invalidate' it.
761 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
762 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
764 CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());
766 break;
767 case TGITCACHECOMMAND_REFRESHALL:
769 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
770 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": refresh all\n"));
771 CGitStatusCache::Instance().Refresh();
773 break;
774 case TGITCACHECOMMAND_RELEASE:
776 CTGitPath changedpath;
777 changedpath.SetFromWin(command.path, true);
778 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": release handle for path %s\n"), changedpath.GetWinPath());
779 CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard());
780 CGitStatusCache::Instance().CloseWatcherHandles(changedpath);
781 CGitStatusCache::Instance().RemoveCacheForPath(changedpath);
783 break;
784 case TGITCACHECOMMAND_BLOCK:
786 CTGitPath changedpath;
787 changedpath.SetFromWin(command.path);
788 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": block path %s\n"), changedpath.GetWinPath());
789 CGitStatusCache::Instance().BlockPath(changedpath);
791 break;
792 case TGITCACHECOMMAND_UNBLOCK:
794 CTGitPath changedpath;
795 changedpath.SetFromWin(command.path);
796 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": unblock path %s\n"), changedpath.GetWinPath());
797 CGitStatusCache::Instance().UnBlockPath(changedpath);
799 break;
803 // Flush the pipe to allow the client to read the pipe's contents
804 // before disconnecting. Then disconnect the pipe, and close the
805 // handle to this pipe instance.
807 FlushFileBuffers(hPipe);
808 DisconnectNamedPipe(hPipe);
809 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Command thread exited\n");
810 return 0;