Fixed issue #1571: Warn author not set when editing notes
[TortoiseGit.git] / src / TortoiseShell / RemoteCacheLink.cpp
blobca7d0c1ab25512b5caf18331c4666da3be2713a2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "Remotecachelink.h"
21 #include "ShellExt.h"
22 #include "..\TGitCache\CacheInterface.h"
23 #include "TGitPath.h"
24 #include "CreateProcessHelper.h"
25 #include "PathUtils.h"
27 CRemoteCacheLink::CRemoteCacheLink(void)
28 : m_hPipe(INVALID_HANDLE_VALUE)
29 , m_hCommandPipe(INVALID_HANDLE_VALUE)
31 SecureZeroMemory(&m_dummyStatus, sizeof(m_dummyStatus));
32 m_dummyStatus.text_status = git_wc_status_none;
33 m_dummyStatus.prop_status = git_wc_status_none;
34 // m_dummyStatus.repos_text_status = git_wc_status_none;
35 // m_dummyStatus.repos_prop_status = git_wc_status_none;
36 m_lastTimeout = 0;
37 m_critSec.Init();
40 CRemoteCacheLink::~CRemoteCacheLink(void)
42 ClosePipe();
43 CloseCommandPipe();
44 m_critSec.Term();
47 bool CRemoteCacheLink::EnsurePipeOpen()
49 AutoLocker lock(m_critSec);
50 if(m_hPipe != INVALID_HANDLE_VALUE)
52 return true;
55 m_hPipe = CreateFile(
56 GetCachePipeName(), // pipe name
57 GENERIC_READ | // read and write access
58 GENERIC_WRITE,
59 0, // no sharing
60 NULL, // default security attributes
61 OPEN_EXISTING, // opens existing pipe
62 FILE_FLAG_OVERLAPPED, // default attributes
63 NULL); // no template file
65 if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
67 // TSVNCache is running but is busy connecting a different client.
68 // Do not give up immediately but wait for a few milliseconds until
69 // the server has created the next pipe instance
70 if (WaitNamedPipe(GetCachePipeName(), 50))
72 m_hPipe = CreateFile(
73 GetCachePipeName(), // pipe name
74 GENERIC_READ | // read and write access
75 GENERIC_WRITE,
76 0, // no sharing
77 NULL, // default security attributes
78 OPEN_EXISTING, // opens existing pipe
79 FILE_FLAG_OVERLAPPED, // default attributes
80 NULL); // no template file
85 if (m_hPipe != INVALID_HANDLE_VALUE)
87 // The pipe connected; change to message-read mode.
88 DWORD dwMode;
90 dwMode = PIPE_READMODE_MESSAGE;
91 if(!SetNamedPipeHandleState(
92 m_hPipe, // pipe handle
93 &dwMode, // new pipe mode
94 NULL, // don't set maximum bytes
95 NULL)) // don't set maximum time
97 ATLTRACE("SetNamedPipeHandleState failed");
98 CloseHandle(m_hPipe);
99 m_hPipe = INVALID_HANDLE_VALUE;
100 return false;
102 // create an unnamed (=local) manual reset event for use in the overlapped structure
103 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
104 if (m_hEvent)
105 return true;
106 ATLTRACE("CreateEvent failed");
107 ClosePipe();
108 return false;
111 return false;
114 bool CRemoteCacheLink::EnsureCommandPipeOpen()
116 AutoLocker lock(m_critSec);
117 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
119 return true;
122 m_hCommandPipe = CreateFile(
123 GetCacheCommandPipeName(), // pipe name
124 GENERIC_READ | // read and write access
125 GENERIC_WRITE,
126 0, // no sharing
127 NULL, // default security attributes
128 OPEN_EXISTING, // opens existing pipe
129 FILE_FLAG_OVERLAPPED, // default attributes
130 NULL); // no template file
132 if (m_hCommandPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
134 // TSVNCache is running but is busy connecting a different client.
135 // Do not give up immediately but wait for a few milliseconds until
136 // the server has created the next pipe instance
137 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))
139 m_hCommandPipe = CreateFile(
140 GetCacheCommandPipeName(), // pipe name
141 GENERIC_READ | // read and write access
142 GENERIC_WRITE,
143 0, // no sharing
144 NULL, // default security attributes
145 OPEN_EXISTING, // opens existing pipe
146 FILE_FLAG_OVERLAPPED, // default attributes
147 NULL); // no template file
152 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
154 // The pipe connected; change to message-read mode.
155 DWORD dwMode;
157 dwMode = PIPE_READMODE_MESSAGE;
158 if(!SetNamedPipeHandleState(
159 m_hCommandPipe, // pipe handle
160 &dwMode, // new pipe mode
161 NULL, // don't set maximum bytes
162 NULL)) // don't set maximum time
164 ATLTRACE("SetNamedPipeHandleState failed");
165 CloseHandle(m_hCommandPipe);
166 m_hCommandPipe = INVALID_HANDLE_VALUE;
167 return false;
169 return true;
172 return false;
175 void CRemoteCacheLink::ClosePipe()
177 AutoLocker lock(m_critSec);
179 if(m_hPipe != INVALID_HANDLE_VALUE)
181 CloseHandle(m_hPipe);
182 CloseHandle(m_hEvent);
183 m_hPipe = INVALID_HANDLE_VALUE;
184 m_hEvent = INVALID_HANDLE_VALUE;
188 void CRemoteCacheLink::CloseCommandPipe()
190 AutoLocker lock(m_critSec);
192 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
194 // now tell the cache we don't need it's command thread anymore
195 DWORD cbWritten;
196 TGITCacheCommand cmd;
197 SecureZeroMemory(&cmd, sizeof(TGITCacheCommand));
198 cmd.command = TGITCACHECOMMAND_END;
199 WriteFile(
200 m_hCommandPipe, // handle to pipe
201 &cmd, // buffer to write from
202 sizeof(cmd), // number of bytes to write
203 &cbWritten, // number of bytes written
204 NULL); // not overlapped I/O
205 DisconnectNamedPipe(m_hCommandPipe);
206 CloseHandle(m_hCommandPipe);
207 m_hCommandPipe = INVALID_HANDLE_VALUE;
211 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TGITCacheResponse* pReturnedStatus, bool bRecursive)
213 if(!EnsurePipeOpen())
215 // We've failed to open the pipe - try and start the cache
216 // but only if the last try to start the cache was a certain time
217 // ago. If we just try over and over again without a small pause
218 // in between, the explorer is rendered unusable!
219 // Failing to start the cache can have different reasons: missing exe,
220 // missing registry key, corrupt exe, ...
221 if (((long)GetTickCount() - m_lastTimeout) < 0)
222 return false;
223 STARTUPINFO startup;
224 PROCESS_INFORMATION process;
225 memset(&startup, 0, sizeof(startup));
226 startup.cb = sizeof(startup);
227 memset(&process, 0, sizeof(process));
229 CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TGitCache.exe");
230 #ifndef _WIN64
231 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
232 LPFN_ISWOW64PROCESS fnIsWow64Process;
233 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
235 if (NULL != fnIsWow64Process)
237 BOOL bIsWow64 = false;
238 if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
240 bIsWow64 = false;
242 if (bIsWow64)
244 CRegString tgitinstalled64 = CRegString(_T("Software\\TortoiseGit\\CachePath"), _T(""), false, HKEY_LOCAL_MACHINE, KEY_WOW64_64KEY);
245 if (!CString(tgitinstalled64).IsEmpty())
246 sCachePath = tgitinstalled64;
249 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL))
251 ATLTRACE("Failed to start x64 cache\n");
252 CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TGitCache.exe");
253 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL))
255 // It's not appropriate to do a message box here, because there may be hundreds of calls
256 ATLTRACE("Failed to start cache\n");
257 return false;
260 #else
261 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL))
263 // It's not appropriate to do a message box here, because there may be hundreds of calls
264 ATLTRACE("Failed to start cache\n");
265 return false;
267 #endif
268 CloseHandle(process.hThread);
269 CloseHandle(process.hProcess);
270 sCachePath.ReleaseBuffer();
272 // Wait for the cache to open
273 long endTime = (long)GetTickCount()+1000;
274 while(!EnsurePipeOpen())
276 if(((long)GetTickCount() - endTime) > 0)
278 m_lastTimeout = (long)GetTickCount()+10000;
279 return false;
284 AutoLocker lock(m_critSec);
286 DWORD nBytesRead;
287 TGITCacheRequest request;
288 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
289 if(bRecursive)
291 request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS;
293 wcsncpy_s(request.path, MAX_PATH+1, Path.GetWinPath(), MAX_PATH);
294 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
295 m_Overlapped.hEvent = m_hEvent;
296 // Do the transaction in overlapped mode.
297 // That way, if anything happens which might block this call
298 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
299 // A blocked shell is a very bad user impression, because users
300 // who don't know why it's blocked might find the only solution
301 // to such a problem is a reboot and therefore they might lose
302 // valuable data.
303 // One particular situation where the shell could hang is when
304 // the cache crashes and our crash report dialog comes up.
305 // Sure, it would be better to have no situations where the shell
306 // even can get blocked, but the timeout of 10 seconds is long enough
307 // so that users still recognize that something might be wrong and
308 // report back to us so we can investigate further.
310 BOOL fSuccess = TransactNamedPipe(m_hPipe,
311 &request, sizeof(request),
312 pReturnedStatus, sizeof(*pReturnedStatus),
313 &nBytesRead, &m_Overlapped);
315 if (!fSuccess)
317 if (GetLastError()!=ERROR_IO_PENDING)
319 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
320 ClosePipe();
321 return false;
324 // TransactNamedPipe is working in an overlapped operation.
325 // Wait for it to finish
326 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);
327 if (dwWait == WAIT_OBJECT_0)
329 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
331 else
333 // the cache didn't respond!
334 fSuccess = FALSE;
338 if (fSuccess)
340 return true;
342 ClosePipe();
343 return false;
346 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)
348 EnsureCommandPipeOpen();
349 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
351 DWORD cbWritten;
352 TGITCacheCommand cmd;
353 SecureZeroMemory(&cmd, sizeof(TGITCacheCommand));
354 cmd.command = TGITCACHECOMMAND_RELEASE;
355 wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH);
356 BOOL fSuccess = WriteFile(
357 m_hCommandPipe, // handle to pipe
358 &cmd, // buffer to write from
359 sizeof(cmd), // number of bytes to write
360 &cbWritten, // number of bytes written
361 NULL); // not overlapped I/O
362 if (! fSuccess || sizeof(cmd) != cbWritten)
364 CloseCommandPipe();
365 return false;
367 return true;
369 return false;