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.
22 #include "DirFileEnum.h"
23 #include "CacheInterface.h"
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
)
50 ON_BN_CLICKED(IDOK
, OnBnClickedOk
)
51 ON_BN_CLICKED(IDC_WATCHTESTBUTTON
, OnBnClickedWatchtestbutton
)
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()
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
);
86 int x
= (rect
.Width() - cxIcon
+ 1) / 2;
87 int y
= (rect
.Height() - cyIcon
+ 1) / 2;
90 dc
.DrawIcon(x
, y
, m_hIcon
);
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()
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();
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();
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
)
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)
149 RemoveFromCache(filepath2
);
151 CTime endtime
= CTime::GetCurrentTime();
152 CString sEnd
= endtime
.Format(L
"%H:%M:%S");
154 ULONGLONG endticks
= GetTickCount64();
157 sEndText
.Format(L
"%s - %I64u ms", (LPCTSTR
)sEnd
, endticks
- startticks
);
159 GetDlgItem(IDC_ENDTIME
)->SetWindowText(sEndText
);
165 bool CCacheDlg::EnsurePipeOpen()
167 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
172 m_hPipe
= CreateFile(
173 GetCachePipeName(), // pipe name
174 GENERIC_READ
| // read and write access
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
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.
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
;
219 // create an unnamed (=local) manual reset event for use in the overlapped structure
220 m_hEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
223 ATLTRACE("CreateEvent failed");
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");
259 sCachePath
.ReleaseBuffer();
261 // Wait for the cache to open
262 ULONGLONG endTime
= GetTickCount64()+1000;
263 while(!EnsurePipeOpen())
265 if((GetTickCount64() - endTime
) > 0)
274 TGITCacheRequest request
;
275 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
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
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
);
303 if (GetLastError()!=ERROR_IO_PENDING
)
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
);
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
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.
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
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
);
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
373 TGITCacheCommand cmd2
;
374 cmd2
.command
= TGITCACHECOMMAND_END
;
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
);
383 hPipe
= INVALID_HANDLE_VALUE
;
388 ATLTRACE("SetNamedPipeHandleState failed");
393 void CCacheDlg::OnBnClickedWatchtestbutton()
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();
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();
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
);
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);
444 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
, m_sRootPath
+ L
'\\' + filepath
+ L
"\\file", NULL
);
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();
463 sEndText
.Format(L
"%s - %I64u ms", (LPCTSTR
)sEnd
, endticks
- startticks
);
465 GetDlgItem(IDC_ENDTIME
)->SetWindowText(sEndText
);
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
)
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
);
488 void CCacheDlg::CopyRemoveCopy(const CString
& path
)
490 if (PathIsDirectory(path
))
492 if (path
.Find(L
".git") >= 0)
494 if (CopyFile(path
, path
+L
".tmp", FALSE
))
496 if (DeleteFile(path
))
498 if (MoveFile(path
+L
".tmp", path
))
501 MessageBox(L
"could not move file!", path
);
504 MessageBox(L
"could not delete file!", path
);
507 MessageBox(L
"could not copy file!", path
);