Use forward slashes in the includes for consistency and also remove double slashes
[TortoiseGit.git] / test / Cache / CacheDlg.cpp
blob0e872d9f3225235dac809510291ee296551e57d1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012, 2014-2017 - TortoiseGit
4 // Copyright (C) 2003-2006, 2009, 2015 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "Cache.h"
22 #include "DirFileEnum.h"
23 #include "CacheInterface.h"
24 #include <WinInet.h>
25 #include "CacheDlg.h"
26 #include <random>
28 #ifdef _DEBUG
29 #define new DEBUG_NEW
30 #endif
33 CCacheDlg::CCacheDlg(CWnd* pParent /*=nullptr*/)
34 : CDialog(CCacheDlg::IDD, pParent)
35 , m_hPipe(INVALID_HANDLE_VALUE)
37 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
40 void CCacheDlg::DoDataExchange(CDataExchange* pDX)
42 CDialog::DoDataExchange(pDX);
43 DDX_Text(pDX, IDC_ROOTPATH, m_sRootPath);
46 BEGIN_MESSAGE_MAP(CCacheDlg, CDialog)
47 ON_WM_PAINT()
48 ON_WM_QUERYDRAGICON()
49 //}}AFX_MSG_MAP
50 ON_BN_CLICKED(IDOK, OnBnClickedOk)
51 ON_BN_CLICKED(IDC_WATCHTESTBUTTON, OnBnClickedWatchtestbutton)
52 END_MESSAGE_MAP()
55 // CCacheDlg message handlers
57 BOOL CCacheDlg::OnInitDialog()
59 CDialog::OnInitDialog();
61 // Set the icon for this dialog. The framework does this automatically
62 // when the application's main window is not a dialog
63 SetIcon(m_hIcon, TRUE); // Set big icon
64 SetIcon(m_hIcon, FALSE); // Set small icon
66 return TRUE; // return TRUE unless you set the focus to a control
69 // If you add a minimize button to your dialog, you will need the code below
70 // to draw the icon. For MFC applications using the document/view model,
71 // this is automatically done for you by the framework.
73 void CCacheDlg::OnPaint()
75 if (IsIconic())
77 CPaintDC dc(this); // device context for painting
79 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
81 // Center icon in client rectangle
82 int cxIcon = GetSystemMetrics(SM_CXICON);
83 int cyIcon = GetSystemMetrics(SM_CYICON);
84 CRect rect;
85 GetClientRect(&rect);
86 int x = (rect.Width() - cxIcon + 1) / 2;
87 int y = (rect.Height() - cyIcon + 1) / 2;
89 // Draw the icon
90 dc.DrawIcon(x, y, m_hIcon);
92 else
94 CDialog::OnPaint();
98 // The system calls this function to obtain the cursor to display while the user drags
99 // the minimized window.
100 HCURSOR CCacheDlg::OnQueryDragIcon()
102 return static_cast<HCURSOR>(m_hIcon);
105 void CCacheDlg::OnBnClickedOk()
107 UpdateData();
108 AfxBeginThread(TestThreadEntry, this);
110 UINT CCacheDlg::TestThreadEntry(LPVOID pVoid)
112 return ((CCacheDlg*)pVoid)->TestThread();
115 //this is the thread function which calls the subversion function
116 UINT CCacheDlg::TestThread()
118 CDirFileEnum direnum(m_sRootPath);
119 m_filelist.RemoveAll();
120 CString filepath;
121 bool bIsDir = false;
122 while (direnum.NextFile(filepath, &bIsDir))
123 if (filepath.Find(L".git") < 0)
124 m_filelist.Add(filepath);
126 CTime starttime = CTime::GetCurrentTime();
127 GetDlgItem(IDC_STARTTIME)->SetWindowText(starttime.Format(L"%H:%M:%S"));
129 ULONGLONG startticks = GetTickCount64();
131 CString sNumber;
132 std::random_device rd;
133 std::mt19937 mt(rd());
134 std::uniform_int_distribution<INT_PTR> dist(0, max(0, m_filelist.GetCount() - 1));
135 std::uniform_int_distribution<INT_PTR> dist2(0, 9);
136 for (int i=0; i < 1; ++i)
138 CString filepath2;
139 //do {
140 filepath2 = m_filelist.GetAt(dist(mt));
141 //}while(filepath.Find(L".git") >= 0);
142 GetDlgItem(IDC_FILEPATH)->SetWindowText(filepath2);
143 GetStatusFromRemoteCache(CTGitPath(filepath2), true);
144 sNumber.Format(L"%d", i);
145 GetDlgItem(IDC_DONE)->SetWindowText(sNumber);
146 if ((GetTickCount64()%10)==1)
147 Sleep(10);
148 if (dist2(mt) == 3)
149 RemoveFromCache(filepath2);
151 CTime endtime = CTime::GetCurrentTime();
152 CString sEnd = endtime.Format(L"%H:%M:%S");
154 ULONGLONG endticks = GetTickCount64();
156 CString sEndText;
157 sEndText.Format(L"%s - %I64u ms", (LPCTSTR)sEnd, endticks - startticks);
159 GetDlgItem(IDC_ENDTIME)->SetWindowText(sEndText);
161 return 0;
165 bool CCacheDlg::EnsurePipeOpen()
167 if(m_hPipe != INVALID_HANDLE_VALUE)
169 return true;
172 m_hPipe = CreateFile(
173 GetCachePipeName(), // pipe name
174 GENERIC_READ | // read and write access
175 GENERIC_WRITE,
176 0, // no sharing
177 nullptr, // default security attributes
178 OPEN_EXISTING, // opens existing pipe
179 FILE_FLAG_OVERLAPPED, // default attributes
180 nullptr); // no template file
182 if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
184 // TSVNCache is running but is busy connecting a different client.
185 // Do not give up immediately but wait for a few milliseconds until
186 // the server has created the next pipe instance
187 if (WaitNamedPipe(GetCachePipeName(), 50))
189 m_hPipe = CreateFile(
190 GetCachePipeName(), // pipe name
191 GENERIC_READ | // read and write access
192 GENERIC_WRITE,
193 0, // no sharing
194 nullptr, // default security attributes
195 OPEN_EXISTING, // opens existing pipe
196 FILE_FLAG_OVERLAPPED, // default attributes
197 nullptr); // no template file
202 if (m_hPipe != INVALID_HANDLE_VALUE)
204 // The pipe connected; change to message-read mode.
205 DWORD dwMode;
207 dwMode = PIPE_READMODE_MESSAGE;
208 if(!SetNamedPipeHandleState(
209 m_hPipe, // pipe handle
210 &dwMode, // new pipe mode
211 nullptr, // don't set maximum bytes
212 nullptr)) // don't set maximum time
214 ATLTRACE("SetNamedPipeHandleState failed");
215 CloseHandle(m_hPipe);
216 m_hPipe = INVALID_HANDLE_VALUE;
217 return false;
219 // create an unnamed (=local) manual reset event for use in the overlapped structure
220 m_hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
221 if (m_hEvent)
222 return true;
223 ATLTRACE("CreateEvent failed");
224 ClosePipe();
225 return false;
228 return false;
232 void CCacheDlg::ClosePipe()
234 if(m_hPipe != INVALID_HANDLE_VALUE)
236 CloseHandle(m_hPipe);
237 CloseHandle(m_hEvent);
238 m_hPipe = INVALID_HANDLE_VALUE;
239 m_hEvent = INVALID_HANDLE_VALUE;
243 bool CCacheDlg::GetStatusFromRemoteCache(const CTGitPath& Path, bool bRecursive)
245 if(!EnsurePipeOpen())
247 STARTUPINFO startup = { 0 };
248 PROCESS_INFORMATION process = { 0 };
249 startup.cb = sizeof(startup);
251 CString sCachePath = L"TGitCache.exe";
252 if (CreateProcess(sCachePath.GetBuffer(sCachePath.GetLength() + 1), L"", nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup, &process) == 0)
254 // It's not appropriate to do a message box here, because there may be hundreds of calls
255 sCachePath.ReleaseBuffer();
256 ATLTRACE("Failed to start cache\n");
257 return false;
259 sCachePath.ReleaseBuffer();
261 // Wait for the cache to open
262 ULONGLONG endTime = GetTickCount64()+1000;
263 while(!EnsurePipeOpen())
265 if((GetTickCount64() - endTime) > 0)
267 return false;
273 DWORD nBytesRead;
274 TGITCacheRequest request;
275 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
276 if(bRecursive)
278 request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS;
280 wcsncpy_s(request.path, Path.GetWinPath(), MAX_PATH);
281 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
282 m_Overlapped.hEvent = m_hEvent;
283 // Do the transaction in overlapped mode.
284 // That way, if anything happens which might block this call
285 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
286 // A blocked shell is a very bad user impression, because users
287 // who don't know why it's blocked might find the only solution
288 // to such a problem is a reboot and therefore they might loose
289 // valuable data.
290 // Sure, it would be better to have no situations where the shell
291 // even can get blocked, but the timeout of 5 seconds is long enough
292 // so that users still recognize that something might be wrong and
293 // report back to us so we can investigate further.
295 TGITCacheResponse ReturnedStatus;
296 BOOL fSuccess = TransactNamedPipe(m_hPipe,
297 &request, sizeof(request),
298 &ReturnedStatus, sizeof(ReturnedStatus),
299 &nBytesRead, &m_Overlapped);
301 if (!fSuccess)
303 if (GetLastError()!=ERROR_IO_PENDING)
305 ClosePipe();
306 return false;
309 // TransactNamedPipe is working in an overlapped operation.
310 // Wait for it to finish
311 DWORD dwWait = WaitForSingleObject(m_hEvent, INFINITE);
312 if (dwWait == WAIT_OBJECT_0)
314 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
315 return TRUE;
317 else
318 fSuccess = FALSE;
321 ClosePipe();
322 return false;
325 void CCacheDlg::RemoveFromCache(const CString& path)
327 // if we use the external cache, we tell the cache directly that something
328 // has changed, without the detour via the shell.
329 HANDLE hPipe = CreateFile(
330 GetCacheCommandPipeName(), // pipe name
331 GENERIC_READ | // read and write access
332 GENERIC_WRITE,
333 0, // no sharing
334 nullptr, // default security attributes
335 OPEN_EXISTING, // opens existing pipe
336 FILE_FLAG_OVERLAPPED, // default attributes
337 nullptr); // no template file
340 if (hPipe != INVALID_HANDLE_VALUE)
342 // The pipe connected; change to message-read mode.
343 DWORD dwMode;
345 dwMode = PIPE_READMODE_MESSAGE;
346 if(SetNamedPipeHandleState(
347 hPipe, // pipe handle
348 &dwMode, // new pipe mode
349 NULL, // don't set maximum bytes
350 NULL)) // don't set maximum time
352 DWORD cbWritten;
353 TGITCacheCommand cmd;
354 cmd.command = TGITCACHECOMMAND_CRAWL;
355 wcsncpy_s(cmd.path, path, MAX_PATH);
356 BOOL fSuccess = WriteFile(
357 hPipe, // handle to pipe
358 &cmd, // buffer to write from
359 sizeof(cmd), // number of bytes to write
360 &cbWritten, // number of bytes written
361 NULL); // not overlapped I/O
363 if (! fSuccess || sizeof(cmd) != cbWritten)
365 DisconnectNamedPipe(hPipe);
366 CloseHandle(hPipe);
367 hPipe = INVALID_HANDLE_VALUE;
369 if (hPipe != INVALID_HANDLE_VALUE)
371 // now tell the cache we don't need it's command thread anymore
372 DWORD cbWritten2;
373 TGITCacheCommand cmd2;
374 cmd2.command = TGITCACHECOMMAND_END;
375 WriteFile(
376 hPipe, // handle to pipe
377 &cmd2, // buffer to write from
378 sizeof(cmd2), // number of bytes to write
379 &cbWritten2, // number of bytes written
380 NULL); // not overlapped I/O
381 DisconnectNamedPipe(hPipe);
382 CloseHandle(hPipe);
383 hPipe = INVALID_HANDLE_VALUE;
386 else
388 ATLTRACE("SetNamedPipeHandleState failed");
389 CloseHandle(hPipe);
393 void CCacheDlg::OnBnClickedWatchtestbutton()
395 UpdateData();
396 AfxBeginThread(WatchTestThreadEntry, this);
399 UINT CCacheDlg::WatchTestThreadEntry(LPVOID pVoid)
401 return ((CCacheDlg*)pVoid)->WatchTestThread();
404 //this is the thread function which calls the subversion function
405 UINT CCacheDlg::WatchTestThread()
407 CDirFileEnum direnum(m_sRootPath);
408 m_filelist.RemoveAll();
409 CString filepath;
410 bool bIsDir = false;
411 while (direnum.NextFile(filepath, &bIsDir))
412 m_filelist.Add(filepath);
414 CTime starttime = CTime::GetCurrentTime();
415 GetDlgItem(IDC_STARTTIME)->SetWindowText(starttime.Format(L"%H:%M:%S"));
417 ULONGLONG startticks = GetTickCount64();
419 CString sNumber;
420 std::random_device rd;
421 std::mt19937 mt(rd());
422 std::uniform_int_distribution<INT_PTR> dist(0, max(0, m_filelist.GetCount() - 1));
423 filepath = m_filelist.GetAt(dist(mt));
424 GetStatusFromRemoteCache(CTGitPath(m_sRootPath), false);
425 for (int i=0; i < 10000; ++i)
427 filepath = m_filelist.GetAt(dist(mt));
428 GetDlgItem(IDC_FILEPATH)->SetWindowText(filepath);
429 TouchFile(filepath);
430 CopyRemoveCopy(filepath);
431 sNumber.Format(L"%d", i);
432 GetDlgItem(IDC_DONE)->SetWindowText(sNumber);
435 // create dummy directories and remove them again several times
436 for (int outer = 0; outer<100; ++outer)
438 for (int i=0; i<10; ++i)
440 filepath.Format(L"__MyDummyFolder%d", i);
441 CreateDirectory(m_sRootPath + L'\\' + filepath, nullptr);
442 HANDLE hFile = CreateFile(m_sRootPath + L'\\' + filepath + L"\\file", GENERIC_READ, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
443 CloseHandle(hFile);
444 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, m_sRootPath + L'\\' + filepath + L"\\file", NULL);
446 Sleep(500);
447 for (int i=0; i<10; ++i)
449 filepath.Format(L"__MyDummyFolder%d", i);
450 DeleteFile(m_sRootPath + L'\\' + filepath + L"\\file");
451 RemoveDirectory(m_sRootPath + L'\\' + filepath);
453 sNumber.Format(L"%d", outer);
454 GetDlgItem(IDC_DONE)->SetWindowText(sNumber);
457 CTime endtime = CTime::GetCurrentTime();
458 CString sEnd = endtime.Format(L"%H:%M:%S");
460 ULONGLONG endticks = GetTickCount64();
462 CString sEndText;
463 sEndText.Format(L"%s - %I64u ms", (LPCTSTR)sEnd, endticks - startticks);
465 GetDlgItem(IDC_ENDTIME)->SetWindowText(sEndText);
467 return 0;
470 void CCacheDlg::TouchFile(const CString& path)
472 SetFileAttributes(path, FILE_ATTRIBUTE_NORMAL);
473 HANDLE hFile = CreateFile(path, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
474 if (hFile == INVALID_HANDLE_VALUE)
475 return;
477 FILETIME ft;
478 SYSTEMTIME st;
480 GetSystemTime(&st); // gets current time
481 SystemTimeToFileTime(&st, &ft); // converts to file time format
482 SetFileTime(hFile, // sets last-write time for file
483 (LPFILETIME) NULL, (LPFILETIME) NULL, &ft);
485 CloseHandle(hFile);
488 void CCacheDlg::CopyRemoveCopy(const CString& path)
490 if (PathIsDirectory(path))
491 return;
492 if (path.Find(L".git") >= 0)
493 return;
494 if (CopyFile(path, path+L".tmp", FALSE))
496 if (DeleteFile(path))
498 if (MoveFile(path+L".tmp", path))
499 return;
500 else
501 MessageBox(L"could not move file!", path);
503 else
504 MessageBox(L"could not delete file!", path);
506 else
507 MessageBox(L"could not copy file!", path);