summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2020-02-18 17:32:11 -0500
committerGitHub <noreply@github.com>2020-02-18 17:32:11 -0500
commite749b2072601830c11495ce210907391dfe7bc6b (patch)
tree45d3082c037c99d60ddbbe5be8ab2bc60e467e23 /src
parent8c2814c9dc0d92e1ab015cde33eee8dcf880e265 (diff)
parenta28bd45c0b4dfbedcb816fedf0783c5287be3b19 (diff)
Merge pull request #1003 from isanae/file-list-improvements
Refresh optimizations
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt31
-rw-r--r--src/datatab.cpp5
-rw-r--r--src/directoryrefresher.cpp466
-rw-r--r--src/directoryrefresher.h124
-rw-r--r--src/envfs.cpp450
-rw-r--r--src/envfs.h193
-rw-r--r--src/filetree.cpp97
-rw-r--r--src/filetree.h4
-rw-r--r--src/filetreeitem.h6
-rw-r--r--src/filetreemodel.cpp12
-rw-r--r--src/filetreemodel.h6
-rw-r--r--src/loglist.h3
-rw-r--r--src/main.cpp3
-rw-r--r--src/mainwindow.cpp77
-rw-r--r--src/mainwindow.h7
-rw-r--r--src/modinfodialog.cpp2
-rw-r--r--src/modinfodialogconflicts.cpp21
-rw-r--r--src/modinfodialogconflicts.h14
-rw-r--r--src/modinfodialogimages.cpp8
-rw-r--r--src/modinfodialogtab.cpp2
-rw-r--r--src/modinfowithconflictinfo.cpp11
-rw-r--r--src/modlist.cpp18
-rw-r--r--src/modlist.h7
-rw-r--r--src/modlistview.cpp2
-rw-r--r--src/organizercore.cpp129
-rw-r--r--src/organizercore.h44
-rw-r--r--src/pch.h4
-rw-r--r--src/pluginlist.cpp21
-rw-r--r--src/pluginlist.h1
-rw-r--r--src/pluginlistview.cpp2
-rw-r--r--src/profile.cpp6
-rw-r--r--src/settings.cpp28
-rw-r--r--src/settings.h11
-rw-r--r--src/shared/directoryentry.cpp1220
-rw-r--r--src/shared/directoryentry.h433
-rw-r--r--src/shared/fileentry.cpp251
-rw-r--r--src/shared/fileentry.h122
-rw-r--r--src/shared/fileregister.cpp184
-rw-r--r--src/shared/fileregister.h53
-rw-r--r--src/shared/fileregisterfwd.h96
-rw-r--r--src/shared/filesorigin.cpp126
-rw-r--r--src/shared/filesorigin.h85
-rw-r--r--src/shared/originconnection.cpp145
-rw-r--r--src/shared/originconnection.h56
-rw-r--r--src/shared/util.cpp27
-rw-r--r--src/shared/util.h22
-rw-r--r--src/stylesheets/Night Eyes.qss10
-rw-r--r--src/stylesheets/Parchment v1.1 by Bob.qss10
-rw-r--r--src/stylesheets/Transparent-Style-101-Green.qss39
-rw-r--r--src/stylesheets/Transparent-Style-BOS.qss13
-rw-r--r--src/stylesheets/Transparent-Style-Skyrim.qss13
-rw-r--r--src/stylesheets/dark.qss8
-rw-r--r--src/stylesheets/dracula.qss8
-rw-r--r--src/stylesheets/skyrim.qss8
-rw-r--r--src/stylesheets/vs15 Dark-Green.qss8
-rw-r--r--src/stylesheets/vs15 Dark-Orange.qss8
-rw-r--r--src/stylesheets/vs15 Dark-Purple.qss8
-rw-r--r--src/stylesheets/vs15 Dark-Red.qss8
-rw-r--r--src/stylesheets/vs15 Dark-Yellow.qss8
-rw-r--r--src/stylesheets/vs15 Dark.qss8
-rw-r--r--src/syncoverwritedialog.cpp7
-rw-r--r--src/syncoverwritedialog.h11
-rw-r--r--src/usvfsconnector.cpp2
-rw-r--r--src/version.rc2
64 files changed, 3300 insertions, 1514 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ad8f74c0..85d8af0f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -132,6 +132,7 @@ SET(organizer_SRCS
filerenamer.cpp
texteditor.cpp
env.cpp
+ envfs.cpp
envmetrics.cpp
envmodule.cpp
envsecurity.cpp
@@ -154,6 +155,10 @@ SET(organizer_SRCS
shared/windows_error.cpp
shared/error_report.cpp
shared/directoryentry.cpp
+ shared/fileentry.cpp
+ shared/filesorigin.cpp
+ shared/fileregister.cpp
+ shared/originconnection.cpp
shared/util.cpp
shared/appconfig.cpp
shared/leaktrace.cpp
@@ -262,6 +267,7 @@ SET(organizer_HDRS
filerenamer.h
texteditor.h
env.h
+ envfs.h
envmetrics.h
envmodule.h
envsecurity.h
@@ -284,6 +290,10 @@ SET(organizer_HDRS
shared/windows_error.h
shared/error_report.h
shared/directoryentry.h
+ shared/fileentry.h
+ shared/filesorigin.h
+ shared/fileregister.h
+ shared/originconnection.h
shared/util.h
shared/appconfig.h
shared/appconfig.inc
@@ -348,8 +358,6 @@ set(browser
set(core
categories
- shared/directoryentry
- directoryrefresher
installationmanager
instancemanager
loadmechanism
@@ -392,6 +400,7 @@ set(downloads
set(env
env
+ envfs
envmetrics
envmodule
envsecurity
@@ -467,6 +476,15 @@ set(profiles
profilesdialog
)
+set(register
+ shared/directoryentry
+ shared/fileentry
+ shared/filesorigin
+ shared/fileregister
+ shared/originconnection
+ directoryrefresher
+)
+
set(settings
settings
settingsutilities
@@ -518,7 +536,7 @@ set(widgets
set(src_filters
application core browser dialogs downloads env executables loot mainwindow
- modinfo modinfo\\dialog modlist plugins previews profiles settings
+ modinfo modinfo\\dialog modlist plugins previews profiles register settings
settingsdialog utilities widgets
)
@@ -534,6 +552,8 @@ endforeach()
file(GLOB_RECURSE rule_files ../vsbuild/CMakeFiles/*.rule)
file(GLOB qm_files ../vsbuild/src/*.qm)
+set(organizer_misc ../modorganizer.natvis)
+
source_group(cmake FILES CMakeLists.txt)
source_group(autogen FILES ${rule_files} ${qm_files})
source_group(resources FILES ${organizer_RCS} ${organizer_QRCS})
@@ -626,7 +646,10 @@ ELSE()
SET(usvfs_name usvfs_x86)
ENDIF()
-ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm})
+ADD_EXECUTABLE(
+ ModOrganizer WIN32
+ ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS}
+ ${organizer_QRCS} ${organizer_translations_qm} ${organizer_misc})
TARGET_LINK_LIBRARIES(ModOrganizer
Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick
diff --git a/src/datatab.cpp b/src/datatab.cpp
index f263c2c6..b6119d52 100644
--- a/src/datatab.cpp
+++ b/src/datatab.cpp
@@ -2,7 +2,6 @@
#include "ui_mainwindow.h"
#include "settings.h"
#include "organizercore.h"
-#include "directoryentry.h"
#include "messagedialog.h"
#include "filetree.h"
#include "filetreemodel.h"
@@ -26,10 +25,10 @@ DataTab::DataTab(
m_firstActivation(true)
{
m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree));
- m_filter.setEdit(mwui->dataTabFilter);
- m_filter.setList(mwui->dataTree);
m_filter.setUseSourceSort(true);
m_filter.setFilterColumn(FileTreeModel::FileName);
+ m_filter.setEdit(mwui->dataTabFilter);
+ m_filter.setList(mwui->dataTree);
if (auto* m=m_filter.proxyModel()) {
m->setDynamicSortFilter(false);
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp
index 87305599..e8c26c90 100644
--- a/src/directoryrefresher.cpp
+++ b/src/directoryrefresher.cpp
@@ -18,40 +18,168 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "directoryrefresher.h"
+#include "shared/fileentry.h"
+#include "shared/filesorigin.h"
+#include "shared/directoryentry.h"
#include "iplugingame.h"
#include "utility.h"
#include "report.h"
#include "modinfo.h"
#include "settings.h"
+#include "envfs.h"
+#include "modinfodialogfwd.h"
+#include "util.h"
+
+#include <gameplugins.h>
#include <QApplication>
#include <QDir>
#include <QString>
#include <QTextCodec>
-#include <gameplugins.h>
+
+#include <fstream>
using namespace MOBase;
using namespace MOShared;
-DirectoryRefresher::DirectoryRefresher()
- : m_DirectoryStructure(nullptr)
+DirectoryStats::DirectoryStats()
{
+ std::memset(this, 0, sizeof(DirectoryStats));
+}
+
+DirectoryStats& DirectoryStats::operator+=(const DirectoryStats& o)
+{
+ dirTimes += o.dirTimes;
+ fileTimes += o.fileTimes;
+ sortTimes += o.sortTimes;
+
+ subdirLookupTimes += o.subdirLookupTimes;
+ addDirectoryTimes += o.addDirectoryTimes;
+
+ filesLookupTimes += o.filesLookupTimes;
+ addFileTimes += o.addFileTimes;
+ addOriginToFileTimes += o.addOriginToFileTimes;
+ addFileToOriginTimes += o.addFileToOriginTimes;
+ addFileToRegisterTimes += o.addFileToRegisterTimes;
+
+ originExists += o.originExists;
+ originCreate += o.originCreate;
+ originsNeededEnabled += o.originsNeededEnabled;
+
+ subdirExists += o.subdirExists;
+ subdirCreate += o.subdirCreate;
+
+ fileExists += o.fileExists;
+ fileCreate += o.fileCreate;
+ filesInsertedInRegister += o.filesInsertedInRegister;
+ filesAssignedInRegister += o.filesAssignedInRegister;
+
+ return *this;
}
-DirectoryRefresher::~DirectoryRefresher()
+std::string DirectoryStats::csvHeader()
{
- delete m_DirectoryStructure;
+ QStringList sl = {
+ "dirTimes",
+ "fileTimes",
+ "sortTimes",
+ "subdirLookupTimes",
+ "addDirectoryTimes",
+ "filesLookupTimes",
+ "addFileTimes",
+ "addOriginToFileTimes",
+ "addFileToOriginTimes",
+ "addFileToRegisterTimes",
+ "originExists",
+ "originCreate",
+ "originsNeededEnabled",
+ "subdirExists",
+ "subdirCreate",
+ "fileExists",
+ "fileCreate",
+ "filesInsertedInRegister",
+ "filesAssignedInRegister"};
+
+ return sl.join(",").toStdString();
}
-DirectoryEntry *DirectoryRefresher::getDirectoryStructure()
+std::string DirectoryStats::toCsv() const
+{
+ QStringList oss;
+
+ auto s = [](auto ns) {
+ return ns.count() / 1000.0 / 1000.0 / 1000.0;
+ };
+
+ oss
+ << QString::number(s(dirTimes))
+ << QString::number(s(fileTimes))
+ << QString::number(s(sortTimes))
+
+ << QString::number(s(subdirLookupTimes))
+ << QString::number(s(addDirectoryTimes))
+
+ << QString::number(s(filesLookupTimes))
+ << QString::number(s(addFileTimes))
+ << QString::number(s(addOriginToFileTimes))
+ << QString::number(s(addFileToOriginTimes))
+ << QString::number(s(addFileToRegisterTimes))
+
+ << QString::number(originExists)
+ << QString::number(originCreate)
+ << QString::number(originsNeededEnabled)
+
+ << QString::number(subdirExists)
+ << QString::number(subdirCreate)
+
+ << QString::number(fileExists)
+ << QString::number(fileCreate)
+ << QString::number(filesInsertedInRegister)
+ << QString::number(filesAssignedInRegister);
+
+ return oss.join(",").toStdString();
+}
+
+void dumpStats(std::vector<DirectoryStats>& stats)
+{
+ static int run = 0;
+ static const std::string file("c:\\tmp\\data.csv");
+
+ if (run == 0) {
+ std::ofstream out(file, std::ios::out|std::ios::trunc);
+ out << fmt::format("what,run,{}", DirectoryStats::csvHeader()) << "\n";
+ }
+
+ std::sort(stats.begin(), stats.end(), [](auto&& a, auto&& b){
+ return (naturalCompare(QString::fromStdString(a.mod), QString::fromStdString(b.mod)) < 0);
+ });
+
+ std::ofstream out(file, std::ios::app);
+
+ DirectoryStats total;
+ for (const auto& s : stats) {
+ out << fmt::format("{},{},{}", s.mod, run, s.toCsv()) << "\n";
+ total += s;
+ }
+
+ out << fmt::format("total,{},{}", run, total.toCsv()) << "\n";
+
+ ++run;
+}
+
+
+DirectoryRefresher::DirectoryRefresher(std::size_t threadCount)
+ : m_threadCount(threadCount), m_lastFileCount(0)
+{
+}
+
+DirectoryEntry *DirectoryRefresher::stealDirectoryStructure()
{
QMutexLocker locker(&m_RefreshLock);
- DirectoryEntry *result = m_DirectoryStructure;
- m_DirectoryStructure = nullptr;
- return result;
+ return m_Root.release();
}
void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods
@@ -82,119 +210,293 @@ void DirectoryRefresher::cleanStructure(DirectoryEntry *structure)
}
}
-void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure, const QString &modName,
- int priority, const QString &directory, const QStringList &archives)
+void DirectoryRefresher::addModBSAToStructure(
+ DirectoryEntry* root, const QString& modName,
+ int priority, const QString& directory, const QStringList& archives)
{
- std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
- IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
+ const IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
GamePlugins *gamePlugins = game->feature<GamePlugins>();
QStringList loadOrder = QStringList();
gamePlugins->getLoadOrder(loadOrder);
- for (const QString &archive : archives) {
- QFileInfo fileInfo(archive);
- if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) {
+ std::vector<std::wstring> lo;
+ for (auto&& s : loadOrder) {
+ lo.push_back(s.toStdWString());
+ }
- int order = -1;
+ std::vector<std::wstring> archivesW;
+ for (auto&& a : archives) {
+ archivesW.push_back(a.toStdWString());
+ }
- for (auto plugin : loadOrder)
- {
- QString name = plugin.left(plugin.size() - 4);
- if (fileInfo.fileName().startsWith(name + " - ", Qt::CaseInsensitive) || fileInfo.fileName().startsWith(name + ".", Qt::CaseInsensitive)) {
- order = loadOrder.indexOf(plugin);
- }
- }
+ std::set<std::wstring> enabledArchives;
+ for (auto&& a : m_EnabledArchives) {
+ enabledArchives.insert(a.toStdWString());
+ }
- try {
- IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
- directoryStructure->addFromBSA(ToWString(modName), directoryW, ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority, order);
- } catch (const std::exception &e) {
- throw MyException(tr("failed to parse bsa %1: %2").arg(archive, e.what()));
+ DirectoryStats dummy;
+
+ root->addFromAllBSAs(
+ modName.toStdWString(),
+ QDir::toNativeSeparators(directory).toStdWString(),
+ priority,
+ archivesW,
+ enabledArchives,
+ lo,
+ dummy);
+}
+
+void DirectoryRefresher::stealModFilesIntoStructure(
+ DirectoryEntry *directoryStructure, const QString &modName,
+ int priority, const QString &directory, const QStringList &stealFiles)
+{
+ std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
+
+ // instead of adding all the files of the target directory, we just change the root of the specified
+ // files to this mod
+ DirectoryStats dummy;
+ FilesOrigin &origin = directoryStructure->createOrigin(
+ ToWString(modName), directoryW, priority, dummy);
+
+ for (const QString &filename : stealFiles) {
+ if (filename.isEmpty()) {
+ log::warn("Trying to find file with no name");
+ continue;
+ }
+ QFileInfo fileInfo(filename);
+ FileEntryPtr file = directoryStructure->findFile(ToWString(fileInfo.fileName()));
+ if (file.get() != nullptr) {
+ if (file->getOrigin() == 0) {
+ // replace data as the origin on this bsa
+ file->removeOrigin(0);
}
+ origin.addFile(file->getIndex());
+ file->addOrigin(origin.getID(), file->getFileTime(), L"", -1);
+ } else {
+ QString warnStr = fileInfo.absolutePath();
+ if (warnStr.isEmpty())
+ warnStr = filename;
+ log::warn("file not found: {}", warnStr);
}
}
}
-void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructure, const QString &modName,
- int priority, const QString &directory, const QStringList &stealFiles)
+void DirectoryRefresher::addModFilesToStructure(
+ DirectoryEntry *directoryStructure, const QString &modName,
+ int priority, const QString &directory, const QStringList &stealFiles)
{
+ TimeThis tt("addModFilesToStructure()");
+
std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
+ DirectoryStats dummy;
if (stealFiles.length() > 0) {
- // instead of adding all the files of the target directory, we just change the root of the specified
- // files to this mod
- FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority);
- for (const QString &filename : stealFiles) {
- if (filename.isEmpty()) {
- log::warn("Trying to find file with no name");
- continue;
- }
- QFileInfo fileInfo(filename);
- FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName()));
- if (file.get() != nullptr) {
- if (file->getOrigin() == 0) {
- // replace data as the origin on this bsa
- file->removeOrigin(0);
- }
- origin.addFile(file->getIndex());
- file->addOrigin(origin.getID(), file->getFileTime(), L"", -1);
- } else {
- QString warnStr = fileInfo.absolutePath();
- if (warnStr.isEmpty())
- warnStr = filename;
- log::warn("file not found: {}", warnStr);
- }
- }
+ stealModFilesIntoStructure(
+ directoryStructure, modName, priority, directory, stealFiles);
} else {
- directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority);
+ directoryStructure->addFromOrigin(
+ ToWString(modName), directoryW, priority, dummy);
}
}
void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure
- , const QString &modName
- , int priority
- , const QString &directory
- , const QStringList &stealFiles
- , const QStringList &archives)
+ , const QString &modName
+ , int priority
+ , const QString &directory
+ , const QStringList &stealFiles
+ , const QStringList &archives)
{
- addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles);
-
+ TimeThis tt("addModToStructure()");
+
+ DirectoryStats dummy;
+
+ if (stealFiles.length() > 0) {
+ stealModFilesIntoStructure(
+ directoryStructure, modName, priority, directory, stealFiles);
+ } else {
+ std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
+ directoryStructure->addFromOrigin(
+ ToWString(modName), directoryW, priority, dummy);
+ }
+
if (Settings::instance().archiveParsing()) {
- addModBSAToStructure(directoryStructure, modName, priority, directory, archives);
+ addModBSAToStructure(
+ directoryStructure, modName, priority, directory, archives);
}
}
-void DirectoryRefresher::refresh()
+
+struct ModThread
{
- QMutexLocker locker(&m_RefreshLock);
+ DirectoryRefreshProgress* progress = nullptr;
+ DirectoryEntry* ds = nullptr;
+ std::wstring modName;
+ std::wstring path;
+ int prio = -1;
+ std::vector<std::wstring> archives;
+ std::set<std::wstring> enabledArchives;
+ DirectoryStats* stats = nullptr;
+ env::DirectoryWalker walker;
- delete m_DirectoryStructure;
+ std::condition_variable cv;
+ std::mutex mutex;
+ bool ready = false;
- m_DirectoryStructure = new DirectoryEntry(L"data", nullptr, 0);
+ void wakeup()
+ {
+ {
+ std::scoped_lock lock(mutex);
+ ready = true;
+ }
+
+ cv.notify_one();
+ }
+
+ void run()
+ {
+ std::unique_lock lock(mutex);
+ cv.wait(lock, [&]{ return ready; });
- IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
+ SetThisThreadName(QString::fromStdWString(modName + L" refresher"));
+ ds->addFromOrigin(walker, modName, path, prio, *stats);
- std::wstring dataDirectory = QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString();
- m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
+ if (Settings::instance().archiveParsing()) {
+ const IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
- std::sort(m_Mods.begin(), m_Mods.end(), [](auto lhs, auto rhs){return lhs.priority < rhs.priority;});
- auto iter = m_Mods.begin();
+ GamePlugins *gamePlugins = game->feature<GamePlugins>();
+ QStringList loadOrder = QStringList();
+ gamePlugins->getLoadOrder(loadOrder);
+
+ std::vector<std::wstring> lo;
+ for (auto&& s : loadOrder) {
+ lo.push_back(s.toStdWString());
+ }
+
+ ds->addFromAllBSAs(
+ modName, path, prio, archives, enabledArchives, lo, *stats);
+ }
- for (int i = 1; iter != m_Mods.end(); ++iter, ++i) {
- try {
- addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives);
- } catch (const std::exception &e) {
- emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what()));
+ if (progress) {
+ progress->addDone();
}
- emit progress((i * 100) / static_cast<int>(m_Mods.size()) + 1);
+
+ SetThisThreadName(QString::fromStdWString(L"idle refresher"));
+ ready = false;
}
+};
+
+env::ThreadPool<ModThread> g_threads;
+
+
+void DirectoryRefresher::updateProgress(const DirectoryRefreshProgress* p)
+{
+ // careful: called from multiple threads
+ emit progress(p);
+}
- m_DirectoryStructure->getFileRegister()->sortOrigins();
+void DirectoryRefresher::addMultipleModsFilesToStructure(
+ MOShared::DirectoryEntry *directoryStructure,
+ const std::vector<EntryInfo>& entries, DirectoryRefreshProgress* progress)
+{
+ std::vector<DirectoryStats> stats(entries.size());
- emit progress(100);
+ if (progress) {
+ progress->start(entries.size());
+ }
+
+ log::debug("refresher: using {} threads", m_threadCount);
+ g_threads.setMax(m_threadCount);
+
+ for (std::size_t i=0; i<entries.size(); ++i) {
+ const auto& e = entries[i];
+ const int prio = static_cast<int>(i + 1);
+
+ if constexpr (DirectoryStats::EnableInstrumentation) {
+ stats[i].mod = entries[i].modName.toStdString();
+ }
+
+ try
+ {
+ if (e.stealFiles.length() > 0) {
+ stealModFilesIntoStructure(
+ directoryStructure, e.modName, prio, e.absolutePath, e.stealFiles);
+
+ if (progress) {
+ progress->addDone();
+ }
+ } else {
+ auto& mt = g_threads.request();
+
+ mt.progress = progress;
+ mt.ds = directoryStructure;
+ mt.modName = e.modName.toStdWString();
+ mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
+ mt.prio = prio;
+
+ mt.archives.clear();
+ for (auto&& a : e.archives) {
+ mt.archives.push_back(a.toStdWString());
+ }
+
+ mt.enabledArchives.clear();
+ for (auto&& a : m_EnabledArchives) {
+ mt.enabledArchives.insert(a.toStdWString());
+ }
+
+ mt.stats = &stats[i];
+
+ mt.wakeup();
+ }
+ } catch (const std::exception& ex) {
+ emit error(tr("failed to read mod (%1): %2").arg(e.modName, ex.what()));
+ }
+ }
+
+ g_threads.waitForAll();
+
+ if constexpr (DirectoryStats::EnableInstrumentation) {
+ dumpStats(stats);
+ }
+}
+
+void DirectoryRefresher::refresh()
+{
+ SetThisThreadName("DirectoryRefresher");
+ TimeThis tt("refresh");
+ auto* p = new DirectoryRefreshProgress(this);
+
+ {
+ QMutexLocker locker(&m_RefreshLock);
+
+ m_Root.reset(new DirectoryEntry(L"data", nullptr, 0));
+
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
+
+ std::wstring dataDirectory =
+ QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString();
+
+ {
+ DirectoryStats dummy;
+ m_Root->addFromOrigin(L"data", dataDirectory, 0, dummy);
+ }
+
+ std::sort(m_Mods.begin(), m_Mods.end(), [](auto lhs, auto rhs) {
+ return lhs.priority < rhs.priority;
+ });
+
+ addMultipleModsFilesToStructure(m_Root.get(), m_Mods, p);
+
+ m_Root->getFileRegister()->sortOrigins();
+
+ cleanStructure(m_Root.get());
+
+ m_lastFileCount = m_Root->getFileRegister()->highestCount();
+ log::debug("refresher saw {} files", m_lastFileCount);
+ }
- cleanStructure(m_DirectoryStructure);
+ p->finish();
+ emit progress(p);
emit refreshed();
}
diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h
index a4fc5dbc..bd08dad6 100644
--- a/src/directoryrefresher.h
+++ b/src/directoryrefresher.h
@@ -20,15 +20,14 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef DIRECTORYREFRESHER_H
#define DIRECTORYREFRESHER_H
-#include <directoryentry.h>
+#include "fileregisterfwd.h"
+#include "profile.h"
#include <QObject>
#include <QMutex>
#include <QStringList>
#include <vector>
#include <set>
#include <tuple>
-#include "profile.h"
-
/**
* @brief used to asynchronously generate the virtual view of the combined data directory
@@ -39,24 +38,37 @@ class DirectoryRefresher : public QObject
Q_OBJECT
public:
+ struct EntryInfo
+ {
+ EntryInfo(const QString &modName, const QString &absolutePath,
+ const QStringList &stealFiles, const QStringList &archives, int priority)
+ : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles)
+ , archives(archives), priority(priority)
+ {
+ }
- /**
- * @brief constructor
- *
- **/
- DirectoryRefresher();
+ QString modName;
+ QString absolutePath;
+ QStringList stealFiles;
+ QStringList archives;
+ int priority;
+ };
+
+ DirectoryRefresher(std::size_t threadCount);
- ~DirectoryRefresher();
+ // noncopyable
+ DirectoryRefresher(const DirectoryRefresher&) = delete;
+ DirectoryRefresher& operator=(const DirectoryRefresher&) = delete;
/**
* @brief retrieve the updated directory structure
*
* returns a pointer to the updated directory structure. DirectoryRefresher
* deletes its own pointer and the caller takes custody of the pointer
- *
+ *
* @return updated directory structure
**/
- MOShared::DirectoryEntry *getDirectoryStructure();
+ MOShared::DirectoryEntry* stealDirectoryStructure();
/**
* @brief sets up the mods to be included in the directory structure
@@ -107,7 +119,16 @@ public:
* @param directory
* @param stealFiles
*/
- void addModFilesToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles);
+ void addModFilesToStructure(
+ MOShared::DirectoryEntry *directoryStructure, const QString &modName,
+ int priority, const QString &directory, const QStringList &stealFiles);
+
+ void addMultipleModsFilesToStructure(
+ MOShared::DirectoryEntry *directoryStructure,
+ const std::vector<EntryInfo>& entries,
+ DirectoryRefreshProgress* progress=nullptr);
+
+ void updateProgress(const DirectoryRefreshProgress* p);
public slots:
@@ -118,31 +139,76 @@ public slots:
signals:
- void progress(int progress);
+ void progress(const DirectoryRefreshProgress* p);
void error(const QString &error);
void refreshed();
private:
-
- struct EntryInfo {
- EntryInfo(const QString &modName, const QString &absolutePath,
- const QStringList &stealFiles, const QStringList &archives, int priority)
- : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles)
- , archives(archives), priority(priority) {}
- QString modName;
- QString absolutePath;
- QStringList stealFiles;
- QStringList archives;
- int priority;
- };
-
-private:
-
std::vector<EntryInfo> m_Mods;
std::set<QString> m_EnabledArchives;
- MOShared::DirectoryEntry *m_DirectoryStructure;
+ std::unique_ptr<MOShared::DirectoryEntry> m_Root;
QMutex m_RefreshLock;
+ std::size_t m_threadCount;
+ std::size_t m_lastFileCount;
+ void stealModFilesIntoStructure(
+ MOShared::DirectoryEntry *directoryStructure, const QString &modName,
+ int priority, const QString &directory, const QStringList &stealFiles);
+};
+
+
+class DirectoryRefreshProgress : QObject
+{
+ Q_OBJECT;
+
+public:
+ DirectoryRefreshProgress(DirectoryRefresher* r) :
+ QObject(r), m_refresher(r), m_modCount(0), m_modDone(0), m_finished(false)
+ {
+ }
+
+ void start(std::size_t modCount)
+ {
+ m_modCount = modCount;
+ m_modDone = 0;
+ m_finished = false;
+ }
+
+
+ bool finished() const
+ {
+ return m_finished;
+ }
+
+ int percentDone() const
+ {
+ int percent = 100;
+
+ if (m_modCount > 0) {
+ const double d = static_cast<double>(m_modDone) / m_modCount;
+ percent = static_cast<int>(d * 100);
+ }
+
+ return percent;
+ }
+
+
+ void finish()
+ {
+ m_finished = true;
+ }
+
+ void addDone()
+ {
+ ++m_modDone;
+ m_refresher->updateProgress(this);
+ }
+
+private:
+ DirectoryRefresher* m_refresher;
+ std::size_t m_modCount;
+ std::atomic<std::size_t> m_modDone;
+ bool m_finished;
};
#endif // DIRECTORYREFRESHER_H
diff --git a/src/envfs.cpp b/src/envfs.cpp
new file mode 100644
index 00000000..a67faf84
--- /dev/null
+++ b/src/envfs.cpp
@@ -0,0 +1,450 @@
+#include "envfs.h"
+#include "env.h"
+#include "util.h"
+#include <utility.h>
+#include <log.h>
+
+using namespace MOBase;
+
+typedef struct _UNICODE_STRING {
+ USHORT Length;
+ USHORT MaximumLength;
+ PWSTR Buffer;
+} UNICODE_STRING, *PUNICODE_STRING;
+typedef const UNICODE_STRING *PCUNICODE_STRING;
+
+typedef struct _OBJECT_ATTRIBUTES {
+ ULONG Length;
+ HANDLE RootDirectory;
+ PUNICODE_STRING ObjectName;
+ ULONG Attributes;
+ PVOID SecurityDescriptor;
+ PVOID SecurityQualityOfService;
+} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+
+
+typedef struct _FILE_DIRECTORY_INFORMATION {
+ ULONG NextEntryOffset;
+ ULONG FileIndex;
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER LastAccessTime;
+ LARGE_INTEGER LastWriteTime;
+ LARGE_INTEGER ChangeTime;
+ LARGE_INTEGER EndOfFile;
+ LARGE_INTEGER AllocationSize;
+ ULONG FileAttributes;
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
+
+
+#define FILE_SHARE_VALID_FLAGS 0x00000007
+
+// copied from ntstatus.h
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
+#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
+#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000FL)
+
+typedef struct _IO_STATUS_BLOCK IO_STATUS_BLOCK;
+
+typedef struct _IO_STATUS_BLOCK *PIO_STATUS_BLOCK;
+// typedef VOID (NTAPI *PIO_APC_ROUTINE )(__in PVOID ApcContext, __in
+// PIO_STATUS_BLOCK IoStatusBlock, __in ULONG Reserved);
+typedef VOID(NTAPI *PIO_APC_ROUTINE)(PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ ULONG Reserved);
+
+
+typedef enum _FILE_INFORMATION_CLASS {
+ FileDirectoryInformation = 1
+} FILE_INFORMATION_CLASS;
+
+typedef NTSTATUS(WINAPI *NtQueryDirectoryFile_type)(
+ HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG,
+ FILE_INFORMATION_CLASS, BOOLEAN, PUNICODE_STRING, BOOLEAN);
+
+typedef NTSTATUS(WINAPI *NtOpenFile_type)(PHANDLE, ACCESS_MASK,
+ POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK,
+ ULONG, ULONG);
+
+typedef NTSTATUS(WINAPI *NtClose_type)(HANDLE);
+
+
+NtOpenFile_type NtOpenFile = nullptr;
+NtQueryDirectoryFile_type NtQueryDirectoryFile = nullptr;
+extern NtClose_type NtClose = nullptr;
+
+
+#define FILE_DIRECTORY_FILE 0x00000001
+#define FILE_WRITE_THROUGH 0x00000002
+#define FILE_SEQUENTIAL_ONLY 0x00000004
+#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008
+
+#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010
+#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
+#define FILE_NON_DIRECTORY_FILE 0x00000040
+#define FILE_CREATE_TREE_CONNECTION 0x00000080
+
+#define FILE_COMPLETE_IF_OPLOCKED 0x00000100
+#define FILE_NO_EA_KNOWLEDGE 0x00000200
+#define FILE_OPEN_REMOTE_INSTANCE 0x00000400
+#define FILE_RANDOM_ACCESS 0x00000800
+
+#define FILE_DELETE_ON_CLOSE 0x00001000
+#define FILE_OPEN_BY_FILE_ID 0x00002000
+#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000
+#define FILE_NO_COMPRESSION 0x00008000
+
+#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
+#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000
+#endif
+
+#define FILE_RESERVE_OPFILTER 0x00100000
+#define FILE_OPEN_REPARSE_POINT 0x00200000
+#define FILE_OPEN_NO_RECALL 0x00400000
+#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000
+
+#define FILE_VALID_OPTION_FLAGS 0x00ffffff
+#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032
+#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032
+#define FILE_VALID_SET_FLAGS 0x00000036
+
+typedef struct _IO_STATUS_BLOCK {
+#pragma warning(push)
+#pragma warning(disable: 4201) // we'll always use the Microsoft compiler
+ union {
+ NTSTATUS Status;
+ PVOID Pointer;
+ } DUMMYUNIONNAME;
+#pragma warning(pop)
+
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+
+
+namespace env
+{
+
+std::wstring_view toStringView(const UNICODE_STRING* s)
+{
+ if (s && s->Buffer) {
+ return {s->Buffer, (s->Length / sizeof(wchar_t))};
+ } else {
+ return {};
+ }
+}
+
+std::wstring_view toStringView(POBJECT_ATTRIBUTES poa)
+{
+ if (poa->ObjectName) {
+ return toStringView(poa->ObjectName);
+ }
+
+ return {};
+}
+
+QString toString(POBJECT_ATTRIBUTES poa)
+{
+ const auto sv = toStringView(poa);
+ return QString::fromWCharArray(sv.data(), static_cast<int>(sv.size()));
+}
+
+
+class HandleCloserThread
+{
+public:
+ HandleCloserThread()
+ : m_ready(false)
+ {
+ m_handles.reserve(50'000);
+ }
+
+ void shrink()
+ {
+ m_handles.shrink_to_fit();
+ }
+
+ void add(HANDLE h)
+ {
+ m_handles.push_back(h);
+ }
+
+ void wakeup()
+ {
+ m_ready = true;
+ m_cv.notify_one();
+ }
+
+ void run()
+ {
+ MOShared::SetThisThreadName("HandleCloserThread");
+
+ std::unique_lock lock(m_mutex);
+ m_cv.wait(lock, [&]{ return m_ready; });
+
+ closeHandles();
+ }
+
+private:
+ std::vector<HANDLE> m_handles;
+ std::condition_variable m_cv;
+ std::mutex m_mutex;
+ bool m_ready;
+
+ void closeHandles()
+ {
+ for (auto& h : m_handles) {
+ NtClose(h);
+ }
+
+ m_handles.clear();
+ m_ready = false;
+ }
+};
+
+constexpr std::size_t AllocSize = 1024 * 1024;
+static ThreadPool<HandleCloserThread> g_handleClosers;
+
+void setHandleCloserThreadCount(std::size_t n)
+{
+ g_handleClosers.setMax(n);
+}
+
+void forEachEntryImpl(
+ void* cx, HandleCloserThread& hc, std::vector<std::unique_ptr<unsigned char[]>>& buffers,
+ POBJECT_ATTRIBUTES poa, std::size_t depth,
+ DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF)
+{
+ IO_STATUS_BLOCK iosb;
+ UNICODE_STRING ObjectName;
+ OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };
+ NTSTATUS status;
+
+ status = NtOpenFile(
+ &oa.RootDirectory, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT);
+
+ if (status < 0) {
+ log::error(
+ "NtOpenFile() failed for '{}', {}",
+ toString(poa), formatSystemMessage(status));
+
+ return;
+ }
+
+ hc.add(oa.RootDirectory);
+ unsigned char* buffer;
+
+ if (depth >= buffers.size()) {
+ buffers.emplace_back(std::make_unique<unsigned char[]>(AllocSize));
+ buffer = buffers.back().get();
+ } else {
+ buffer = buffers[depth].get();
+ }
+
+ union
+ {
+ PVOID pv;
+ PBYTE pb;
+ PFILE_DIRECTORY_INFORMATION DirInfo;
+ };
+
+ for (;;) {
+ status = NtQueryDirectoryFile(
+ oa.RootDirectory, NULL, NULL, NULL, &iosb,
+ buffer, AllocSize, FileDirectoryInformation, FALSE, NULL, FALSE);
+
+ if (status == STATUS_NO_MORE_FILES) {
+ break;
+ } else if (status < 0) {
+ log::error(
+ "NtQueryDirectoryFile() failed for '{}', {}",
+ toString(poa), formatSystemMessage(status));
+ break;
+ }
+
+ ULONG NextEntryOffset = 0;
+
+ pv = buffer;
+
+ auto isDotDir = [](auto* o) {
+ if (o->Length == 2 && o->Buffer[0] == '.') {
+ return true;
+ }
+
+ if (o->Length == 4 && o->Buffer[0] == '.' && o->Buffer[1] == '.') {
+ return true;
+ }
+
+ return false;
+ };
+
+ std::size_t count = 0;
+
+ for (;;) {
+ ++count;
+ pb += NextEntryOffset;
+
+ ObjectName.Buffer = DirInfo->FileName;
+ ObjectName.Length = (USHORT)DirInfo->FileNameLength;
+
+ if (!isDotDir(&ObjectName)) {
+ ObjectName.MaximumLength = ObjectName.Length;
+
+ if (DirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ dirStartF(cx, toStringView(&oa));
+ forEachEntryImpl(cx, hc, buffers, &oa, depth+1, dirStartF, dirEndF, fileF);
+ dirEndF(cx, toStringView(&oa));
+ } else {
+ FILETIME ft;
+ ft.dwLowDateTime = DirInfo->LastWriteTime.LowPart;
+ ft.dwHighDateTime = DirInfo->LastWriteTime.HighPart;
+ fileF(cx, toStringView(&oa), ft);
+ }
+ }
+
+ NextEntryOffset = DirInfo->NextEntryOffset;
+
+ if (NextEntryOffset == 0) {
+ break;
+ }
+ }
+
+ if (AllocSize - iosb.Information > (ULONG)FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName[256])) {
+ // NO_MORE_FILES
+ break;
+ }
+ }
+}
+
+
+void DirectoryWalker::forEachEntry(
+ const std::wstring& path, void* cx,
+ DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF)
+{
+ auto& hc = g_handleClosers.request();
+
+ if (!NtOpenFile) {
+ LibraryPtr m(::LoadLibraryW(L"ntdll.dll"));
+ NtOpenFile = (NtOpenFile_type)::GetProcAddress(m.get(), "NtOpenFile");
+ NtQueryDirectoryFile = (NtQueryDirectoryFile_type)::GetProcAddress(m.get(), "NtQueryDirectoryFile");
+ NtClose = (NtClose_type)::GetProcAddress(m.get(), "NtClose");
+ }
+
+ const std::wstring ntpath = std::wstring(L"\\??\\") + path;
+
+ UNICODE_STRING ObjectName = {};
+ ObjectName.Buffer = const_cast<wchar_t*>(ntpath.c_str());
+ ObjectName.Length = (USHORT)ntpath.size() * sizeof(wchar_t);
+ ObjectName.MaximumLength = ObjectName.Length;
+
+ OBJECT_ATTRIBUTES oa = {};
+ oa.Length = sizeof(oa);
+ oa.ObjectName = &ObjectName;
+
+ forEachEntryImpl(cx, hc, m_buffers, &oa, 0, dirStartF, dirEndF, fileF);
+ hc.wakeup();
+}
+
+
+void forEachEntry(
+ const std::wstring& path, void* cx,
+ DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF)
+{
+ DirectoryWalker().forEachEntry(path, cx, dirStartF, dirEndF, fileF);
+}
+
+Directory getFilesAndDirs(const std::wstring& path)
+{
+ struct Context
+ {
+ std::stack<Directory*> current;
+ };
+
+ Directory root;
+
+ Context cx;
+ cx.current.push(&root);
+
+ env::forEachEntry(path, &cx,
+ [](void* pcx, std::wstring_view path) {
+ Context* cx = (Context*)pcx;
+
+ cx->current.top()->dirs.push_back(Directory(path));
+ cx->current.push(&cx->current.top()->dirs.back());
+ },
+
+ [](void* pcx, std::wstring_view path) {
+ Context* cx = (Context*)pcx;
+ cx->current.pop();
+ },
+
+ [](void* pcx, std::wstring_view path, FILETIME ft) {
+ Context* cx = (Context*)pcx;
+
+ cx->current.top()->files.push_back(File(path, ft));
+ }
+ );
+
+ return root;
+}
+
+File::File(std::wstring_view n, FILETIME ft) :
+ name(n.begin(), n.end()),
+ lcname(MOShared::ToLowerCopy(name)),
+ lastModified(ft)
+{
+}
+
+Directory::Directory()
+{
+}
+
+Directory::Directory(std::wstring_view n)
+ : name(n.begin(), n.end()), lcname(MOShared::ToLowerCopy(name))
+{
+}
+
+
+void getFilesAndDirsWithFindImpl(const std::wstring& path, Directory& d)
+{
+ const std::wstring searchString = path + L"\\*";
+
+ WIN32_FIND_DATAW findData;
+
+ HANDLE searchHandle = ::FindFirstFileExW(
+ searchString.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch,
+ nullptr, FIND_FIRST_EX_LARGE_FETCH);
+
+ if (searchHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = true;
+
+ while (result) {
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if ((wcscmp(findData.cFileName, L".") != 0) &&
+ (wcscmp(findData.cFileName, L"..") != 0)) {
+ const std::wstring newPath = path + L"\\" + findData.cFileName;
+ d.dirs.push_back(Directory(findData.cFileName));
+ getFilesAndDirsWithFindImpl(newPath, d.dirs.back());
+ }
+ } else {
+ d.files.push_back(File(findData.cFileName, findData.ftLastWriteTime));
+ }
+
+ result = ::FindNextFileW(searchHandle, &findData);
+ }
+ }
+
+ ::FindClose(searchHandle);
+}
+
+Directory getFilesAndDirsWithFind(const std::wstring& path)
+{
+ Directory d;
+ getFilesAndDirsWithFindImpl(path, d);
+ return d;
+}
+
+} // namespace
diff --git a/src/envfs.h b/src/envfs.h
new file mode 100644
index 00000000..8790b071
--- /dev/null
+++ b/src/envfs.h
@@ -0,0 +1,193 @@
+#ifndef ENV_ENVFS_H
+#define ENV_ENVFS_H
+
+#include <thread>
+
+namespace env
+{
+
+struct File
+{
+ std::wstring name;
+ std::wstring lcname;
+ FILETIME lastModified;
+
+ File(std::wstring_view name, FILETIME ft);
+};
+
+struct Directory
+{
+ std::wstring name;
+ std::wstring lcname;
+
+ std::vector<Directory> dirs;
+ std::vector<File> files;
+
+ Directory();
+ Directory(std::wstring_view name);
+};
+
+
+template <class T>
+class ThreadPool
+{
+public:
+ ThreadPool(std::size_t max=1)
+ {
+ setMax(max);
+ }
+
+ ~ThreadPool()
+ {
+ stopAndJoin();
+ }
+
+ void setMax(std::size_t n)
+ {
+ m_threads.resize(n);
+ }
+
+ void stopAndJoin()
+ {
+ for (auto& ti : m_threads) {
+ ti.stop = true;
+ ti.wakeup();
+ }
+
+ for (auto& ti : m_threads) {
+ if (ti.thread.joinable()) {
+ ti.thread.join();
+ }
+ }
+ }
+
+ void waitForAll()
+ {
+ for (;;) {
+ bool done = true;
+
+ for (auto& ti : m_threads) {
+ if (ti.busy) {
+ done = false;
+ break;
+ }
+ }
+
+ if (done) {
+ break;
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ }
+
+ T& request()
+ {
+ if (m_threads.empty()) {
+ std::terminate();
+ }
+
+ for (;;) {
+ for (auto& ti : m_threads) {
+ bool expected = false;
+
+ if (ti.busy.compare_exchange_strong(expected, true)) {
+ ti.wakeup();
+ return ti.o;
+ }
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ }
+
+ template <class F>
+ void forEach(F&& f)
+ {
+ for (auto& ti : m_threads) {
+ f(ti.o);
+ }
+ }
+
+private:
+ struct ThreadInfo
+ {
+ std::thread thread;
+ std::atomic<bool> busy;
+ T o;
+
+ std::condition_variable cv;
+ std::mutex mutex;
+ bool ready;
+
+ std::atomic<bool> stop;
+
+ ThreadInfo()
+ : busy(true), ready(false), stop(false)
+ {
+ thread = std::thread([&]{ run(); });
+ }
+
+ void wakeup()
+ {
+ {
+ std::scoped_lock lock(mutex);
+ ready = true;
+ }
+
+ cv.notify_one();
+ }
+
+ void run()
+ {
+ busy = false;
+
+ while (!stop) {
+ std::unique_lock lock(mutex);
+ cv.wait(lock, [&]{ return ready; });
+
+ if (stop) {
+ break;
+ }
+
+ o.run();
+
+ ready = false;
+ busy = false;
+ }
+ }
+ };
+
+ std::list<ThreadInfo> m_threads;
+};
+
+
+using DirStartF = void (void*, std::wstring_view);
+using DirEndF = void (void*, std::wstring_view);
+using FileF = void (void*, std::wstring_view, FILETIME);
+
+void setHandleCloserThreadCount(std::size_t n);
+
+
+class DirectoryWalker
+{
+public:
+ void forEachEntry(
+ const std::wstring& path, void* cx,
+ DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF);
+
+private:
+ std::vector<std::unique_ptr<unsigned char[]>> m_buffers;
+};
+
+
+void forEachEntry(
+ const std::wstring& path, void* cx,
+ DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF);
+
+Directory getFilesAndDirs(const std::wstring& path);
+Directory getFilesAndDirsWithFind(const std::wstring& path);
+
+} // namespace
+
+#endif // ENV_ENVFS_H
diff --git a/src/filetree.cpp b/src/filetree.cpp
index 19f2555a..cdcf2feb 100644
--- a/src/filetree.cpp
+++ b/src/filetree.cpp
@@ -3,7 +3,11 @@
#include "filetreeitem.h"
#include "organizercore.h"
#include "envshell.h"
+#include "shared/fileentry.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
#include <log.h>
+#include <widgetutility.h>
using namespace MOShared;
using namespace MOBase;
@@ -124,6 +128,8 @@ FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree)
m_tree->setModel(m_model);
m_tree->header()->resizeSection(0, 200);
+ MOBase::setCustomizableColumns(m_tree);
+
connect(
m_tree, &QTreeView::customContextMenuRequested,
[&](auto pos){ onContextMenu(pos); });
@@ -435,69 +441,7 @@ void FileTree::dumpToFile() const
return;
}
- QFile out(file);
-
- if (!out.open(QIODevice::WriteOnly)) {
- QMessageBox::critical(
- m_tree->window(), tr("Error"), tr("Failed to open file '%1': %2")
- .arg(file)
- .arg(out.errorString()));
-
- return;
- }
-
- try
- {
- dumpToFile(out, "Data", *m_core.directoryStructure());
- }
- catch(DumpFailed&)
- {
- // try to remove it silently
- if (out.exists()) {
- if (!out.remove()) {
- log::error("failed to remove '{}', ignoring", file);
- }
- }
- }
-}
-
-void FileTree::dumpToFile(
- QFile& out, const QString& parentPath, const DirectoryEntry& entry) const
-{
- entry.forEachFile([&](auto&& file) {
- bool isArchive = false;
- const int originID = file.getOrigin(isArchive);
-
- if (isArchive) {
- // TODO: don't list files from archives. maybe make this an option?
- return true;
- }
-
- const auto& origin = m_core.directoryStructure()->getOriginByID(originID);
- const auto originName = QString::fromStdWString(origin.getName());
-
- const QString path =
- parentPath + "\\" + QString::fromStdWString(file.getName());
-
- if (out.write(path.toUtf8() + "\t(" + originName.toUtf8() + ")\r\n") == -1) {
- QMessageBox::critical(
- m_tree->window(), tr("Error"), tr("Failed to write to file %1: %2")
- .arg(out.fileName())
- .arg(out.errorString()));
-
- throw DumpFailed();
- }
-
- return true;
- });
-
- entry.forEachDirectory([&](auto&& dir) {
- const auto newParentPath =
- parentPath + "\\" + QString::fromStdWString(dir.getName());
-
- dumpToFile(out, newParentPath, dir);
- return true;
- });
+ m_core.directoryStructure()->dump(file.toStdWString());
}
void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded)
@@ -522,6 +466,21 @@ void FileTree::onContextMenu(const QPoint &pos)
const auto m = QApplication::keyboardModifiers();
if (m & Qt::ShiftModifier) {
+ // shift+right-click won't select individual items because it interferes
+ // with regular shift selection; this makes it behave like explorer:
+ // - if the right-clicked item is currently part of the selection, show
+ // the menu for the selection
+ // - if not, select this item only and show the menu for it
+ const auto index = m_tree->indexAt(pos);
+
+ if (!m_tree->selectionModel()->isSelected(index)) {
+ m_tree->selectionModel()->select(
+ index,
+ QItemSelectionModel::ClearAndSelect |
+ QItemSelectionModel::Rows |
+ QItemSelectionModel::Current);
+ }
+
// if no shell menu was available, continue on and show the regular
// context menu
if (showShellMenu(pos)) {
@@ -604,6 +563,12 @@ bool FileTree::showShellMenu(QPoint pos)
itor = menus.emplace(item->originID(), mw).first;
}
+ if (!QFile::exists(item->realPath())) {
+ log::error("{}",
+ tr("File '%1' does not exist, you may need to refresh.")
+ .arg(item->realPath()));
+ }
+
itor->second.addFile(item->realPath());
++totalFiles;
@@ -641,6 +606,12 @@ bool FileTree::showShellMenu(QPoint pos)
continue;
}
+ if (!QFile::exists(QString::fromStdWString(fullPath))) {
+ log::error("{}",
+ tr("File '%1' does not exist, you may need to refresh.")
+ .arg(QString::fromStdWString(fullPath)));
+ }
+
itor->second.addFile(QString::fromStdWString(fullPath));
}
}
diff --git a/src/filetree.h b/src/filetree.h
index c1458179..2669e53b 100644
--- a/src/filetree.h
+++ b/src/filetree.h
@@ -65,10 +65,6 @@ private:
void toggleVisibility(bool b, FileTreeItem* item=nullptr);
QModelIndex proxiedIndex(const QModelIndex& index);
-
- void dumpToFile(
- QFile& out, const QString& parentPath,
- const MOShared::DirectoryEntry& entry) const;
};
#endif // MODORGANIZER_FILETREE_INCLUDED
diff --git a/src/filetreeitem.h b/src/filetreeitem.h
index 390d5499..2092782e 100644
--- a/src/filetreeitem.h
+++ b/src/filetreeitem.h
@@ -1,7 +1,7 @@
#ifndef MODORGANIZER_FILETREEITEM_INCLUDED
#define MODORGANIZER_FILETREEITEM_INCLUDED
-#include "directoryentry.h"
+#include "shared/fileregisterfwd.h"
#include <QFileIconProvider>
class FileTreeModel;
@@ -126,7 +126,7 @@ public:
return m_wsLcFile;
}
- const MOShared::DirectoryEntry::FileKey& key() const
+ const MOShared::DirectoryEntryFileKey& key() const
{
return m_key;
}
@@ -288,7 +288,7 @@ private:
const QString m_virtualParentPath;
const std::wstring m_wsFile, m_wsLcFile;
- const MOShared::DirectoryEntry::FileKey m_key;
+ const MOShared::DirectoryEntryFileKey m_key;
const QString m_file;
const bool m_isDirectory;
diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp
index 2130bf89..1a5433c5 100644
--- a/src/filetreemodel.cpp
+++ b/src/filetreemodel.cpp
@@ -1,5 +1,9 @@
#include "filetreemodel.h"
#include "organizercore.h"
+#include "filesorigin.h"
+#include "util.h"
+#include "shared/directoryentry.h"
+#include "shared/fileentry.h"
#include <log.h>
using namespace MOBase;
@@ -637,7 +641,7 @@ bool FileTreeModel::updateFiles(
// removeDisappearingFiles() will add files that are in the tree and still on
// the filesystem to this set; addNewFiless() will use this to figure out if
// a file is new or not
- std::unordered_set<FileEntry::Index> seen;
+ std::unordered_set<FileIndex> seen;
int firstFileRow = 0;
@@ -647,7 +651,7 @@ bool FileTreeModel::updateFiles(
void FileTreeModel::removeDisappearingFiles(
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
- int& firstFileRow, std::unordered_set<FileEntry::Index>& seen)
+ int& firstFileRow, std::unordered_set<FileIndex>& seen)
{
auto& children = parentItem.children();
auto itor = children.begin();
@@ -708,7 +712,7 @@ void FileTreeModel::removeDisappearingFiles(
bool FileTreeModel::addNewFiles(
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
const std::wstring& parentPath, const int firstFileRow,
- const std::unordered_set<FileEntry::Index>& seen)
+ const std::unordered_set<FileIndex>& seen)
{
// keeps track of the contiguous files that need to be added to
// avoid calling beginAddRows(), etc. for each item
@@ -958,7 +962,7 @@ std::wstring FileTreeModel::makeModName(
{
static const std::wstring Unmanaged = UnmanagedModName().toStdWString();
- const auto origin = m_core.directoryStructure()->getOriginByID(originID);
+ const auto& origin = m_core.directoryStructure()->getOriginByID(originID);
if (origin.getID() == 0) {
return Unmanaged;
diff --git a/src/filetreemodel.h b/src/filetreemodel.h
index 093407b0..5bfb75aa 100644
--- a/src/filetreemodel.h
+++ b/src/filetreemodel.h
@@ -3,7 +3,7 @@
#include "filetreeitem.h"
#include "iconfetcher.h"
-#include "directoryentry.h"
+#include "shared/fileregisterfwd.h"
#include <unordered_set>
class OrganizerCore;
@@ -126,12 +126,12 @@ private:
void removeDisappearingFiles(
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
- int& firstFileRow, std::unordered_set<MOShared::FileEntry::Index>& seen);
+ int& firstFileRow, std::unordered_set<MOShared::FileIndex>& seen);
bool addNewFiles(
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
const std::wstring& parentPath, int firstFileRow,
- const std::unordered_set<MOShared::FileEntry::Index>& seen);
+ const std::unordered_set<MOShared::FileIndex>& seen);
FileTreeItem::Ptr createDirectoryItem(
diff --git a/src/loglist.h b/src/loglist.h
index 56b95d65..b26f1561 100644
--- a/src/loglist.h
+++ b/src/loglist.h
@@ -20,8 +20,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef LOGBUFFER_H
#define LOGBUFFER_H
-#include <QTreeView>
#include <log.h>
+#include <QTreeView>
+#include <deque>
class OrganizerCore;
diff --git a/src/main.cpp b/src/main.cpp
index 4b291e75..105299a8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -48,6 +48,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "organizercore.h"
#include "env.h"
#include "envmodule.h"
+#include "util.h"
#include <eh.h>
#include <windows_error.h>
@@ -885,6 +886,8 @@ int main(int argc, char *argv[])
MOApplication application(argc, argv);
QStringList arguments = application.arguments();
+ SetThisThreadName("main");
+
setupPath();
bool forcePrimary = false;
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 8f2dd250..21a6fa41 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -20,8 +20,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "mainwindow.h"
#include "ui_mainwindow.h"
-#include "directoryentry.h"
-#include "directoryrefresher.h"
#include "executableinfo.h"
#include "executableslist.h"
#include "guessedvalue.h"
@@ -89,6 +87,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "envshortcut.h"
#include "browserdialog.h"
+#include "directoryrefresher.h"
+#include "shared/directoryentry.h"
+#include "shared/fileentry.h"
+#include "shared/filesorigin.h"
+
#include <QAbstractItemDelegate>
#include <QAbstractProxyModel>
#include <QAction>
@@ -364,7 +367,10 @@ MainWindow::MainWindow(Settings &settings
connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString)));
connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed()));
- connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int)));
+ connect(
+ m_OrganizerCore.directoryRefresher(),
+ &DirectoryRefresher::progress,
+ this, &MainWindow::refresherProgress);
connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString)));
connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen()));
@@ -1870,7 +1876,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString
std::vector<std::pair<UINT32, QTreeWidgetItem*>> items;
BSAInvalidation * invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
- std::vector<FileEntry::Ptr> files = m_OrganizerCore.directoryStructure()->getFiles();
+ std::vector<FileEntryPtr> files = m_OrganizerCore.directoryStructure()->getFiles();
QStringList plugins = m_OrganizerCore.findFiles("", [](const QString &fileName) -> bool {
return fileName.endsWith(".esp", Qt::CaseInsensitive)
@@ -1889,7 +1895,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString
return false;
};
- for (FileEntry::Ptr current : files) {
+ for (FileEntryPtr current : files) {
QFileInfo fileInfo(ToQString(current->getName().c_str()));
if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") {
@@ -1942,7 +1948,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString
for (auto iter = items.begin(); iter != items.end(); ++iter) {
int originID = iter->second->data(1, Qt::UserRole).toInt();
- FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
+ const FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
QString modName;
const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
@@ -2075,6 +2081,8 @@ void MainWindow::readSettings()
s.geometry().restoreVisibility(ui->menuBar);
s.geometry().restoreVisibility(ui->statusBar);
+ FilterWidget::setOptions(s.interface().filterOptions());
+
{
// special case in case someone puts 0 in the INI
auto v = s.widgets().index(ui->executablesListBox);
@@ -2159,6 +2167,8 @@ void MainWindow::storeSettings()
m_Filters->saveState(s);
m_DataTab->saveState(s);
+
+ s.interface().setFilterOptions(FilterWidget::options());
}
QWidget* MainWindow::qtWidget()
@@ -2375,10 +2385,15 @@ void MainWindow::setESPListSorting(int index)
}
}
-void MainWindow::refresher_progress(int percent)
+void MainWindow::refresherProgress(const DirectoryRefreshProgress* p)
{
- setEnabled(percent == 100);
- ui->statusBar->setProgress(percent);
+ if (p->finished()) {
+ setEnabled(true);
+ ui->statusBar->setProgress(100);
+ } else {
+ setEnabled(false);
+ ui->statusBar->setProgress(p->percentDone());
+ }
}
void MainWindow::directory_refreshed()
@@ -2500,7 +2515,7 @@ void MainWindow::modRenamed(const QString &oldName, const QString &newName)
void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName)
{
- const FileEntry::Ptr filePtr = m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath));
+ const FileEntryPtr filePtr = m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath));
if (filePtr.get() != nullptr) {
try {
if (m_OrganizerCore.directoryStructure()->originExists(ToWString(newOriginName))) {
@@ -4112,9 +4127,7 @@ void MainWindow::saveArchiveList()
}
}
}
- if (archiveFile.commitIfDifferent(m_ArchiveListHash)) {
- log::debug("{} saved", QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()));
- }
+ archiveFile.commitIfDifferent(m_ArchiveListHash);
} else {
log::warn("archive list not initialised");
}
@@ -5144,7 +5157,11 @@ void MainWindow::originModified(int originID)
{
FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
origin.enable(false);
- m_OrganizerCore.directoryStructure()->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority());
+
+ DirectoryStats dummy;
+ m_OrganizerCore.directoryStructure()->addFromOrigin(
+ origin.getName(), origin.getPath(), origin.getPriority(), dummy);
+
DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure());
}
@@ -5814,38 +5831,6 @@ void MainWindow::extractBSATriggered()
}
}
-
-void MainWindow::displayColumnSelection(const QPoint &pos)
-{
- QMenu menu;
-
- // display a list of all headers as checkboxes
- QAbstractItemModel *model = ui->modList->header()->model();
- for (int i = 1; i < model->columnCount(); ++i) {
- QString columnName = model->headerData(i, Qt::Horizontal).toString();
- QCheckBox *checkBox = new QCheckBox(&menu);
- checkBox->setText(columnName);
- checkBox->setChecked(!ui->modList->header()->isSectionHidden(i));
- QWidgetAction *checkableAction = new QWidgetAction(&menu);
- checkableAction->setDefaultWidget(checkBox);
- menu.addAction(checkableAction);
- }
- menu.exec(pos);
-
- // view/hide columns depending on check-state
- int i = 1;
- for (const QAction *action : menu.actions()) {
- const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
- if (widgetAction != nullptr) {
- const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
- if (checkBox != nullptr) {
- ui->modList->header()->setSectionHidden(i, !checkBox->isChecked());
- }
- }
- ++i;
- }
-}
-
void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
{
m_ContextItem = ui->bsaList->itemAt(pos);
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 1016c061..b839e85e 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -31,6 +31,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorialcontrol.h"
#include "plugincontainer.h" //class PluginContainer;
#include "iplugingame.h" //namespace MOBase { class IPluginGame; }
+#include "shared/fileregisterfwd.h"
#include <log.h>
class Executable;
@@ -153,12 +154,10 @@ public:
ModInfo::Ptr previousModInList();
public slots:
-
- void displayColumnSelection(const QPoint &pos);
-
void modorder_changed();
void esplist_changed();
- void refresher_progress(int percent);
+ void refresherProgress(const DirectoryRefreshProgress* p);
+
void directory_refreshed();
void toolPluginInvoke();
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index bfb8d2c7..c3239e0d 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -29,6 +29,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfodialogcategories.h"
#include "modinfodialognexus.h"
#include "modinfodialogfiletree.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
#include <filesystem>
using namespace MOBase;
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 63d89a1a..b61dbb7b 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -4,6 +4,9 @@
#include "utility.h"
#include "settings.h"
#include "organizercore.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
+#include "shared/fileentry.h"
using namespace MOShared;
using namespace MOBase;
@@ -17,7 +20,7 @@ class ConflictItem
public:
ConflictItem(
QString before, QString relativeName, QString after,
- FileEntry::Index index, QString fileName,
+ FileIndex index, QString fileName,
bool hasAltOrigins, QString altOrigin, bool archive) :
m_before(std::move(before)),
m_relativeName(std::move(relativeName)),
@@ -65,7 +68,7 @@ public:
return m_isArchive;
}
- FileEntry::Index fileIndex() const
+ FileIndex fileIndex() const
{
return m_index;
}
@@ -104,7 +107,7 @@ private:
QString m_before;
QString m_relativeName;
QString m_after;
- FileEntry::Index m_index;
+ FileIndex m_index;
QString m_fileName;
bool m_hasAltOrigins;
QString m_altOrigin;
@@ -1006,8 +1009,8 @@ bool GeneralConflictsTab::update()
}
ConflictItem GeneralConflictsTab::createOverwriteItem(
- FileEntry::Index index, bool archive, QString fileName, QString relativeName,
- const FileEntry::AlternativesVector& alternatives)
+ FileIndex index, bool archive, QString fileName, QString relativeName,
+ const MOShared::AlternativesVector& alternatives)
{
const auto& ds = *m_core.directoryStructure();
std::wstring altString;
@@ -1028,7 +1031,7 @@ ConflictItem GeneralConflictsTab::createOverwriteItem(
}
ConflictItem GeneralConflictsTab::createNoConflictItem(
- FileEntry::Index index, bool archive, QString fileName, QString relativeName)
+ FileIndex index, bool archive, QString fileName, QString relativeName)
{
return ConflictItem(
QString(), std::move(relativeName), QString(), index,
@@ -1036,7 +1039,7 @@ ConflictItem GeneralConflictsTab::createNoConflictItem(
}
ConflictItem GeneralConflictsTab::createOverwrittenItem(
- FileEntry::Index index, int fileOrigin, bool archive,
+ FileIndex index, int fileOrigin, bool archive,
QString fileName, QString relativeName)
{
const auto& ds = *m_core.directoryStructure();
@@ -1205,9 +1208,9 @@ void AdvancedConflictsTab::update()
}
std::optional<ConflictItem> AdvancedConflictsTab::createItem(
- FileEntry::Index index, int fileOrigin, bool archive,
+ FileIndex index, int fileOrigin, bool archive,
QString fileName, QString relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives)
+ const MOShared::AlternativesVector& alternatives)
{
const auto& ds = *m_core.directoryStructure();
diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h
index c4845019..945f464d 100644
--- a/src/modinfodialogconflicts.h
+++ b/src/modinfodialogconflicts.h
@@ -4,7 +4,7 @@
#include "modinfodialogtab.h"
#include "expanderwidget.h"
#include "filterwidget.h"
-#include "directoryentry.h"
+#include "shared/fileregisterfwd.h"
#include <QTreeWidget>
#include <optional>
@@ -52,16 +52,16 @@ private:
FilterWidget m_filterNoConflicts;
ConflictItem createOverwriteItem(
- MOShared::FileEntry::Index index, bool archive,
+ MOShared::FileIndex index, bool archive,
QString fileName, QString relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives);
+ const MOShared::AlternativesVector& alternatives);
ConflictItem createNoConflictItem(
- MOShared::FileEntry::Index index, bool archive,
+ MOShared::FileIndex index, bool archive,
QString fileName, QString relativeName);
ConflictItem createOverwrittenItem(
- MOShared::FileEntry::Index index, int fileOrigin, bool archive,
+ MOShared::FileIndex index, int fileOrigin, bool archive,
QString fileName, QString relativeName);
void onOverwriteActivated(const QModelIndex& index);
@@ -94,9 +94,9 @@ private:
ConflictListModel* m_model;
std::optional<ConflictItem> createItem(
- MOShared::FileEntry::Index index, int fileOrigin, bool archive,
+ MOShared::FileIndex index, int fileOrigin, bool archive,
QString fileName, QString relativeName,
- const MOShared::FileEntry::AlternativesVector& alternatives);
+ const MOShared::AlternativesVector& alternatives);
};
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index 48b3bb3b..8a22045e 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -186,11 +186,11 @@ void ImagesTab::switchToFiltered()
const bool hasTextFilter = !m_filter.empty();
- for (auto& f : m_files.allFiles()) {
+ for (File& f : m_files.allFiles()) {
if (hasTextFilter) {
// check filter widget
- const auto m = m_filter.matches([&](auto&& what) {
- return f.path().contains(what, Qt::CaseInsensitive);
+ const auto m = m_filter.matches([&](const QRegularExpression& regex) {
+ return regex.match(f.filename()).hasMatch();
});
if (!m) {
@@ -201,7 +201,7 @@ void ImagesTab::switchToFiltered()
if (!m_ddsEnabled) {
// skip .dds files
- if (f.path().endsWith(".dds", Qt::CaseInsensitive)) {
+ if (f.filename().endsWith(".dds", Qt::CaseInsensitive)) {
continue;
}
}
diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp
index 9748d059..d662e2b2 100644
--- a/src/modinfodialogtab.cpp
+++ b/src/modinfodialogtab.cpp
@@ -1,8 +1,8 @@
#include "modinfodialogtab.h"
#include "ui_modinfodialog.h"
#include "texteditor.h"
-#include "directoryentry.h"
#include "modinfo.h"
+#include "shared/filesorigin.h"
ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) :
ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent),
diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp
index 861782d9..2b4fa11c 100644
--- a/src/modinfowithconflictinfo.cpp
+++ b/src/modinfowithconflictinfo.cpp
@@ -1,7 +1,8 @@
#include "modinfowithconflictinfo.h"
-
-#include "directoryentry.h"
#include "utility.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
+#include "shared/fileentry.h"
#include <filesystem>
using namespace MOBase;
@@ -98,11 +99,11 @@ void ModInfoWithConflictInfo::doConflictCheck() const
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
- std::vector<FileEntry::Ptr> files = origin.getFiles();
+ std::vector<FileEntryPtr> files = origin.getFiles();
std::set<const DirectoryEntry*> checkedDirs;
// for all files in this origin
- for (FileEntry::Ptr file : files) {
+ for (FileEntryPtr file : files) {
// skip hiidden file check if already found one
if (!hasHiddenFiles) {
@@ -267,7 +268,7 @@ bool ModInfoWithConflictInfo::isRedundant() const
std::wstring name = ToWString(this->name());
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
- std::vector<FileEntry::Ptr> files = origin.getFiles();
+ std::vector<FileEntryPtr> files = origin.getFiles();
bool ignore = false;
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if ((*iter)->getOrigin(ignore) == origin.getID()) {
diff --git a/src/modlist.cpp b/src/modlist.cpp
index 31eb8387..e84910fd 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -27,6 +27,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "pluginlist.h"
#include "settings.h"
#include "modinforegular.h"
+#include "shared/directoryentry.h"
+#include "shared/fileentry.h"
+#include "shared/filesorigin.h"
+
#include <appconfig.h>
#include <utility.h>
#include <report.h>
@@ -872,7 +876,7 @@ void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared
for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) {
QString modName = idx.data().toString();
- const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString());
+ const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(modName.toStdWString());
if (fileEntry.get() != nullptr) {
bool archive = false;
std::vector<std::pair<int, std::pair<std::wstring, int>>> origins;
@@ -881,7 +885,7 @@ void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared
origins.insert(origins.end(), std::pair<int, std::pair<std::wstring, int>>(fileEntry->getOrigin(archive), fileEntry->getArchive()));
}
for (auto originInfo : origins) {
- MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first);
+ MOShared::FilesOrigin& origin = directoryEntry.getOriginByID(originInfo.first);
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) {
ModInfo::getByIndex(i)->setPluginSelected(true);
@@ -1434,15 +1438,7 @@ bool ModList::toggleSelection(QAbstractItemView *itemView)
bool ModList::eventFilter(QObject *obj, QEvent *event)
{
- if (event->type() == QEvent::ContextMenu) {
- QContextMenuEvent *contextEvent = static_cast<QContextMenuEvent*>(event);
- QWidget *object = qobject_cast<QWidget*>(obj);
- if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) {
- emit requestColumnSelect(object->mapToGlobal(contextEvent->pos()));
-
- return true;
- }
- } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) {
+ if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) {
QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
diff --git a/src/modlist.h b/src/modlist.h
index 9a44b761..d8980dec 100644
--- a/src/modlist.h
+++ b/src/modlist.h
@@ -27,7 +27,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "profile.h"
#include <imodlist.h>
-#include <directoryentry.h>
#include <QFile>
#include <QListWidget>
@@ -202,12 +201,6 @@ signals:
void resizeHeaders();
/**
- * @brief requestColumnSelect is emitted whenever the user requested a menu to select visible columns
- * @param pos the position to display the menu at
- */
- void requestColumnSelect(QPoint pos);
-
- /**
* @brief emitted to remove a file origin
* @param name name of the orign to remove
*/
diff --git a/src/modlistview.cpp b/src/modlistview.cpp
index fe2d3e57..c6dbc6ea 100644
--- a/src/modlistview.cpp
+++ b/src/modlistview.cpp
@@ -1,4 +1,5 @@
#include "modlistview.h"
+#include <widgetutility.h>
#include <QUrl>
#include <QMimeData>
#include <QProxyStyle>
@@ -39,6 +40,7 @@ ModListView::ModListView(QWidget *parent)
, m_Scrollbar(new ViewMarkingScrollBar(this->model(), this))
{
setVerticalScrollBar(m_Scrollbar);
+ MOBase::setCustomizableColumns(this);
}
void ModListView::dragEnterEvent(QDragEnterEvent *event)
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 04d78ca8..80df044a 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -22,7 +22,6 @@
#include <ipluginmodpage.h>
#include <dataarchives.h>
#include <localsavegames.h>
-#include <directoryentry.h>
#include <scopeguard.h>
#include <utility.h>
#include <usvfs.h>
@@ -34,6 +33,11 @@
#include "previewdialog.h"
#include "env.h"
#include "envmodule.h"
+#include "envfs.h"
+#include "directoryrefresher.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
+#include "shared/fileentry.h"
#include <QApplication>
#include <QCoreApplication>
@@ -88,24 +92,19 @@ QStringList toStringList(InputIterator current, InputIterator end)
OrganizerCore::OrganizerCore(Settings &settings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
- , m_GameName()
, m_CurrentProfile(nullptr)
, m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
- , m_AboutToRun()
- , m_FinishedRun()
- , m_ModInstalled()
, m_ModList(m_PluginContainer, this)
, m_PluginList(this)
- , m_DirectoryRefresher()
+ , m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount()))
, m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0))
, m_DownloadManager(NexusInterface::instance(m_PluginContainer), this)
- , m_InstallationManager()
- , m_RefresherThread()
, m_DirectoryUpdate(false)
, m_ArchivesInit(false)
, m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this))
{
+ env::setHandleCloserThreadCount(settings.refreshThreadCount());
m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads(), false);
NexusInterface::instance(m_PluginContainer)->setCacheDirectory(
@@ -116,7 +115,7 @@ OrganizerCore::OrganizerCore(Settings &settings)
connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this,
SLOT(downloadSpeed(QString, int)));
- connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this,
+ connect(m_DirectoryRefresher.get(), SIGNAL(refreshed()), this,
SLOT(directory_refreshed()));
connect(&m_ModList, SIGNAL(removeOrigin(QString)), this,
@@ -141,7 +140,7 @@ OrganizerCore::OrganizerCore(Settings &settings)
// make directory refresher run in a separate thread
m_RefresherThread.start();
- m_DirectoryRefresher.moveToThread(&m_RefresherThread);
+ m_DirectoryRefresher->moveToThread(&m_RefresherThread);
}
OrganizerCore::~OrganizerCore()
@@ -149,6 +148,10 @@ OrganizerCore::~OrganizerCore()
m_RefresherThread.exit();
m_RefresherThread.wait();
+ if (m_StructureDeleter.joinable()) {
+ m_StructureDeleter.join();
+ }
+
saveCurrentProfile();
// profile has to be cleaned up before the modinfo-buffer is cleared
@@ -234,8 +237,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui)
SLOT(removeMod_clicked()));
connect(&m_ModList, SIGNAL(clearOverwrite()), w,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w,
- SLOT(displayColumnSelection(QPoint)));
connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
SLOT(fileMoved(QString, QString, QString)));
connect(&m_ModList, SIGNAL(modorder_changed()), w,
@@ -813,7 +814,7 @@ QString OrganizerCore::resolvePath(const QString &fileName) const
if (m_DirectoryStructure == nullptr) {
return QString();
}
- const FileEntry::Ptr file
+ const FileEntryPtr file
= m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
if (file.get() != nullptr) {
return ToQString(file->getFullPath());
@@ -847,8 +848,8 @@ QStringList OrganizerCore::findFiles(
if (!path.isEmpty())
dir = dir->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
- std::vector<FileEntry::Ptr> files = dir->getFiles();
- foreach (FileEntry::Ptr file, files) {
+ std::vector<FileEntryPtr> files = dir->getFiles();
+ foreach (FileEntryPtr file, files) {
if (filter(ToQString(file->getFullPath()))) {
result.append(ToQString(file->getFullPath()));
}
@@ -860,7 +861,7 @@ QStringList OrganizerCore::findFiles(
QStringList OrganizerCore::getFileOrigins(const QString &fileName) const
{
QStringList result;
- const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
+ const FileEntryPtr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
if (file.get() != nullptr) {
result.append(ToQString(
@@ -883,8 +884,8 @@ QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos(
if (!path.isEmpty())
dir = dir->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
- std::vector<FileEntry::Ptr> files = dir->getFiles();
- foreach (FileEntry::Ptr file, files) {
+ std::vector<FileEntryPtr> files = dir->getFiles();
+ foreach (FileEntryPtr file, files) {
IOrganizer::FileInfo info;
info.filePath = ToQString(file->getFullPath());
bool fromArchive = false;
@@ -966,7 +967,7 @@ bool OrganizerCore::previewFileWithAlternatives(
- const FileEntry::Ptr file = directoryStructure()->searchFile(ToWString(fileName), nullptr);
+ const FileEntryPtr file = directoryStructure()->searchFile(ToWString(fileName), nullptr);
if (file.get() == nullptr) {
reportError(tr("file not found: %1").arg(qUtf8Printable(fileName)));
@@ -1180,7 +1181,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
QDir dir(modInfo->absolutePath());
for (const QString &esm :
dir.entryList(QStringList() << "*.esm", QDir::Files)) {
- const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm));
+ const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esm));
if (file.get() == nullptr) {
log::warn("failed to activate {}", esm);
continue;
@@ -1196,7 +1197,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
for (const QString &esl :
dir.entryList(QStringList() << "*.esl", QDir::Files)) {
- const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl));
+ const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esl));
if (file.get() == nullptr) {
log::warn("failed to activate {}", esl);
continue;
@@ -1212,7 +1213,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
}
QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files);
for (const QString &esp : esps) {
- const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp));
+ const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esp));
if (file.get() == nullptr) {
log::warn("failed to activate {}", esp);
continue;
@@ -1247,13 +1248,17 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index,
void OrganizerCore::updateModsInDirectoryStructure(QMap<unsigned int, ModInfo::Ptr> modInfo)
{
+ std::vector<DirectoryRefresher::EntryInfo> entries;
+
for (auto idx : modInfo.keys()) {
- // add files of the bsa to the directory structure
- m_DirectoryRefresher.addModFilesToStructure(
- m_DirectoryStructure, modInfo[idx]->name(),
- m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(),
- modInfo[idx]->stealFiles());
+ entries.push_back({
+ modInfo[idx]->name(), modInfo[idx]->absolutePath(),
+ modInfo[idx]->stealFiles(), {}, m_CurrentProfile->getModPriority(idx)});
}
+
+ m_DirectoryRefresher->addMultipleModsFilesToStructure(
+ m_DirectoryStructure, entries);
+
DirectoryRefresher::cleanStructure(m_DirectoryStructure);
// need to refresh plugin list now so we can activate esps
refreshESPList(true);
@@ -1270,13 +1275,13 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap<unsigned int, ModInfo::P
}
std::vector<QString> archives = enabledArchives();
- m_DirectoryRefresher.setMods(
+ m_DirectoryRefresher->setMods(
m_CurrentProfile->getActiveMods(),
std::set<QString>(archives.begin(), archives.end()));
// finally also add files from bsas to the directory structure
for (auto idx : modInfo.keys()) {
- m_DirectoryRefresher.addModBSAToStructure(
+ m_DirectoryRefresher->addModBSAToStructure(
m_DirectoryStructure, modInfo[idx]->name(),
m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(),
modInfo[idx]->archives());
@@ -1382,46 +1387,74 @@ std::vector<QString> OrganizerCore::enabledArchives()
void OrganizerCore::refreshDirectoryStructure()
{
- if (!m_DirectoryUpdate) {
- m_CurrentProfile->writeModlistNow(true);
+ if (m_DirectoryUpdate) {
+ log::debug("can't refresh, already in progress");
+ return;
+ }
- m_DirectoryUpdate = true;
- std::vector<std::tuple<QString, QString, int>> activeModList
- = m_CurrentProfile->getActiveMods();
- auto archives = enabledArchives();
- m_DirectoryRefresher.setMods(
- activeModList, std::set<QString>(archives.begin(), archives.end()));
+ log::debug("refreshing structure");
+ m_DirectoryUpdate = true;
- QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
- }
+ m_CurrentProfile->writeModlistNow(true);
+ const auto activeModList = m_CurrentProfile->getActiveMods();
+ const auto archives = enabledArchives();
+
+ m_DirectoryRefresher->setMods(
+ activeModList, std::set<QString>(archives.begin(), archives.end()));
+
+ // runs refresh() in a thread
+ QTimer::singleShot(0, m_DirectoryRefresher.get(), SLOT(refresh()));
}
void OrganizerCore::directory_refreshed()
{
- DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure();
+ log::debug("structure refreshed");
+
+ DirectoryEntry *newStructure = m_DirectoryRefresher->stealDirectoryStructure();
Q_ASSERT(newStructure != m_DirectoryStructure);
- if (newStructure != nullptr) {
- std::swap(m_DirectoryStructure, newStructure);
- delete newStructure;
- } else {
+
+ if (newStructure == nullptr) {
// TODO: don't know why this happens, this slot seems to get called twice
// with only one emit
return;
}
+
+ std::swap(m_DirectoryStructure, newStructure);
+
+ if (m_StructureDeleter.joinable()) {
+ m_StructureDeleter.join();
+ }
+
+ m_StructureDeleter = std::thread([=]{
+ log::debug("structure deleter thread start");
+ delete newStructure;
+ log::debug("structure deleter thread done");
+ });
+
m_DirectoryUpdate = false;
+ log::debug("clearing caches");
for (int i = 0; i < m_ModList.rowCount(); ++i) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
modInfo->clearCaches();
}
- for (auto task : m_PostRefreshTasks) {
- task();
+
+ if (!m_PostRefreshTasks.empty()) {
+ log::debug("running {} post refresh tasks", m_PostRefreshTasks.size());
+
+ for (auto task : m_PostRefreshTasks) {
+ task();
+ }
+
+ m_PostRefreshTasks.clear();
}
- m_PostRefreshTasks.clear();
if (m_CurrentProfile != nullptr) {
+ log::debug("refreshing lists");
refreshLists();
}
+
+ log::debug("refresh done");
}
void OrganizerCore::profileRefresh()
@@ -1867,7 +1900,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(
{
std::vector<Mapping> result;
- for (FileEntry::Ptr current : directoryEntry->getFiles()) {
+ for (FileEntryPtr current : directoryEntry->getFiles()) {
bool isArchive = false;
int origin = current->getOrigin(isArchive);
if (isArchive || (origin == 0)) {
diff --git a/src/organizercore.h b/src/organizercore.h
index a63dc959..a4d1a799 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -1,13 +1,11 @@
#ifndef ORGANIZERCORE_H
#define ORGANIZERCORE_H
-
#include "selfupdater.h"
#include "settings.h"
#include "modlist.h"
#include "modinfo.h"
#include "pluginlist.h"
-#include "directoryrefresher.h"
#include "installationmanager.h"
#include "downloadmanager.h"
#include "executableslist.h"
@@ -15,7 +13,6 @@
#include "moshortcut.h"
#include "processrunner.h"
#include "uilocker.h"
-#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
#include <versioninfo.h>
@@ -24,17 +21,6 @@
#include "executableinfo.h"
#include <log.h>
-class ModListSortProxy;
-class PluginListSortProxy;
-class Profile;
-class IUserInterface;
-
-namespace MOBase {
- template <typename T> class GuessedValue;
- class IModInterface;
-}
-namespace MOShared { class DirectoryEntry; }
-
#include <QDir>
#include <QFileInfo>
#include <QList>
@@ -45,21 +31,25 @@ namespace MOShared { class DirectoryEntry; }
#include <QThread>
#include <QVariant>
-class QNetworkReply;
-class QUrl;
-class QWidget;
-
-#include <Windows.h> //for HANDLE, LPDWORD
-
-#include <functional>
-#include <vector>
-
+class ModListSortProxy;
+class PluginListSortProxy;
+class Profile;
+class IUserInterface;
class PluginContainer;
+class DirectoryRefresher;
-namespace MOBase {
+namespace MOBase
+{
+ template <typename T> class GuessedValue;
+ class IModInterface;
class IPluginGame;
}
+namespace MOShared
+{
+ class DirectoryEntry;
+}
+
class OrganizerCore : public QObject, public MOBase::IPluginDiagnose
{
@@ -114,7 +104,7 @@ public:
SelfUpdater *updater() { return &m_Updater; }
InstallationManager *installationManager();
MOShared::DirectoryEntry *directoryStructure() { return m_DirectoryStructure; }
- DirectoryRefresher *directoryRefresher() { return &m_DirectoryRefresher; }
+ DirectoryRefresher *directoryRefresher() { return m_DirectoryRefresher.get(); }
ExecutablesList *executablesList() { return &m_ExecutablesList; }
void setExecutablesList(const ExecutablesList &executablesList) {
m_ExecutablesList = executablesList;
@@ -338,7 +328,7 @@ private:
QStringList m_DefaultArchives;
QStringList m_ActiveArchives;
- DirectoryRefresher m_DirectoryRefresher;
+ std::unique_ptr<DirectoryRefresher> m_DirectoryRefresher;
MOShared::DirectoryEntry *m_DirectoryStructure;
DownloadManager m_DownloadManager;
@@ -346,6 +336,8 @@ private:
QThread m_RefresherThread;
+ std::thread m_StructureDeleter;
+
bool m_DirectoryUpdate;
bool m_ArchivesInit;
bool m_ArchiveParsing{ m_Settings.archiveParsing() };
diff --git a/src/pch.h b/src/pch.h
index 030ee634..c66550be 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -5,14 +5,17 @@
#include <cstdarg>
#include <cstdint>
#include <ctime>
+#include <deque>
#include <exception>
#include <functional>
+#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits.h>
#include <list>
#include <map>
#include <memory>
+#include <mutex>
#include <optional>
#include <regex>
#include <set>
@@ -21,6 +24,7 @@
#include <stdexcept>
#include <string.h>
#include <string>
+#include <thread>
#include <tuple>
#include <unordered_set>
#include <utility>
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 266fe35c..4b2eedbd 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -23,6 +23,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfo.h"
#include "modlist.h"
#include "viewmarkingscrollbar.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
+#include "shared/fileentry.h"
+
#include <utility.h>
#include <iplugingame.h>
#include <espfile.h>
@@ -144,10 +148,10 @@ void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MO
if (!selectedMod.isNull() && profile.modEnabled(modIndex)) {
QDir dir(selectedMod->absolutePath());
QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl");
- MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString());
+ const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString());
if (plugins.size() > 0) {
for (auto plugin : plugins) {
- MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString());
+ MOShared::FileEntryPtr file = directoryEntry.findFile(plugin.toStdWString());
if (file && file->getOrigin() != origin.getID()) {
const std::vector<std::pair<int, std::pair<std::wstring, int>>> alternatives = file->getAlternatives();
if (std::find_if(alternatives.begin(), alternatives.end(), [&](const std::pair<int, std::pair<std::wstring, int>>& element) { return element.first == origin.getID(); }) == alternatives.end())
@@ -185,8 +189,8 @@ void PluginList::refresh(const QString &profileName
QStringList availablePlugins;
- std::vector<FileEntry::Ptr> files = baseDirectory.getFiles();
- for (FileEntry::Ptr current : files) {
+ std::vector<FileEntryPtr> files = baseDirectory.getFiles();
+ for (FileEntryPtr current : files) {
if (current.get() == nullptr) {
continue;
}
@@ -217,7 +221,7 @@ void PluginList::refresh(const QString &profileName
bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr;
std::set<QString> loadedArchives;
QString candidateName;
- for (FileEntry::Ptr archiveCandidate : files) {
+ for (FileEntryPtr archiveCandidate : files) {
candidateName = ToQString(archiveCandidate->getName());
if (candidateName.startsWith(baseName, Qt::CaseInsensitive) &&
(candidateName.endsWith(".bsa", Qt::CaseInsensitive) ||
@@ -507,7 +511,6 @@ void PluginList::writeLockedOrder(const QString &fileName) const
file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8());
}
file.commit();
- log::debug("{} saved", QDir::toNativeSeparators(fileName));
}
@@ -531,9 +534,7 @@ void PluginList::saveTo(const QString &lockedOrderFileName
deleterFile->write("\r\n");
}
}
- if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) {
- log::debug("{} saved", QDir::toNativeSeparators(deleterFileName));
- }
+ deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName]);
} else if (QFile::exists(deleterFileName)) {
shellDelete(QStringList() << deleterFileName);
}
@@ -551,7 +552,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
for (ESPInfo &esp : m_ESPs) {
std::wstring espName = ToWString(esp.name);
- const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName);
+ const FileEntryPtr fileEntry = directoryStructure.findFile(espName);
if (fileEntry.get() != nullptr) {
QString fileName;
bool archive = false;
diff --git a/src/pluginlist.h b/src/pluginlist.h
index fb6d0543..cdab18b5 100644
--- a/src/pluginlist.h
+++ b/src/pluginlist.h
@@ -20,7 +20,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef PLUGINLIST_H
#define PLUGINLIST_H
-#include <directoryentry.h>
#include <ipluginlist.h>
#include "profile.h"
#include "loot.h"
diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp
index 0fcf8183..4217971d 100644
--- a/src/pluginlistview.cpp
+++ b/src/pluginlistview.cpp
@@ -1,4 +1,5 @@
#include "pluginlistview.h"
+#include <widgetutility.h>
#include <QUrl>
#include <QMimeData>
#include <QProxyStyle>
@@ -40,6 +41,7 @@ PluginListView::PluginListView(QWidget *parent)
, m_Scrollbar(new ViewMarkingScrollBar(this->model(), this))
{
setVerticalScrollBar(m_Scrollbar);
+ MOBase::setCustomizableColumns(this);
}
void PluginListView::dragEnterEvent(QDragEnterEvent *event)
diff --git a/src/profile.cpp b/src/profile.cpp
index f041a241..19c0d750 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -250,9 +250,7 @@ void Profile::doWriteModlist()
}
}
- if (file.commitIfDifferent(m_LastModlistHash)) {
- log::debug("{} saved", QDir::toNativeSeparators(fileName));
- }
+ file.commitIfDifferent(m_LastModlistHash);
} catch (const std::exception &e) {
reportError(tr("failed to write mod list: %1").arg(e.what()));
return;
@@ -292,8 +290,6 @@ void Profile::createTweakedIniFile()
reportError(tr("failed to create tweaked ini: %1")
.arg(QString::fromStdWString(formatSystemMessage(e))));
}
-
- log::debug("{} saved", QDir::toNativeSeparators(tweakedIni));
}
// static
diff --git a/src/settings.cpp b/src/settings.cpp
index 4431e7d9..f0496fe8 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -209,6 +209,16 @@ void Settings::setUseSplash(bool b)
set(m_Settings, "Settings", "use_splash", b);
}
+std::size_t Settings::refreshThreadCount() const
+{
+ return get<std::size_t>(m_Settings, "Settings", "refresh_thread_count", 10);
+}
+
+void Settings::setRefreshThreadCount(std::size_t n) const
+{
+ return set(m_Settings, "Settings", "refresh_thread_count", n);
+}
+
std::optional<QVersionNumber> Settings::version() const
{
if (auto v=getOptional<QString>(m_Settings, "General", "version")) {
@@ -1992,6 +2002,24 @@ void InterfaceSettings::setDoubleClicksOpenPreviews(bool b)
set(m_Settings, "Settings", "double_click_previews", b);
}
+FilterWidget::Options InterfaceSettings::filterOptions() const
+{
+ FilterWidget::Options o;
+
+ o.useRegex = get<bool>(m_Settings, "Settings", "filter_regex", false);
+ o.regexCaseSensitive = get<bool>(m_Settings, "Settings", "regex_case_sensitive", false);
+ o.regexExtended = get<bool>(m_Settings, "Settings", "regex_extended", false);
+
+ return o;
+}
+
+void InterfaceSettings::setFilterOptions(const FilterWidget::Options& o)
+{
+ set(m_Settings, "Settings", "filter_regex", o.useRegex);
+ set(m_Settings, "Settings", "regex_case_sensitive", o.regexCaseSensitive);
+ set(m_Settings, "Settings", "regex_extended", o.regexExtended);
+}
+
DiagnosticsSettings::DiagnosticsSettings(QSettings& settings)
: m_Settings(settings)
diff --git a/src/settings.h b/src/settings.h
index 678226c1..b2cb6be5 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#define SETTINGS_H
#include "loadmechanism.h"
+#include <filterwidget.h>
#include <lootcli/lootcli.h>
#include <questionboxmemory.h>
#include <log.h>
@@ -615,6 +616,11 @@ public:
bool doubleClicksOpenPreviews() const;
void setDoubleClicksOpenPreviews(bool b);
+ // filter widget options
+ //
+ MOBase::FilterWidget::Options filterOptions() const;
+ void setFilterOptions(const MOBase::FilterWidget::Options& o);
+
private:
QSettings& m_Settings;
};
@@ -725,6 +731,11 @@ public:
bool useSplash() const;
void setUseSplash(bool b);
+ // number of threads to use when refreshing
+ //
+ std::size_t refreshThreadCount() const;
+ void setRefreshThreadCount(std::size_t n) const;
+
GameSettings& game();
const GameSettings& game() const;
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp
index 6e44cc91..639392d2 100644
--- a/src/shared/directoryentry.cpp
+++ b/src/shared/directoryentry.cpp
@@ -18,35 +18,39 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "directoryentry.h"
+#include "originconnection.h"
+#include "filesorigin.h"
+#include "fileentry.h"
+#include "envfs.h"
+#include "util.h"
#include "windows_error.h"
-#include "error_report.h"
#include <log.h>
-#include <bsatk.h>
-#include <boost/bind.hpp>
-#include <boost/scoped_array.hpp>
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-#include <sstream>
-#include <ctime>
-#include <algorithm>
-#include <map>
-#include <atomic>
+#include <utility.h>
namespace MOShared
{
using namespace MOBase;
-static const int MAXPATH_UNICODE = 32767;
+const int MAXPATH_UNICODE = 32767;
-static std::wstring tail(const std::wstring &source, const size_t count)
+template <class F>
+void elapsedImpl(std::chrono::nanoseconds& out, F&& f)
{
- if (count >= source.length()) {
- return source;
+ if constexpr (DirectoryStats::EnableInstrumentation) {
+ const auto start = std::chrono::high_resolution_clock::now();
+ f();
+ const auto end = std::chrono::high_resolution_clock::now();
+ out += (end - start);
+ } else {
+ f();
}
-
- return source.substr(source.length() - count);
}
+// elapsed() is not optimized out when EnableInstrumentation is false even
+// though it's equivalent that this macro
+#define elapsed(OUT, F) (F)();
+//#define elapsed(OUT, F) elapsedImpl(OUT, F);
+
static bool SupportOptimizedFind()
{
// large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, win 7 and newer
@@ -63,681 +67,186 @@ static bool SupportOptimizedFind()
return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE);
}
-static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs)
+static bool DirCompareByName(const DirectoryEntry* lhs, const DirectoryEntry* rhs)
{
return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0;
}
-class OriginConnection
-{
-public:
- typedef int Index;
- static const int INVALID_INDEX = INT_MIN;
-
- OriginConnection()
- : m_NextID(0)
- {
- }
-
- FilesOrigin& createOrigin(
- const std::wstring &originName, const std::wstring &directory, int priority,
- boost::shared_ptr<FileRegister> fileRegister,
- boost::shared_ptr<OriginConnection> originConnection)
- {
- int newID = createID();
-
- m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection);
- m_OriginsNameMap[originName] = newID;
- m_OriginsPriorityMap[priority] = newID;
-
- return m_Origins[newID];
- }
-
- bool exists(const std::wstring &name)
- {
- return m_OriginsNameMap.find(name) != m_OriginsNameMap.end();
- }
-
- FilesOrigin &getByID(Index ID)
- {
- return m_Origins[ID];
- }
-
- const FilesOrigin* findByID(Index ID) const
- {
- auto itor = m_Origins.find(ID);
-
- if (itor == m_Origins.end()) {
- return nullptr;
- } else {
- return &itor->second;
- }
- }
-
- FilesOrigin &getByName(const std::wstring &name)
- {
- std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
-
- if (iter != m_OriginsNameMap.end()) {
- return m_Origins[iter->second];
- } else {
- std::ostringstream stream;
- stream << QObject::tr("invalid origin name: ").toStdString() << ToString(name, true);
- throw std::runtime_error(stream.str());
- }
- }
-
- void changePriorityLookup(int oldPriority, int newPriority)
- {
- auto iter = m_OriginsPriorityMap.find(oldPriority);
-
- if (iter != m_OriginsPriorityMap.end()) {
- Index idx = iter->second;
- m_OriginsPriorityMap.erase(iter);
- m_OriginsPriorityMap[newPriority] = idx;
- }
- }
-
- void changeNameLookup(const std::wstring &oldName, const std::wstring &newName)
- {
- auto iter = m_OriginsNameMap.find(oldName);
-
- if (iter != m_OriginsNameMap.end()) {
- Index idx = iter->second;
- m_OriginsNameMap.erase(iter);
- m_OriginsNameMap[newName] = idx;
- } else {
- log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), oldName, newName);
- }
- }
-
-private:
- Index m_NextID;
- std::map<Index, FilesOrigin> m_Origins;
- std::map<std::wstring, Index> m_OriginsNameMap;
- std::map<int, Index> m_OriginsPriorityMap;
-
- Index createID()
- {
- return m_NextID++;
- }
-};
-
-
-FileEntry::FileEntry() :
- m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr),
- m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize),
- m_LastAccessed(time(nullptr))
+DirectoryEntry::DirectoryEntry(
+ std::wstring name, DirectoryEntry* parent, int originID) :
+ m_OriginConnection(new OriginConnection),
+ m_Name(std::move(name)), m_Parent(parent), m_Populated(false), m_TopLevel(true)
{
+ m_FileRegister.reset(new FileRegister(m_OriginConnection));
+ m_Origins.insert(originID);
}
-FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) :
- m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent),
- m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize),
- m_LastAccessed(time(nullptr))
+DirectoryEntry::DirectoryEntry(
+ std::wstring name, DirectoryEntry* parent, int originID,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection) :
+ m_FileRegister(fileRegister), m_OriginConnection(originConnection),
+ m_Name(std::move(name)), m_Parent(parent), m_Populated(false), m_TopLevel(false)
{
+ m_Origins.insert(originID);
}
-void FileEntry::addOrigin(
- int origin, FILETIME fileTime, const std::wstring &archive, int order)
+DirectoryEntry::~DirectoryEntry()
{
- m_LastAccessed = time(nullptr);
- if (m_Parent != nullptr) {
- m_Parent->propagateOrigin(origin);
- }
-
- if (m_Origin == -1) {
- // If this file has no previous origin, this mod is now the origin with no
- // alternatives
- m_Origin = origin;
- m_FileTime = fileTime;
- m_Archive = std::pair<std::wstring, int>(archive, order);
- }
- else if (
- (m_Parent != nullptr) && (
- (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) ||
- (archive.size() == 0 && m_Archive.first.size() > 0 ))
- ) {
- // If this mod has a higher priority than the origin mod OR
- // this mod has a loose file and the origin mod has an archived file,
- // this mod is now the origin and the previous origin is the first alternative
-
- auto itor = std::find_if(
- m_Alternatives.begin(), m_Alternatives.end(),
- [&](auto&& i) { return i.first == m_Origin; });
-
- if (itor == m_Alternatives.end()) {
- m_Alternatives.push_back({m_Origin, m_Archive});
- }
-
- m_Origin = origin;
- m_FileTime = fileTime;
- m_Archive = std::pair<std::wstring, int>(archive, order);
- }
- else {
- // This mod is just an alternative
- bool found = false;
-
- if (m_Origin == origin) {
- // already an origin
- return;
- }
-
- for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if (iter->first == origin) {
- // already an origin
- return;
- }
-
- if ((m_Parent != nullptr) &&
- (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) {
- m_Alternatives.insert(iter, {origin, {archive, order}});
- found = true;
- break;
- }
- }
-
- if (!found) {
- m_Alternatives.push_back({origin, {archive, order}});
- }
- }
+ clear();
}
-bool FileEntry::removeOrigin(int origin)
+void DirectoryEntry::clear()
{
- if (m_Origin == origin) {
- if (!m_Alternatives.empty()) {
- // find alternative with the highest priority
- auto currentIter = m_Alternatives.begin();
- for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if (iter->first != origin) {
- //Both files are not from archives.
- if (!iter->second.first.size() && !currentIter->second.first.size()) {
- if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority())) {
- currentIter = iter;
- }
- }
- else {
- //Both files are from archives
- if (iter->second.first.size() && currentIter->second.first.size()) {
- if (iter->second.second > currentIter->second.second) {
- currentIter = iter;
- }
- }
- else {
- //Only one of the two is an archive, so we change currentIter only if he is the archive one.
- if (currentIter->second.first.size()) {
- currentIter = iter;
- }
- }
- }
- }
- }
-
- int currentID = currentIter->first;
- m_Archive = currentIter->second;
- m_Alternatives.erase(currentIter);
-
- m_Origin = currentID;
- } else {
- m_Origin = -1;
- m_Archive = std::pair<std::wstring, int>(L"", -1);
- return true;
- }
- } else {
- auto newEnd = std::remove_if(
- m_Alternatives.begin(), m_Alternatives.end(),
- [&](auto &i) { return i.first == origin; });
-
- if (newEnd != m_Alternatives.end()) {
- m_Alternatives.erase(newEnd, m_Alternatives.end());
- }
+ for (auto itor=m_SubDirectories.rbegin(); itor!=m_SubDirectories.rend(); ++itor) {
+ delete *itor;
}
- return false;
-}
-
-void FileEntry::sortOrigins()
-{
- m_Alternatives.push_back({m_Origin, m_Archive});
-
- std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](auto&& LHS, auto&& RHS) {
- if (!LHS.second.first.size() && !RHS.second.first.size()) {
- int l = m_Parent->getOriginByID(LHS.first).getPriority();
- if (l < 0) {
- l = INT_MAX;
- }
- int r = m_Parent->getOriginByID(RHS.first).getPriority();
- if (r < 0) {
- r = INT_MAX;
- }
-
- return l < r;
- }
-
- if (LHS.second.first.size() && RHS.second.first.size()) {
- int l = LHS.second.second; if (l < 0) l = INT_MAX;
- int r = RHS.second.second; if (r < 0) r = INT_MAX;
-
- return l < r;
- }
-
- if (RHS.second.first.size()) {
- return false;
- }
-
- return true;
- });
-
- if (!m_Alternatives.empty()) {
- m_Origin = m_Alternatives.back().first;
- m_Archive = m_Alternatives.back().second;
- m_Alternatives.pop_back();
- }
+ m_Files.clear();
+ m_FilesLookup.clear();
+ m_SubDirectories.clear();
+ m_SubDirectoriesLookup.clear();
}
-bool FileEntry::isFromArchive(std::wstring archiveName) const
+void DirectoryEntry::addFromOrigin(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ DirectoryStats& stats)
{
- if (archiveName.length() == 0) {
- return m_Archive.first.length() != 0;
- }
-
- if (m_Archive.first.compare(archiveName) == 0) {
- return true;
- }
-
- for (auto alternative : m_Alternatives) {
- if (alternative.second.first.compare(archiveName) == 0) {
- return true;
- }
- }
-
- return false;
+ env::DirectoryWalker walker;
+ addFromOrigin(walker, originName, directory, priority, stats);
}
-std::wstring FileEntry::getFullPath(int originID) const
+void DirectoryEntry::addFromOrigin(
+ env::DirectoryWalker& walker, const std::wstring &originName,
+ const std::wstring &directory, int priority, DirectoryStats& stats)
{
- if (originID == -1) {
- bool ignore = false;
- originID = getOrigin(ignore);
- }
+ FilesOrigin &origin = createOrigin(originName, directory, priority, stats);
- // base directory for origin
- const auto* o = m_Parent->findOriginByID(originID);
- if (!o) {
- return {};
+ if (!directory.empty()) {
+ addFiles(walker, origin, directory, stats);
}
- std::wstring result = o->getPath();
-
- // all intermediate directories
- recurseParents(result, m_Parent);
-
- return result + L"\\" + m_Name;
+ m_Populated = true;
}
-std::wstring FileEntry::getRelativePath() const
+void DirectoryEntry::addFromList(
+ const std::wstring &originName, const std::wstring &directory,
+ env::Directory& root, int priority, DirectoryStats& stats)
{
- std::wstring result;
-
- // all intermediate directories
- recurseParents(result, m_Parent);
+ stats = {};
- return result + L"\\" + m_Name;
+ FilesOrigin &origin = createOrigin(originName, directory, priority, stats);
+ addDir(origin, root, stats);
}
-bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const
+void DirectoryEntry::addDir(
+ FilesOrigin& origin, env::Directory& d, DirectoryStats& stats)
{
- if (parent == nullptr) {
- return false;
- } else {
- // don't append the topmost parent because it is the virtual data-root
- if (recurseParents(path, parent->getParent())) {
- path.append(L"\\").append(parent->getName());
+ elapsed(stats.dirTimes, [&]{
+ for (auto& sd : d.dirs) {
+ auto* sdirEntry = getSubDirectory(sd, true, stats, origin.getID());
+ sdirEntry->addDir(origin, sd, stats);
}
+ });
- return true;
- }
-}
-
-
-FilesOrigin::FilesOrigin()
- : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0)
-{
-}
-
-FilesOrigin::FilesOrigin(const FilesOrigin &reference)
- : m_ID(reference.m_ID)
- , m_Disabled(reference.m_Disabled)
- , m_Name(reference.m_Name)
- , m_Path(reference.m_Path)
- , m_Priority(reference.m_Priority)
- , m_FileRegister(reference.m_FileRegister)
- , m_OriginConnection(reference.m_OriginConnection)
-{
-}
-
-FilesOrigin::FilesOrigin(
- int ID, const std::wstring &name, const std::wstring &path, int priority,
- boost::shared_ptr<MOShared::FileRegister> fileRegister,
- boost::shared_ptr<MOShared::OriginConnection> originConnection) :
- m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path),
- m_Priority(priority), m_FileRegister(fileRegister),
- m_OriginConnection(originConnection)
-{
-}
-
-void FilesOrigin::setPriority(int priority)
-{
- m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority);
-
- m_Priority = priority;
-}
-
-void FilesOrigin::setName(const std::wstring &name)
-{
- m_OriginConnection.lock()->changeNameLookup(m_Name, name);
+ elapsed(stats.fileTimes, [&]{
+ for (auto& f : d.files) {
+ insert(f, origin, L"", -1, stats);
+ }
+ });
- // change path too
- if (tail(m_Path, m_Name.length()) == m_Name) {
- m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name);
- }
+ elapsed(stats.sortTimes, [&]{
+ std::sort(
+ m_SubDirectories.begin(),
+ m_SubDirectories.end(),
+ &DirCompareByName);
+ });
- m_Name = name;
+ m_Populated = true;
}
-std::vector<FileEntry::Ptr> FilesOrigin::getFiles() const
+void DirectoryEntry::addFromAllBSAs(
+ const std::wstring& originName, const std::wstring& directory,
+ int priority, const std::vector<std::wstring>& archives,
+ const std::set<std::wstring>& enabledArchives,
+ const std::vector<std::wstring>& loadOrder,
+ DirectoryStats& stats)
{
- std::vector<FileEntry::Ptr> result;
+ for (const auto& archive : archives) {
+ const std::filesystem::path archivePath(archive);
+ const auto filename = archivePath.filename().native();
- for (FileEntry::Index fileIdx : m_Files) {
- if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) {
- result.push_back(p);
+ if (!enabledArchives.contains(filename)) {
+ continue;
}
- }
- return result;
-}
+ const auto filenameLc = ToLowerCopy(filename);
-FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const
-{
- return m_FileRegister.lock()->getFile(index);
-}
+ int order = -1;
-void FilesOrigin::enable(bool enabled, time_t notAfter)
-{
- if (!enabled) {
- std::set<FileEntry::Index> copy = m_Files;
- m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter);
- m_Files.clear();
- }
-
- m_Disabled = !enabled;
-}
-
-void FilesOrigin::removeFile(FileEntry::Index index)
-{
- auto iter = m_Files.find(index);
-
- if (iter != m_Files.end()) {
- m_Files.erase(iter);
- }
-}
+ for (auto plugin : loadOrder)
+ {
+ const auto pluginNameLc =
+ ToLowerCopy(std::filesystem::path(plugin).stem().native());
-bool FilesOrigin::containsArchive(std::wstring archiveName)
-{
- for (FileEntry::Index fileIdx : m_Files) {
- if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) {
- if (p->isFromArchive(archiveName)) {
- return true;
+ if (filenameLc.starts_with(pluginNameLc + L" - ") ||
+ filenameLc.starts_with(pluginNameLc + L".")) {
+ auto itor = std::find(loadOrder.begin(), loadOrder.end(), plugin);
+ if (itor != loadOrder.end()) {
+ order = std::distance(loadOrder.begin(), itor);
+ }
}
}
- }
- return false;
-}
-
-
-FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection)
- : m_OriginConnection(originConnection)
-{
-}
-
-bool FileRegister::indexValid(FileEntry::Index index) const
-{
- return (m_Files.find(index) != m_Files.end());
-}
-
-FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent)
-{
- FileEntry::Index index = generateIndex();
-
- auto r = m_Files.insert_or_assign(
- index, FileEntry::Ptr(new FileEntry(index, name, parent)));
-
- return r.first->second;
-}
-
-FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const
-{
- auto iter = m_Files.find(index);
-
- if (iter != m_Files.end()) {
- return iter->second;
- } else {
- return FileEntry::Ptr();
- }
-}
-
-bool FileRegister::removeFile(FileEntry::Index index)
-{
- auto iter = m_Files.find(index);
-
- if (iter != m_Files.end()) {
- unregisterFile(iter->second);
- m_Files.erase(index);
- return true;
- } else {
- log::error(QObject::tr("invalid file index for remove: {}").toStdString(), index);
- return false;
- }
-}
-
-void FileRegister::removeOrigin(FileEntry::Index index, int originID)
-{
- auto iter = m_Files.find(index);
-
- if (iter != m_Files.end()) {
- if (iter->second->removeOrigin(originID)) {
- unregisterFile(iter->second);
- m_Files.erase(iter);
- }
- } else {
- log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index);
+ addFromBSA(
+ originName, directory, archivePath.native(),
+ priority, order, stats);
}
}
-void FileRegister::removeOriginMulti(
- std::set<FileEntry::Index> indices, int originID, time_t notAfter)
+void DirectoryEntry::addFromBSA(
+ const std::wstring& originName, const std::wstring& directory,
+ const std::wstring& archivePath, int priority, int order, DirectoryStats& stats)
{
- std::vector<FileEntry::Ptr> removedFiles;
-
- for (auto iter = indices.begin(); iter != indices.end(); ) {
- auto pos = m_Files.find(*iter);
-
- if (pos != m_Files.end()
- && (pos->second->lastAccessed() < notAfter)
- && pos->second->removeOrigin(originID)) {
- removedFiles.push_back(pos->second);
- m_Files.erase(pos);
- ++iter;
- } else {
- indices.erase(iter++);
- }
- }
-
- // optimization: this is only called when disabling an origin and in this case
- // we don't have to remove the file from the origin
-
- // need to remove files from their parent directories. multiple ways to go
- // about this:
- // a) for each file, search its parents file-list (preferably by name) and
- // remove what is found
- // b) gather the parent directories, go through the file list for each once
- // and remove all files that have been removed
- //
- // the latter should be faster when there are many files in few directories.
- // since this is called only when disabling an origin that is probably
- // frequently the case
-
- std::set<DirectoryEntry*> parents;
- for (const FileEntry::Ptr &file : removedFiles) {
- if (file->getParent() != nullptr) {
- parents.insert(file->getParent());
- }
- }
+ FilesOrigin& origin = createOrigin(originName, directory, priority, stats);
+ const auto archiveName = std::filesystem::path(archivePath).filename().native();
- for (DirectoryEntry *parent : parents) {
- parent->removeFiles(indices);
+ if (containsArchive(archiveName)) {
+ return;
}
-}
-
-void FileRegister::sortOrigins()
-{
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- iter->second->sortOrigins();
- }
-}
-
-FileEntry::Index FileRegister::generateIndex()
-{
- static std::atomic<FileEntry::Index> sIndex(0);
- return sIndex++;
-}
-
-void FileRegister::unregisterFile(FileEntry::Ptr file)
-{
- bool ignore;
- // unregister from origin
- int originID = file->getOrigin(ignore);
- m_OriginConnection->getByID(originID).removeFile(file->getIndex());
- const auto& alternatives = file->getAlternatives();
+ BSA::Archive archive;
+ BSA::EErrorCode res = archive.read(ToString(archivePath, false).c_str(), false);
- for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
- m_OriginConnection->getByID(iter->first).removeFile(file->getIndex());
+ if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
+ log::error("invalid bsa '{}', error {}", archivePath, res);
+ return;
}
- // unregister from directory
- if (file->getParent() != nullptr) {
- file->getParent()->removeFile(file->getIndex());
- }
-}
-
-
-DirectoryEntry::DirectoryEntry(
- const std::wstring &name, DirectoryEntry *parent, int originID) :
- m_OriginConnection(new OriginConnection),
- m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true)
-{
- m_FileRegister.reset(new FileRegister(m_OriginConnection));
- m_Origins.insert(originID);
-}
-
-DirectoryEntry::DirectoryEntry(
- const std::wstring &name, DirectoryEntry *parent, int originID,
- boost::shared_ptr<FileRegister> fileRegister,
- boost::shared_ptr<OriginConnection> originConnection) :
- m_FileRegister(fileRegister), m_OriginConnection(originConnection),
- m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false)
-{
- m_Origins.insert(originID);
-}
-
-DirectoryEntry::~DirectoryEntry()
-{
- clear();
-}
-
-void DirectoryEntry::clear()
-{
- m_Files.clear();
- m_FilesLookup.clear();
+ std::error_code ec;
+ const auto lwt = std::filesystem::last_write_time(archivePath, ec);
+ FILETIME ft = {};
- for (DirectoryEntry *entry : m_SubDirectories) {
- delete entry;
+ if (ec) {
+ log::warn(
+ "failed to get last modified date for '{}', {}",
+ archivePath, ec.message());
+ } else {
+ ft = ToFILETIME(lwt);
}
- m_SubDirectories.clear();
- m_SubDirectoriesLookup.clear();
-}
-
-void DirectoryEntry::addFromOrigin(
- const std::wstring &originName, const std::wstring &directory, int priority)
-{
- FilesOrigin &origin = createOrigin(originName, directory, priority);
-
- if (directory.length() != 0) {
- boost::scoped_array<wchar_t> buffer(new wchar_t[MAXPATH_UNICODE + 1]);
- memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1);
- int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str());
- buffer.get()[offset] = L'\0';
- addFiles(origin, buffer.get(), offset);
- }
+ addFiles(origin, archive.getRoot(), ft, archiveName, order, stats);
m_Populated = true;
}
-void DirectoryEntry::addFromBSA(
- const std::wstring &originName, std::wstring &directory,
- const std::wstring &fileName, int priority, int order)
-{
- FilesOrigin &origin = createOrigin(originName, directory, priority);
-
- WIN32_FILE_ATTRIBUTE_DATA fileData;
- if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) {
- throw windows_error(QObject::tr("failed to determine file time").toStdString());
- }
-
- FILETIME now;
- ::GetSystemTimeAsFileTime(&now);
-
- const double clfSecondsPer100ns = 100. * 1.E-9;
-
- ((ULARGE_INTEGER *)&now)->QuadPart -= ((double)5) / clfSecondsPer100ns;
-
- size_t namePos = fileName.find_last_of(L"\\/");
- if (namePos == std::wstring::npos) {
- namePos = 0;
- }
- else {
- ++namePos;
- }
-
- if (!containsArchive(fileName.substr(namePos)) || ::CompareFileTime(&fileData.ftLastWriteTime, &now) > 0) {
- BSA::Archive archive;
- BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false);
-
- if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
- std::ostringstream stream;
-
- stream
- << QObject::tr("invalid bsa file: ").toStdString()
- << ToString(fileName, false)
- << " error code " << res << " - " << ::GetLastError();
-
- throw std::runtime_error(stream.str());
- }
-
- addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos), order);
- m_Populated = true;
- }
-}
-
void DirectoryEntry::propagateOrigin(int origin)
{
- m_Origins.insert(origin);
+ {
+ std::scoped_lock lock(m_OriginsMutex);
+ m_Origins.insert(origin);
+ }
if (m_Parent != nullptr) {
m_Parent->propagateOrigin(origin);
@@ -769,7 +278,7 @@ int DirectoryEntry::anyOrigin() const
bool ignore;
for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- FileEntry::Ptr entry = m_FileRegister->getFile(iter->second);
+ FileEntryPtr entry = m_FileRegister->getFile(iter->second);
if ((entry.get() != nullptr) && !entry->isFromArchive()) {
return entry->getOrigin(ignore);
}
@@ -777,9 +286,9 @@ int DirectoryEntry::anyOrigin() const
// if we got here, no file directly within this directory is a valid indicator for a mod, thus
// we continue looking in subdirectories
- for (DirectoryEntry *entry : m_SubDirectories) {
+ for (DirectoryEntry* entry : m_SubDirectories) {
int res = entry->anyOrigin();
- if (res != -1){
+ if (res != InvalidOriginID){
return res;
}
}
@@ -787,9 +296,9 @@ int DirectoryEntry::anyOrigin() const
return *(m_Origins.begin());
}
-std::vector<FileEntry::Ptr> DirectoryEntry::getFiles() const
+std::vector<FileEntryPtr> DirectoryEntry::getFiles() const
{
- std::vector<FileEntry::Ptr> result;
+ std::vector<FileEntryPtr> result;
for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
result.push_back(m_FileRegister->getFile(iter->second));
@@ -798,7 +307,7 @@ std::vector<FileEntry::Ptr> DirectoryEntry::getFiles() const
return result;
}
-DirectoryEntry *DirectoryEntry::findSubDirectory(
+DirectoryEntry* DirectoryEntry::findSubDirectory(
const std::wstring &name, bool alreadyLowerCase) const
{
SubDirectoriesLookup::const_iterator itor;
@@ -816,37 +325,38 @@ DirectoryEntry *DirectoryEntry::findSubDirectory(
return itor->second;
}
-DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path)
+DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path)
{
- return getSubDirectoryRecursive(path, false, -1);
+ DirectoryStats dummy;
+ return getSubDirectoryRecursive(path, false, dummy, InvalidOriginID);
}
-const FileEntry::Ptr DirectoryEntry::findFile(
+const FileEntryPtr DirectoryEntry::findFile(
const std::wstring &name, bool alreadyLowerCase) const
{
FilesLookup::const_iterator iter;
if (alreadyLowerCase) {
- iter = m_FilesLookup.find(FileKey(name));
+ iter = m_FilesLookup.find(DirectoryEntryFileKey(name));
} else {
- iter = m_FilesLookup.find(FileKey(ToLowerCopy(name)));
+ iter = m_FilesLookup.find(DirectoryEntryFileKey(ToLowerCopy(name)));
}
if (iter != m_FilesLookup.end()) {
return m_FileRegister->getFile(iter->second);
} else {
- return FileEntry::Ptr();
+ return FileEntryPtr();
}
}
-const FileEntry::Ptr DirectoryEntry::findFile(const FileKey& key) const
+const FileEntryPtr DirectoryEntry::findFile(const DirectoryEntryFileKey& key) const
{
auto iter = m_FilesLookup.find(key);
if (iter != m_FilesLookup.end()) {
return m_FileRegister->getFile(iter->second);
} else {
- return FileEntry::Ptr();
+ return FileEntryPtr();
}
}
@@ -858,7 +368,7 @@ bool DirectoryEntry::hasFile(const std::wstring& name) const
bool DirectoryEntry::containsArchive(std::wstring archiveName)
{
for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- FileEntry::Ptr entry = m_FileRegister->getFile(iter->second);
+ FileEntryPtr entry = m_FileRegister->getFile(iter->second);
if (entry->isFromArchive(archiveName)) {
return true;
}
@@ -867,8 +377,8 @@ bool DirectoryEntry::containsArchive(std::wstring archiveName)
return false;
}
-const FileEntry::Ptr DirectoryEntry::searchFile(
- const std::wstring &path, const DirectoryEntry **directory) const
+const FileEntryPtr DirectoryEntry::searchFile(
+ const std::wstring &path, const DirectoryEntry** directory) const
{
if (directory != nullptr) {
*directory = nullptr;
@@ -880,7 +390,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(
*directory = this;
}
- return FileEntry::Ptr();
+ return FileEntryPtr();
}
const size_t len = path.find_first_of(L"\\/");
@@ -892,7 +402,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(
if (iter != m_Files.end()) {
return m_FileRegister->getFile(iter->second);
} else if (directory != nullptr) {
- DirectoryEntry *temp = findSubDirectory(path);
+ DirectoryEntry* temp = findSubDirectory(path);
if (temp != nullptr) {
*directory = temp;
}
@@ -900,41 +410,27 @@ const FileEntry::Ptr DirectoryEntry::searchFile(
} else {
// file is in a subdirectory, recurse into the matching subdirectory
std::wstring pathComponent = path.substr(0, len);
- DirectoryEntry *temp = findSubDirectory(pathComponent);
+ DirectoryEntry* temp = findSubDirectory(pathComponent);
if (temp != nullptr) {
if (len >= path.size()) {
log::error(QObject::tr("unexpected end of path").toStdString());
- return FileEntry::Ptr();
+ return FileEntryPtr();
}
return temp->searchFile(path.substr(len + 1), directory);
}
}
- return FileEntry::Ptr();
-}
-
-void DirectoryEntry::insertFile(
- const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime)
-{
- size_t pos = filePath.find_first_of(L"\\/");
-
- if (pos == std::string::npos) {
- this->insert(filePath, origin, fileTime, std::wstring(), -1);
- } else {
- std::wstring dirName = filePath.substr(0, pos);
- std::wstring rest = filePath.substr(pos + 1);
- getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime);
- }
+ return FileEntryPtr();
}
-void DirectoryEntry::removeFile(FileEntry::Index index)
+void DirectoryEntry::removeFile(FileIndex index)
{
removeFileFromList(index);
}
-bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin)
+bool DirectoryEntry::removeFile(const std::wstring &filePath, int* origin)
{
size_t pos = filePath.find_first_of(L"\\/");
@@ -944,7 +440,9 @@ bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin)
std::wstring dirName = filePath.substr(0, pos);
std::wstring rest = filePath.substr(pos + 1);
- DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+
+ DirectoryStats dummy;
+ DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy);
if (entry != nullptr) {
return entry->removeFile(rest, origin);
@@ -959,7 +457,7 @@ void DirectoryEntry::removeDir(const std::wstring &path)
if (pos == std::string::npos) {
for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- DirectoryEntry *entry = *iter;
+ DirectoryEntry* entry = *iter;
if (CaseInsensitiveEqual(entry->getName(), path)) {
entry->removeDirRecursive();
@@ -971,7 +469,9 @@ void DirectoryEntry::removeDir(const std::wstring &path)
} else {
std::wstring dirName = path.substr(0, pos);
std::wstring rest = path.substr(pos + 1);
- DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+
+ DirectoryStats dummy;
+ DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy);
if (entry != nullptr) {
entry->removeDir(rest);
@@ -979,7 +479,7 @@ void DirectoryEntry::removeDir(const std::wstring &path)
}
}
-bool DirectoryEntry::remove(const std::wstring &fileName, int *origin)
+bool DirectoryEntry::remove(const std::wstring &fileName, int* origin)
{
const auto lcFileName = ToLowerCopy(fileName);
@@ -988,7 +488,7 @@ bool DirectoryEntry::remove(const std::wstring &fileName, int *origin)
if (iter != m_Files.end()) {
if (origin != nullptr) {
- FileEntry::Ptr entry = m_FileRegister->getFile(iter->second);
+ FileEntryPtr entry = m_FileRegister->getFile(iter->second);
if (entry.get() != nullptr) {
bool ignore;
*origin = entry->getOrigin(ignore);
@@ -1007,98 +507,204 @@ bool DirectoryEntry::hasContentsFromOrigin(int originID) const
}
FilesOrigin &DirectoryEntry::createOrigin(
- const std::wstring &originName, const std::wstring &directory, int priority)
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ DirectoryStats& stats)
{
- if (m_OriginConnection->exists(originName)) {
- FilesOrigin &origin = m_OriginConnection->getByName(originName);
- origin.enable(true);
- return origin;
+ auto r = m_OriginConnection->getOrCreate(
+ originName, directory, priority,
+ m_FileRegister, m_OriginConnection, stats);
+
+ if (r.second) {
+ ++stats.originCreate;
} else {
- return m_OriginConnection->createOrigin(
- originName, directory, priority, m_FileRegister, m_OriginConnection);
+ ++stats.originExists;
}
+
+ return r.first;
}
-void DirectoryEntry::removeFiles(const std::set<FileEntry::Index> &indices)
+void DirectoryEntry::removeFiles(const std::set<FileIndex> &indices)
{
removeFilesFromList(indices);
}
-FileEntry::Ptr DirectoryEntry::insert(
- const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime,
- const std::wstring &archive, int order)
+FileEntryPtr DirectoryEntry::insert(
+ std::wstring_view fileName, FilesOrigin &origin, FILETIME fileTime,
+ std::wstring_view archive, int order, DirectoryStats& stats)
{
std::wstring fileNameLower = ToLowerCopy(fileName);
+ FileEntryPtr fe;
- auto iter = m_Files.find(fileNameLower);
- FileEntry::Ptr file;
+ DirectoryEntryFileKey key(std::move(fileNameLower));
- if (iter != m_Files.end()) {
- file = m_FileRegister->getFile(iter->second);
- } else {
- file = m_FileRegister->createFile(fileName, this);
- addFileToList(std::move(fileNameLower), file->getIndex());
+ {
+ std::unique_lock lock(m_FilesMutex);
+
+ FilesLookup::iterator itor;
+
+ elapsed(stats.filesLookupTimes, [&]{
+ itor = m_FilesLookup.find(key);
+ });
+
+ if (itor != m_FilesLookup.end()) {
+ lock.unlock();
+ ++stats.fileExists;
+ fe = m_FileRegister->getFile(itor->second);
+ } else {
+ ++stats.fileCreate;
+ fe = m_FileRegister->createFile(
+ std::wstring(fileName.begin(), fileName.end()), this, stats);
- // fileNameLower has moved from this point
+ elapsed(stats.addFileTimes, [&] {
+ addFileToList(std::move(key.value), fe->getIndex());
+ });
+
+ // fileNameLower has moved from this point
+ }
}
- file->addOrigin(origin.getID(), fileTime, archive, order);
- origin.addFile(file->getIndex());
+ elapsed(stats.addOriginToFileTimes, [&]{
+ fe->addOrigin(origin.getID(), fileTime, archive, order);
+ });
- return file;
+ elapsed(stats.addFileToOriginTimes, [&]{
+ origin.addFile(fe->getIndex());
+ });
+
+ return fe;
}
-void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset)
+FileEntryPtr DirectoryEntry::insert(
+ env::File& file, FilesOrigin &origin, std::wstring_view archive, int order,
+ DirectoryStats& stats)
{
- WIN32_FIND_DATAW findData;
+ FileEntryPtr fe;
- _snwprintf_s(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, _TRUNCATE, L"\\*");
+ {
+ std::unique_lock lock(m_FilesMutex);
- HANDLE searchHandle = nullptr;
+ FilesMap::iterator itor;
- if (SupportOptimizedFind()) {
- searchHandle = ::FindFirstFileExW(
- buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr,
- FIND_FIRST_EX_LARGE_FETCH);
- } else {
- searchHandle = ::FindFirstFileExW(
- buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0);
+ elapsed(stats.filesLookupTimes, [&]{
+ itor = m_Files.find(file.lcname);
+ });
+
+ if (itor != m_Files.end()) {
+ lock.unlock();
+ ++stats.fileExists;
+ fe = m_FileRegister->getFile(itor->second);
+ } else {
+ ++stats.fileCreate;
+ fe = m_FileRegister->createFile(std::move(file.name), this, stats);
+ // file.name has been moved from this point
+
+ elapsed(stats.addFileTimes, [&]{
+ addFileToList(std::move(file.lcname), fe->getIndex());
+ });
+
+ // file.lcname has been moved from this point
+ }
}
- if (searchHandle != INVALID_HANDLE_VALUE) {
- BOOL result = true;
+ elapsed(stats.addOriginToFileTimes, [&]{
+ fe->addOrigin(origin.getID(), file.lastModified, archive, order);
+ });
+
+ elapsed(stats.addFileToOriginTimes, [&]{
+ origin.addFile(fe->getIndex());
+ });
- while (result) {
- if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- if ((wcscmp(findData.cFileName, L".") != 0) &&
- (wcscmp(findData.cFileName, L"..") != 0)) {
- int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName);
+ return fe;
+}
- // recurse into subdirectories
- DirectoryEntry* sd = getSubDirectory(findData.cFileName, true, origin.getID());
- sd->addFiles(origin, buffer, bufferOffset + offset);
- }
- } else {
- insert(findData.cFileName, origin, findData.ftLastWriteTime, L"", -1);
- }
+struct DirectoryEntry::Context
+{
+ FilesOrigin& origin;
+ DirectoryStats& stats;
+ std::stack<DirectoryEntry*> current;
+};
+
+void DirectoryEntry::addFiles(
+ env::DirectoryWalker& walker, FilesOrigin &origin,
+ const std::wstring& path, DirectoryStats& stats)
+{
+ Context cx = {origin, stats};
+ cx.current.push(this);
+
+ walker.forEachEntry(path, &cx,
+ [](void* pcx, std::wstring_view path)
+ {
+ onDirectoryStart((Context*)pcx, path);
+ },
+
+ [](void* pcx, std::wstring_view path)
+ {
+ onDirectoryEnd((Context*)pcx, path);
+ },
- result = ::FindNextFileW(searchHandle, &findData);
+ [](void* pcx, std::wstring_view path, FILETIME ft)
+ {
+ onFile((Context*)pcx, path, ft);
}
+ );
+
+ {
+ std::scoped_lock lock(m_SubDirMutex);
+
+ std::sort(
+ m_SubDirectories.begin(),
+ m_SubDirectories.end(),
+ &DirCompareByName);
}
+}
+
+void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path)
+{
+ elapsed(cx->stats.dirTimes, [&] {
+ auto* sd = cx->current.top()->getSubDirectory(
+ path, true, cx->stats, cx->origin.getID());
+
+ cx->current.push(sd);
+ });
+}
+
+void DirectoryEntry::onDirectoryEnd(Context* cx, std::wstring_view path)
+{
+ elapsed(cx->stats.dirTimes, [&] {
+ auto* current = cx->current.top();
+
+ {
+ std::scoped_lock lock(current->m_SubDirMutex);
+
+ std::sort(
+ current->m_SubDirectories.begin(),
+ current->m_SubDirectories.end(),
+ &DirCompareByName);
+ }
- std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName);
- ::FindClose(searchHandle);
+ cx->current.pop();
+ });
+}
+
+void DirectoryEntry::onFile(Context* cx, std::wstring_view path, FILETIME ft)
+{
+ elapsed(cx->stats.fileTimes, [&]{
+ cx->current.top()->insert(path, cx->origin, ft, L"", -1, cx->stats);
+ });
}
void DirectoryEntry::addFiles(
- FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime,
- const std::wstring &archiveName, int order)
+ FilesOrigin& origin, const BSA::Folder::Ptr archiveFolder, FILETIME fileTime,
+ const std::wstring& archiveName, int order, DirectoryStats& stats)
{
// add files
- for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) {
- BSA::File::Ptr file = archiveFolder->getFile(fileIdx);
+ const auto fileCount = archiveFolder->getNumFiles();
+ for (unsigned int i=0; i<fileCount; ++i) {
+ const BSA::File::Ptr file = archiveFolder->getFile(i);
- auto f = insert(ToWString(file->getName(), true), origin, fileTime, archiveName, order);
+ auto f = insert(
+ ToWString(file->getName(), true), origin, fileTime,
+ archiveName, order, stats);
if (f) {
if (file->getUncompressedFileSize() > 0) {
@@ -1110,28 +716,45 @@ void DirectoryEntry::addFiles(
}
// recurse into subdirectories
- for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) {
- BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx);
- DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID());
+ const auto dirCount = archiveFolder->getNumSubFolders();
+ for (unsigned int i=0; i<dirCount; ++i) {
+ const BSA::Folder::Ptr folder = archiveFolder->getSubFolder(i);
+
+ DirectoryEntry* folderEntry = getSubDirectoryRecursive(
+ ToWString(folder->getName(), true), true, stats, origin.getID());
- folderEntry->addFiles(origin, folder, fileTime, archiveName, order);
+ folderEntry->addFiles(origin, folder, fileTime, archiveName, order, stats);
}
}
-DirectoryEntry *DirectoryEntry::getSubDirectory(
- const std::wstring &name, bool create, int originID)
+DirectoryEntry* DirectoryEntry::getSubDirectory(
+ std::wstring_view name, bool create, DirectoryStats& stats, int originID)
{
- for (DirectoryEntry *entry : m_SubDirectories) {
- if (CaseInsensitiveEqual(entry->getName(), name)) {
- return entry;
- }
+ std::wstring nameLc = ToLowerCopy(name);
+
+ std::scoped_lock lock(m_SubDirMutex);
+
+ SubDirectoriesLookup::iterator itor;
+ elapsed(stats.subdirLookupTimes, [&] {
+ itor = m_SubDirectoriesLookup.find(nameLc);
+ });
+
+ if (itor != m_SubDirectoriesLookup.end()) {
+ ++stats.subdirExists;
+ return itor->second;
}
if (create) {
+ ++stats.subdirCreate;
+
auto* entry = new DirectoryEntry(
- name, this, originID, m_FileRegister, m_OriginConnection);
+ std::wstring(name.begin(), name.end()), this, originID,
+ m_FileRegister, m_OriginConnection);
- addDirectoryToList(entry);
+ elapsed(stats.addDirectoryTimes, [&] {
+ addDirectoryToList(entry, std::move(nameLc));
+ // nameLc is moved from this point
+ });
return entry;
} else {
@@ -1139,8 +762,44 @@ DirectoryEntry *DirectoryEntry::getSubDirectory(
}
}
-DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(
- const std::wstring &path, bool create, int originID)
+DirectoryEntry* DirectoryEntry::getSubDirectory(
+ env::Directory& dir, bool create, DirectoryStats& stats, int originID)
+{
+ SubDirectoriesLookup::iterator itor;
+
+ std::scoped_lock lock(m_SubDirMutex);
+
+ elapsed(stats.subdirLookupTimes, [&] {
+ itor = m_SubDirectoriesLookup.find(dir.lcname);
+ });
+
+ if (itor != m_SubDirectoriesLookup.end()) {
+ ++stats.subdirExists;
+ return itor->second;
+ }
+
+ if (create) {
+ ++stats.subdirCreate;
+
+ auto* entry = new DirectoryEntry(
+ std::move(dir.name), this, originID,
+ m_FileRegister, m_OriginConnection);
+ // dir.name is moved from this point
+
+ elapsed(stats.addDirectoryTimes, [&]{
+ addDirectoryToList(entry, std::move(dir.lcname));
+ });
+
+ // dir.lcname is moved from this point
+
+ return entry;
+ } else {
+ return nullptr;
+ }
+}
+
+DirectoryEntry* DirectoryEntry::getSubDirectoryRecursive(
+ const std::wstring& path, bool create, DirectoryStats& stats, int originID)
{
if (path.length() == 0) {
// path ended with a backslash?
@@ -1150,14 +809,16 @@ DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(
const size_t pos = path.find_first_of(L"\\/");
if (pos == std::wstring::npos) {
- return getSubDirectory(path, create);
+ return getSubDirectory(path, create, stats);
} else {
- DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID);
+ DirectoryEntry* nextChild = getSubDirectory(
+ path.substr(0, pos), create, stats, originID);
if (nextChild == nullptr) {
return nullptr;
} else {
- return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID);
+ return nextChild->getSubDirectoryRecursive(
+ path.substr(pos + 1), create, stats, originID);
}
}
}
@@ -1170,7 +831,7 @@ void DirectoryEntry::removeDirRecursive()
m_FilesLookup.clear();
- for (DirectoryEntry *entry : m_SubDirectories) {
+ for (DirectoryEntry* entry : m_SubDirectories) {
entry->removeDirRecursive();
delete entry;
}
@@ -1179,10 +840,10 @@ void DirectoryEntry::removeDirRecursive()
m_SubDirectoriesLookup.clear();
}
-void DirectoryEntry::addDirectoryToList(DirectoryEntry* e)
+void DirectoryEntry::addDirectoryToList(DirectoryEntry* e, std::wstring nameLc)
{
m_SubDirectories.push_back(e);
- m_SubDirectoriesLookup.emplace(ToLowerCopy(e->getName()), e);
+ m_SubDirectoriesLookup.emplace(std::move(nameLc), e);
}
void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor)
@@ -1204,7 +865,7 @@ void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor)
m_SubDirectories.erase(itor);
}
-void DirectoryEntry::removeFileFromList(FileEntry::Index index)
+void DirectoryEntry::removeFileFromList(FileIndex index)
{
auto removeFrom = [&](auto& list) {
auto iter = std::find_if(
@@ -1234,7 +895,7 @@ void DirectoryEntry::removeFileFromList(FileEntry::Index index)
removeFrom(m_Files);
}
-void DirectoryEntry::removeFilesFromList(const std::set<FileEntry::Index>& indices)
+void DirectoryEntry::removeFilesFromList(const std::set<FileIndex>& indices)
{
for (auto iter = m_Files.begin(); iter != m_Files.end();) {
if (indices.find(iter->second) != indices.end()) {
@@ -1253,12 +914,79 @@ void DirectoryEntry::removeFilesFromList(const std::set<FileEntry::Index>& indic
}
}
-void DirectoryEntry::addFileToList(
- std::wstring fileNameLower, FileEntry::Index index)
+void DirectoryEntry::addFileToList(std::wstring fileNameLower, FileIndex index)
{
m_FilesLookup.emplace(fileNameLower, index);
m_Files.emplace(std::move(fileNameLower), index);
// fileNameLower has been moved from this point
}
+struct DumpFailed : public std::runtime_error
+{
+ using runtime_error::runtime_error;
+};
+
+void DirectoryEntry::dump(const std::wstring& file) const
+{
+ try
+ {
+ std::FILE* f = nullptr;
+ auto e = _wfopen_s(&f, file.c_str(), L"wb");
+
+ if (e != 0 || !f) {
+ throw DumpFailed(fmt::format(
+ "failed to open, {} ({})", std::strerror(e), e));
+ }
+
+ Guard g([&]{ std::fclose(f); });
+
+ dump(f, L"Data");
+ }
+ catch(DumpFailed& e)
+ {
+ log::error(
+ "failed to write list to '{}': {}",
+ QString::fromStdWString(file).toStdString(), e.what());
+ }
+}
+
+void DirectoryEntry::dump(std::FILE* f, const std::wstring& parentPath) const
+{
+ {
+ std::scoped_lock lock(m_FilesMutex);
+
+ for (auto&& index : m_Files) {
+ const auto file = m_FileRegister->getFile(index.second);
+ if (!file) {
+ continue;
+ }
+
+ if (file->isFromArchive()) {
+ // TODO: don't list files from archives. maybe make this an option?
+ continue;
+ }
+
+ const auto& o = m_OriginConnection->getByID(file->getOrigin());
+ const auto path = parentPath + L"\\" + file->getName();
+ const auto line = path + L"\t(" + o.getName() + L")\r\n";
+
+ const auto lineu8 = MOShared::ToString(line, true);
+
+ if (std::fwrite(lineu8.data(), lineu8.size(), 1, f) != 1) {
+ const auto e = errno;
+ throw DumpFailed(fmt::format(
+ "failed to write, {} ({})", std::strerror(e), e));
+ }
+ }
+ }
+
+ {
+ std::scoped_lock lock(m_SubDirMutex);
+ for (auto&& d : m_SubDirectories) {
+ const auto path = parentPath + L"\\" + d->m_Name;
+ d->dump(f, path);
+ }
+ }
+}
+
} // namespace MOShared
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h
index e26011d7..a28ceeae 100644
--- a/src/shared/directoryentry.h
+++ b/src/shared/directoryentry.h
@@ -17,25 +17,18 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef DIRECTORYENTRY_H
-#define DIRECTORYENTRY_H
+#ifndef MO_REGISTER_DIRECTORYENTRY_INCLUDED
+#define MO_REGISTER_DIRECTORYENTRY_INCLUDED
-
-#include <string>
-#include <set>
-#include <vector>
-#include <map>
-#include <cassert>
-#define WIN32_MEAN_AND_LEAN
-#include <Windows.h>
+#include "fileregister.h"
#include <bsatk.h>
-#ifndef Q_MOC_RUN
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#endif
-#include "util.h"
-namespace MOShared { struct DirectoryEntryFileKey; }
+namespace env
+{
+ class DirectoryWalker;
+ struct Directory;
+ struct File;
+}
namespace std
{
@@ -53,274 +46,23 @@ namespace std
namespace MOShared
{
-class DirectoryEntry;
-class OriginConnection;
-class FileRegister;
-
-
-class FileEntry
-{
-public:
- static constexpr uint64_t NoFileSize =
- std::numeric_limits<uint64_t>::max();
-
- typedef unsigned int Index;
- typedef boost::shared_ptr<FileEntry> Ptr;
-
- // a vector of {originId, {archiveName, order}}
- //
- // if a file is in an archive, archiveName is the name of the bsa and order
- // is the order of the associated plugin in the plugins list
- //
- // is a file is not in an archive, archiveName is empty and order is usually
- // -1
- typedef std::vector<std::pair<int, std::pair<std::wstring, int>>>
- AlternativesVector;
-
- FileEntry();
- FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent);
-
- Index getIndex() const
- {
- return m_Index;
- }
-
- time_t lastAccessed() const
- {
- return m_LastAccessed;
- }
-
- void addOrigin(
- int origin, FILETIME fileTime, const std::wstring &archive, int order);
-
- // remove the specified origin from the list of origins that contain this
- // file. if no origin is left, the file is effectively deleted and true is
- // returned. otherwise, false is returned
- bool removeOrigin(int origin);
-
- void sortOrigins();
-
- // gets the list of alternative origins (origins with lower priority than
- // the primary one). if sortOrigins has been called, it is sorted by priority
- // (ascending)
- const AlternativesVector &getAlternatives() const
- {
- return m_Alternatives;
- }
-
- const std::wstring &getName() const
- {
- return m_Name;
- }
-
- int getOrigin() const
- {
- return m_Origin;
- }
-
- int getOrigin(bool &archive) const
- {
- archive = (m_Archive.first.length() != 0);
- return m_Origin;
- }
-
- const std::pair<std::wstring, int> &getArchive() const
- {
- return m_Archive;
- }
-
- bool isFromArchive(std::wstring archiveName = L"") const;
-
- // if originID is -1, uses the main origin; if this file doesn't exist in the
- // given origin, returns an empty string
- //
- std::wstring getFullPath(int originID=-1) const;
-
- std::wstring getRelativePath() const;
-
- DirectoryEntry *getParent()
- {
- return m_Parent;
- }
-
- void setFileTime(FILETIME fileTime) const
- {
- m_FileTime = fileTime;
- }
-
- FILETIME getFileTime() const
- {
- return m_FileTime;
- }
-
- void setFileSize(uint64_t size, uint64_t compressedSize)
- {
- m_FileSize = size;
- m_CompressedFileSize = compressedSize;
- }
-
- uint64_t getFileSize() const
- {
- return m_FileSize;
- }
-
- uint64_t getCompressedFileSize() const
- {
- return m_CompressedFileSize;
- }
-
-private:
- Index m_Index;
- std::wstring m_Name;
- int m_Origin;
- std::pair<std::wstring, int> m_Archive;
- AlternativesVector m_Alternatives;
- DirectoryEntry *m_Parent;
- mutable FILETIME m_FileTime;
- uint64_t m_FileSize, m_CompressedFileSize;
-
- time_t m_LastAccessed;
-
- bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const;
-};
-
-
-// represents a mod or the data directory, providing files to the tree
-class FilesOrigin
-{
- friend class OriginConnection;
-
-public:
- FilesOrigin();
- FilesOrigin(const FilesOrigin &reference);
-
- // sets priority for this origin, but it will overwrite the existing mapping
- // for this priority, the previous origin will no longer be referenced
- void setPriority(int priority);
-
- int getPriority() const
- {
- return m_Priority;
- }
-
- void setName(const std::wstring &name);
- const std::wstring &getName() const
- {
- return m_Name;
- }
-
- int getID() const
- {
- return m_ID;
- }
-
- const std::wstring &getPath() const
- {
- return m_Path;
- }
-
- std::vector<FileEntry::Ptr> getFiles() const;
- FileEntry::Ptr findFile(FileEntry::Index index) const;
-
- void enable(bool enabled, time_t notAfter = LONG_MAX);
- bool isDisabled() const
- {
- return m_Disabled;
- }
-
- void addFile(FileEntry::Index index)
- {
- m_Files.insert(index);
- }
-
- void removeFile(FileEntry::Index index);
-
- bool containsArchive(std::wstring archiveName);
-
-private:
- int m_ID;
- bool m_Disabled;
- std::set<FileEntry::Index> m_Files;
- std::wstring m_Name;
- std::wstring m_Path;
- int m_Priority;
- boost::weak_ptr<FileRegister> m_FileRegister;
- boost::weak_ptr<OriginConnection> m_OriginConnection;
-
- FilesOrigin(
- int ID, const std::wstring &name, const std::wstring &path, int priority,
- boost::shared_ptr<FileRegister> fileRegister,
- boost::shared_ptr<OriginConnection> originConnection);
-};
-
-
-class FileRegister
-{
-public:
- FileRegister(boost::shared_ptr<OriginConnection> originConnection);
-
- bool indexValid(FileEntry::Index index) const;
-
- FileEntry::Ptr createFile(const std::wstring &name, DirectoryEntry *parent);
- FileEntry::Ptr getFile(FileEntry::Index index) const;
-
- size_t size() const
- {
- return m_Files.size();
- }
-
- bool removeFile(FileEntry::Index index);
- void removeOrigin(FileEntry::Index index, int originID);
- void removeOriginMulti(std::set<FileEntry::Index> indices, int originID, time_t notAfter);
-
- void sortOrigins();
-
-private:
- std::map<FileEntry::Index, FileEntry::Ptr> m_Files;
- boost::shared_ptr<OriginConnection> m_OriginConnection;
-
- FileEntry::Index generateIndex();
- void unregisterFile(FileEntry::Ptr file);
-};
-
-
-struct DirectoryEntryFileKey
-{
- DirectoryEntryFileKey(std::wstring v)
- : value(std::move(v)), hash(getHash(value))
- {
- }
-
- bool operator==(const DirectoryEntryFileKey& o) const
- {
- return (value == o.value);
- }
-
- static std::size_t getHash(const std::wstring& value)
- {
- return std::hash<std::wstring>()(value);
- }
-
- const std::wstring value;
- const std::size_t hash;
-};
-
-
class DirectoryEntry
{
public:
- using FileKey = DirectoryEntryFileKey;
-
DirectoryEntry(
- const std::wstring &name, DirectoryEntry *parent, int originID);
+ std::wstring name, DirectoryEntry* parent, OriginID originID);
DirectoryEntry(
- const std::wstring &name, DirectoryEntry *parent, int originID,
+ std::wstring name, DirectoryEntry* parent, OriginID originID,
boost::shared_ptr<FileRegister> fileRegister,
boost::shared_ptr<OriginConnection> originConnection);
~DirectoryEntry();
+ // noncopyable
+ DirectoryEntry(const DirectoryEntry&) = delete;
+ DirectoryEntry& operator=(const DirectoryEntry&) = delete;
+
void clear();
bool isPopulated() const
@@ -343,7 +85,7 @@ public:
return !m_Files.empty();
}
- const DirectoryEntry *getParent() const
+ const DirectoryEntry* getParent() const
{
return m_Parent;
}
@@ -351,16 +93,32 @@ public:
// add files to this directory (and subdirectories) from the specified origin.
// That origin may exist or not
void addFromOrigin(
- const std::wstring &originName,
- const std::wstring &directory, int priority);
+ const std::wstring& originName,
+ const std::wstring& directory, int priority, DirectoryStats& stats);
+
+ void addFromOrigin(
+ env::DirectoryWalker& walker, const std::wstring& originName,
+ const std::wstring& directory, int priority, DirectoryStats& stats);
+
+ void addFromAllBSAs(
+ const std::wstring& originName, const std::wstring& directory,
+ int priority, const std::vector<std::wstring>& archives,
+ const std::set<std::wstring>& enabledArchives,
+ const std::vector<std::wstring>& loadOrder,
+ DirectoryStats& stats);
void addFromBSA(
- const std::wstring &originName, std::wstring &directory,
- const std::wstring &fileName, int priority, int order);
+ const std::wstring& originName, const std::wstring& directory,
+ const std::wstring& archivePath, int priority, int order,
+ DirectoryStats& stats);
+
+ void addFromList(
+ const std::wstring& originName, const std::wstring& directory,
+ env::Directory& root, int priority, DirectoryStats& stats);
- void propagateOrigin(int origin);
+ void propagateOrigin(OriginID origin);
- const std::wstring &getName() const
+ const std::wstring& getName() const
{
return m_Name;
}
@@ -370,18 +128,18 @@ public:
return m_FileRegister;
}
- bool originExists(const std::wstring &name) const;
- FilesOrigin &getOriginByID(int ID) const;
- FilesOrigin &getOriginByName(const std::wstring &name) const;
- const FilesOrigin* findOriginByID(int ID) const;
+ bool originExists(const std::wstring& name) const;
+ FilesOrigin& getOriginByID(OriginID ID) const;
+ FilesOrigin& getOriginByName(const std::wstring& name) const;
+ const FilesOrigin* findOriginByID(OriginID ID) const;
- int anyOrigin() const;
+ OriginID anyOrigin() const;
- std::vector<FileEntry::Ptr> getFiles() const;
+ std::vector<FileEntryPtr> getFiles() const;
void getSubDirectories(
- std::vector<DirectoryEntry*>::const_iterator &begin,
- std::vector<DirectoryEntry*>::const_iterator &end) const
+ std::vector<DirectoryEntry*>::const_iterator& begin,
+ std::vector<DirectoryEntry*>::const_iterator& end) const
{
begin = m_SubDirectories.begin();
end = m_SubDirectories.end();
@@ -424,22 +182,22 @@ public:
}
}
- FileEntry::Ptr getFileByIndex(FileEntry::Index index) const
+ FileEntryPtr getFileByIndex(FileIndex index) const
{
return m_FileRegister->getFile(index);
}
- DirectoryEntry *findSubDirectory(
- const std::wstring &name, bool alreadyLowerCase=false) const;
+ DirectoryEntry* findSubDirectory(
+ const std::wstring& name, bool alreadyLowerCase=false) const;
- DirectoryEntry *findSubDirectoryRecursive(const std::wstring &path);
+ DirectoryEntry* findSubDirectoryRecursive(const std::wstring& path);
/** retrieve a file in this directory by name.
* @param name name of the file
* @return fileentry object for the file or nullptr if no file matches
*/
- const FileEntry::Ptr findFile(const std::wstring &name, bool alreadyLowerCase=false) const;
- const FileEntry::Ptr findFile(const FileKey& key) const;
+ const FileEntryPtr findFile(const std::wstring& name, bool alreadyLowerCase=false) const;
+ const FileEntryPtr findFile(const DirectoryEntryFileKey& key) const;
bool hasFile(const std::wstring& name) const;
bool containsArchive(std::wstring archiveName);
@@ -450,36 +208,36 @@ public:
// if directory is not nullptr, the referenced variable will be set to the
// path containing the file
//
- const FileEntry::Ptr searchFile(
- const std::wstring &path, const DirectoryEntry **directory=nullptr) const;
+ const FileEntryPtr searchFile(
+ const std::wstring& path, const DirectoryEntry** directory=nullptr) const;
- void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime);
-
- void removeFile(FileEntry::Index index);
+ void removeFile(FileIndex index);
// remove the specified file from the tree. This can be a path leading to a
// file in a subdirectory
- bool removeFile(const std::wstring &filePath, int *origin = nullptr);
+ bool removeFile(const std::wstring& filePath, OriginID* origin = nullptr);
/**
* @brief remove the specified directory
* @param path directory to remove
*/
- void removeDir(const std::wstring &path);
+ void removeDir(const std::wstring& path);
+
+ bool remove(const std::wstring& fileName, OriginID* origin);
- bool remove(const std::wstring &fileName, int *origin);
+ bool hasContentsFromOrigin(OriginID originID) const;
- bool hasContentsFromOrigin(int originID) const;
+ FilesOrigin& createOrigin(
+ const std::wstring& originName,
+ const std::wstring& directory, int priority, DirectoryStats& stats);
- FilesOrigin &createOrigin(
- const std::wstring &originName,
- const std::wstring &directory, int priority);
+ void removeFiles(const std::set<FileIndex>& indices);
- void removeFiles(const std::set<FileEntry::Index> &indices);
+ void dump(const std::wstring& file) const;
private:
- using FilesMap = std::map<std::wstring, FileEntry::Index>;
- using FilesLookup = std::unordered_map<FileKey, FileEntry::Index>;
+ using FilesMap = std::map<std::wstring, FileIndex>;
+ using FilesLookup = std::unordered_map<DirectoryEntryFileKey, FileIndex>;
using SubDirectories = std::vector<DirectoryEntry*>;
using SubDirectoriesLookup = std::unordered_map<std::wstring, DirectoryEntry*>;
@@ -492,39 +250,60 @@ private:
SubDirectories m_SubDirectories;
SubDirectoriesLookup m_SubDirectoriesLookup;
- DirectoryEntry *m_Parent;
- std::set<int> m_Origins;
+ DirectoryEntry* m_Parent;
+ std::set<OriginID> m_Origins;
bool m_Populated;
bool m_TopLevel;
+ mutable std::mutex m_SubDirMutex;
+ mutable std::mutex m_FilesMutex;
+ mutable std::mutex m_OriginsMutex;
- DirectoryEntry(const DirectoryEntry &reference);
+ FileEntryPtr insert(
+ std::wstring_view fileName, FilesOrigin& origin, FILETIME fileTime,
+ std::wstring_view archive, int order, DirectoryStats& stats);
- FileEntry::Ptr insert(
- const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime,
- const std::wstring &archive, int order);
+ FileEntryPtr insert(
+ env::File& file, FilesOrigin& origin,
+ std::wstring_view archive, int order, DirectoryStats& stats);
void addFiles(
- FilesOrigin &origin, wchar_t *buffer, int bufferOffset);
+ env::DirectoryWalker& walker, FilesOrigin& origin,
+ const std::wstring& path, DirectoryStats& stats);
void addFiles(
- FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime,
- const std::wstring &archiveName, int order);
+ FilesOrigin& origin, BSA::Folder::Ptr archiveFolder, FILETIME fileTime,
+ const std::wstring& archiveName, int order, DirectoryStats& stats);
- DirectoryEntry *getSubDirectory(
- const std::wstring &name, bool create, int originID = -1);
+ void addDir(FilesOrigin& origin, env::Directory& d, DirectoryStats& stats);
- DirectoryEntry *getSubDirectoryRecursive(
- const std::wstring &path, bool create, int originID = -1);
+ DirectoryEntry* getSubDirectory(
+ std::wstring_view name, bool create, DirectoryStats& stats,
+ OriginID originID = InvalidOriginID);
+
+ DirectoryEntry* getSubDirectory(
+ env::Directory& dir, bool create, DirectoryStats& stats,
+ OriginID originID = InvalidOriginID);
+
+ DirectoryEntry* getSubDirectoryRecursive(
+ const std::wstring& path, bool create, DirectoryStats& stats,
+ OriginID originID = InvalidOriginID);
void removeDirRecursive();
- void addDirectoryToList(DirectoryEntry* e);
+ void addDirectoryToList(DirectoryEntry* e, std::wstring nameLc);
void removeDirectoryFromList(SubDirectories::iterator itor);
- void addFileToList(std::wstring fileNameLower, FileEntry::Index index);
- void removeFileFromList(FileEntry::Index index);
- void removeFilesFromList(const std::set<FileEntry::Index>& indices);
+ void addFileToList(std::wstring fileNameLower, FileIndex index);
+ void removeFileFromList(FileIndex index);
+ void removeFilesFromList(const std::set<FileIndex>& indices);
+
+ struct Context;
+ static void onDirectoryStart(Context* cx, std::wstring_view path);
+ static void onDirectoryEnd(Context* cx, std::wstring_view path);
+ static void onFile(Context* cx, std::wstring_view path, FILETIME ft);
+
+ void dump(std::FILE* f, const std::wstring& parentPath) const;
};
} // namespace MOShared
@@ -540,4 +319,4 @@ namespace std
}
}
-#endif // DIRECTORYENTRY_H
+#endif // MO_REGISTER_DIRECTORYENTRY_INCLUDED
diff --git a/src/shared/fileentry.cpp b/src/shared/fileentry.cpp
new file mode 100644
index 00000000..e57c3cc5
--- /dev/null
+++ b/src/shared/fileentry.cpp
@@ -0,0 +1,251 @@
+#include "fileentry.h"
+#include "directoryentry.h"
+#include "filesorigin.h"
+
+namespace MOShared
+{
+
+FileEntry::FileEntry() :
+ m_Index(InvalidFileIndex), m_Name(), m_Origin(-1), m_Parent(nullptr),
+ m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize)
+{
+}
+
+FileEntry::FileEntry(FileIndex index, std::wstring name, DirectoryEntry *parent) :
+ m_Index(index), m_Name(std::move(name)), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent),
+ m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize)
+{
+}
+
+void FileEntry::addOrigin(
+ OriginID origin, FILETIME fileTime, std::wstring_view archive, int order)
+{
+ std::scoped_lock lock(m_OriginsMutex);
+
+ if (m_Parent != nullptr) {
+ m_Parent->propagateOrigin(origin);
+ }
+
+ if (m_Origin == -1) {
+ // If this file has no previous origin, this mod is now the origin with no
+ // alternatives
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = std::pair<std::wstring, int>(std::wstring(archive.begin(), archive.end()), order);
+ }
+ else if (
+ (m_Parent != nullptr) && (
+ (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) ||
+ (archive.size() == 0 && m_Archive.first.size() > 0 ))
+ ) {
+ // If this mod has a higher priority than the origin mod OR
+ // this mod has a loose file and the origin mod has an archived file,
+ // this mod is now the origin and the previous origin is the first alternative
+
+ auto itor = std::find_if(
+ m_Alternatives.begin(), m_Alternatives.end(),
+ [&](auto&& i) { return i.first == m_Origin; });
+
+ if (itor == m_Alternatives.end()) {
+ m_Alternatives.push_back({m_Origin, m_Archive});
+ }
+
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = std::pair<std::wstring, int>(std::wstring(archive.begin(), archive.end()), order);
+ }
+ else {
+ // This mod is just an alternative
+ bool found = false;
+
+ if (m_Origin == origin) {
+ // already an origin
+ return;
+ }
+
+ for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if (iter->first == origin) {
+ // already an origin
+ return;
+ }
+
+ if ((m_Parent != nullptr) &&
+ (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) {
+ m_Alternatives.insert(iter, {origin, {std::wstring(archive.begin(), archive.end()), order}});
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ m_Alternatives.push_back({origin, {std::wstring(archive.begin(), archive.end()), order}});
+ }
+ }
+}
+
+bool FileEntry::removeOrigin(OriginID origin)
+{
+ std::scoped_lock lock(m_OriginsMutex);
+
+ if (m_Origin == origin) {
+ if (!m_Alternatives.empty()) {
+ // find alternative with the highest priority
+ auto currentIter = m_Alternatives.begin();
+ for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if (iter->first != origin) {
+ //Both files are not from archives.
+ if (!iter->second.first.size() && !currentIter->second.first.size()) {
+ if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority())) {
+ currentIter = iter;
+ }
+ }
+ else {
+ //Both files are from archives
+ if (iter->second.first.size() && currentIter->second.first.size()) {
+ if (iter->second.second > currentIter->second.second) {
+ currentIter = iter;
+ }
+ }
+ else {
+ //Only one of the two is an archive, so we change currentIter only if he is the archive one.
+ if (currentIter->second.first.size()) {
+ currentIter = iter;
+ }
+ }
+ }
+ }
+ }
+
+ OriginID currentID = currentIter->first;
+ m_Archive = currentIter->second;
+ m_Alternatives.erase(currentIter);
+
+ m_Origin = currentID;
+ } else {
+ m_Origin = -1;
+ m_Archive = std::pair<std::wstring, int>(L"", -1);
+ return true;
+ }
+ } else {
+ auto newEnd = std::remove_if(
+ m_Alternatives.begin(), m_Alternatives.end(),
+ [&](auto &i) { return i.first == origin; });
+
+ if (newEnd != m_Alternatives.end()) {
+ m_Alternatives.erase(newEnd, m_Alternatives.end());
+ }
+ }
+ return false;
+}
+
+void FileEntry::sortOrigins()
+{
+ std::scoped_lock lock(m_OriginsMutex);
+
+ m_Alternatives.push_back({m_Origin, m_Archive});
+
+ std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](auto&& LHS, auto&& RHS) {
+ if (!LHS.second.first.size() && !RHS.second.first.size()) {
+ int l = m_Parent->getOriginByID(LHS.first).getPriority();
+ if (l < 0) {
+ l = INT_MAX;
+ }
+
+ int r = m_Parent->getOriginByID(RHS.first).getPriority();
+ if (r < 0) {
+ r = INT_MAX;
+ }
+
+ return l < r;
+ }
+
+ if (LHS.second.first.size() && RHS.second.first.size()) {
+ int l = LHS.second.second; if (l < 0) l = INT_MAX;
+ int r = RHS.second.second; if (r < 0) r = INT_MAX;
+
+ return l < r;
+ }
+
+ if (RHS.second.first.size()) {
+ return false;
+ }
+
+ return true;
+ });
+
+ if (!m_Alternatives.empty()) {
+ m_Origin = m_Alternatives.back().first;
+ m_Archive = m_Alternatives.back().second;
+ m_Alternatives.pop_back();
+ }
+}
+
+bool FileEntry::isFromArchive(std::wstring archiveName) const
+{
+ std::scoped_lock lock(m_OriginsMutex);
+
+ if (archiveName.length() == 0) {
+ return m_Archive.first.length() != 0;
+ }
+
+ if (m_Archive.first.compare(archiveName) == 0) {
+ return true;
+ }
+
+ for (auto alternative : m_Alternatives) {
+ if (alternative.second.first.compare(archiveName) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::wstring FileEntry::getFullPath(OriginID originID) const
+{
+ std::scoped_lock lock(m_OriginsMutex);
+
+ if (originID == InvalidOriginID) {
+ bool ignore = false;
+ originID = getOrigin(ignore);
+ }
+
+ // base directory for origin
+ const auto* o = m_Parent->findOriginByID(originID);
+ if (!o) {
+ return {};
+ }
+
+ std::wstring result = o->getPath();
+
+ // all intermediate directories
+ recurseParents(result, m_Parent);
+
+ return result + L"\\" + m_Name;
+}
+
+std::wstring FileEntry::getRelativePath() const
+{
+ std::wstring result;
+
+ // all intermediate directories
+ recurseParents(result, m_Parent);
+
+ return result + L"\\" + m_Name;
+}
+
+bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const
+{
+ if (parent == nullptr) {
+ return false;
+ } else {
+ // don't append the topmost parent because it is the virtual data-root
+ if (recurseParents(path, parent->getParent())) {
+ path.append(L"\\").append(parent->getName());
+ }
+
+ return true;
+ }
+}
+
+} // namespace
diff --git a/src/shared/fileentry.h b/src/shared/fileentry.h
new file mode 100644
index 00000000..aceeec57
--- /dev/null
+++ b/src/shared/fileentry.h
@@ -0,0 +1,122 @@
+#ifndef MO_REGISTER_FILEENTRY_INCLUDED
+#define MO_REGISTER_FILEENTRY_INCLUDED
+
+#include "fileregisterfwd.h"
+
+namespace MOShared
+{
+
+class FileEntry
+{
+public:
+ static constexpr uint64_t NoFileSize =
+ std::numeric_limits<uint64_t>::max();
+
+ FileEntry();
+ FileEntry(FileIndex index, std::wstring name, DirectoryEntry *parent);
+
+ // noncopyable
+ FileEntry(const FileEntry&) = delete;
+ FileEntry& operator=(const FileEntry&) = delete;
+
+ FileIndex getIndex() const
+ {
+ return m_Index;
+ }
+
+ void addOrigin(
+ OriginID origin, FILETIME fileTime, std::wstring_view archive, int order);
+
+ // remove the specified origin from the list of origins that contain this
+ // file. if no origin is left, the file is effectively deleted and true is
+ // returned. otherwise, false is returned
+ bool removeOrigin(OriginID origin);
+
+ void sortOrigins();
+
+ // gets the list of alternative origins (origins with lower priority than
+ // the primary one). if sortOrigins has been called, it is sorted by priority
+ // (ascending)
+ const AlternativesVector &getAlternatives() const
+ {
+ return m_Alternatives;
+ }
+
+ const std::wstring &getName() const
+ {
+ return m_Name;
+ }
+
+ OriginID getOrigin() const
+ {
+ return m_Origin;
+ }
+
+ OriginID getOrigin(bool &archive) const
+ {
+ archive = (m_Archive.first.length() != 0);
+ return m_Origin;
+ }
+
+ const std::pair<std::wstring, int> &getArchive() const
+ {
+ return m_Archive;
+ }
+
+ bool isFromArchive(std::wstring archiveName = L"") const;
+
+ // if originID is -1, uses the main origin; if this file doesn't exist in the
+ // given origin, returns an empty string
+ //
+ std::wstring getFullPath(OriginID originID=InvalidOriginID) const;
+
+ std::wstring getRelativePath() const;
+
+ DirectoryEntry *getParent()
+ {
+ return m_Parent;
+ }
+
+ void setFileTime(FILETIME fileTime) const
+ {
+ m_FileTime = fileTime;
+ }
+
+ FILETIME getFileTime() const
+ {
+ return m_FileTime;
+ }
+
+ void setFileSize(uint64_t size, uint64_t compressedSize)
+ {
+ m_FileSize = size;
+ m_CompressedFileSize = compressedSize;
+ }
+
+ uint64_t getFileSize() const
+ {
+ return m_FileSize;
+ }
+
+ uint64_t getCompressedFileSize() const
+ {
+ return m_CompressedFileSize;
+ }
+
+private:
+ FileIndex m_Index;
+ std::wstring m_Name;
+ OriginID m_Origin;
+ std::pair<std::wstring, int> m_Archive;
+ AlternativesVector m_Alternatives;
+ DirectoryEntry *m_Parent;
+ mutable FILETIME m_FileTime;
+ uint64_t m_FileSize, m_CompressedFileSize;
+ mutable std::mutex m_OriginsMutex;
+
+ bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const;
+};
+
+} // namespace
+
+#endif // MO_REGISTER_FILEENTRY_INCLUDED
diff --git a/src/shared/fileregister.cpp b/src/shared/fileregister.cpp
new file mode 100644
index 00000000..b56599d1
--- /dev/null
+++ b/src/shared/fileregister.cpp
@@ -0,0 +1,184 @@
+#include "fileregister.h"
+#include "fileentry.h"
+#include "directoryentry.h"
+#include "originconnection.h"
+#include "filesorigin.h"
+#include <log.h>
+
+namespace MOShared
+{
+
+using namespace MOBase;
+
+FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection)
+ : m_OriginConnection(originConnection), m_NextIndex(0)
+{
+}
+
+bool FileRegister::indexValid(FileIndex index) const
+{
+ std::scoped_lock lock(m_Mutex);
+
+ if (index < m_Files.size()) {
+ return (m_Files[index].get() != nullptr);
+ }
+
+ return false;
+}
+
+FileEntryPtr FileRegister::createFile(
+ std::wstring name, DirectoryEntry *parent, DirectoryStats& stats)
+{
+ const auto index = generateIndex();
+ auto p = FileEntryPtr(new FileEntry(index, std::move(name), parent));
+
+ {
+ std::scoped_lock lock(m_Mutex);
+
+ if (index >= m_Files.size()) {
+ m_Files.resize(index + 1);
+ }
+
+ m_Files[index] = p;
+ }
+
+ return p;
+}
+
+FileIndex FileRegister::generateIndex()
+{
+ return m_NextIndex++;
+}
+
+FileEntryPtr FileRegister::getFile(FileIndex index) const
+{
+ std::scoped_lock lock(m_Mutex);
+
+ if (index < m_Files.size()) {
+ return m_Files[index];
+ } else {
+ return {};
+ }
+}
+
+bool FileRegister::removeFile(FileIndex index)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ if (index < m_Files.size()) {
+ FileEntryPtr p;
+ m_Files[index].swap(p);
+
+ if (p) {
+ unregisterFile(p);
+ return true;
+ }
+ }
+
+ log::error(QObject::tr("invalid file index for remove: {}").toStdString(), index);
+ return false;
+}
+
+void FileRegister::removeOrigin(FileIndex index, OriginID originID)
+{
+ std::unique_lock lock(m_Mutex);
+
+ if (index < m_Files.size()) {
+ FileEntryPtr& p = m_Files[index];
+
+ if (p) {
+ if (p->removeOrigin(originID)) {
+ m_Files[index] = {};
+ lock.unlock();
+ unregisterFile(p);
+ return;
+ }
+ }
+ }
+
+ log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index);
+}
+
+void FileRegister::removeOriginMulti(
+ std::set<FileIndex> indices, OriginID originID)
+{
+ std::vector<FileEntryPtr> removedFiles;
+
+ {
+ std::scoped_lock lock(m_Mutex);
+
+ for (auto iter = indices.begin(); iter != indices.end(); ) {
+ const auto index = *iter;
+
+ if (index < m_Files.size()) {
+ const auto& p = m_Files[index];
+
+ if (p && p->removeOrigin(originID)) {
+ removedFiles.push_back(p);
+ m_Files[index] = {};
+ ++iter;
+ continue;
+ }
+ }
+
+ iter = indices.erase(iter);
+ }
+ }
+
+ // optimization: this is only called when disabling an origin and in this case
+ // we don't have to remove the file from the origin
+
+ // need to remove files from their parent directories. multiple ways to go
+ // about this:
+ // a) for each file, search its parents file-list (preferably by name) and
+ // remove what is found
+ // b) gather the parent directories, go through the file list for each once
+ // and remove all files that have been removed
+ //
+ // the latter should be faster when there are many files in few directories.
+ // since this is called only when disabling an origin that is probably
+ // frequently the case
+
+ std::set<DirectoryEntry*> parents;
+ for (const FileEntryPtr &file : removedFiles) {
+ if (file->getParent() != nullptr) {
+ parents.insert(file->getParent());
+ }
+ }
+
+ for (DirectoryEntry *parent : parents) {
+ parent->removeFiles(indices);
+ }
+}
+
+void FileRegister::sortOrigins()
+{
+ std::scoped_lock lock(m_Mutex);
+
+ for (auto&& p : m_Files) {
+ if (p) {
+ p->sortOrigins();
+ }
+ }
+}
+
+void FileRegister::unregisterFile(FileEntryPtr file)
+{
+ bool ignore;
+
+ // unregister from origin
+ OriginID originID = file->getOrigin(ignore);
+ m_OriginConnection->getByID(originID).removeFile(file->getIndex());
+ const auto& alternatives = file->getAlternatives();
+
+ for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
+ m_OriginConnection->getByID(iter->first).removeFile(file->getIndex());
+ }
+
+ // unregister from directory
+ if (file->getParent() != nullptr) {
+ file->getParent()->removeFile(file->getIndex());
+ }
+}
+
+} // namespace
diff --git a/src/shared/fileregister.h b/src/shared/fileregister.h
new file mode 100644
index 00000000..c27dc93b
--- /dev/null
+++ b/src/shared/fileregister.h
@@ -0,0 +1,53 @@
+#ifndef MO_REGISTER_FILESREGISTER_INCLUDED
+#define MO_REGISTER_FILESREGISTER_INCLUDED
+
+#include "fileregisterfwd.h"
+#include <mutex>
+#include <boost/shared_ptr.hpp>
+
+namespace MOShared
+{
+
+class FileRegister
+{
+public:
+ FileRegister(boost::shared_ptr<OriginConnection> originConnection);
+
+ // noncopyable
+ FileRegister(const FileRegister&) = delete;
+ FileRegister& operator=(const FileRegister&) = delete;
+
+ bool indexValid(FileIndex index) const;
+
+ FileEntryPtr createFile(
+ std::wstring name, DirectoryEntry *parent, DirectoryStats& stats);
+
+ FileEntryPtr getFile(FileIndex index) const;
+
+ size_t highestCount() const
+ {
+ std::scoped_lock lock(m_Mutex);
+ return m_Files.size();
+ }
+
+ bool removeFile(FileIndex index);
+ void removeOrigin(FileIndex index, OriginID originID);
+ void removeOriginMulti(std::set<FileIndex> indices, OriginID originID);
+
+ void sortOrigins();
+
+private:
+ using FileMap = std::deque<FileEntryPtr>;
+
+ mutable std::mutex m_Mutex;
+ FileMap m_Files;
+ boost::shared_ptr<OriginConnection> m_OriginConnection;
+ std::atomic<FileIndex> m_NextIndex;
+
+ void unregisterFile(FileEntryPtr file);
+ FileIndex generateIndex();
+};
+
+} // namespace
+
+#endif // MO_REGISTER_FILESREGISTER_INCLUDED
diff --git a/src/shared/fileregisterfwd.h b/src/shared/fileregisterfwd.h
new file mode 100644
index 00000000..720e6e30
--- /dev/null
+++ b/src/shared/fileregisterfwd.h
@@ -0,0 +1,96 @@
+#ifndef MO_REGISTER_FILEREGISTERFWD_INCLUDED
+#define MO_REGISTER_FILEREGISTERFWD_INCLUDED
+
+class DirectoryRefreshProgress;
+
+namespace MOShared
+{
+
+struct DirectoryEntryFileKey
+{
+ DirectoryEntryFileKey(std::wstring v)
+ : value(std::move(v)), hash(getHash(value))
+ {
+ }
+
+ bool operator==(const DirectoryEntryFileKey& o) const
+ {
+ return (value == o.value);
+ }
+
+ static std::size_t getHash(const std::wstring& value)
+ {
+ return std::hash<std::wstring>()(value);
+ }
+
+ std::wstring value;
+ const std::size_t hash;
+};
+
+
+class DirectoryEntry;
+class OriginConnection;
+class FileRegister;
+class FilesOrigin;
+class FileEntry;
+struct DirectoryStats;
+
+using FileEntryPtr = boost::shared_ptr<FileEntry>;
+using FileIndex = unsigned int;
+using OriginID = int;
+
+constexpr FileIndex InvalidFileIndex = UINT_MAX;
+constexpr OriginID InvalidOriginID = -1;
+
+
+// a vector of {originId, {archiveName, order}}
+//
+// if a file is in an archive, archiveName is the name of the bsa and order
+// is the order of the associated plugin in the plugins list
+//
+// is a file is not in an archive, archiveName is empty and order is usually
+// -1
+using AlternativesVector = std::vector<std::pair<OriginID, std::pair<std::wstring, int>>>;
+
+struct DirectoryStats
+{
+ static constexpr bool EnableInstrumentation = false;
+
+ std::string mod;
+
+ std::chrono::nanoseconds dirTimes;
+ std::chrono::nanoseconds fileTimes;
+ std::chrono::nanoseconds sortTimes;
+
+ std::chrono::nanoseconds subdirLookupTimes;
+ std::chrono::nanoseconds addDirectoryTimes;
+
+ std::chrono::nanoseconds filesLookupTimes;
+ std::chrono::nanoseconds addFileTimes;
+ std::chrono::nanoseconds addOriginToFileTimes;
+ std::chrono::nanoseconds addFileToOriginTimes;
+ std::chrono::nanoseconds addFileToRegisterTimes;
+
+ int64_t originExists;
+ int64_t originCreate;
+ int64_t originsNeededEnabled;
+
+ int64_t subdirExists;
+ int64_t subdirCreate;
+
+ int64_t fileExists;
+ int64_t fileCreate;
+ int64_t filesInsertedInRegister;
+ int64_t filesAssignedInRegister;
+
+ DirectoryStats();
+
+ DirectoryStats& operator+=(const DirectoryStats& o);
+
+ static std::string csvHeader();
+ std::string toCsv() const;
+};
+
+} // namespace
+
+#endif // MO_REGISTER_FILEREGISTERFWD_INCLUDED
diff --git a/src/shared/filesorigin.cpp b/src/shared/filesorigin.cpp
new file mode 100644
index 00000000..7476b6c5
--- /dev/null
+++ b/src/shared/filesorigin.cpp
@@ -0,0 +1,126 @@
+#include "filesorigin.h"
+#include "originconnection.h"
+#include "fileregister.h"
+#include "fileentry.h"
+
+namespace MOShared
+{
+
+std::wstring tail(const std::wstring &source, const size_t count)
+{
+ if (count >= source.length()) {
+ return source;
+ }
+
+ return source.substr(source.length() - count);
+}
+
+
+FilesOrigin::FilesOrigin()
+ : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0)
+{
+}
+
+FilesOrigin::FilesOrigin(
+ OriginID ID, const std::wstring &name, const std::wstring &path, int priority,
+ boost::shared_ptr<MOShared::FileRegister> fileRegister,
+ boost::shared_ptr<MOShared::OriginConnection> originConnection) :
+ m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path),
+ m_Priority(priority), m_FileRegister(fileRegister),
+ m_OriginConnection(originConnection)
+{
+}
+
+void FilesOrigin::setPriority(int priority)
+{
+ m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority);
+
+ m_Priority = priority;
+}
+
+void FilesOrigin::setName(const std::wstring &name)
+{
+ m_OriginConnection.lock()->changeNameLookup(m_Name, name);
+
+ // change path too
+ if (tail(m_Path, m_Name.length()) == m_Name) {
+ m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name);
+ }
+
+ m_Name = name;
+}
+
+std::vector<FileEntryPtr> FilesOrigin::getFiles() const
+{
+ std::vector<FileEntryPtr> result;
+
+ {
+ std::scoped_lock lock(m_Mutex);
+
+ for (FileIndex fileIdx : m_Files) {
+ if (FileEntryPtr p = m_FileRegister.lock()->getFile(fileIdx)) {
+ result.push_back(p);
+ }
+ }
+ }
+
+ return result;
+}
+
+FileEntryPtr FilesOrigin::findFile(FileIndex index) const
+{
+ return m_FileRegister.lock()->getFile(index);
+}
+
+void FilesOrigin::enable(bool enabled)
+{
+ DirectoryStats dummy;
+ enable(enabled, dummy);
+}
+
+void FilesOrigin::enable(bool enabled, DirectoryStats& stats)
+{
+ if (!enabled) {
+ ++stats.originsNeededEnabled;
+
+ std::set<FileIndex> copy;
+
+ {
+ std::scoped_lock lock(m_Mutex);
+ copy = m_Files;
+ m_Files.clear();
+ }
+
+ m_FileRegister.lock()->removeOriginMulti(copy, m_ID);
+ }
+
+ m_Disabled = !enabled;
+}
+
+void FilesOrigin::removeFile(FileIndex index)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ auto iter = m_Files.find(index);
+
+ if (iter != m_Files.end()) {
+ m_Files.erase(iter);
+ }
+}
+
+bool FilesOrigin::containsArchive(std::wstring archiveName)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ for (FileIndex fileIdx : m_Files) {
+ if (FileEntryPtr p = m_FileRegister.lock()->getFile(fileIdx)) {
+ if (p->isFromArchive(archiveName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+} // namespace
diff --git a/src/shared/filesorigin.h b/src/shared/filesorigin.h
new file mode 100644
index 00000000..222a834f
--- /dev/null
+++ b/src/shared/filesorigin.h
@@ -0,0 +1,85 @@
+#ifndef MO_REGISTER_FILESORIGIN_INCLUDED
+#define MO_REGISTER_FILESORIGIN_INCLUDED
+
+#include "fileregisterfwd.h"
+
+namespace MOShared
+{
+
+// represents a mod or the data directory, providing files to the tree
+class FilesOrigin
+{
+public:
+ FilesOrigin();
+
+ FilesOrigin(
+ OriginID ID, const std::wstring &name, const std::wstring &path,
+ int priority,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection);
+
+ // noncopyable
+ FilesOrigin(const FilesOrigin&) = delete;
+ FilesOrigin& operator=(const FilesOrigin&) = delete;
+
+ // sets priority for this origin, but it will overwrite the existing mapping
+ // for this priority, the previous origin will no longer be referenced
+ void setPriority(int priority);
+
+ int getPriority() const
+ {
+ return m_Priority;
+ }
+
+ void setName(const std::wstring &name);
+ const std::wstring &getName() const
+ {
+ return m_Name;
+ }
+
+ OriginID getID() const
+ {
+ return m_ID;
+ }
+
+ const std::wstring &getPath() const
+ {
+ return m_Path;
+ }
+
+ std::vector<FileEntryPtr> getFiles() const;
+ FileEntryPtr findFile(FileIndex index) const;
+
+ void enable(bool enabled, DirectoryStats& stats);
+ void enable(bool enabled);
+
+ bool isDisabled() const
+ {
+ return m_Disabled;
+ }
+
+ void addFile(FileIndex index)
+ {
+ std::scoped_lock lock(m_Mutex);
+ m_Files.insert(index);
+ }
+
+ void removeFile(FileIndex index);
+
+ bool containsArchive(std::wstring archiveName);
+
+private:
+ OriginID m_ID;
+ bool m_Disabled;
+ std::set<FileIndex> m_Files;
+ std::wstring m_Name;
+ std::wstring m_Path;
+ int m_Priority;
+ boost::weak_ptr<FileRegister> m_FileRegister;
+ boost::weak_ptr<OriginConnection> m_OriginConnection;
+ mutable std::mutex m_Mutex;
+};
+
+} // namespace
+
+#endif // MO_REGISTER_FILESORIGIN_INCLUDED
diff --git a/src/shared/originconnection.cpp b/src/shared/originconnection.cpp
new file mode 100644
index 00000000..c1c096f0
--- /dev/null
+++ b/src/shared/originconnection.cpp
@@ -0,0 +1,145 @@
+#include "originconnection.h"
+#include "filesorigin.h"
+#include "util.h"
+#include <log.h>
+
+namespace MOShared
+{
+
+using namespace MOBase;
+
+OriginConnection::OriginConnection()
+ : m_NextID(0)
+{
+}
+
+std::pair<FilesOrigin&, bool> OriginConnection::getOrCreate(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ const boost::shared_ptr<FileRegister>& fileRegister,
+ const boost::shared_ptr<OriginConnection>& originConnection,
+ DirectoryStats& stats)
+{
+ std::unique_lock lock(m_Mutex);
+
+ auto itor = m_OriginsNameMap.find(originName);
+
+ if (itor == m_OriginsNameMap.end()) {
+ FilesOrigin& origin = createOriginNoLock(
+ originName, directory, priority, fileRegister, originConnection);
+
+ return {origin, true};
+ } else {
+ FilesOrigin& origin = m_Origins[itor->second];
+ lock.unlock();
+
+ origin.enable(true, stats);
+ return {origin, false};
+ }
+}
+
+FilesOrigin& OriginConnection::createOrigin(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ return createOriginNoLock(
+ originName, directory, priority, fileRegister, originConnection);
+}
+
+bool OriginConnection::exists(const std::wstring &name)
+{
+ std::scoped_lock lock(m_Mutex);
+ return m_OriginsNameMap.find(name) != m_OriginsNameMap.end();
+}
+
+FilesOrigin& OriginConnection::getByID(OriginID ID)
+{
+ std::scoped_lock lock(m_Mutex);
+ return m_Origins[ID];
+}
+
+const FilesOrigin* OriginConnection::findByID(OriginID ID) const
+{
+ std::scoped_lock lock(m_Mutex);
+
+ auto itor = m_Origins.find(ID);
+
+ if (itor == m_Origins.end()) {
+ return nullptr;
+ } else {
+ return &itor->second;
+ }
+}
+
+FilesOrigin& OriginConnection::getByName(const std::wstring &name)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ auto iter = m_OriginsNameMap.find(name);
+
+ if (iter != m_OriginsNameMap.end()) {
+ return m_Origins[iter->second];
+ } else {
+ std::ostringstream stream;
+ stream << QObject::tr("invalid origin name: ").toStdString() << ToString(name, true);
+ throw std::runtime_error(stream.str());
+ }
+}
+
+void OriginConnection::changePriorityLookup(int oldPriority, int newPriority)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ auto iter = m_OriginsPriorityMap.find(oldPriority);
+
+ if (iter != m_OriginsPriorityMap.end()) {
+ OriginID idx = iter->second;
+ m_OriginsPriorityMap.erase(iter);
+ m_OriginsPriorityMap[newPriority] = idx;
+ }
+}
+
+void OriginConnection::changeNameLookup(const std::wstring &oldName, const std::wstring &newName)
+{
+ std::scoped_lock lock(m_Mutex);
+
+ auto iter = m_OriginsNameMap.find(oldName);
+
+ if (iter != m_OriginsNameMap.end()) {
+ OriginID idx = iter->second;
+ m_OriginsNameMap.erase(iter);
+ m_OriginsNameMap[newName] = idx;
+ } else {
+ log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), oldName, newName);
+ }
+}
+
+OriginID OriginConnection::createID()
+{
+ return m_NextID++;
+}
+
+FilesOrigin& OriginConnection::createOriginNoLock(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection)
+{
+ OriginID newID = createID();
+
+ auto itor = m_Origins.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(newID),
+ std::forward_as_tuple(
+ newID, originName, directory, priority,
+ fileRegister, originConnection))
+ .first;
+
+ m_OriginsNameMap.insert({originName, newID});
+ m_OriginsPriorityMap.insert({priority, newID});
+
+ return itor->second;
+}
+
+} // namespace
diff --git a/src/shared/originconnection.h b/src/shared/originconnection.h
new file mode 100644
index 00000000..5fecd63a
--- /dev/null
+++ b/src/shared/originconnection.h
@@ -0,0 +1,56 @@
+#ifndef MO_REGISTER_ORIGINCONNECTION_INCLUDED
+#define MO_REGISTER_ORIGINCONNECTION_INCLUDED
+
+#include "fileregisterfwd.h"
+
+namespace MOShared
+{
+
+class OriginConnection
+{
+public:
+ OriginConnection();
+
+ // noncopyable
+ OriginConnection(const OriginConnection&) = delete;
+ OriginConnection& operator=(const OriginConnection&) = delete;
+
+ std::pair<FilesOrigin&, bool> getOrCreate(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ const boost::shared_ptr<FileRegister>& fileRegister,
+ const boost::shared_ptr<OriginConnection>& originConnection,
+ DirectoryStats& stats);
+
+ FilesOrigin& createOrigin(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection);
+
+ bool exists(const std::wstring &name);
+
+ FilesOrigin &getByID(OriginID ID);
+ const FilesOrigin* findByID(OriginID ID) const;
+ FilesOrigin &getByName(const std::wstring &name);
+
+ void changePriorityLookup(int oldPriority, int newPriority);
+
+ void changeNameLookup(const std::wstring &oldName, const std::wstring &newName);
+
+private:
+ std::atomic<OriginID> m_NextID;
+ std::map<OriginID, FilesOrigin> m_Origins;
+ std::map<std::wstring, OriginID> m_OriginsNameMap;
+ std::map<int, OriginID> m_OriginsPriorityMap;
+ mutable std::mutex m_Mutex;
+
+ OriginID createID();
+
+ FilesOrigin& createOriginNoLock(
+ const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection);
+};
+
+} // namespace
+
+#endif // MO_REGISTER_ORIGINCONNECTION_INCLUDED
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index aa4ad4b3..483b36a9 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -130,6 +130,13 @@ std::wstring ToLowerCopy(const std::wstring& text)
return result;
}
+std::wstring ToLowerCopy(std::wstring_view text)
+{
+ std::wstring result(text.begin(), text.end());
+ ToLowerInPlace(result);
+ return result;
+}
+
bool CaseInsenstiveComparePred(wchar_t lhs, wchar_t rhs)
{
return std::tolower(lhs, loc) == std::tolower(rhs, loc);
@@ -375,26 +382,6 @@ void checkDuplicateShortcuts(const QMenu& m)
} // namespace MOShared
-TimeThis::TimeThis(QString what)
- : m_what(std::move(what)), m_start(Clock::now())
-{
-}
-
-TimeThis::~TimeThis()
-{
- using namespace std::chrono;
-
- const auto end = Clock::now();
- const auto d = duration_cast<milliseconds>(end - m_start).count();
-
- if (m_what.isEmpty()) {
- log::debug("{} ms", d);
- } else {
- log::debug("{} {} ms", m_what, d);
- }
-}
-
-
static bool g_exiting = false;
static bool g_canClose = false;
diff --git a/src/shared/util.h b/src/shared/util.h
index 05522c2d..2761b64f 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -20,7 +20,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef UTIL_H
#define UTIL_H
+#include <log.h>
#include <string>
+#include <filesystem>
#include <versioninfo.h>
class Executable;
@@ -41,6 +43,7 @@ std::string ToLowerCopy(const std::string& text);
std::wstring& ToLowerInPlace(std::wstring& text);
std::wstring ToLowerCopy(const std::wstring& text);
+std::wstring ToLowerCopy(std::wstring_view text);
bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs);
@@ -50,21 +53,16 @@ QString getUsvfsVersionString();
void SetThisThreadName(const QString& s);
void checkDuplicateShortcuts(const QMenu& m);
-} // namespace MOShared
-
-
-class TimeThis
+inline FILETIME ToFILETIME(std::filesystem::file_time_type t)
{
-public:
- TimeThis(QString what={});
- ~TimeThis();
+ FILETIME ft;
+ static_assert(sizeof(t) == sizeof(ft));
-private:
- using Clock = std::chrono::high_resolution_clock;
+ std::memcpy(&ft, &t, sizeof(FILETIME));
+ return ft;
+}
- QString m_what;
- Clock::time_point m_start;
-};
+} // namespace MOShared
enum class Exit
diff --git a/src/stylesheets/Night Eyes.qss b/src/stylesheets/Night Eyes.qss
index 14f7434e..135f0c09 100644
--- a/src/stylesheets/Night Eyes.qss
+++ b/src/stylesheets/Night Eyes.qss
@@ -691,4 +691,12 @@ QProgressBar
border-radius: 5px;
text-align: center;
margin: 0px;
-} \ No newline at end of file
+}
+
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/Parchment v1.1 by Bob.qss b/src/stylesheets/Parchment v1.1 by Bob.qss
index 61c2f84d..269869ad 100644
--- a/src/stylesheets/Parchment v1.1 by Bob.qss
+++ b/src/stylesheets/Parchment v1.1 by Bob.qss
@@ -145,6 +145,10 @@ QWebView {
border-radius: 6px;
}
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
/* Group Boxes */
QGroupBox {
@@ -596,4 +600,8 @@ QSlider::handle:horizontal:hover {
QTableView {
gridline-color: #DBD399;
border: 0px;
-} \ No newline at end of file
+}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/Transparent-Style-101-Green.qss b/src/stylesheets/Transparent-Style-101-Green.qss
index 3bbdb5db..0d35ec7e 100644
--- a/src/stylesheets/Transparent-Style-101-Green.qss
+++ b/src/stylesheets/Transparent-Style-101-Green.qss
@@ -6,13 +6,13 @@
}
QWidget {
color: #fff;
- background-color: rgb(0, 204, 0, 5);
- alternate-background-color: rgb(0, 204, 0, 10);
+ background-color: rgba(0, 204, 0, 5);
+ alternate-background-color: rgba(0, 204, 0, 10);
}
QAbstractItemView {
color: #fff;
- background-color: rgb(0, 204, 0, 5);
- alternate-background-color: rgb(0, 204, 0, 10);
+ background-color: rgba(0, 204, 0, 5);
+ alternate-background-color: rgba(0, 204, 0, 10);
border: 1px solid #001a00;
border-style:outset;
border-radius: 3px;
@@ -29,7 +29,7 @@
border-image: url(./Transparent-Style/Green/vault-tec-small.png) 0 0 0 0 stretch stretch;
}
#nameLabel {
- background-color: rgb(0, 0, 0, 20);
+ background-color: rgba(0, 0, 0, 20);
border-top:1px solid black;
border-bottom:1px solid black;
}
@@ -269,7 +269,7 @@
padding-right: 5%;
}
QPushButton:open {
- background-color: rgb(0, 204, 0, 100);
+ background-color: rgba(0, 204, 0, 100);
}
QPushButton:!enabled {
border: 1px grey;
@@ -331,29 +331,34 @@
border-radius: 5px;
border-top:1px solid #b9d7fc;
border-bottom:1px solid #b9d7fc;
- background-color: rgb(0, 204, 0, 100);
+ background-color: rgba(0, 204, 0, 100);
}
QTreeView::branch:has-siblings:!adjoins-item {
- background:url(Vault-101/Vault-101-item-line-v.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-item-line-v.png) center center no-repeat;
}
QTreeView::branch:has-siblings {
- background:url(Vault-101/Vault-101-itemB-line.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-line.png) center center no-repeat;
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
- background:url(Vault-101/Vault-101-itemB-end.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-end.png) center center no-repeat;
}
QTreeView::branch:closed:has-children:has-siblings {
- background:url(Vault-101/Vault-101-itemB-close.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-close.png) center center no-repeat;
}
QTreeView::branch:closed:has-children:!has-siblings {
- background:url(Vault-101/Vault-101-itemB-close-last.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-close-last.png) center center no-repeat;
}
QTreeView::branch:open:has-children:has-siblings {
- background:url(Vault-101/Vault-101-itemB-open.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-open.png) center center no-repeat;
}
QTreeView::branch:open:has-children:!has-siblings {
- background:url(Vault-101/Vault-101-itemB-open-last.png) center center no-repeat;
+ background:url(Transparent-Style/Green/Vault-101-itemB-open-last.png) center center no-repeat;
}
+
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
/* **************************************************************** */
/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */
/* common */
@@ -477,7 +482,7 @@ QStatusBar::item {border: None;}
border-radius:5px;
margin: 0px;
padding-left:2px;
- selection-background-color: rgb(0, 204, 0, 10);
+ selection-background-color: rgba(0, 204, 0, 10);
}
/* Font size */
QLabel, QMenu, QPushButton, QTabBar, QTextEdit, QLineEdit, QWebView, QComboBox, QComboBox:editable, QAbstractSpinBox, QGroupBox, QCheckBox, QRadioButton {
@@ -495,3 +500,7 @@ LinkLabel {
font-family: Source Sans Pro;
font-size: 14px;
}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/Transparent-Style-BOS.qss b/src/stylesheets/Transparent-Style-BOS.qss
index 33eed327..0b4ba23d 100644
--- a/src/stylesheets/Transparent-Style-BOS.qss
+++ b/src/stylesheets/Transparent-Style-BOS.qss
@@ -12,8 +12,8 @@
}
QAbstractItemView{
color: #dcdcdc;
- background-color: rgb(45,45,48,30);
- alternate-background-color: rgb(38,38,38,50);
+ background-color: rgba(45,45,48,30);
+ alternate-background-color: rgba(38,38,38,50);
border: 1px solid #3F3F46;
/* Grey - Blue*/
border-style:outset;
@@ -507,6 +507,11 @@ QMenu::item:selected{
QTreeView::branch:open:has-children:!has-siblings{
background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat;
}
+
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
/* **************************************************************** */
/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */
/* common */
@@ -747,3 +752,7 @@ QStatusBar::item {border: None;}
LinkLabel {
qproperty-linkColor: #3399FF;
}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/Transparent-Style-Skyrim.qss b/src/stylesheets/Transparent-Style-Skyrim.qss
index 308580a5..cbc43033 100644
--- a/src/stylesheets/Transparent-Style-Skyrim.qss
+++ b/src/stylesheets/Transparent-Style-Skyrim.qss
@@ -12,8 +12,8 @@
}
QAbstractItemView{
color: #dcdcdc;
- background-color: rgb(45,45,48,30);
- alternate-background-color: rgb(38,38,38,50);
+ background-color: rgba(45,45,48,30);
+ alternate-background-color: rgba(38,38,38,50);
border: 1px solid #3F3F46;
/* Grey - Blue*/
border-style:outset;
@@ -507,6 +507,11 @@ QMenu::item:selected{
QTreeView::branch:open:has-children:!has-siblings{
background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat;
}
+
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
/* **************************************************************** */
/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */
/* common */
@@ -747,3 +752,7 @@ QStatusBar::item {border: None;}
LinkLabel {
qproperty-linkColor: #3399FF;
}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/dark.qss b/src/stylesheets/dark.qss
index 91f808bc..1bf5c819 100644
--- a/src/stylesheets/dark.qss
+++ b/src/stylesheets/dark.qss
@@ -356,6 +356,10 @@ QTreeView, QListView
alternate-background-color: #2F3031;
}
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings
{
@@ -385,3 +389,7 @@ DownloadListWidget[downloadView=compact]::item {
LinkLabel {
qproperty-linkColor: #3399FF;
}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/dracula.qss b/src/stylesheets/dracula.qss
index a78d2414..9beba52d 100644
--- a/src/stylesheets/dracula.qss
+++ b/src/stylesheets/dracula.qss
@@ -373,6 +373,10 @@ QTreeView::branch:open:has-children:has-siblings {
image: url(:/stylesheet/branch-open.png);
}
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
/*
* Tool bar
*/
@@ -435,3 +439,7 @@ QProgressBar::chunk
{
background-color: #427683;
}
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/skyrim.qss b/src/stylesheets/skyrim.qss
index a516f5ff..ffd42640 100644
--- a/src/stylesheets/skyrim.qss
+++ b/src/stylesheets/skyrim.qss
@@ -79,6 +79,10 @@ QListView,
QTreeView {
show-decoration-selected: 1; }
+QAbstractItemView[filtered=true] {
+ background-color: #f00 !important;
+}
+
QAbstractItemView::item:hover,
QListView::item:hover,
QTreeView::branch:hover,
@@ -659,3 +663,7 @@ QDialog#CredentialsDialog {
#pluginsList {
min-width: 260px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss
index 0a3150ad..a4c6a2cc 100644
--- a/src/stylesheets/vs15 Dark-Green.qss
+++ b/src/stylesheets/vs15 Dark-Green.qss
@@ -71,6 +71,10 @@ QAbstractItemView {
background-color: #009933;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -755,3 +759,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss
index 0e1d96b1..c909d103 100644
--- a/src/stylesheets/vs15 Dark-Orange.qss
+++ b/src/stylesheets/vs15 Dark-Orange.qss
@@ -72,6 +72,10 @@ QAbstractItemView {
background-color: #CC6600;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -756,3 +760,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss
index c19d188b..2cff0e06 100644
--- a/src/stylesheets/vs15 Dark-Purple.qss
+++ b/src/stylesheets/vs15 Dark-Purple.qss
@@ -72,6 +72,10 @@ QAbstractItemView {
background-color: #7E2AD2;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -756,3 +760,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss
index a6221dff..2df39fc4 100644
--- a/src/stylesheets/vs15 Dark-Red.qss
+++ b/src/stylesheets/vs15 Dark-Red.qss
@@ -72,6 +72,10 @@ QAbstractItemView {
background-color: #990000;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -756,3 +760,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss
index fb2e0422..63688511 100644
--- a/src/stylesheets/vs15 Dark-Yellow.qss
+++ b/src/stylesheets/vs15 Dark-Yellow.qss
@@ -71,6 +71,10 @@ QAbstractItemView {
background-color: #9A9A00;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -755,3 +759,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss
index 17def4dd..af29a696 100644
--- a/src/stylesheets/vs15 Dark.qss
+++ b/src/stylesheets/vs15 Dark.qss
@@ -71,6 +71,10 @@ QAbstractItemView {
background-color: #3399FF;
color: #F1F1F1; }
+QAbstractItemView[filtered=true] {
+ border: 2px solid #f00 !important;
+}
+
QAbstractItemView,
QListView,
QTreeView {
@@ -755,3 +759,7 @@ QDialog#QueryOverwriteDialog {
QDialog#ModInfoDialog {
min-width: 850px; }
+
+QLineEdit[valid-filter=false] {
+ background-color: #661111 !important;
+}
diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp
index b1643b2d..0afb1121 100644
--- a/src/syncoverwritedialog.cpp
+++ b/src/syncoverwritedialog.cpp
@@ -18,8 +18,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "syncoverwritedialog.h"
-
+#include "shared/directoryentry.h"
+#include "shared/fileentry.h"
+#include "shared/filesorigin.h"
#include "ui_syncoverwritedialog.h"
+
#include <utility.h>
#include <report.h>
#include <log.h>
@@ -92,7 +95,7 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director
newItem = nullptr;
}
} else {
- const FileEntry::Ptr entry = directoryStructure->findFile(ToWString(file));
+ const FileEntryPtr entry = directoryStructure->findFile(ToWString(file));
QComboBox* combo = new QComboBox(ui->syncTree);
combo->addItem(tr("<don't sync>"), -1);
if (entry.get() != nullptr) {
diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h
index 79b30a8d..cb4c92fb 100644
--- a/src/syncoverwritedialog.h
+++ b/src/syncoverwritedialog.h
@@ -20,11 +20,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef SYNCOVERWRITEDIALOG_H
#define SYNCOVERWRITEDIALOG_H
-
#include "tutorabledialog.h"
+#include "shared/fileregisterfwd.h"
#include <QTreeWidgetItem>
-#include <directoryentry.h>
-
namespace Ui {
class SyncOverwriteDialog;
@@ -33,9 +31,12 @@ class SyncOverwriteDialog;
class SyncOverwriteDialog : public MOBase::TutorableDialog
{
Q_OBJECT
-
+
public:
- explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0);
+ explicit SyncOverwriteDialog(
+ const QString &path, MOShared::DirectoryEntry *directoryStructure,
+ QWidget *parent = 0);
+
~SyncOverwriteDialog();
void apply(const QString &modDirectory);
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index 59880754..ed6e31ce 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -70,6 +70,8 @@ LogWorker::~LogWorker()
void LogWorker::process()
{
+ MOShared::SetThisThreadName("LogWorker");
+
int noLogCycles = 0;
while (!m_QuitRequested) {
if (GetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) {
diff --git a/src/version.rc b/src/version.rc
index a5fbb7a6..4c77bfb6 100644
--- a/src/version.rc
+++ b/src/version.rc
@@ -4,7 +4,7 @@
// Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser
// Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha
#define VER_FILEVERSION 2,3,0
-#define VER_FILEVERSION_STR "2.3.0alpha5\0"
+#define VER_FILEVERSION_STR "2.3.0alpha6\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION