/*
* This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
* http://www.gnu.org/licenses/gpl-3.0.html
*
* $Revision$
* $Id$
* $HeadURL$
*/
#include <sdk.h>
#ifndef CB_PRECOMP
#include <wx/app.h> // wxPostEvent
#include <wx/dir.h> // wxDirTraverser
#include <wx/event.h>
#include <wx/filename.h>
#endif
#include "systemheadersthread.h"
#define CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT 0
#if defined(CC_GLOBAL_DEBUG_OUTPUT)
#if CC_GLOBAL_DEBUG_OUTPUT == 1
#undef CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT
#define CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT 1
#elif CC_GLOBAL_DEBUG_OUTPUT == 2
#undef CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT
#define CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT 2
#endif
#endif
#if CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT == 1
#define TRACE(format, args...) \
CCLogger::Get()->DebugLog(F(format, ##args))
#define TRACE2(format, args...)
#elif CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT == 2
#define TRACE(format, args...) \
do \
{ \
if (g_EnableDebugTrace) \
CCLogger::Get()->DebugLog(F(format, ##args)); \
} \
while (false)
#define TRACE2(format, args...) \
CCLogger::Get()->DebugLog(F(format, ##args))
#else
#define TRACE(format, args...)
#define TRACE2(format, args...)
#endif
// sent message when finish traversing all the folders, before the thread dies
long idSystemHeadersThreadFinish = wxNewId();
// Message used logging.
long idSystemHeadersThreadMessage = wxNewId();
// internal class declaration of HeaderDirTraverser (implementation below)
class HeaderDirTraverser : public wxDirTraverser
{
public:
HeaderDirTraverser(wxThread* thread, wxCriticalSection* critSect,
SystemHeadersMap& headersMap, const wxString& searchDir);
~HeaderDirTraverser() override;
/** call back function when we meet a file */
wxDirTraverseResult OnFile(const wxString& filename) override;
/** call back function when we meet a dir */
wxDirTraverseResult OnDir(const wxString& dirname) override;
private:
/** this function will be called every time we meet a file or a dir, and we count the file and
* dir, we temporary leave the critical section to give other thread a change to access the file
* maps. As the traversing may take a long time, in the same time, the UI may need to show the code
* completion prompt for header files, so a critical section is used here.
*/
void AddLock(bool is_file);
wxDirTraverseResult GetStatus(const wxString &path);
private:
/* the thread call Traverse() on this instance*/
wxThread* m_Thread;
/* critical section to protect accessing m_SystemHeadersMap */
wxCriticalSection* m_SystemHeadersThreadCS;
/* dir to files map, for example, you have two dirs c:/a and c:/b
* so the map looks like: (usually the relative file path is stored
* c:/a ---> {c:/a/a1.h, c:/a/a2.h} ---> {a1.h, a2.h}
* c:/b ---> {c:/b/b1.h, c:/b/b2.h} ---> {b1.h, b2.h}
*/
const SystemHeadersMap& m_SystemHeadersMap;
#ifndef __WXMSW__
struct FileID
{
dev_t st_dev;
ino_t st_ino;
bool operator< (const FileID &f) const
{
if (st_dev == f.st_dev)
return st_ino < f.st_ino;
else
return st_dev<f.st_dev;
}
};
/// Set of already visited directories (stored as device id and inode).
std::set<FileID> m_VisitedDirsByID;
#endif // __WXMSW__
/* which top level dir we are traversing header files in this Traverser object, the folder is in absolute
* format, like c:/a
*/
const wxString& m_SearchDir;
/* string set for header files, this is actually a file set reference in the m_SystemHeadersMap
* it store all the header files in relative format, such as: a1.h, a2.h
*/
StringSet& m_Headers;
/** indicates whether the critical section is entered or not, used in AddLock() function*/
bool m_Locked;
/* numbers of dirs in the traversing */
size_t m_Dirs;
/* numbers of files in the traversing */
size_t m_Files;
};
// class SystemHeadersThread
SystemHeadersThread::SystemHeadersThread(wxEvtHandler* parent,
wxCriticalSection* critSect,
SystemHeadersMap& headersMap,
const wxArrayString& incDirs) :
wxThread(wxTHREAD_JOINABLE),
m_Parent(parent),
m_SystemHeadersThreadCS(critSect),
m_SystemHeadersMap(headersMap),
m_IncludeDirs(incDirs)
{
cbAssert(incDirs.size()>0);
Create();
SetPriority(60u);
}
SystemHeadersThread::~SystemHeadersThread()
{
TRACE(_T("SystemHeadersThread: Terminated."));
}
void* SystemHeadersThread::Entry()
{
wxArrayString dirs;
{
wxCriticalSectionLocker locker(*m_SystemHeadersThreadCS);
// check to see m_SystemHeadersMap already contains the element of m_IncludeDirs, if not,
// just add one entry in the map.
for (size_t i=0; i<m_IncludeDirs.GetCount(); ++i)
{
if (m_SystemHeadersMap.find(m_IncludeDirs[i]) == m_SystemHeadersMap.end())
{
dirs.Add(m_IncludeDirs[i]);
m_SystemHeadersMap[m_IncludeDirs[i]] = StringSet();
}
}
}
// collect header files in each dir, this is done by HeaderDirTraverser
for (size_t i=0; i<dirs.GetCount(); ++i)
{
wxStopWatch timer;
timer.Start();
if ( TestDestroy() )
break;
// Detect if this directory is for the file system root and skip it.
// The user probably doesn't want to wait for the whole disk to be scanned!
wxFileName dirName(dirs[i]);
if (dirName.IsAbsolute() && dirName.GetDirCount() == 0)
continue;
// check the dir is ready for traversing
wxDir dir(dirs[i]);
if (!dir.IsOpened())
{
CodeBlocksThreadEvent evt(wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadMessage);
evt.SetClientData(this);
evt.SetString(wxString::Format(_T("SystemHeadersThread: Unable to open: %s"),
dirs[i].wx_str()));
wxPostEvent(m_Parent, evt);
continue;
}
{
CodeBlocksThreadEvent evt(wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadMessage);
evt.SetClientData(this);
evt.SetString(wxString::Format(_T("SystemHeadersThread: Start traversing: %s"),
dirs[i].wx_str()));
wxPostEvent(m_Parent, evt);
}
HeaderDirTraverser traverser(this, m_SystemHeadersThreadCS, m_SystemHeadersMap, dirs[i]);
dir.Traverse(traverser, wxEmptyString, wxDIR_FILES | wxDIR_DIRS);
if ( TestDestroy() )
break;
CodeBlocksThreadEvent evt(wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadMessage);
evt.SetClientData(this);
evt.SetString(wxString::Format("SystemHeadersThread: Traversing %s finished, found %zu headers; time: %.3lf sec",
dirs[i], m_SystemHeadersMap[dirs[i]].size(), timer.Time()*0.001));
wxPostEvent(m_Parent, evt);
}
// send the idSystemHeadersThreadFinish event to notify its parent before the thread dies.
if ( !TestDestroy() )
{
CodeBlocksThreadEvent evt(wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadFinish);
evt.SetClientData(this);
if (!dirs.IsEmpty())
evt.SetString(wxString::Format("SystemHeadersThread: Total number of paths: %zu", dirs.GetCount()));
wxPostEvent(m_Parent, evt);
}
TRACE(_T("SystemHeadersThread: Done."));
return NULL;
}
// class HeaderDirTraverser
HeaderDirTraverser::HeaderDirTraverser(wxThread* thread,
wxCriticalSection* critSect,
SystemHeadersMap& headersMap,
const wxString& searchDir) :
m_Thread(thread),
m_SystemHeadersThreadCS(critSect),
m_SystemHeadersMap(headersMap),
m_SearchDir(searchDir),
m_Headers(headersMap[searchDir]),
m_Locked(false),
m_Dirs(0),
m_Files(0)
{
}
HeaderDirTraverser::~HeaderDirTraverser()
{
if (m_Locked)
m_SystemHeadersThreadCS->Leave();
}
wxDirTraverseResult HeaderDirTraverser::OnFile(const wxString& filename)
{
// HeaderDirTraverser is used in a worker thread, so call TestDestroy() as often as it can to
// quickly terminate the thread
if (m_Thread->TestDestroy())
return wxDIR_STOP;
AddLock(true); // true means we are adding a file
wxFileName fn(filename);
if (!fn.HasExt() || fn.GetExt().GetChar(0) == _T('h'))
{
fn.MakeRelativeTo(m_SearchDir);
wxString header(fn.GetFullPath());
header.Replace(_T("\\"), _T("/"), true); // Unix style
m_Headers.insert(header);
}
return wxDIR_CONTINUE;
}
wxDirTraverseResult HeaderDirTraverser::OnDir(const wxString& dirname)
{
// HeaderDirTraverser is used in a worker thread, so call TestDestroy() as often as it can to
// quickly terminate the thread
if (m_Thread->TestDestroy())
return wxDIR_STOP;
AddLock(false); // false means we are adding a dir
#ifndef __WXMSW__
// Use stat to identify unique files. This is needed to prevent loops caused by sym-linking.
// The st_dev and st_ino are enough to uniquely identify a file on Unix like systems.
// On windows we don't detect loops because they are lest frequent and harder to make, if at all
// possible.
struct stat s;
if (stat(dirname.utf8_str().data(), &s)==0)
{
FileID f;
f.st_dev = s.st_dev;
f.st_ino = s.st_ino;
if (m_VisitedDirsByID.find(f) != m_VisitedDirsByID.end())
return wxDIR_IGNORE;
m_VisitedDirsByID.insert(f);
}
else
return wxDIR_STOP;
#endif // __WXMSW__
wxString path = cbResolveSymLinkedDirPathRecursive(dirname);
if (path.empty())
return wxDIR_IGNORE;
if (path.Last() != wxFILE_SEP_PATH)
path.Append(wxFILE_SEP_PATH);
return GetStatus(path);
}
void HeaderDirTraverser::AddLock(bool is_file)
{
if (is_file)
m_Files++;
else
m_Dirs++;
if ((m_Files+m_Dirs) % 100 == 1)
{
TRACE(wxString::Format("HeaderDirTraverser: %zu directories and %zu files traversed. Unlocking temporarily.", m_Dirs, m_Files));
if (m_Locked)
{
m_SystemHeadersThreadCS->Leave();
m_Locked = false;
}
m_SystemHeadersThreadCS->Enter();
m_Locked = true;
}
}
wxDirTraverseResult HeaderDirTraverser::GetStatus(const wxString &path)
{
if (m_SystemHeadersMap.find(path) != m_SystemHeadersMap.end())
return wxDIR_IGNORE;
return wxDIR_CONTINUE;
}