1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2006, 2009 - 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.
21 #include "DirFileEnum.h"
22 #include "CacheInterface.h"
24 #include ".\cachedlg.h"
31 CCacheDlg::CCacheDlg(CWnd
* pParent
/*=NULL*/)
32 : CDialog(CCacheDlg::IDD
, pParent
)
34 , m_hPipe(INVALID_HANDLE_VALUE
)
36 m_hIcon
= AfxGetApp()->LoadIcon(IDR_MAINFRAME
);
39 void CCacheDlg::DoDataExchange(CDataExchange
* pDX
)
41 CDialog::DoDataExchange(pDX
);
42 DDX_Text(pDX
, IDC_ROOTPATH
, m_sRootPath
);
45 BEGIN_MESSAGE_MAP(CCacheDlg
, CDialog
)
49 ON_BN_CLICKED(IDOK
, OnBnClickedOk
)
50 ON_BN_CLICKED(IDC_WATCHTESTBUTTON
, OnBnClickedWatchtestbutton
)
54 // CCacheDlg message handlers
56 BOOL
CCacheDlg::OnInitDialog()
58 CDialog::OnInitDialog();
60 // Set the icon for this dialog. The framework does this automatically
61 // when the application's main window is not a dialog
62 SetIcon(m_hIcon
, TRUE
); // Set big icon
63 SetIcon(m_hIcon
, FALSE
); // Set small icon
65 return TRUE
; // return TRUE unless you set the focus to a control
68 // If you add a minimize button to your dialog, you will need the code below
69 // to draw the icon. For MFC applications using the document/view model,
70 // this is automatically done for you by the framework.
72 void CCacheDlg::OnPaint()
76 CPaintDC
dc(this); // device context for painting
78 SendMessage(WM_ICONERASEBKGND
, reinterpret_cast<WPARAM
>(dc
.GetSafeHdc()), 0);
80 // Center icon in client rectangle
81 int cxIcon
= GetSystemMetrics(SM_CXICON
);
82 int cyIcon
= GetSystemMetrics(SM_CYICON
);
85 int x
= (rect
.Width() - cxIcon
+ 1) / 2;
86 int y
= (rect
.Height() - cyIcon
+ 1) / 2;
89 dc
.DrawIcon(x
, y
, m_hIcon
);
97 // The system calls this function to obtain the cursor to display while the user drags
98 // the minimized window.
99 HCURSOR
CCacheDlg::OnQueryDragIcon()
101 return static_cast<HCURSOR
>(m_hIcon
);
104 void CCacheDlg::OnBnClickedOk()
107 AfxBeginThread(TestThreadEntry
, this);
109 UINT
CCacheDlg::TestThreadEntry(LPVOID pVoid
)
111 return ((CCacheDlg
*)pVoid
)->TestThread();
114 //this is the thread function which calls the subversion function
115 UINT
CCacheDlg::TestThread()
117 CDirFileEnum
direnum(m_sRootPath
);
118 m_filelist
.RemoveAll();
121 while (direnum
.NextFile(filepath
, &bIsDir
))
122 if(filepath
.Find(_T(".git"))<0)
123 m_filelist
.Add(filepath
);
125 CTime starttime
= CTime::GetCurrentTime();
126 GetDlgItem(IDC_STARTTIME
)->SetWindowText(starttime
.Format(_T("%H:%M:%S")));
129 DWORD startticks
= GetTickCount();
132 srand(GetTickCount());
133 for (int i
=0; i
< 1; ++i
)
137 filepath
= m_filelist
.GetAt(rand() % m_filelist
.GetCount());
138 //}while(filepath.Find(_T(".git"))>=0);
139 GetDlgItem(IDC_FILEPATH
)->SetWindowText(filepath
);
140 GetStatusFromRemoteCache(CTGitPath(filepath
), true);
141 sNumber
.Format(_T("%d"), i
);
142 GetDlgItem(IDC_DONE
)->SetWindowText(sNumber
);
143 if ((GetTickCount()%10)==1)
146 RemoveFromCache(filepath
);
148 CTime endtime
= CTime::GetCurrentTime();
149 CString sEnd
= endtime
.Format(_T("%H:%M:%S"));
151 DWORD endticks
= GetTickCount();
154 sEndText
.Format(_T("%s - %d ms"), sEnd
, endticks
-startticks
);
156 GetDlgItem(IDC_ENDTIME
)->SetWindowText(sEndText
);
162 bool CCacheDlg::EnsurePipeOpen()
164 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
169 m_hPipe
= CreateFile(
170 GetCachePipeName(), // pipe name
171 GENERIC_READ
| // read and write access
174 NULL
, // default security attributes
175 OPEN_EXISTING
, // opens existing pipe
176 FILE_FLAG_OVERLAPPED
, // default attributes
177 NULL
); // no template file
179 if (m_hPipe
== INVALID_HANDLE_VALUE
&& GetLastError() == ERROR_PIPE_BUSY
)
181 // TSVNCache is running but is busy connecting a different client.
182 // Do not give up immediately but wait for a few milliseconds until
183 // the server has created the next pipe instance
184 if (WaitNamedPipe(GetCachePipeName(), 50))
186 m_hPipe
= CreateFile(
187 GetCachePipeName(), // pipe name
188 GENERIC_READ
| // read and write access
191 NULL
, // default security attributes
192 OPEN_EXISTING
, // opens existing pipe
193 FILE_FLAG_OVERLAPPED
, // default attributes
194 NULL
); // no template file
199 if (m_hPipe
!= INVALID_HANDLE_VALUE
)
201 // The pipe connected; change to message-read mode.
204 dwMode
= PIPE_READMODE_MESSAGE
;
205 if(!SetNamedPipeHandleState(
206 m_hPipe
, // pipe handle
207 &dwMode
, // new pipe mode
208 NULL
, // don't set maximum bytes
209 NULL
)) // don't set maximum time
211 ATLTRACE("SetNamedPipeHandleState failed");
212 CloseHandle(m_hPipe
);
213 m_hPipe
= INVALID_HANDLE_VALUE
;
216 // create an unnamed (=local) manual reset event for use in the overlapped structure
217 m_hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
220 ATLTRACE("CreateEvent failed");
229 void CCacheDlg::ClosePipe()
231 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
233 CloseHandle(m_hPipe
);
234 CloseHandle(m_hEvent
);
235 m_hPipe
= INVALID_HANDLE_VALUE
;
236 m_hEvent
= INVALID_HANDLE_VALUE
;
240 bool CCacheDlg::GetStatusFromRemoteCache(const CTGitPath
& Path
, bool bRecursive
)
242 if(!EnsurePipeOpen())
245 PROCESS_INFORMATION process
;
246 memset(&startup
, 0, sizeof(startup
));
247 startup
.cb
= sizeof(startup
);
248 memset(&process
, 0, sizeof(process
));
250 CString sCachePath
= _T("TGitCache.exe");
251 if (CreateProcess(sCachePath
.GetBuffer(sCachePath
.GetLength()+1), _T(""), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
)==0)
253 // It's not appropriate to do a message box here, because there may be hundreds of calls
254 sCachePath
.ReleaseBuffer();
255 ATLTRACE("Failed to start cache\n");
258 sCachePath
.ReleaseBuffer();
260 // Wait for the cache to open
261 long endTime
= (long)GetTickCount()+1000;
262 while(!EnsurePipeOpen())
264 if(((long)GetTickCount() - endTime
) > 0)
273 TGITCacheRequest request
;
274 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
277 request
.flags
|= TGITCACHE_FLAGS_RECUSIVE_STATUS
;
279 wcsncpy(request
.path
, Path
.GetWinPath(), MAX_PATH
);
280 ZeroMemory(&m_Overlapped
, sizeof(OVERLAPPED
));
281 m_Overlapped
.hEvent
= m_hEvent
;
282 // Do the transaction in overlapped mode.
283 // That way, if anything happens which might block this call
284 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
285 // A blocked shell is a very bad user impression, because users
286 // who don't know why it's blocked might find the only solution
287 // to such a problem is a reboot and therefore they might loose
289 // Sure, it would be better to have no situations where the shell
290 // even can get blocked, but the timeout of 5 seconds is long enough
291 // so that users still recognize that something might be wrong and
292 // report back to us so we can investigate further.
294 TGITCacheResponse ReturnedStatus
;
295 BOOL fSuccess
= TransactNamedPipe(m_hPipe
,
296 &request
, sizeof(request
),
297 &ReturnedStatus
, sizeof(ReturnedStatus
),
298 &nBytesRead
, &m_Overlapped
);
302 if (GetLastError()!=ERROR_IO_PENDING
)
308 // TransactNamedPipe is working in an overlapped operation.
309 // Wait for it to finish
310 DWORD dwWait
= WaitForSingleObject(m_hEvent
, INFINITE
);
311 if (dwWait
== WAIT_OBJECT_0
)
313 fSuccess
= GetOverlappedResult(m_hPipe
, &m_Overlapped
, &nBytesRead
, FALSE
);
324 void CCacheDlg::RemoveFromCache(const CString
& path
)
326 // if we use the external cache, we tell the cache directly that something
327 // has changed, without the detour via the shell.
328 HANDLE hPipe
= CreateFile(
329 GetCacheCommandPipeName(), // pipe name
330 GENERIC_READ
| // read and write access
333 NULL
, // default security attributes
334 OPEN_EXISTING
, // opens existing pipe
335 FILE_FLAG_OVERLAPPED
, // default attributes
336 NULL
); // no template file
339 if (hPipe
!= INVALID_HANDLE_VALUE
)
341 // The pipe connected; change to message-read mode.
344 dwMode
= PIPE_READMODE_MESSAGE
;
345 if(SetNamedPipeHandleState(
346 hPipe
, // pipe handle
347 &dwMode
, // new pipe mode
348 NULL
, // don't set maximum bytes
349 NULL
)) // don't set maximum time
352 TGITCacheCommand cmd
;
353 cmd
.command
= TGITCACHECOMMAND_CRAWL
;
354 wcsncpy(cmd
.path
, path
, MAX_PATH
);
355 BOOL fSuccess
= WriteFile(
356 hPipe
, // handle to pipe
357 &cmd
, // buffer to write from
358 sizeof(cmd
), // number of bytes to write
359 &cbWritten
, // number of bytes written
360 NULL
); // not overlapped I/O
362 if (! fSuccess
|| sizeof(cmd
) != cbWritten
)
364 DisconnectNamedPipe(hPipe
);
366 hPipe
= INVALID_HANDLE_VALUE
;
368 if (hPipe
!= INVALID_HANDLE_VALUE
)
370 // now tell the cache we don't need it's command thread anymore
372 TGITCacheCommand cmd
;
373 cmd
.command
= TGITCACHECOMMAND_END
;
375 hPipe
, // handle to pipe
376 &cmd
, // buffer to write from
377 sizeof(cmd
), // number of bytes to write
378 &cbWritten
, // number of bytes written
379 NULL
); // not overlapped I/O
380 DisconnectNamedPipe(hPipe
);
382 hPipe
= INVALID_HANDLE_VALUE
;
387 ATLTRACE("SetNamedPipeHandleState failed");
392 void CCacheDlg::OnBnClickedWatchtestbutton()
395 AfxBeginThread(WatchTestThreadEntry
, this);
398 UINT
CCacheDlg::WatchTestThreadEntry(LPVOID pVoid
)
400 return ((CCacheDlg
*)pVoid
)->WatchTestThread();
403 //this is the thread function which calls the subversion function
404 UINT
CCacheDlg::WatchTestThread()
406 CDirFileEnum
direnum(m_sRootPath
);
407 m_filelist
.RemoveAll();
410 while (direnum
.NextFile(filepath
, &bIsDir
))
411 m_filelist
.Add(filepath
);
413 CTime starttime
= CTime::GetCurrentTime();
414 GetDlgItem(IDC_STARTTIME
)->SetWindowText(starttime
.Format(_T("%H:%M:%S")));
417 DWORD startticks
= GetTickCount();
420 srand(GetTickCount());
421 filepath
= m_filelist
.GetAt(rand() % m_filelist
.GetCount());
422 GetStatusFromRemoteCache(CTGitPath(m_sRootPath
), false);
423 for (int i
=0; i
< 10000; ++i
)
425 filepath
= m_filelist
.GetAt(rand() % m_filelist
.GetCount());
426 GetDlgItem(IDC_FILEPATH
)->SetWindowText(filepath
);
428 CopyRemoveCopy(filepath
);
429 sNumber
.Format(_T("%d"), i
);
430 GetDlgItem(IDC_DONE
)->SetWindowText(sNumber
);
433 // create dummy directories and remove them again several times
434 for (int outer
= 0; outer
<100; ++outer
)
436 for (int i
=0; i
<10; ++i
)
438 filepath
.Format(_T("__MyDummyFolder%d"), i
);
439 CreateDirectory(m_sRootPath
+_T("\\")+filepath
, NULL
);
440 HANDLE hFile
= CreateFile(m_sRootPath
+_T("\\")+filepath
+_T("\\file"), GENERIC_READ
, FILE_SHARE_READ
, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
442 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
, m_sRootPath
+_T("\\")+filepath
+_T("\\file"), NULL
);
445 for (int i
=0; i
<10; ++i
)
447 filepath
.Format(_T("__MyDummyFolder%d"), i
);
448 DeleteFile(m_sRootPath
+_T("\\")+filepath
+_T("\\file"));
449 RemoveDirectory(m_sRootPath
+_T("\\")+filepath
);
451 sNumber
.Format(_T("%d"), outer
);
452 GetDlgItem(IDC_DONE
)->SetWindowText(sNumber
);
455 CTime endtime
= CTime::GetCurrentTime();
456 CString sEnd
= endtime
.Format(_T("%H:%M:%S"));
458 DWORD endticks
= GetTickCount();
461 sEndText
.Format(_T("%s - %d ms"), sEnd
, endticks
-startticks
);
463 GetDlgItem(IDC_ENDTIME
)->SetWindowText(sEndText
);
468 void CCacheDlg::TouchFile(const CString
& path
)
470 SetFileAttributes(path
, FILE_ATTRIBUTE_NORMAL
);
471 HANDLE hFile
= CreateFile(path
, GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
472 if (hFile
== INVALID_HANDLE_VALUE
)
478 GetSystemTime(&st
); // gets current time
479 SystemTimeToFileTime(&st
, &ft
); // converts to file time format
480 SetFileTime(hFile
, // sets last-write time for file
481 (LPFILETIME
) NULL
, (LPFILETIME
) NULL
, &ft
);
486 void CCacheDlg::CopyRemoveCopy(const CString
& path
)
488 if (PathIsDirectory(path
))
490 if (path
.Find(_T(".svn"))>=0)
492 if (CopyFile(path
, path
+_T(".tmp"), FALSE
))
494 if (DeleteFile(path
))
496 if (MoveFile(path
+_T(".tmp"), path
))
499 MessageBox(_T("could not move file!"), path
);
502 MessageBox(_T("could not delete file!"), path
);
505 MessageBox(_T("could not copy file!"), path
);