Do not use GitAdminDir objects
[TortoiseGit.git] / src / TGitCache / ShellUpdater.cpp
blobc2302c75f6aa1dace4c0547023ab0ae2ba3a3195
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 - 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.
21 #include "stdafx.h"
22 #include <ShlObj.h>
23 #include "GitStatusCache.h"
25 CShellUpdater::CShellUpdater(void)
27 m_hWakeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
28 m_hTerminationEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
29 m_bRunning = FALSE;
30 m_bItemsAddedSinceLastUpdate = false;
33 CShellUpdater::~CShellUpdater(void)
35 Stop();
38 void CShellUpdater::Stop()
40 InterlockedExchange(&m_bRunning, FALSE);
41 if (m_hTerminationEvent)
43 SetEvent(m_hTerminationEvent);
44 if(WaitForSingleObject(m_hThread, 200) != WAIT_OBJECT_0)
46 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Error terminating shell updater thread\n");
49 m_hThread.CloseHandle();
50 m_hTerminationEvent.CloseHandle();
51 m_hWakeEvent.CloseHandle();
54 void CShellUpdater::Initialise()
56 // Don't call Initialize more than once
57 ATLASSERT(!m_hThread);
59 // Just start the worker thread.
60 // It will wait for event being signaled.
61 // If m_hWakeEvent is already signaled the worker thread
62 // will behave properly (with normal priority at worst).
64 InterlockedExchange(&m_bRunning, TRUE);
65 unsigned int threadId;
66 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
67 SetThreadPriority(m_hThread, THREAD_PRIORITY_LOWEST);
70 void CShellUpdater::AddPathForUpdate(const CTGitPath& path)
73 AutoLocker lock(m_critSec);
74 for (unsigned int i = 0; i < m_pathsToUpdate.size(); ++i)
76 if(m_pathsToUpdate[i] == path)
77 return;
80 m_pathsToUpdate.push_back(path);
82 // set this flag while we are synced
83 // with the worker thread
84 m_bItemsAddedSinceLastUpdate = true;
87 SetEvent(m_hWakeEvent);
91 unsigned int CShellUpdater::ThreadEntry(void* pContext)
93 ((CShellUpdater*)pContext)->WorkerThread();
94 return 0;
97 void CShellUpdater::WorkerThread()
99 HANDLE hWaitHandles[2];
100 hWaitHandles[0] = m_hTerminationEvent;
101 hWaitHandles[1] = m_hWakeEvent;
103 for(;;)
105 DWORD waitResult = WaitForMultipleObjects(_countof(hWaitHandles), hWaitHandles, FALSE, INFINITE);
107 // exit event/working loop if the first event (m_hTerminationEvent)
108 // has been signaled or if one of the events has been abandoned
109 // (i.e. ~CShellUpdater() is being executed)
110 if(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1)
112 // Termination event
113 break;
115 // wait some time before we notify the shell
116 Sleep(50);
117 for(;;)
119 CTGitPath workingPath;
120 if (!m_bRunning)
121 return;
122 Sleep(0);
124 AutoLocker lock(m_critSec);
125 if(m_pathsToUpdate.empty())
127 // Nothing left to do
128 break;
131 if(m_bItemsAddedSinceLastUpdate)
133 m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTGitPath::PredLeftEquivalentToRight), m_pathsToUpdate.end());
134 m_bItemsAddedSinceLastUpdate = false;
137 workingPath = m_pathsToUpdate.front();
138 m_pathsToUpdate.pop_front();
140 if (workingPath.IsEmpty())
141 continue;
142 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": shell notification for %s\n"), workingPath.GetWinPath());
143 if (workingPath.IsDirectory())
145 // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache
146 // for that path and add it to the watcher.
147 if (!CGitStatusCache::Instance().IsPathWatched(workingPath))
149 if (workingPath.HasAdminDir())
150 CGitStatusCache::Instance().AddPathToWatch(workingPath);
152 // first send a notification about a sub folder change, so explorer doesn't discard
153 // the folder notification. Since we only know for sure that the git admin
154 // dir is present, we send a notification for that folder.
155 CString admindir = workingPath.GetWinPathString() + _T("\\") + GitAdminDir::GetAdminDirName();
156 if(::PathFileExists(admindir))
157 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, (LPCTSTR)admindir, NULL);
159 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);
160 // Sending an UPDATEDIR notification somehow overwrites/deletes the UPDATEITEM message. And without
161 // that message, the folder overlays in the current view don't get updated without hitting F5.
162 // Drawback is, without UPDATEDIR, the left tree view isn't always updated...
164 SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);
166 else
167 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);
170 _endthread();