1
// TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2011, 2013, 2015-2017, 2019, 2023 - TortoiseGit
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.
23 #include "GitAdminDir.h"
24 #include "GitStatusCache.h"
26 CShellUpdater::CShellUpdater()
28 m_hWakeEvent
= CreateEvent(nullptr, FALSE
, FALSE
, nullptr);
29 m_hTerminationEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
32 CShellUpdater::~CShellUpdater()
37 void CShellUpdater::Stop()
39 InterlockedExchange(&m_bRunning
, FALSE
);
40 if (m_hTerminationEvent
)
42 SetEvent(m_hTerminationEvent
);
43 if(WaitForSingleObject(m_hThread
, 200) != WAIT_OBJECT_0
)
44 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Error terminating shell updater thread\n");
46 m_hThread
.CloseHandle();
47 m_hTerminationEvent
.CloseHandle();
48 m_hWakeEvent
.CloseHandle();
51 void CShellUpdater::Initialise()
53 // Don't call Initialize more than once
54 ATLASSERT(!m_hThread
);
56 // Just start the worker thread.
57 // It will wait for event being signaled.
58 // If m_hWakeEvent is already signaled the worker thread
59 // will behave properly (with normal priority at worst).
61 InterlockedExchange(&m_bRunning
, TRUE
);
62 unsigned int threadId
;
63 m_hThread
= reinterpret_cast<HANDLE
>(_beginthreadex(nullptr, 0, ThreadEntry
, this, 0, &threadId
));
64 SetThreadPriority(m_hThread
, THREAD_PRIORITY_LOWEST
);
67 void CShellUpdater::AddPathForUpdate(const CTGitPath
& path
)
70 AutoLocker
lock(m_critSec
);
72 m_pathsToUpdate
.push_back(path
);
74 // set this flag while we are synced
75 // with the worker thread
76 m_bItemsAddedSinceLastUpdate
= true;
79 SetEvent(m_hWakeEvent
);
83 unsigned int CShellUpdater::ThreadEntry(void* pContext
)
85 reinterpret_cast<CShellUpdater
*>(pContext
)->WorkerThread();
89 void CShellUpdater::WorkerThread()
91 HANDLE hWaitHandles
[2];
92 hWaitHandles
[0] = m_hTerminationEvent
;
93 hWaitHandles
[1] = m_hWakeEvent
;
97 DWORD waitResult
= WaitForMultipleObjects(_countof(hWaitHandles
), hWaitHandles
, FALSE
, INFINITE
);
99 // exit event/working loop if the first event (m_hTerminationEvent)
100 // has been signaled or if one of the events has been abandoned
101 // (i.e. ~CShellUpdater() is being executed)
102 if(waitResult
== WAIT_OBJECT_0
|| waitResult
== WAIT_ABANDONED_0
|| waitResult
== WAIT_ABANDONED_0
+1)
107 // wait some time before we notify the shell
111 CTGitPath workingPath
;
116 AutoLocker
lock(m_critSec
);
117 if(m_pathsToUpdate
.empty())
119 // Nothing left to do
123 if(m_bItemsAddedSinceLastUpdate
)
125 m_pathsToUpdate
.erase(std::unique(m_pathsToUpdate
.begin(), m_pathsToUpdate
.end(), &CTGitPath::PredLeftEquivalentToRight
), m_pathsToUpdate
.end());
126 m_bItemsAddedSinceLastUpdate
= false;
129 workingPath
= m_pathsToUpdate
.front();
130 m_pathsToUpdate
.pop_front();
132 if (workingPath
.IsEmpty())
134 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": shell notification for %s\n", workingPath
.GetWinPath());
135 if (workingPath
.IsDirectory())
137 // first send a notification about a sub folder change, so explorer doesn't discard
138 // the folder notification. Since we only know for sure that the git admin
139 // dir is present, we send a notification for that folder.
140 CString
admindir(workingPath
.GetWinPathString());
142 admindir
+= GitAdminDir::GetAdminDirName();
143 if(::PathFileExists(admindir
))
144 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
, static_cast<LPCWSTR
>(admindir
), nullptr);
146 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
, workingPath
.GetWinPath(), nullptr);
147 // Sending an UPDATEDIR notification somehow overwrites/deletes the UPDATEITEM message. And without
148 // that message, the folder overlays in the current view don't get updated without hitting F5.
149 // Drawback is, without UPDATEDIR, the left tree view isn't always updated...
151 //SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), nullptr);
154 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
, workingPath
.GetWinPath(), nullptr);