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