fixed the ContextMenuStash.png screenshot
[TortoiseGit.git] / test / Cache / CacheDlg.cpp
blobfa93431be8971dbd365f8cd1c8b5b3c68710853f
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.
19 #include "stdafx.h"
20 #include "Cache.h"
21 #include "DirFileEnum.h"
22 #include "CacheInterface.h"
23 #include <WinInet.h>
24 #include ".\cachedlg.h"
26 #ifdef _DEBUG
27 #define new DEBUG_NEW
28 #endif
31 CCacheDlg::CCacheDlg(CWnd* pParent /*=NULL*/)
32 : CDialog(CCacheDlg::IDD, pParent)
33 , m_sRootPath(_T(""))
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)
46 ON_WM_PAINT()
47 ON_WM_QUERYDRAGICON()
48 //}}AFX_MSG_MAP
49 ON_BN_CLICKED(IDOK, OnBnClickedOk)
50 ON_BN_CLICKED(IDC_WATCHTESTBUTTON, OnBnClickedWatchtestbutton)
51 END_MESSAGE_MAP()
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()
74 if (IsIconic())
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);
83 CRect rect;
84 GetClientRect(&rect);
85 int x = (rect.Width() - cxIcon + 1) / 2;
86 int y = (rect.Height() - cyIcon + 1) / 2;
88 // Draw the icon
89 dc.DrawIcon(x, y, m_hIcon);
91 else
93 CDialog::OnPaint();
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()
106 UpdateData();
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();
119 CString filepath;
120 bool bIsDir = false;
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")));
127 int filecounter = 0;
129 DWORD startticks = GetTickCount();
131 CString sNumber;
132 srand(GetTickCount());
133 for (int i=0; i < 1; ++i)
135 CString filepath;
136 //do {
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)
144 Sleep(10);
145 if ((rand()%10)==3)
146 RemoveFromCache(filepath);
148 CTime endtime = CTime::GetCurrentTime();
149 CString sEnd = endtime.Format(_T("%H:%M:%S"));
151 DWORD endticks = GetTickCount();
153 CString sEndText;
154 sEndText.Format(_T("%s - %d ms"), sEnd, endticks-startticks);
156 GetDlgItem(IDC_ENDTIME)->SetWindowText(sEndText);
158 return 0;
162 bool CCacheDlg::EnsurePipeOpen()
164 if(m_hPipe != INVALID_HANDLE_VALUE)
166 return true;
169 m_hPipe = CreateFile(
170 GetCachePipeName(), // pipe name
171 GENERIC_READ | // read and write access
172 GENERIC_WRITE,
173 0, // no sharing
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
189 GENERIC_WRITE,
190 0, // no sharing
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.
202 DWORD dwMode;
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;
214 return false;
216 // create an unnamed (=local) manual reset event for use in the overlapped structure
217 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
218 if (m_hEvent)
219 return true;
220 ATLTRACE("CreateEvent failed");
221 ClosePipe();
222 return false;
225 return false;
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())
244 STARTUPINFO startup;
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");
256 return false;
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)
266 return false;
272 DWORD nBytesRead;
273 TGITCacheRequest request;
274 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
275 if(bRecursive)
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
288 // valuable data.
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);
300 if (!fSuccess)
302 if (GetLastError()!=ERROR_IO_PENDING)
304 ClosePipe();
305 return false;
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);
314 return TRUE;
316 else
317 fSuccess = FALSE;
320 ClosePipe();
321 return 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
331 GENERIC_WRITE,
332 0, // no sharing
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.
342 DWORD dwMode;
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
351 DWORD cbWritten;
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);
365 CloseHandle(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
371 DWORD cbWritten;
372 TGITCacheCommand cmd;
373 cmd.command = TGITCACHECOMMAND_END;
374 WriteFile(
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);
381 CloseHandle(hPipe);
382 hPipe = INVALID_HANDLE_VALUE;
385 else
387 ATLTRACE("SetNamedPipeHandleState failed");
388 CloseHandle(hPipe);
392 void CCacheDlg::OnBnClickedWatchtestbutton()
394 UpdateData();
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();
408 CString filepath;
409 bool bIsDir = false;
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")));
415 int filecounter = 0;
417 DWORD startticks = GetTickCount();
419 CString sNumber;
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);
427 TouchFile(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);
441 CloseHandle(hFile);
442 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, m_sRootPath+_T("\\")+filepath+_T("\\file"), NULL);
444 Sleep(500);
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();
460 CString sEndText;
461 sEndText.Format(_T("%s - %d ms"), sEnd, endticks-startticks);
463 GetDlgItem(IDC_ENDTIME)->SetWindowText(sEndText);
465 return 0;
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)
473 return;
475 FILETIME ft;
476 SYSTEMTIME st;
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);
483 CloseHandle(hFile);
486 void CCacheDlg::CopyRemoveCopy(const CString& path)
488 if (PathIsDirectory(path))
489 return;
490 if (path.Find(_T(".svn"))>=0)
491 return;
492 if (CopyFile(path, path+_T(".tmp"), FALSE))
494 if (DeleteFile(path))
496 if (MoveFile(path+_T(".tmp"), path))
497 return;
498 else
499 MessageBox(_T("could not move file!"), path);
501 else
502 MessageBox(_T("could not delete file!"), path);
504 else
505 MessageBox(_T("could not copy file!"), path);