OSDN Git Service

Fix Show Log boundary show more than 1 log item when using --boundary & -n1
[tortoisegit/TortoiseGitJp.git] / src / TortoiseShell / RemoteCacheLink.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \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
9 \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
14 \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
18 //\r
19 #include "StdAfx.h"\r
20 #include "Remotecachelink.h"\r
21 #include "ShellExt.h"\r
22 //#include "..\TSVNCache\CacheInterface.h"\r
23 //#include "TSVNPath.h"\r
24 #define GetCachePipeName() _T("HH")\r
25 #define GetCacheCommandPipeName() _T("CC")\r
26 \r
27 CRemoteCacheLink::CRemoteCacheLink(void) \r
28         : m_hPipe(INVALID_HANDLE_VALUE)\r
29         , m_hCommandPipe(INVALID_HANDLE_VALUE)\r
30 {\r
31 //      SecureZeroMemory(&m_dummyStatus, sizeof(m_dummyStatus));\r
32 //      m_dummyStatus.text_status = svn_wc_status_none;\r
33 //      m_dummyStatus.prop_status = svn_wc_status_none;\r
34 //      m_dummyStatus.repos_text_status = svn_wc_status_none;\r
35 //      m_dummyStatus.repos_prop_status = svn_wc_status_none;\r
36         m_lastTimeout = 0;\r
37         m_critSec.Init();\r
38 }\r
39 \r
40 CRemoteCacheLink::~CRemoteCacheLink(void)\r
41 {\r
42         ClosePipe();\r
43         CloseCommandPipe();\r
44         m_critSec.Term();\r
45 }\r
46 \r
47 bool CRemoteCacheLink::EnsurePipeOpen()\r
48 {\r
49         AutoLocker lock(m_critSec);\r
50         if(m_hPipe != INVALID_HANDLE_VALUE)\r
51         {\r
52                 return true;\r
53         }\r
54 \r
55         m_hPipe = CreateFile(\r
56                 GetCachePipeName(),                             // pipe name\r
57                 GENERIC_READ |                                  // read and write access\r
58                 GENERIC_WRITE,\r
59                 0,                                                              // no sharing\r
60                 NULL,                                                   // default security attributes\r
61                 OPEN_EXISTING,                                  // opens existing pipe\r
62                 FILE_FLAG_OVERLAPPED,                   // default attributes\r
63                 NULL);                                                  // no template file\r
64 \r
65         if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)\r
66         {\r
67                 // TSVNCache is running but is busy connecting a different client.\r
68                 // Do not give up immediately but wait for a few milliseconds until\r
69                 // the server has created the next pipe instance\r
70                 if (WaitNamedPipe(GetCachePipeName(), 50))\r
71                 {\r
72                         m_hPipe = CreateFile(\r
73                                 GetCachePipeName(),                             // pipe name\r
74                                 GENERIC_READ |                                  // read and write access\r
75                                 GENERIC_WRITE,\r
76                                 0,                                                              // no sharing\r
77                                 NULL,                                                   // default security attributes\r
78                                 OPEN_EXISTING,                                  // opens existing pipe\r
79                                 FILE_FLAG_OVERLAPPED,                   // default attributes\r
80                                 NULL);                                                  // no template file\r
81                 }\r
82         }\r
83 \r
84 \r
85         if (m_hPipe != INVALID_HANDLE_VALUE)\r
86         {\r
87                 // The pipe connected; change to message-read mode.\r
88                 DWORD dwMode;\r
89 \r
90                 dwMode = PIPE_READMODE_MESSAGE;\r
91                 if(!SetNamedPipeHandleState(\r
92                         m_hPipe,    // pipe handle\r
93                         &dwMode,  // new pipe mode\r
94                         NULL,     // don't set maximum bytes\r
95                         NULL))    // don't set maximum time\r
96                 {\r
97                         ATLTRACE("SetNamedPipeHandleState failed");\r
98                         CloseHandle(m_hPipe);\r
99                         m_hPipe = INVALID_HANDLE_VALUE;\r
100                         return false;\r
101                 }\r
102                 // create an unnamed (=local) manual reset event for use in the overlapped structure\r
103                 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);\r
104                 if (m_hEvent)\r
105                         return true;\r
106                 ATLTRACE("CreateEvent failed");\r
107                 ClosePipe();\r
108                 return false;\r
109         }\r
110 \r
111         return false;\r
112 }\r
113 \r
114 bool CRemoteCacheLink::EnsureCommandPipeOpen()\r
115 {\r
116         AutoLocker lock(m_critSec);\r
117         if(m_hCommandPipe != INVALID_HANDLE_VALUE)\r
118         {\r
119                 return true;\r
120         }\r
121 \r
122         m_hCommandPipe = CreateFile(\r
123                 GetCacheCommandPipeName(),              // pipe name\r
124                 GENERIC_READ |                                  // read and write access\r
125                 GENERIC_WRITE,\r
126                 0,                                                              // no sharing\r
127                 NULL,                                                   // default security attributes\r
128                 OPEN_EXISTING,                                  // opens existing pipe\r
129                 FILE_FLAG_OVERLAPPED,                   // default attributes\r
130                 NULL);                                                  // no template file\r
131 \r
132         if (m_hCommandPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)\r
133         {\r
134                 // TSVNCache is running but is busy connecting a different client.\r
135                 // Do not give up immediately but wait for a few milliseconds until\r
136                 // the server has created the next pipe instance\r
137                 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))\r
138                 {\r
139                         m_hCommandPipe = CreateFile(\r
140                                 GetCacheCommandPipeName(),              // pipe name\r
141                                 GENERIC_READ |                                  // read and write access\r
142                                 GENERIC_WRITE,\r
143                                 0,                                                              // no sharing\r
144                                 NULL,                                                   // default security attributes\r
145                                 OPEN_EXISTING,                                  // opens existing pipe\r
146                                 FILE_FLAG_OVERLAPPED,                   // default attributes\r
147                                 NULL);                                                  // no template file\r
148                 }\r
149         }\r
150 \r
151 \r
152         if (m_hCommandPipe != INVALID_HANDLE_VALUE)\r
153         {\r
154                 // The pipe connected; change to message-read mode.\r
155                 DWORD dwMode;\r
156 \r
157                 dwMode = PIPE_READMODE_MESSAGE;\r
158                 if(!SetNamedPipeHandleState(\r
159                         m_hCommandPipe,    // pipe handle\r
160                         &dwMode,  // new pipe mode\r
161                         NULL,     // don't set maximum bytes\r
162                         NULL))    // don't set maximum time\r
163                 {\r
164                         ATLTRACE("SetNamedPipeHandleState failed");\r
165                         CloseHandle(m_hCommandPipe);\r
166                         m_hCommandPipe = INVALID_HANDLE_VALUE;\r
167                         return false;\r
168                 }\r
169                 return true;\r
170         }\r
171 \r
172         return false;\r
173 }\r
174 \r
175 void CRemoteCacheLink::ClosePipe()\r
176 {\r
177         AutoLocker lock(m_critSec);\r
178 \r
179         if(m_hPipe != INVALID_HANDLE_VALUE)\r
180         {\r
181                 CloseHandle(m_hPipe);\r
182                 CloseHandle(m_hEvent);\r
183                 m_hPipe = INVALID_HANDLE_VALUE;\r
184                 m_hEvent = INVALID_HANDLE_VALUE;\r
185         }\r
186 }\r
187 \r
188 void CRemoteCacheLink::CloseCommandPipe()\r
189 {\r
190         AutoLocker lock(m_critSec);\r
191 \r
192         if(m_hCommandPipe != INVALID_HANDLE_VALUE)\r
193         {\r
194                 // now tell the cache we don't need it's command thread anymore\r
195                 DWORD cbWritten; \r
196 /*              TSVNCacheCommand cmd;\r
197                 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));\r
198                 cmd.command = TSVNCACHECOMMAND_END;\r
199                 WriteFile( \r
200                         m_hCommandPipe,                 // handle to pipe \r
201                         &cmd,                   // buffer to write from \r
202                         sizeof(cmd),    // number of bytes to write \r
203                         &cbWritten,             // number of bytes written \r
204                         NULL);                  // not overlapped I/O \r
205                 DisconnectNamedPipe(m_hCommandPipe); \r
206                 CloseHandle(m_hCommandPipe); \r
207                 m_hCommandPipe = INVALID_HANDLE_VALUE;\r
208 */\r
209         }\r
210 }\r
211 \r
212 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTSVNPath& Path, TSVNCacheResponse* pReturnedStatus, bool bRecursive)\r
213 {\r
214         if(!EnsurePipeOpen())\r
215         {\r
216                 // We've failed to open the pipe - try and start the cache\r
217                 // but only if the last try to start the cache was a certain time\r
218                 // ago. If we just try over and over again without a small pause\r
219                 // in between, the explorer is rendered unusable!\r
220                 // Failing to start the cache can have different reasons: missing exe,\r
221                 // missing registry key, corrupt exe, ...\r
222                 if (((long)GetTickCount() - m_lastTimeout) < 0)\r
223                         return false;\r
224                 STARTUPINFO startup;\r
225                 PROCESS_INFORMATION process;\r
226                 memset(&startup, 0, sizeof(startup));\r
227                 startup.cb = sizeof(startup);\r
228                 memset(&process, 0, sizeof(process));\r
229 \r
230                 CRegString cachePath(_T("Software\\TortoiseGit\\CachePath"), _T("TSVNCache.exe"), false, HKEY_LOCAL_MACHINE);\r
231                 CString sCachePath = cachePath;\r
232                 if (CreateProcess(sCachePath.GetBuffer(sCachePath.GetLength()+1), _T(""), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)\r
233                 {\r
234                         // It's not appropriate to do a message box here, because there may be hundreds of calls\r
235                         sCachePath.ReleaseBuffer();\r
236                         ATLTRACE("Failed to start cache\n");\r
237                         return false;\r
238                 }\r
239                 CloseHandle(process.hThread);\r
240                 CloseHandle(process.hProcess);\r
241                 sCachePath.ReleaseBuffer();\r
242 \r
243                 // Wait for the cache to open\r
244                 long endTime = (long)GetTickCount()+1000;\r
245                 while(!EnsurePipeOpen())\r
246                 {\r
247                         if(((long)GetTickCount() - endTime) > 0)\r
248                         {\r
249                                 m_lastTimeout = (long)GetTickCount()+10000;\r
250                                 return false;\r
251                         }\r
252                 }\r
253         }\r
254 \r
255         AutoLocker lock(m_critSec);\r
256 \r
257         DWORD nBytesRead;\r
258         TSVNCacheRequest request;\r
259         request.flags = TSVNCACHE_FLAGS_NONOTIFICATIONS;\r
260         if(bRecursive)\r
261         {\r
262                 request.flags |= TSVNCACHE_FLAGS_RECUSIVE_STATUS;\r
263         }\r
264         wcsncpy_s(request.path, MAX_PATH+1, Path.GetWinPath(), MAX_PATH);\r
265         SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));\r
266         m_Overlapped.hEvent = m_hEvent;\r
267         // Do the transaction in overlapped mode.\r
268         // That way, if anything happens which might block this call\r
269         // we still can get out of it. We NEVER MUST BLOCK THE SHELL!\r
270         // A blocked shell is a very bad user impression, because users\r
271         // who don't know why it's blocked might find the only solution\r
272         // to such a problem is a reboot and therefore they might loose\r
273         // valuable data.\r
274         // One particular situation where the shell could hang is when\r
275         // the cache crashes and our crash report dialog comes up.\r
276         // Sure, it would be better to have no situations where the shell\r
277         // even can get blocked, but the timeout of 10 seconds is long enough\r
278         // so that users still recognize that something might be wrong and\r
279         // report back to us so we can investigate further.\r
280 \r
281         BOOL fSuccess = TransactNamedPipe(m_hPipe,\r
282                 &request, sizeof(request),\r
283                 pReturnedStatus, sizeof(*pReturnedStatus),\r
284                 &nBytesRead, &m_Overlapped);\r
285 \r
286         if (!fSuccess)\r
287         {\r
288                 if (GetLastError()!=ERROR_IO_PENDING)\r
289                 {\r
290                         //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");\r
291                         ClosePipe();\r
292                         return false;\r
293                 }\r
294 \r
295                 // TransactNamedPipe is working in an overlapped operation.\r
296                 // Wait for it to finish\r
297                 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);\r
298                 if (dwWait == WAIT_OBJECT_0)\r
299                 {\r
300                         fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);\r
301                 }\r
302                 else\r
303                 {\r
304                         // the cache didn't respond!\r
305                         fSuccess = FALSE;\r
306                 }\r
307         }\r
308 \r
309         if (fSuccess)\r
310         {\r
311                 if(nBytesRead == sizeof(TSVNCacheResponse))\r
312                 {\r
313                         // This is a full response - we need to fix-up some pointers\r
314                         pReturnedStatus->m_status.entry = &pReturnedStatus->m_entry;\r
315                         pReturnedStatus->m_entry.url = pReturnedStatus->m_url;\r
316                 }\r
317                 else\r
318                 {\r
319                         pReturnedStatus->m_status.entry = NULL;\r
320                 }\r
321 \r
322                 return true;\r
323         }\r
324         ClosePipe();\r
325         return false;\r
326 }\r
327 \r
328 bool CRemoteCacheLink::ReleaseLockForPath(const CTSVNPath& path)\r
329 {\r
330         EnsureCommandPipeOpen();\r
331         if (m_hCommandPipe != INVALID_HANDLE_VALUE) \r
332         {\r
333                 DWORD cbWritten; \r
334                 TSVNCacheCommand cmd;\r
335                 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));\r
336                 cmd.command = TSVNCACHECOMMAND_RELEASE;\r
337                 wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH);\r
338                 BOOL fSuccess = WriteFile( \r
339                         m_hCommandPipe, // handle to pipe \r
340                         &cmd,                   // buffer to write from \r
341                         sizeof(cmd),    // number of bytes to write \r
342                         &cbWritten,             // number of bytes written \r
343                         NULL);                  // not overlapped I/O \r
344                 if (! fSuccess || sizeof(cmd) != cbWritten)\r
345                 {\r
346                         CloseCommandPipe();\r
347                         return false;\r
348                 }\r
349                 return true;\r
350         }\r
351         return false;\r
352 }\r