1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "Remotecachelink.h"
\r
21 #include "ShellExt.h"
\r
22 #include "..\TGitCache\CacheInterface.h"
\r
23 #include "TGitPath.h"
\r
25 CRemoteCacheLink::CRemoteCacheLink(void)
\r
26 : m_hPipe(INVALID_HANDLE_VALUE)
\r
27 , m_hCommandPipe(INVALID_HANDLE_VALUE)
\r
29 SecureZeroMemory(&m_dummyStatus, sizeof(m_dummyStatus));
\r
30 m_dummyStatus.text_status = git_wc_status_none;
\r
31 m_dummyStatus.prop_status = git_wc_status_none;
\r
32 // m_dummyStatus.repos_text_status = git_wc_status_none;
\r
33 // m_dummyStatus.repos_prop_status = git_wc_status_none;
\r
38 CRemoteCacheLink::~CRemoteCacheLink(void)
\r
45 bool CRemoteCacheLink::EnsurePipeOpen()
\r
47 AutoLocker lock(m_critSec);
\r
48 if(m_hPipe != INVALID_HANDLE_VALUE)
\r
53 m_hPipe = CreateFile(
\r
54 GetCachePipeName(), // pipe name
\r
55 GENERIC_READ | // read and write access
\r
58 NULL, // default security attributes
\r
59 OPEN_EXISTING, // opens existing pipe
\r
60 FILE_FLAG_OVERLAPPED, // default attributes
\r
61 NULL); // no template file
\r
63 if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
\r
65 // TSVNCache is running but is busy connecting a different client.
\r
66 // Do not give up immediately but wait for a few milliseconds until
\r
67 // the server has created the next pipe instance
\r
68 if (WaitNamedPipe(GetCachePipeName(), 50))
\r
70 m_hPipe = CreateFile(
\r
71 GetCachePipeName(), // pipe name
\r
72 GENERIC_READ | // read and write access
\r
75 NULL, // default security attributes
\r
76 OPEN_EXISTING, // opens existing pipe
\r
77 FILE_FLAG_OVERLAPPED, // default attributes
\r
78 NULL); // no template file
\r
83 if (m_hPipe != INVALID_HANDLE_VALUE)
\r
85 // The pipe connected; change to message-read mode.
\r
88 dwMode = PIPE_READMODE_MESSAGE;
\r
89 if(!SetNamedPipeHandleState(
\r
90 m_hPipe, // pipe handle
\r
91 &dwMode, // new pipe mode
\r
92 NULL, // don't set maximum bytes
\r
93 NULL)) // don't set maximum time
\r
95 ATLTRACE("SetNamedPipeHandleState failed");
\r
96 CloseHandle(m_hPipe);
\r
97 m_hPipe = INVALID_HANDLE_VALUE;
\r
100 // create an unnamed (=local) manual reset event for use in the overlapped structure
\r
101 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
\r
104 ATLTRACE("CreateEvent failed");
\r
112 bool CRemoteCacheLink::EnsureCommandPipeOpen()
\r
114 AutoLocker lock(m_critSec);
\r
115 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
120 m_hCommandPipe = CreateFile(
\r
121 GetCacheCommandPipeName(), // pipe name
\r
122 GENERIC_READ | // read and write access
\r
125 NULL, // default security attributes
\r
126 OPEN_EXISTING, // opens existing pipe
\r
127 FILE_FLAG_OVERLAPPED, // default attributes
\r
128 NULL); // no template file
\r
130 if (m_hCommandPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
\r
132 // TSVNCache is running but is busy connecting a different client.
\r
133 // Do not give up immediately but wait for a few milliseconds until
\r
134 // the server has created the next pipe instance
\r
135 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))
\r
137 m_hCommandPipe = CreateFile(
\r
138 GetCacheCommandPipeName(), // pipe name
\r
139 GENERIC_READ | // read and write access
\r
142 NULL, // default security attributes
\r
143 OPEN_EXISTING, // opens existing pipe
\r
144 FILE_FLAG_OVERLAPPED, // default attributes
\r
145 NULL); // no template file
\r
150 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
152 // The pipe connected; change to message-read mode.
\r
155 dwMode = PIPE_READMODE_MESSAGE;
\r
156 if(!SetNamedPipeHandleState(
\r
157 m_hCommandPipe, // pipe handle
\r
158 &dwMode, // new pipe mode
\r
159 NULL, // don't set maximum bytes
\r
160 NULL)) // don't set maximum time
\r
162 ATLTRACE("SetNamedPipeHandleState failed");
\r
163 CloseHandle(m_hCommandPipe);
\r
164 m_hCommandPipe = INVALID_HANDLE_VALUE;
\r
173 void CRemoteCacheLink::ClosePipe()
\r
175 AutoLocker lock(m_critSec);
\r
177 if(m_hPipe != INVALID_HANDLE_VALUE)
\r
179 CloseHandle(m_hPipe);
\r
180 CloseHandle(m_hEvent);
\r
181 m_hPipe = INVALID_HANDLE_VALUE;
\r
182 m_hEvent = INVALID_HANDLE_VALUE;
\r
186 void CRemoteCacheLink::CloseCommandPipe()
\r
188 AutoLocker lock(m_critSec);
\r
190 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
192 // now tell the cache we don't need it's command thread anymore
\r
194 TSVNCacheCommand cmd;
\r
195 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));
\r
196 cmd.command = TSVNCACHECOMMAND_END;
\r
198 m_hCommandPipe, // handle to pipe
\r
199 &cmd, // buffer to write from
\r
200 sizeof(cmd), // number of bytes to write
\r
201 &cbWritten, // number of bytes written
\r
202 NULL); // not overlapped I/O
\r
203 DisconnectNamedPipe(m_hCommandPipe);
\r
204 CloseHandle(m_hCommandPipe);
\r
205 m_hCommandPipe = INVALID_HANDLE_VALUE;
\r
209 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TSVNCacheResponse* pReturnedStatus, bool bRecursive)
\r
211 if(!EnsurePipeOpen())
\r
213 // We've failed to open the pipe - try and start the cache
\r
214 // but only if the last try to start the cache was a certain time
\r
215 // ago. If we just try over and over again without a small pause
\r
216 // in between, the explorer is rendered unusable!
\r
217 // Failing to start the cache can have different reasons: missing exe,
\r
218 // missing registry key, corrupt exe, ...
\r
219 if (((long)GetTickCount() - m_lastTimeout) < 0)
\r
221 STARTUPINFO startup;
\r
222 PROCESS_INFORMATION process;
\r
223 memset(&startup, 0, sizeof(startup));
\r
224 startup.cb = sizeof(startup);
\r
225 memset(&process, 0, sizeof(process));
\r
227 CRegString cachePath(_T("Software\\TortoiseGit\\CachePath"), _T("TGitCache.exe"), false, HKEY_LOCAL_MACHINE);
\r
228 CString sCachePath = cachePath;
\r
229 if (CreateProcess(sCachePath.GetBuffer(sCachePath.GetLength()+1), _T(""), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
\r
231 // It's not appropriate to do a message box here, because there may be hundreds of calls
\r
232 sCachePath.ReleaseBuffer();
\r
233 ATLTRACE("Failed to start cache\n");
\r
236 CloseHandle(process.hThread);
\r
237 CloseHandle(process.hProcess);
\r
238 sCachePath.ReleaseBuffer();
\r
240 // Wait for the cache to open
\r
241 long endTime = (long)GetTickCount()+1000;
\r
242 while(!EnsurePipeOpen())
\r
244 if(((long)GetTickCount() - endTime) > 0)
\r
246 m_lastTimeout = (long)GetTickCount()+10000;
\r
252 AutoLocker lock(m_critSec);
\r
255 TSVNCacheRequest request;
\r
256 request.flags = TSVNCACHE_FLAGS_NONOTIFICATIONS;
\r
259 request.flags |= TSVNCACHE_FLAGS_RECUSIVE_STATUS;
\r
261 wcsncpy_s(request.path, MAX_PATH+1, Path.GetWinPath(), MAX_PATH);
\r
262 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
\r
263 m_Overlapped.hEvent = m_hEvent;
\r
264 // Do the transaction in overlapped mode.
\r
265 // That way, if anything happens which might block this call
\r
266 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
\r
267 // A blocked shell is a very bad user impression, because users
\r
268 // who don't know why it's blocked might find the only solution
\r
269 // to such a problem is a reboot and therefore they might loose
\r
271 // One particular situation where the shell could hang is when
\r
272 // the cache crashes and our crash report dialog comes up.
\r
273 // Sure, it would be better to have no situations where the shell
\r
274 // even can get blocked, but the timeout of 10 seconds is long enough
\r
275 // so that users still recognize that something might be wrong and
\r
276 // report back to us so we can investigate further.
\r
278 BOOL fSuccess = TransactNamedPipe(m_hPipe,
\r
279 &request, sizeof(request),
\r
280 pReturnedStatus, sizeof(*pReturnedStatus),
\r
281 &nBytesRead, &m_Overlapped);
\r
285 if (GetLastError()!=ERROR_IO_PENDING)
\r
287 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
\r
292 // TransactNamedPipe is working in an overlapped operation.
\r
293 // Wait for it to finish
\r
294 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);
\r
295 if (dwWait == WAIT_OBJECT_0)
\r
297 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
\r
301 // the cache didn't respond!
\r
308 if(nBytesRead == sizeof(TSVNCacheResponse))
\r
310 // This is a full response - we need to fix-up some pointers
\r
311 // pReturnedStatus->m_status.entry = &pReturnedStatus->m_entry;
\r
312 // pReturnedStatus->m_entry.url = pReturnedStatus->m_url;
\r
316 // pReturnedStatus->m_status.entry = NULL;
\r
325 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)
\r
327 EnsureCommandPipeOpen();
\r
328 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
331 TSVNCacheCommand cmd;
\r
332 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));
\r
333 cmd.command = TSVNCACHECOMMAND_RELEASE;
\r
334 wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH);
\r
335 BOOL fSuccess = WriteFile(
\r
336 m_hCommandPipe, // handle to pipe
\r
337 &cmd, // buffer to write from
\r
338 sizeof(cmd), // number of bytes to write
\r
339 &cbWritten, // number of bytes written
\r
340 NULL); // not overlapped I/O
\r
341 if (! fSuccess || sizeof(cmd) != cbWritten)
\r
343 CloseCommandPipe();
\r