OSDN Git Service

igit added as submodule
[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 "..\TGitCache\CacheInterface.h"\r
23 #include "TGitPath.h"\r
24 \r
25 CRemoteCacheLink::CRemoteCacheLink(void) \r
26         : m_hPipe(INVALID_HANDLE_VALUE)\r
27         , m_hCommandPipe(INVALID_HANDLE_VALUE)\r
28 {\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
34         m_lastTimeout = 0;\r
35         m_critSec.Init();\r
36 }\r
37 \r
38 CRemoteCacheLink::~CRemoteCacheLink(void)\r
39 {\r
40         ClosePipe();\r
41         CloseCommandPipe();\r
42         m_critSec.Term();\r
43 }\r
44 \r
45 bool CRemoteCacheLink::EnsurePipeOpen()\r
46 {\r
47         AutoLocker lock(m_critSec);\r
48         if(m_hPipe != INVALID_HANDLE_VALUE)\r
49         {\r
50                 return true;\r
51         }\r
52 \r
53         m_hPipe = CreateFile(\r
54                 GetCachePipeName(),                             // pipe name\r
55                 GENERIC_READ |                                  // read and write access\r
56                 GENERIC_WRITE,\r
57                 0,                                                              // no sharing\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
62 \r
63         if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)\r
64         {\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
69                 {\r
70                         m_hPipe = CreateFile(\r
71                                 GetCachePipeName(),                             // pipe name\r
72                                 GENERIC_READ |                                  // read and write access\r
73                                 GENERIC_WRITE,\r
74                                 0,                                                              // no sharing\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
79                 }\r
80         }\r
81 \r
82 \r
83         if (m_hPipe != INVALID_HANDLE_VALUE)\r
84         {\r
85                 // The pipe connected; change to message-read mode.\r
86                 DWORD dwMode;\r
87 \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
94                 {\r
95                         ATLTRACE("SetNamedPipeHandleState failed");\r
96                         CloseHandle(m_hPipe);\r
97                         m_hPipe = INVALID_HANDLE_VALUE;\r
98                         return false;\r
99                 }\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
102                 if (m_hEvent)\r
103                         return true;\r
104                 ATLTRACE("CreateEvent failed");\r
105                 ClosePipe();\r
106                 return false;\r
107         }\r
108 \r
109         return false;\r
110 }\r
111 \r
112 bool CRemoteCacheLink::EnsureCommandPipeOpen()\r
113 {\r
114         AutoLocker lock(m_critSec);\r
115         if(m_hCommandPipe != INVALID_HANDLE_VALUE)\r
116         {\r
117                 return true;\r
118         }\r
119 \r
120         m_hCommandPipe = CreateFile(\r
121                 GetCacheCommandPipeName(),              // pipe name\r
122                 GENERIC_READ |                                  // read and write access\r
123                 GENERIC_WRITE,\r
124                 0,                                                              // no sharing\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
129 \r
130         if (m_hCommandPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)\r
131         {\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
136                 {\r
137                         m_hCommandPipe = CreateFile(\r
138                                 GetCacheCommandPipeName(),              // pipe name\r
139                                 GENERIC_READ |                                  // read and write access\r
140                                 GENERIC_WRITE,\r
141                                 0,                                                              // no sharing\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
146                 }\r
147         }\r
148 \r
149 \r
150         if (m_hCommandPipe != INVALID_HANDLE_VALUE)\r
151         {\r
152                 // The pipe connected; change to message-read mode.\r
153                 DWORD dwMode;\r
154 \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
161                 {\r
162                         ATLTRACE("SetNamedPipeHandleState failed");\r
163                         CloseHandle(m_hCommandPipe);\r
164                         m_hCommandPipe = INVALID_HANDLE_VALUE;\r
165                         return false;\r
166                 }\r
167                 return true;\r
168         }\r
169 \r
170         return false;\r
171 }\r
172 \r
173 void CRemoteCacheLink::ClosePipe()\r
174 {\r
175         AutoLocker lock(m_critSec);\r
176 \r
177         if(m_hPipe != INVALID_HANDLE_VALUE)\r
178         {\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
183         }\r
184 }\r
185 \r
186 void CRemoteCacheLink::CloseCommandPipe()\r
187 {\r
188         AutoLocker lock(m_critSec);\r
189 \r
190         if(m_hCommandPipe != INVALID_HANDLE_VALUE)\r
191         {\r
192                 // now tell the cache we don't need it's command thread anymore\r
193                 DWORD cbWritten; \r
194                 TSVNCacheCommand cmd;\r
195                 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));\r
196                 cmd.command = TSVNCACHECOMMAND_END;\r
197                 WriteFile( \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
206         }\r
207 }\r
208 \r
209 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TSVNCacheResponse* pReturnedStatus, bool bRecursive)\r
210 {\r
211         if(!EnsurePipeOpen())\r
212         {\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
220                         return false;\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
226 \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
230                 {\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
234                         return false;\r
235                 }\r
236                 CloseHandle(process.hThread);\r
237                 CloseHandle(process.hProcess);\r
238                 sCachePath.ReleaseBuffer();\r
239 \r
240                 // Wait for the cache to open\r
241                 long endTime = (long)GetTickCount()+1000;\r
242                 while(!EnsurePipeOpen())\r
243                 {\r
244                         if(((long)GetTickCount() - endTime) > 0)\r
245                         {\r
246                                 m_lastTimeout = (long)GetTickCount()+10000;\r
247                                 return false;\r
248                         }\r
249                 }\r
250         }\r
251 \r
252         AutoLocker lock(m_critSec);\r
253 \r
254         DWORD nBytesRead;\r
255         TSVNCacheRequest request;\r
256         request.flags = TSVNCACHE_FLAGS_NONOTIFICATIONS;\r
257         if(bRecursive)\r
258         {\r
259                 request.flags |= TSVNCACHE_FLAGS_RECUSIVE_STATUS;\r
260         }\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
270         // valuable data.\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
277 \r
278         BOOL fSuccess = TransactNamedPipe(m_hPipe,\r
279                 &request, sizeof(request),\r
280                 pReturnedStatus, sizeof(*pReturnedStatus),\r
281                 &nBytesRead, &m_Overlapped);\r
282 \r
283         if (!fSuccess)\r
284         {\r
285                 if (GetLastError()!=ERROR_IO_PENDING)\r
286                 {\r
287                         //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");\r
288                         ClosePipe();\r
289                         return false;\r
290                 }\r
291 \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
296                 {\r
297                         fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);\r
298                 }\r
299                 else\r
300                 {\r
301                         // the cache didn't respond!\r
302                         fSuccess = FALSE;\r
303                 }\r
304         }\r
305 \r
306         if (fSuccess)\r
307         {\r
308                 if(nBytesRead == sizeof(TSVNCacheResponse))\r
309                 {\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
313                 }\r
314                 else\r
315                 {\r
316 //                      pReturnedStatus->m_status.entry = NULL;\r
317                 }\r
318 \r
319                 return true;\r
320         }\r
321         ClosePipe();\r
322         return false;\r
323 }\r
324 \r
325 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)\r
326 {\r
327         EnsureCommandPipeOpen();\r
328         if (m_hCommandPipe != INVALID_HANDLE_VALUE) \r
329         {\r
330                 DWORD cbWritten; \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
342                 {\r
343                         CloseCommandPipe();\r
344                         return false;\r
345                 }\r
346                 return true;\r
347         }\r
348         return false;\r
349 }\r