diff options
| author | Mikaƫl Capelle <capelle.mikael@gmail.com> | 2023-07-09 17:36:03 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-09 17:36:03 +0200 |
| commit | ef94aee28464039672b277243a0181ae93550d6c (patch) | |
| tree | 0575a68af5f154e9d6d0738edbaefb16a11d6f5c /src/shared | |
| parent | 7d6cb8528d20e36a4cee822263865ee2f7f32481 (diff) | |
| parent | 3de050e9c03e553f7ae3f780f6bd080a30ae123e (diff) | |
Merge pull request #1839 from Holt59/ci/initial-gh-action-for-build
Clang-Format + Github Workflow
Diffstat (limited to 'src/shared')
| -rw-r--r-- | src/shared/appconfig.cpp | 72 | ||||
| -rw-r--r-- | src/shared/appconfig.h | 81 | ||||
| -rw-r--r-- | src/shared/directoryentry.cpp | 1938 | ||||
| -rw-r--r-- | src/shared/directoryentry.h | 605 | ||||
| -rw-r--r-- | src/shared/fileentry.cpp | 104 | ||||
| -rw-r--r-- | src/shared/fileentry.h | 75 | ||||
| -rw-r--r-- | src/shared/fileregister.cpp | 30 | ||||
| -rw-r--r-- | src/shared/fileregister.h | 12 | ||||
| -rw-r--r-- | src/shared/fileregisterfwd.h | 45 | ||||
| -rw-r--r-- | src/shared/filesorigin.cpp | 32 | ||||
| -rw-r--r-- | src/shared/filesorigin.h | 41 | ||||
| -rw-r--r-- | src/shared/originconnection.cpp | 62 | ||||
| -rw-r--r-- | src/shared/originconnection.h | 40 | ||||
| -rw-r--r-- | src/shared/util.cpp | 873 | ||||
| -rw-r--r-- | src/shared/util.h | 178 | ||||
| -rw-r--r-- | src/shared/windows_error.cpp | 101 | ||||
| -rw-r--r-- | src/shared/windows_error.h | 91 |
17 files changed, 2131 insertions, 2249 deletions
diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 49edcbcc..221732de 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -1,33 +1,39 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "appconfig.h"
-
-namespace AppConfig {
-
-#define PARWSTRING wstring
-#define APPPARAM(partype, parid, value) partype parid () { return value; }
-#include "appconfig.inc"
-
-namespace MOShared {
-#undef PARWSTRING
-#undef APPPARAM
-
-}
-} // namespace MOShared
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "appconfig.h" + +namespace AppConfig +{ + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) \ + partype parid() \ + { \ + return value; \ + } +#include "appconfig.inc" + +namespace MOShared +{ +#undef PARWSTRING +#undef APPPARAM + +} // namespace MOShared +} // namespace AppConfig diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h index b32043e5..598ee19c 100644 --- a/src/shared/appconfig.h +++ b/src/shared/appconfig.h @@ -1,40 +1,41 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 APPCONFIG_H
-#define APPCONFIG_H
-
-#include <string>
-
-namespace AppConfig {
-
-
-#define PARWSTRING wstring
-#define APPPARAM(partype, parid, value) partype parid ();
-#include "appconfig.inc"
-
-namespace MOShared {
-#undef PARWSTRING
-#undef APPPARAM
-
-}
-
-} // namespace MOShared
-
-#endif // APPCONFIG_H
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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 APPCONFIG_H +#define APPCONFIG_H + +#include <string> + +namespace AppConfig +{ + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) partype parid(); +#include "appconfig.inc" + +namespace MOShared +{ +#undef PARWSTRING +#undef APPPARAM + +} // namespace MOShared + +} // namespace AppConfig + +#endif // APPCONFIG_H diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index badb5636..84da66e2 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -1,978 +1,960 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-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 <log.h>
-#include <utility.h>
-
-namespace MOShared
-{
-
-using namespace MOBase;
-const int MAXPATH_UNICODE = 32767;
-
-template <class F>
-void elapsedImpl(std::chrono::nanoseconds& out, F&& f)
-{
- 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();
- }
-}
-
-// 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
-
- OSVERSIONINFOEX versionInfo;
- versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- versionInfo.dwMajorVersion = 6;
- versionInfo.dwMinorVersion = 1;
-
- ULONGLONG mask = ::VerSetConditionMask(
- ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL),
- VER_MINORVERSION, VER_GREATER_EQUAL);
-
- return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE);
-}
-
-bool DirCompareByName::operator()(
- const DirectoryEntry* lhs, const DirectoryEntry* rhs) const
-{
- return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0;
-}
-
-
-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);
-}
-
-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);
-}
-
-DirectoryEntry::~DirectoryEntry()
-{
- clear();
-}
-
-void DirectoryEntry::clear()
-{
- for (auto itor=m_SubDirectories.rbegin(); itor!=m_SubDirectories.rend(); ++itor) {
- delete *itor;
- }
-
- m_Files.clear();
- m_FilesLookup.clear();
- m_SubDirectories.clear();
- m_SubDirectoriesLookup.clear();
-}
-
-void DirectoryEntry::addFromOrigin(
- const std::wstring &originName, const std::wstring &directory, int priority,
- DirectoryStats& stats)
-{
- env::DirectoryWalker walker;
- addFromOrigin(walker, originName, directory, priority, stats);
-}
-
-void DirectoryEntry::addFromOrigin(
- env::DirectoryWalker& walker, const std::wstring &originName,
- const std::wstring &directory, int priority, DirectoryStats& stats)
-{
- FilesOrigin &origin = createOrigin(originName, directory, priority, stats);
-
- if (!directory.empty()) {
- addFiles(walker, origin, directory, stats);
- }
-
- m_Populated = true;
-}
-
-void DirectoryEntry::addFromList(
- const std::wstring &originName, const std::wstring &directory,
- env::Directory& root, int priority, DirectoryStats& stats)
-{
- stats = {};
-
- FilesOrigin &origin = createOrigin(originName, directory, priority, stats);
- addDir(origin, root, stats);
-}
-
-void DirectoryEntry::addDir(
- FilesOrigin& origin, env::Directory& d, DirectoryStats& stats)
-{
- elapsed(stats.dirTimes, [&]{
- for (auto& sd : d.dirs) {
- auto* sdirEntry = getSubDirectory(sd, true, stats, origin.getID());
- sdirEntry->addDir(origin, sd, stats);
- }
- });
-
- elapsed(stats.fileTimes, [&]{
- for (auto& f : d.files) {
- insert(f, origin, L"", -1, stats);
- }
- });
-
- m_Populated = true;
-}
-
-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)
-{
- for (const auto& archive : archives) {
- const std::filesystem::path archivePath(archive);
- const auto filename = archivePath.filename().native();
-
- if (!enabledArchives.contains(filename)) {
- continue;
- }
-
- const auto filenameLc = ToLowerCopy(filename);
-
- int order = -1;
-
- for (auto plugin : loadOrder)
- {
- const auto pluginNameLc =
- ToLowerCopy(std::filesystem::path(plugin).stem().native());
-
- 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);
- }
- }
- }
-
- addFromBSA(
- originName, directory, archivePath.native(),
- priority, order, stats);
- }
-}
-
-void DirectoryEntry::addFromBSA(
- const std::wstring& originName, const std::wstring& directory,
- const std::wstring& archivePath, int priority, int order, DirectoryStats& stats)
-{
- FilesOrigin& origin = createOrigin(originName, directory, priority, stats);
- const auto archiveName = std::filesystem::path(archivePath).filename().native();
-
- if (containsArchive(archiveName)) {
- return;
- }
-
- BSA::Archive archive;
- BSA::EErrorCode res = BSA::ERROR_NONE;
-
- try
- {
- // read() can return an error, but it can also throw if the file is not a
- // valid bsa
- res = archive.read(ToString(archivePath, false).c_str(), false);
- }
- catch(std::exception& e)
- {
- log::error("invalid bsa '{}', error {}", archivePath, e.what());
- return;
- }
-
- if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
- log::error("invalid bsa '{}', error {}", archivePath, res);
- return;
- }
-
- std::error_code ec;
- const auto lwt = std::filesystem::last_write_time(archivePath, ec);
- FILETIME ft = {};
-
- if (ec) {
- log::warn(
- "failed to get last modified date for '{}', {}",
- archivePath, ec.message());
- } else {
- ft = ToFILETIME(lwt);
- }
-
- addFiles(origin, archive.getRoot(), ft, archiveName, order, stats);
-
- m_Populated = true;
-}
-
-void DirectoryEntry::propagateOrigin(int origin)
-{
- {
- std::scoped_lock lock(m_OriginsMutex);
- m_Origins.insert(origin);
- }
-
- if (m_Parent != nullptr) {
- m_Parent->propagateOrigin(origin);
- }
-}
-
-bool DirectoryEntry::originExists(const std::wstring &name) const
-{
- return m_OriginConnection->exists(name);
-}
-
-FilesOrigin &DirectoryEntry::getOriginByID(int ID) const
-{
- return m_OriginConnection->getByID(ID);
-}
-
-FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const
-{
- return m_OriginConnection->getByName(name);
-}
-
-const FilesOrigin* DirectoryEntry::findOriginByID(int ID) const
-{
- return m_OriginConnection->findByID(ID);
-}
-
-int DirectoryEntry::anyOrigin() const
-{
- bool ignore;
-
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- FileEntryPtr entry = m_FileRegister->getFile(iter->second);
- if ((entry.get() != nullptr) && !entry->isFromArchive()) {
- return entry->getOrigin(ignore);
- }
- }
-
- // 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) {
- int res = entry->anyOrigin();
- if (res != InvalidOriginID){
- return res;
- }
- }
-
- return *(m_Origins.begin());
-}
-
-std::vector<FileEntryPtr> DirectoryEntry::getFiles() const
-{
- std::vector<FileEntryPtr> result;
-
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- result.push_back(m_FileRegister->getFile(iter->second));
- }
-
- return result;
-}
-
-DirectoryEntry* DirectoryEntry::findSubDirectory(
- const std::wstring &name, bool alreadyLowerCase) const
-{
- SubDirectoriesLookup::const_iterator itor;
-
- if (alreadyLowerCase) {
- itor = m_SubDirectoriesLookup.find(name);
- } else {
- itor = m_SubDirectoriesLookup.find(ToLowerCopy(name));
- }
-
- if (itor == m_SubDirectoriesLookup.end()) {
- return nullptr;
- }
-
- return itor->second;
-}
-
-DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path)
-{
- DirectoryStats dummy;
- return getSubDirectoryRecursive(path, false, dummy, InvalidOriginID);
-}
-
-const FileEntryPtr DirectoryEntry::findFile(
- const std::wstring &name, bool alreadyLowerCase) const
-{
- FilesLookup::const_iterator iter;
-
- if (alreadyLowerCase) {
- iter = m_FilesLookup.find(DirectoryEntryFileKey(name));
- } else {
- iter = m_FilesLookup.find(DirectoryEntryFileKey(ToLowerCopy(name)));
- }
-
- if (iter != m_FilesLookup.end()) {
- return m_FileRegister->getFile(iter->second);
- } else {
- return FileEntryPtr();
- }
-}
-
-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 FileEntryPtr();
- }
-}
-
-bool DirectoryEntry::hasFile(const std::wstring& name) const
-{
- return m_Files.contains(ToLowerCopy(name));
-}
-
-bool DirectoryEntry::containsArchive(std::wstring archiveName)
-{
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- FileEntryPtr entry = m_FileRegister->getFile(iter->second);
- if (entry->isFromArchive(archiveName)) {
- return true;
- }
- }
-
- return false;
-}
-
-const FileEntryPtr DirectoryEntry::searchFile(
- const std::wstring &path, const DirectoryEntry** directory) const
-{
- if (directory != nullptr) {
- *directory = nullptr;
- }
-
- if ((path.length() == 0) || (path == L"*")) {
- // no file name -> the path ended on a (back-)slash
- if (directory != nullptr) {
- *directory = this;
- }
-
- return FileEntryPtr();
- }
-
- const size_t len = path.find_first_of(L"\\/");
-
- if (len == std::string::npos) {
- // no more path components
- auto iter = m_Files.find(ToLowerCopy(path));
-
- if (iter != m_Files.end()) {
- return m_FileRegister->getFile(iter->second);
- } else if (directory != nullptr) {
- DirectoryEntry* temp = findSubDirectory(path);
- if (temp != nullptr) {
- *directory = temp;
- }
- }
- } else {
- // file is in a subdirectory, recurse into the matching subdirectory
- std::wstring pathComponent = path.substr(0, len);
- DirectoryEntry* temp = findSubDirectory(pathComponent);
-
- if (temp != nullptr) {
- if (len >= path.size()) {
- log::error(QObject::tr("unexpected end of path").toStdString());
- return FileEntryPtr();
- }
-
- return temp->searchFile(path.substr(len + 1), directory);
- }
- }
-
- return FileEntryPtr();
-}
-
-void DirectoryEntry::removeFile(FileIndex index)
-{
- removeFileFromList(index);
-}
-
-bool DirectoryEntry::removeFile(const std::wstring &filePath, int* origin)
-{
- size_t pos = filePath.find_first_of(L"\\/");
-
- if (pos == std::string::npos) {
- return this->remove(filePath, origin);
- }
-
- std::wstring dirName = filePath.substr(0, pos);
- std::wstring rest = filePath.substr(pos + 1);
-
- DirectoryStats dummy;
- DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy);
-
- if (entry != nullptr) {
- return entry->removeFile(rest, origin);
- } else {
- return false;
- }
-}
-
-void DirectoryEntry::removeDir(const std::wstring &path)
-{
- size_t pos = path.find_first_of(L"\\/");
-
- if (pos == std::string::npos) {
- for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- DirectoryEntry* entry = *iter;
-
- if (CaseInsensitiveEqual(entry->getName(), path)) {
- entry->removeDirRecursive();
- removeDirectoryFromList(iter);
- delete entry;
- break;
- }
- }
- } else {
- std::wstring dirName = path.substr(0, pos);
- std::wstring rest = path.substr(pos + 1);
-
- DirectoryStats dummy;
- DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy);
-
- if (entry != nullptr) {
- entry->removeDir(rest);
- }
- }
-}
-
-bool DirectoryEntry::remove(const std::wstring &fileName, int* origin)
-{
- const auto lcFileName = ToLowerCopy(fileName);
-
- auto iter = m_Files.find(lcFileName);
- bool b = false;
-
- if (iter != m_Files.end()) {
- if (origin != nullptr) {
- FileEntryPtr entry = m_FileRegister->getFile(iter->second);
- if (entry.get() != nullptr) {
- bool ignore;
- *origin = entry->getOrigin(ignore);
- }
- }
-
- b = m_FileRegister->removeFile(iter->second);
- }
-
- return b;
-}
-
-bool DirectoryEntry::hasContentsFromOrigin(int originID) const
-{
- return m_Origins.find(originID) != m_Origins.end();
-}
-
-FilesOrigin &DirectoryEntry::createOrigin(
- const std::wstring &originName, const std::wstring &directory, int priority,
- DirectoryStats& stats)
-{
- auto r = m_OriginConnection->getOrCreate(
- originName, directory, priority,
- m_FileRegister, m_OriginConnection, stats);
-
- if (r.second) {
- ++stats.originCreate;
- } else {
- ++stats.originExists;
- }
-
- return r.first;
-}
-
-void DirectoryEntry::removeFiles(const std::set<FileIndex> &indices)
-{
- removeFilesFromList(indices);
-}
-
-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;
-
- DirectoryEntryFileKey key(std::move(fileNameLower));
-
- {
- 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);
-
- elapsed(stats.addFileTimes, [&] {
- addFileToList(std::move(key.value), fe->getIndex());
- });
-
- // fileNameLower has moved from this point
- }
- }
-
- elapsed(stats.addOriginToFileTimes, [&]{
- fe->addOrigin(origin.getID(), fileTime, archive, order);
- });
-
- elapsed(stats.addFileToOriginTimes, [&]{
- origin.addFile(fe->getIndex());
- });
-
- return fe;
-}
-
-FileEntryPtr DirectoryEntry::insert(
- env::File& file, FilesOrigin &origin, std::wstring_view archive, int order,
- DirectoryStats& stats)
-{
- FileEntryPtr fe;
-
- {
- std::unique_lock lock(m_FilesMutex);
-
- FilesMap::iterator itor;
-
- 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
- }
- }
-
- elapsed(stats.addOriginToFileTimes, [&]{
- fe->addOrigin(origin.getID(), file.lastModified, archive, order);
- });
-
- elapsed(stats.addFileToOriginTimes, [&]{
- origin.addFile(fe->getIndex());
- });
-
- return fe;
-}
-
-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);
- },
-
- [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t)
- {
- onFile((Context*)pcx, path, ft);
- }
- );
-}
-
-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, [&] {
- 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, const BSA::Folder::Ptr archiveFolder, FILETIME fileTime,
- const std::wstring& archiveName, int order, DirectoryStats& stats)
-{
- // add files
- 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, stats);
-
- if (f) {
- if (file->getUncompressedFileSize() > 0) {
- f->setFileSize(file->getFileSize(), file->getUncompressedFileSize());
- } else {
- f->setFileSize(file->getFileSize(), FileEntry::NoFileSize);
- }
- }
- }
-
- // recurse into subdirectories
- 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, stats);
- }
-}
-
-DirectoryEntry* DirectoryEntry::getSubDirectory(
- std::wstring_view name, bool create, DirectoryStats& stats, int originID)
-{
- 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(
- std::wstring(name.begin(), name.end()), this, originID,
- m_FileRegister, m_OriginConnection);
-
- elapsed(stats.addDirectoryTimes, [&] {
- addDirectoryToList(entry, std::move(nameLc));
- // nameLc is moved from this point
- });
-
- return entry;
- } else {
- return nullptr;
- }
-}
-
-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?
- return this;
- }
-
- const size_t pos = path.find_first_of(L"\\/");
-
- if (pos == std::wstring::npos) {
- return getSubDirectory(path, create, stats);
- } else {
- DirectoryEntry* nextChild = getSubDirectory(
- path.substr(0, pos), create, stats, originID);
-
- if (nextChild == nullptr) {
- return nullptr;
- } else {
- return nextChild->getSubDirectoryRecursive(
- path.substr(pos + 1), create, stats, originID);
- }
- }
-}
-
-void DirectoryEntry::removeDirRecursive()
-{
- while (!m_Files.empty()) {
- m_FileRegister->removeFile(m_Files.begin()->second);
- }
-
- m_FilesLookup.clear();
-
- for (DirectoryEntry* entry : m_SubDirectories) {
- entry->removeDirRecursive();
- delete entry;
- }
-
- m_SubDirectories.clear();
- m_SubDirectoriesLookup.clear();
-}
-
-void DirectoryEntry::addDirectoryToList(DirectoryEntry* e, std::wstring nameLc)
-{
- m_SubDirectories.insert(e);
- m_SubDirectoriesLookup.emplace(std::move(nameLc), e);
-}
-
-void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor)
-{
- const auto* entry = *itor;
-
- {
- auto itor2 = std::find_if(
- m_SubDirectoriesLookup.begin(), m_SubDirectoriesLookup.end(),
- [&](auto&& d) { return (d.second == entry); });
-
- if (itor2 == m_SubDirectoriesLookup.end()) {
- log::error("entry {} not in sub directories map", entry->getName());
- } else {
- m_SubDirectoriesLookup.erase(itor2);
- }
- }
-
- m_SubDirectories.erase(itor);
-}
-
-void DirectoryEntry::removeFileFromList(FileIndex index)
-{
- auto removeFrom = [&](auto& list) {
- auto iter = std::find_if(
- list.begin(), list.end(),
- [&index](auto&& pair) { return (pair.second == index); }
- );
-
- if (iter == list.end()) {
- auto f = m_FileRegister->getFile(index);
-
- if (f) {
- log::error(
- "can't remove file '{}', not in directory entry '{}'",
- f->getName(), getName());
- } else {
- log::error(
- "can't remove file with index {}, not in directory entry '{}' and "
- "not in register",
- index, getName());
- }
- } else {
- list.erase(iter);
- }
- };
-
- removeFrom(m_FilesLookup);
- removeFrom(m_Files);
-}
-
-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()) {
- iter = m_Files.erase(iter);
- } else {
- ++iter;
- }
- }
-
- for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) {
- if (indices.find(iter->second) != indices.end()) {
- iter = m_FilesLookup.erase(iter);
- } else {
- ++iter;
- }
- }
-}
-
-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
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "directoryentry.h" +#include "../envfs.h" +#include "fileentry.h" +#include "filesorigin.h" +#include "originconnection.h" +#include "util.h" +#include "windows_error.h" +#include <log.h> +#include <utility.h> + +namespace MOShared +{ + +using namespace MOBase; +const int MAXPATH_UNICODE = 32767; + +template <class F> +void elapsedImpl(std::chrono::nanoseconds& out, F&& f) +{ + 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(); + } +} + +// 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 + + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + versionInfo.dwMajorVersion = 6; + versionInfo.dwMinorVersion = 1; + + ULONGLONG mask = ::VerSetConditionMask( + ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, + VER_GREATER_EQUAL); + + return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, + mask) == TRUE); +} + +bool DirCompareByName::operator()(const DirectoryEntry* lhs, + const DirectoryEntry* rhs) const +{ + return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; +} + +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); +} + +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); +} + +DirectoryEntry::~DirectoryEntry() +{ + clear(); +} + +void DirectoryEntry::clear() +{ + for (auto itor = m_SubDirectories.rbegin(); itor != m_SubDirectories.rend(); ++itor) { + delete *itor; + } + + m_Files.clear(); + m_FilesLookup.clear(); + m_SubDirectories.clear(); + m_SubDirectoriesLookup.clear(); +} + +void DirectoryEntry::addFromOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + env::DirectoryWalker walker; + addFromOrigin(walker, originName, directory, priority, stats); +} + +void DirectoryEntry::addFromOrigin(env::DirectoryWalker& walker, + const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + + if (!directory.empty()) { + addFiles(walker, origin, directory, stats); + } + + m_Populated = true; +} + +void DirectoryEntry::addFromList(const std::wstring& originName, + const std::wstring& directory, env::Directory& root, + int priority, DirectoryStats& stats) +{ + stats = {}; + + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + addDir(origin, root, stats); +} + +void DirectoryEntry::addDir(FilesOrigin& origin, env::Directory& d, + DirectoryStats& stats) +{ + elapsed(stats.dirTimes, [&] { + for (auto& sd : d.dirs) { + auto* sdirEntry = getSubDirectory(sd, true, stats, origin.getID()); + sdirEntry->addDir(origin, sd, stats); + } + }); + + elapsed(stats.fileTimes, [&] { + for (auto& f : d.files) { + insert(f, origin, L"", -1, stats); + } + }); + + m_Populated = true; +} + +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) +{ + for (const auto& archive : archives) { + const std::filesystem::path archivePath(archive); + const auto filename = archivePath.filename().native(); + + if (!enabledArchives.contains(filename)) { + continue; + } + + const auto filenameLc = ToLowerCopy(filename); + + int order = -1; + + for (auto plugin : loadOrder) { + const auto pluginNameLc = + ToLowerCopy(std::filesystem::path(plugin).stem().native()); + + 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); + } + } + } + + addFromBSA(originName, directory, archivePath.native(), priority, order, stats); + } +} + +void DirectoryEntry::addFromBSA(const std::wstring& originName, + const std::wstring& directory, + const std::wstring& archivePath, int priority, + int order, DirectoryStats& stats) +{ + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + const auto archiveName = std::filesystem::path(archivePath).filename().native(); + + if (containsArchive(archiveName)) { + return; + } + + BSA::Archive archive; + BSA::EErrorCode res = BSA::ERROR_NONE; + + try { + // read() can return an error, but it can also throw if the file is not a + // valid bsa + res = archive.read(ToString(archivePath, false).c_str(), false); + } catch (std::exception& e) { + log::error("invalid bsa '{}', error {}", archivePath, e.what()); + return; + } + + if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { + log::error("invalid bsa '{}', error {}", archivePath, res); + return; + } + + std::error_code ec; + const auto lwt = std::filesystem::last_write_time(archivePath, ec); + FILETIME ft = {}; + + if (ec) { + log::warn("failed to get last modified date for '{}', {}", archivePath, + ec.message()); + } else { + ft = ToFILETIME(lwt); + } + + addFiles(origin, archive.getRoot(), ft, archiveName, order, stats); + + m_Populated = true; +} + +void DirectoryEntry::propagateOrigin(int origin) +{ + { + std::scoped_lock lock(m_OriginsMutex); + m_Origins.insert(origin); + } + + if (m_Parent != nullptr) { + m_Parent->propagateOrigin(origin); + } +} + +bool DirectoryEntry::originExists(const std::wstring& name) const +{ + return m_OriginConnection->exists(name); +} + +FilesOrigin& DirectoryEntry::getOriginByID(int ID) const +{ + return m_OriginConnection->getByID(ID); +} + +FilesOrigin& DirectoryEntry::getOriginByName(const std::wstring& name) const +{ + return m_OriginConnection->getByName(name); +} + +const FilesOrigin* DirectoryEntry::findOriginByID(int ID) const +{ + return m_OriginConnection->findByID(ID); +} + +int DirectoryEntry::anyOrigin() const +{ + bool ignore; + + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if ((entry.get() != nullptr) && !entry->isFromArchive()) { + return entry->getOrigin(ignore); + } + } + + // 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) { + int res = entry->anyOrigin(); + if (res != InvalidOriginID) { + return res; + } + } + + return *(m_Origins.begin()); +} + +std::vector<FileEntryPtr> DirectoryEntry::getFiles() const +{ + std::vector<FileEntryPtr> result; + + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + result.push_back(m_FileRegister->getFile(iter->second)); + } + + return result; +} + +DirectoryEntry* DirectoryEntry::findSubDirectory(const std::wstring& name, + bool alreadyLowerCase) const +{ + SubDirectoriesLookup::const_iterator itor; + + if (alreadyLowerCase) { + itor = m_SubDirectoriesLookup.find(name); + } else { + itor = m_SubDirectoriesLookup.find(ToLowerCopy(name)); + } + + if (itor == m_SubDirectoriesLookup.end()) { + return nullptr; + } + + return itor->second; +} + +DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring& path) +{ + DirectoryStats dummy; + return getSubDirectoryRecursive(path, false, dummy, InvalidOriginID); +} + +const FileEntryPtr DirectoryEntry::findFile(const std::wstring& name, + bool alreadyLowerCase) const +{ + FilesLookup::const_iterator iter; + + if (alreadyLowerCase) { + iter = m_FilesLookup.find(DirectoryEntryFileKey(name)); + } else { + iter = m_FilesLookup.find(DirectoryEntryFileKey(ToLowerCopy(name))); + } + + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntryPtr(); + } +} + +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 FileEntryPtr(); + } +} + +bool DirectoryEntry::hasFile(const std::wstring& name) const +{ + return m_Files.contains(ToLowerCopy(name)); +} + +bool DirectoryEntry::containsArchive(std::wstring archiveName) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if (entry->isFromArchive(archiveName)) { + return true; + } + } + + return false; +} + +const FileEntryPtr DirectoryEntry::searchFile(const std::wstring& path, + const DirectoryEntry** directory) const +{ + if (directory != nullptr) { + *directory = nullptr; + } + + if ((path.length() == 0) || (path == L"*")) { + // no file name -> the path ended on a (back-)slash + if (directory != nullptr) { + *directory = this; + } + + return FileEntryPtr(); + } + + const size_t len = path.find_first_of(L"\\/"); + + if (len == std::string::npos) { + // no more path components + auto iter = m_Files.find(ToLowerCopy(path)); + + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else if (directory != nullptr) { + DirectoryEntry* temp = findSubDirectory(path); + if (temp != nullptr) { + *directory = temp; + } + } + } else { + // file is in a subdirectory, recurse into the matching subdirectory + std::wstring pathComponent = path.substr(0, len); + DirectoryEntry* temp = findSubDirectory(pathComponent); + + if (temp != nullptr) { + if (len >= path.size()) { + log::error(QObject::tr("unexpected end of path").toStdString()); + return FileEntryPtr(); + } + + return temp->searchFile(path.substr(len + 1), directory); + } + } + + return FileEntryPtr(); +} + +void DirectoryEntry::removeFile(FileIndex index) +{ + removeFileFromList(index); +} + +bool DirectoryEntry::removeFile(const std::wstring& filePath, int* origin) +{ + size_t pos = filePath.find_first_of(L"\\/"); + + if (pos == std::string::npos) { + return this->remove(filePath, origin); + } + + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + + DirectoryStats dummy; + DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); + + if (entry != nullptr) { + return entry->removeFile(rest, origin); + } else { + return false; + } +} + +void DirectoryEntry::removeDir(const std::wstring& path) +{ + size_t pos = path.find_first_of(L"\\/"); + + if (pos == std::string::npos) { + for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + DirectoryEntry* entry = *iter; + + if (CaseInsensitiveEqual(entry->getName(), path)) { + entry->removeDirRecursive(); + removeDirectoryFromList(iter); + delete entry; + break; + } + } + } else { + std::wstring dirName = path.substr(0, pos); + std::wstring rest = path.substr(pos + 1); + + DirectoryStats dummy; + DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); + + if (entry != nullptr) { + entry->removeDir(rest); + } + } +} + +bool DirectoryEntry::remove(const std::wstring& fileName, int* origin) +{ + const auto lcFileName = ToLowerCopy(fileName); + + auto iter = m_Files.find(lcFileName); + bool b = false; + + if (iter != m_Files.end()) { + if (origin != nullptr) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if (entry.get() != nullptr) { + bool ignore; + *origin = entry->getOrigin(ignore); + } + } + + b = m_FileRegister->removeFile(iter->second); + } + + return b; +} + +bool DirectoryEntry::hasContentsFromOrigin(int originID) const +{ + return m_Origins.find(originID) != m_Origins.end(); +} + +FilesOrigin& DirectoryEntry::createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + auto r = m_OriginConnection->getOrCreate(originName, directory, priority, + m_FileRegister, m_OriginConnection, stats); + + if (r.second) { + ++stats.originCreate; + } else { + ++stats.originExists; + } + + return r.first; +} + +void DirectoryEntry::removeFiles(const std::set<FileIndex>& indices) +{ + removeFilesFromList(indices); +} + +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; + + DirectoryEntryFileKey key(std::move(fileNameLower)); + + { + 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); + + elapsed(stats.addFileTimes, [&] { + addFileToList(std::move(key.value), fe->getIndex()); + }); + + // fileNameLower has moved from this point + } + } + + elapsed(stats.addOriginToFileTimes, [&] { + fe->addOrigin(origin.getID(), fileTime, archive, order); + }); + + elapsed(stats.addFileToOriginTimes, [&] { + origin.addFile(fe->getIndex()); + }); + + return fe; +} + +FileEntryPtr DirectoryEntry::insert(env::File& file, FilesOrigin& origin, + std::wstring_view archive, int order, + DirectoryStats& stats) +{ + FileEntryPtr fe; + + { + std::unique_lock lock(m_FilesMutex); + + FilesMap::iterator itor; + + 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 + } + } + + elapsed(stats.addOriginToFileTimes, [&] { + fe->addOrigin(origin.getID(), file.lastModified, archive, order); + }); + + elapsed(stats.addFileToOriginTimes, [&] { + origin.addFile(fe->getIndex()); + }); + + return fe; +} + +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); + }, + + [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t) { + onFile((Context*)pcx, path, ft); + }); +} + +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, [&] { + 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, const BSA::Folder::Ptr archiveFolder, + FILETIME fileTime, const std::wstring& archiveName, + int order, DirectoryStats& stats) +{ + // add files + 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, stats); + + if (f) { + if (file->getUncompressedFileSize() > 0) { + f->setFileSize(file->getFileSize(), file->getUncompressedFileSize()); + } else { + f->setFileSize(file->getFileSize(), FileEntry::NoFileSize); + } + } + } + + // recurse into subdirectories + 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, stats); + } +} + +DirectoryEntry* DirectoryEntry::getSubDirectory(std::wstring_view name, bool create, + DirectoryStats& stats, int originID) +{ + 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(std::wstring(name.begin(), name.end()), this, + originID, m_FileRegister, m_OriginConnection); + + elapsed(stats.addDirectoryTimes, [&] { + addDirectoryToList(entry, std::move(nameLc)); + // nameLc is moved from this point + }); + + return entry; + } else { + return nullptr; + } +} + +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? + return this; + } + + const size_t pos = path.find_first_of(L"\\/"); + + if (pos == std::wstring::npos) { + return getSubDirectory(path, create, stats); + } else { + DirectoryEntry* nextChild = + getSubDirectory(path.substr(0, pos), create, stats, originID); + + if (nextChild == nullptr) { + return nullptr; + } else { + return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, stats, + originID); + } + } +} + +void DirectoryEntry::removeDirRecursive() +{ + while (!m_Files.empty()) { + m_FileRegister->removeFile(m_Files.begin()->second); + } + + m_FilesLookup.clear(); + + for (DirectoryEntry* entry : m_SubDirectories) { + entry->removeDirRecursive(); + delete entry; + } + + m_SubDirectories.clear(); + m_SubDirectoriesLookup.clear(); +} + +void DirectoryEntry::addDirectoryToList(DirectoryEntry* e, std::wstring nameLc) +{ + m_SubDirectories.insert(e); + m_SubDirectoriesLookup.emplace(std::move(nameLc), e); +} + +void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor) +{ + const auto* entry = *itor; + + { + auto itor2 = std::find_if(m_SubDirectoriesLookup.begin(), + m_SubDirectoriesLookup.end(), [&](auto&& d) { + return (d.second == entry); + }); + + if (itor2 == m_SubDirectoriesLookup.end()) { + log::error("entry {} not in sub directories map", entry->getName()); + } else { + m_SubDirectoriesLookup.erase(itor2); + } + } + + m_SubDirectories.erase(itor); +} + +void DirectoryEntry::removeFileFromList(FileIndex index) +{ + auto removeFrom = [&](auto& list) { + auto iter = std::find_if(list.begin(), list.end(), [&index](auto&& pair) { + return (pair.second == index); + }); + + if (iter == list.end()) { + auto f = m_FileRegister->getFile(index); + + if (f) { + log::error("can't remove file '{}', not in directory entry '{}'", f->getName(), + getName()); + } else { + log::error("can't remove file with index {}, not in directory entry '{}' and " + "not in register", + index, getName()); + } + } else { + list.erase(iter); + } + }; + + removeFrom(m_FilesLookup); + removeFrom(m_Files); +} + +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()) { + iter = m_Files.erase(iter); + } else { + ++iter; + } + } + + for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_FilesLookup.erase(iter); + } else { + ++iter; + } + } +} + +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 0ee3c919..42aef8c7 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -1,321 +1,284 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 MO_REGISTER_DIRECTORYENTRY_INCLUDED
-#define MO_REGISTER_DIRECTORYENTRY_INCLUDED
-
-#include "fileregister.h"
-#include <bsatk.h>
-
-namespace env
-{
- class DirectoryWalker;
- struct Directory;
- struct File;
-}
-
-namespace std
-{
- template <>
- struct hash<MOShared::DirectoryEntryFileKey>
- {
- using argument_type = MOShared::DirectoryEntryFileKey;
- using result_type = std::size_t;
-
- inline result_type operator()(const argument_type& key) const;
- };
-}
-
-
-namespace MOShared
-{
-
-struct DirCompareByName
-{
- bool operator()(const DirectoryEntry* a, const DirectoryEntry* b) const;
-};
-
-
-class DirectoryEntry
-{
-public:
- using SubDirectories = std::set<DirectoryEntry*, DirCompareByName>;
-
- DirectoryEntry(
- std::wstring name, DirectoryEntry* parent, OriginID originID);
-
- DirectoryEntry(
- 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
- {
- return m_Populated;
- }
-
- bool isTopLevel() const
- {
- return m_TopLevel;
- }
-
- bool isEmpty() const
- {
- return m_Files.empty() && m_SubDirectories.empty();
- }
-
- bool hasFiles() const
- {
- return !m_Files.empty();
- }
-
- const DirectoryEntry* getParent() const
- {
- return m_Parent;
- }
-
- // 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, 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, 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(OriginID origin);
-
- const std::wstring& getName() const
- {
- return m_Name;
- }
-
- boost::shared_ptr<FileRegister> getFileRegister()
- {
- return m_FileRegister;
- }
-
- 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;
-
- OriginID anyOrigin() const;
-
- std::vector<FileEntryPtr> getFiles() const;
-
- const SubDirectories& getSubDirectories() const
- {
- return m_SubDirectories;
- }
-
- template <class F>
- void forEachDirectory(F&& f) const
- {
- for (auto&& d : m_SubDirectories) {
- if (!f(*d)) {
- break;
- }
- }
- }
-
- template <class F>
- void forEachFile(F&& f) const
- {
- for (auto&& p : m_Files) {
- if (auto file=m_FileRegister->getFile(p.second)) {
- if (!f(*file)) {
- break;
- }
- }
- }
- }
-
- template <class F>
- void forEachFileIndex(F&& f) const
- {
- for (auto&& p : m_Files) {
- if (!f(p.second)) {
- break;
- }
- }
- }
-
- FileEntryPtr getFileByIndex(FileIndex index) const
- {
- return m_FileRegister->getFile(index);
- }
-
- DirectoryEntry* findSubDirectory(
- const std::wstring& name, bool alreadyLowerCase=false) const;
-
- 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 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);
-
- // search through this directory and all subdirectories for a file by the
- // specified name (relative path).
- //
- // if directory is not nullptr, the referenced variable will be set to the
- // path containing the file
- //
- const FileEntryPtr searchFile(
- const std::wstring& path, const DirectoryEntry** directory=nullptr) const;
-
- 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, OriginID* origin = nullptr);
-
- /**
- * @brief remove the specified directory
- * @param path directory to remove
- */
- void removeDir(const std::wstring& path);
-
- bool remove(const std::wstring& fileName, OriginID* origin);
-
- bool hasContentsFromOrigin(OriginID originID) const;
-
- FilesOrigin& createOrigin(
- const std::wstring& originName,
- const std::wstring& directory, int priority, DirectoryStats& stats);
-
- void removeFiles(const std::set<FileIndex>& indices);
-
- void dump(const std::wstring& file) const;
-
-private:
- using FilesMap = std::map<std::wstring, FileIndex>;
- using FilesLookup = std::unordered_map<DirectoryEntryFileKey, FileIndex>;
- using SubDirectoriesLookup = std::unordered_map<std::wstring, DirectoryEntry*>;
-
- boost::shared_ptr<FileRegister> m_FileRegister;
- boost::shared_ptr<OriginConnection> m_OriginConnection;
-
- std::wstring m_Name;
- FilesMap m_Files;
- FilesLookup m_FilesLookup;
- SubDirectories m_SubDirectories;
- SubDirectoriesLookup m_SubDirectoriesLookup;
-
- 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;
-
-
- FileEntryPtr insert(
- std::wstring_view fileName, FilesOrigin& origin, FILETIME fileTime,
- std::wstring_view archive, int order, DirectoryStats& stats);
-
- FileEntryPtr insert(
- env::File& file, FilesOrigin& origin,
- std::wstring_view archive, int order, DirectoryStats& stats);
-
- void addFiles(
- 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, DirectoryStats& stats);
-
- void addDir(FilesOrigin& origin, env::Directory& d, DirectoryStats& stats);
-
- 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, std::wstring nameLc);
- void removeDirectoryFromList(SubDirectories::iterator itor);
-
- 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
-
-
-namespace std
-{
- hash<MOShared::DirectoryEntryFileKey>::result_type
- hash<MOShared::DirectoryEntryFileKey>::operator()(
- const argument_type& key) const
- {
- return key.hash;
- }
-}
-
-#endif // MO_REGISTER_DIRECTORYENTRY_INCLUDED
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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 MO_REGISTER_DIRECTORYENTRY_INCLUDED +#define MO_REGISTER_DIRECTORYENTRY_INCLUDED + +#include "fileregister.h" +#include <bsatk.h> + +namespace env +{ +class DirectoryWalker; +struct Directory; +struct File; +} // namespace env + +namespace std +{ +template <> +struct hash<MOShared::DirectoryEntryFileKey> +{ + using argument_type = MOShared::DirectoryEntryFileKey; + using result_type = std::size_t; + + inline result_type operator()(const argument_type& key) const; +}; +} // namespace std + +namespace MOShared +{ + +struct DirCompareByName +{ + bool operator()(const DirectoryEntry* a, const DirectoryEntry* b) const; +}; + +class DirectoryEntry +{ +public: + using SubDirectories = std::set<DirectoryEntry*, DirCompareByName>; + + DirectoryEntry(std::wstring name, DirectoryEntry* parent, OriginID originID); + + DirectoryEntry(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 { return m_Populated; } + + bool isTopLevel() const { return m_TopLevel; } + + bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } + + bool hasFiles() const { return !m_Files.empty(); } + + const DirectoryEntry* getParent() const { return m_Parent; } + + // 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, 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, 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(OriginID origin); + + const std::wstring& getName() const { return m_Name; } + + boost::shared_ptr<FileRegister> getFileRegister() { return m_FileRegister; } + + 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; + + OriginID anyOrigin() const; + + std::vector<FileEntryPtr> getFiles() const; + + const SubDirectories& getSubDirectories() const { return m_SubDirectories; } + + template <class F> + void forEachDirectory(F&& f) const + { + for (auto&& d : m_SubDirectories) { + if (!f(*d)) { + break; + } + } + } + + template <class F> + void forEachFile(F&& f) const + { + for (auto&& p : m_Files) { + if (auto file = m_FileRegister->getFile(p.second)) { + if (!f(*file)) { + break; + } + } + } + } + + template <class F> + void forEachFileIndex(F&& f) const + { + for (auto&& p : m_Files) { + if (!f(p.second)) { + break; + } + } + } + + FileEntryPtr getFileByIndex(FileIndex index) const + { + return m_FileRegister->getFile(index); + } + + DirectoryEntry* findSubDirectory(const std::wstring& name, + bool alreadyLowerCase = false) const; + + 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 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); + + // search through this directory and all subdirectories for a file by the + // specified name (relative path). + // + // if directory is not nullptr, the referenced variable will be set to the + // path containing the file + // + const FileEntryPtr searchFile(const std::wstring& path, + const DirectoryEntry** directory = nullptr) const; + + 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, OriginID* origin = nullptr); + + /** + * @brief remove the specified directory + * @param path directory to remove + */ + void removeDir(const std::wstring& path); + + bool remove(const std::wstring& fileName, OriginID* origin); + + bool hasContentsFromOrigin(OriginID originID) const; + + FilesOrigin& createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats); + + void removeFiles(const std::set<FileIndex>& indices); + + void dump(const std::wstring& file) const; + +private: + using FilesMap = std::map<std::wstring, FileIndex>; + using FilesLookup = std::unordered_map<DirectoryEntryFileKey, FileIndex>; + using SubDirectoriesLookup = std::unordered_map<std::wstring, DirectoryEntry*>; + + boost::shared_ptr<FileRegister> m_FileRegister; + boost::shared_ptr<OriginConnection> m_OriginConnection; + + std::wstring m_Name; + FilesMap m_Files; + FilesLookup m_FilesLookup; + SubDirectories m_SubDirectories; + SubDirectoriesLookup m_SubDirectoriesLookup; + + 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; + + FileEntryPtr insert(std::wstring_view fileName, FilesOrigin& origin, + FILETIME fileTime, std::wstring_view archive, int order, + DirectoryStats& stats); + + FileEntryPtr insert(env::File& file, FilesOrigin& origin, std::wstring_view archive, + int order, DirectoryStats& stats); + + void addFiles(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, DirectoryStats& stats); + + void addDir(FilesOrigin& origin, env::Directory& d, DirectoryStats& stats); + + 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, std::wstring nameLc); + void removeDirectoryFromList(SubDirectories::iterator itor); + + 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 + +namespace std +{ +hash<MOShared::DirectoryEntryFileKey>::result_type +hash<MOShared::DirectoryEntryFileKey>::operator()(const argument_type& key) const +{ + return key.hash; +} +} // namespace std + +#endif // MO_REGISTER_DIRECTORYENTRY_INCLUDED diff --git a/src/shared/fileentry.cpp b/src/shared/fileentry.cpp index 5be9a9dc..48670617 100644 --- a/src/shared/fileentry.cpp +++ b/src/shared/fileentry.cpp @@ -5,20 +5,18 @@ namespace MOShared { -FileEntry::FileEntry() : - m_Index(InvalidFileIndex), m_Name(), m_Origin(-1), m_Parent(nullptr), - m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize) -{ -} +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) -{ -} +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) +void FileEntry::addOrigin(OriginID origin, FILETIME fileTime, std::wstring_view archive, + int order) { std::scoped_lock lock(m_OriginsMutex); @@ -29,32 +27,30 @@ void FileEntry::addOrigin( if (m_Origin == -1) { // If this file has no previous origin, this mod is now the origin with no // alternatives - m_Origin = origin; + m_Origin = origin; m_FileTime = fileTime; - m_Archive = DataArchiveOrigin(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.isValid())) - ) { + m_Archive = DataArchiveOrigin(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.isValid()))) { // 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.originID() == m_Origin; }); + auto itor = + std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](auto&& i) { + return i.originID() == m_Origin; + }); if (itor == m_Alternatives.end()) { m_Alternatives.push_back({m_Origin, m_Archive}); } - m_Origin = origin; + m_Origin = origin; m_FileTime = fileTime; - m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); - } - else { + m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); + } else { // This mod is just an alternative bool found = false; @@ -70,15 +66,18 @@ void FileEntry::addOrigin( } if ((m_Parent != nullptr) && - (m_Parent->getOriginByID(iter->originID()).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { - m_Alternatives.insert(iter, {origin, {std::wstring(archive.begin(), archive.end()), order}}); + (m_Parent->getOriginByID(iter->originID()).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}}); + m_Alternatives.push_back( + {origin, {std::wstring(archive.begin(), archive.end()), order}}); } } } @@ -93,21 +92,21 @@ bool FileEntry::removeOrigin(OriginID origin) auto currentIter = m_Alternatives.begin(); for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { if (iter->originID() != origin) { - //Both files are not from archives. + // Both files are not from archives. if (!iter->isFromArchive() && !currentIter->isFromArchive()) { - if ((m_Parent->getOriginByID(iter->originID()).getPriority() > m_Parent->getOriginByID(currentIter->originID()).getPriority())) { + if ((m_Parent->getOriginByID(iter->originID()).getPriority() > + m_Parent->getOriginByID(currentIter->originID()).getPriority())) { currentIter = iter; } - } - else { - //Both files are from archives + } else { + // Both files are from archives if (iter->isFromArchive() && currentIter->isFromArchive()) { if (iter->archive().order() > currentIter->archive().order()) { currentIter = iter; } - } - else { - //Only one of the two is an archive, so we change currentIter only if he is the archive one. + } else { + // Only one of the two is an archive, so we change currentIter only if he + // is the archive one. if (currentIter->isFromArchive()) { currentIter = iter; } @@ -117,19 +116,20 @@ bool FileEntry::removeOrigin(OriginID origin) } OriginID currentID = currentIter->originID(); - m_Archive = currentIter->archive(); + m_Archive = currentIter->archive(); m_Alternatives.erase(currentIter); m_Origin = currentID; } else { - m_Origin = -1; + m_Origin = -1; m_Archive = DataArchiveOrigin(L"", -1); return true; } } else { - auto newEnd = std::remove_if( - m_Alternatives.begin(), m_Alternatives.end(), - [&](auto &i) { return i.originID() == origin; }); + auto newEnd = + std::remove_if(m_Alternatives.begin(), m_Alternatives.end(), [&](auto& i) { + return i.originID() == origin; + }); if (newEnd != m_Alternatives.end()) { m_Alternatives.erase(newEnd, m_Alternatives.end()); @@ -160,8 +160,12 @@ void FileEntry::sortOrigins() } if (LHS.isFromArchive() && RHS.isFromArchive()) { - int l = LHS.archive().order(); if (l < 0) l = INT_MAX; - int r = RHS.archive().order(); if (r < 0) r = INT_MAX; + int l = LHS.archive().order(); + if (l < 0) + l = INT_MAX; + int r = RHS.archive().order(); + if (r < 0) + r = INT_MAX; return l < r; } @@ -171,10 +175,10 @@ void FileEntry::sortOrigins() } return true; - }); + }); if (!m_Alternatives.empty()) { - m_Origin = m_Alternatives.back().originID(); + m_Origin = m_Alternatives.back().originID(); m_Archive = m_Alternatives.back().archive(); m_Alternatives.pop_back(); } @@ -207,7 +211,7 @@ std::wstring FileEntry::getFullPath(OriginID originID) const if (originID == InvalidOriginID) { bool ignore = false; - originID = getOrigin(ignore); + originID = getOrigin(ignore); } // base directory for origin @@ -234,7 +238,7 @@ std::wstring FileEntry::getRelativePath() const return result + L"\\" + m_Name; } -bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const +bool FileEntry::recurseParents(std::wstring& path, const DirectoryEntry* parent) const { if (parent == nullptr) { return false; @@ -248,4 +252,4 @@ bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) } } -} // namespace +} // namespace MOShared diff --git a/src/shared/fileentry.h b/src/shared/fileentry.h index 31655bb1..80d8b292 100644 --- a/src/shared/fileentry.h +++ b/src/shared/fileentry.h @@ -9,23 +9,19 @@ namespace MOShared class FileEntry { public: - static constexpr uint64_t NoFileSize = - std::numeric_limits<uint64_t>::max(); + static constexpr uint64_t NoFileSize = std::numeric_limits<uint64_t>::max(); FileEntry(); - FileEntry(FileIndex index, std::wstring name, DirectoryEntry *parent); + FileEntry(FileIndex index, std::wstring name, DirectoryEntry* parent); // noncopyable - FileEntry(const FileEntry&) = delete; + FileEntry(const FileEntry&) = delete; FileEntry& operator=(const FileEntry&) = delete; - FileIndex getIndex() const - { - return m_Index; - } + FileIndex getIndex() const { return m_Index; } - void addOrigin( - OriginID origin, FILETIME fileTime, std::wstring_view archive, int order); + 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 @@ -37,71 +33,44 @@ public: // 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 AlternativesVector& getAlternatives() const { return m_Alternatives; } - const std::wstring &getName() const - { - return m_Name; - } + const std::wstring& getName() const { return m_Name; } - OriginID getOrigin() const - { - return m_Origin; - } + OriginID getOrigin() const { return m_Origin; } - OriginID getOrigin(bool &archive) const + OriginID getOrigin(bool& archive) const { archive = m_Archive.isValid(); return m_Origin; } - const DataArchiveOrigin &getArchive() const - { - return m_Archive; - } + const DataArchiveOrigin& 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 getFullPath(OriginID originID = InvalidOriginID) const; std::wstring getRelativePath() const; - DirectoryEntry *getParent() - { - return m_Parent; - } + DirectoryEntry* getParent() { return m_Parent; } - void setFileTime(FILETIME fileTime) const - { - m_FileTime = fileTime; - } + void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } - FILETIME getFileTime() const - { - return m_FileTime; - } + FILETIME getFileTime() const { return m_FileTime; } void setFileSize(uint64_t size, uint64_t compressedSize) { - m_FileSize = size; + m_FileSize = size; m_CompressedFileSize = compressedSize; } - uint64_t getFileSize() const - { - return m_FileSize; - } + uint64_t getFileSize() const { return m_FileSize; } - uint64_t getCompressedFileSize() const - { - return m_CompressedFileSize; - } + uint64_t getCompressedFileSize() const { return m_CompressedFileSize; } private: FileIndex m_Index; @@ -109,14 +78,14 @@ private: OriginID m_Origin; DataArchiveOrigin m_Archive; AlternativesVector m_Alternatives; - DirectoryEntry *m_Parent; + 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; + bool recurseParents(std::wstring& path, const DirectoryEntry* parent) const; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILEENTRY_INCLUDED +#endif // MO_REGISTER_FILEENTRY_INCLUDED diff --git a/src/shared/fileregister.cpp b/src/shared/fileregister.cpp index 98e49452..ab94367f 100644 --- a/src/shared/fileregister.cpp +++ b/src/shared/fileregister.cpp @@ -1,8 +1,8 @@ #include "fileregister.h" -#include "fileentry.h" #include "directoryentry.h" -#include "originconnection.h" +#include "fileentry.h" #include "filesorigin.h" +#include "originconnection.h" #include <log.h> namespace MOShared @@ -11,9 +11,8 @@ namespace MOShared using namespace MOBase; FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection) - : m_OriginConnection(originConnection), m_NextIndex(0) -{ -} + : m_OriginConnection(originConnection), m_NextIndex(0) +{} bool FileRegister::indexValid(FileIndex index) const { @@ -26,11 +25,11 @@ bool FileRegister::indexValid(FileIndex index) const return false; } -FileEntryPtr FileRegister::createFile( - std::wstring name, DirectoryEntry *parent, DirectoryStats& stats) +FileEntryPtr FileRegister::createFile(std::wstring name, DirectoryEntry* parent, + DirectoryStats& stats) { const auto index = generateIndex(); - auto p = FileEntryPtr(new FileEntry(index, std::move(name), parent)); + auto p = FileEntryPtr(new FileEntry(index, std::move(name), parent)); { std::scoped_lock lock(m_Mutex); @@ -96,18 +95,19 @@ void FileRegister::removeOrigin(FileIndex index, OriginID originID) } } - log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index); + log::error( + QObject::tr("invalid file index for remove (for origin): {}").toStdString(), + index); } -void FileRegister::removeOriginMulti( - std::set<FileIndex> indices, OriginID originID) +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(); ) { + for (auto iter = indices.begin(); iter != indices.end();) { const auto index = *iter; if (index < m_Files.size()) { @@ -140,13 +140,13 @@ void FileRegister::removeOriginMulti( // frequently the case std::set<DirectoryEntry*> parents; - for (const FileEntryPtr &file : removedFiles) { + for (const FileEntryPtr& file : removedFiles) { if (file->getParent() != nullptr) { parents.insert(file->getParent()); } } - for (DirectoryEntry *parent : parents) { + for (DirectoryEntry* parent : parents) { parent->removeFiles(indices); } } @@ -181,4 +181,4 @@ void FileRegister::unregisterFile(FileEntryPtr file) } } -} // namespace +} // namespace MOShared diff --git a/src/shared/fileregister.h b/src/shared/fileregister.h index c27dc93b..aeb2c5b7 100644 --- a/src/shared/fileregister.h +++ b/src/shared/fileregister.h @@ -2,8 +2,8 @@ #define MO_REGISTER_FILESREGISTER_INCLUDED #include "fileregisterfwd.h" -#include <mutex> #include <boost/shared_ptr.hpp> +#include <mutex> namespace MOShared { @@ -14,13 +14,13 @@ public: FileRegister(boost::shared_ptr<OriginConnection> originConnection); // noncopyable - FileRegister(const FileRegister&) = delete; + FileRegister(const FileRegister&) = delete; FileRegister& operator=(const FileRegister&) = delete; bool indexValid(FileIndex index) const; - FileEntryPtr createFile( - std::wstring name, DirectoryEntry *parent, DirectoryStats& stats); + FileEntryPtr createFile(std::wstring name, DirectoryEntry* parent, + DirectoryStats& stats); FileEntryPtr getFile(FileIndex index) const; @@ -48,6 +48,6 @@ private: FileIndex generateIndex(); }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILESREGISTER_INCLUDED +#endif // MO_REGISTER_FILESREGISTER_INCLUDED diff --git a/src/shared/fileregisterfwd.h b/src/shared/fileregisterfwd.h index 57b3715a..dce40ab5 100644 --- a/src/shared/fileregisterfwd.h +++ b/src/shared/fileregisterfwd.h @@ -8,15 +8,9 @@ namespace MOShared struct DirectoryEntryFileKey { - DirectoryEntryFileKey(std::wstring v) - : value(std::move(v)), hash(getHash(value)) - { - } + DirectoryEntryFileKey(std::wstring v) : value(std::move(v)), hash(getHash(value)) {} - bool operator==(const DirectoryEntryFileKey& o) const - { - return (value == o.value); - } + bool operator==(const DirectoryEntryFileKey& o) const { return (value == o.value); } static std::size_t getHash(const std::wstring& value) { @@ -27,7 +21,6 @@ struct DirectoryEntryFileKey const std::size_t hash; }; - class DirectoryEntry; class OriginConnection; class FileRegister; @@ -36,11 +29,11 @@ class FileEntry; struct DirectoryStats; using FileEntryPtr = boost::shared_ptr<FileEntry>; -using FileIndex = unsigned int; -using OriginID = int; +using FileIndex = unsigned int; +using OriginID = int; constexpr FileIndex InvalidFileIndex = UINT_MAX; -constexpr OriginID InvalidOriginID = -1; +constexpr OriginID InvalidOriginID = -1; // if a file is in an archive, name is the name of the bsa and order // is the order of the associated plugin in the plugins list @@ -49,19 +42,17 @@ constexpr OriginID InvalidOriginID = -1; class DataArchiveOrigin { std::wstring name_ = L""; - int order_ = -1; - -public: + int order_ = -1; +public: int order() const { return order_; } const std::wstring& name() const { return name_; } - - bool isValid() const { - return name_.size() > 0; - } + + bool isValid() const { return name_.size() > 0; } DataArchiveOrigin(std::wstring name, int order) - : name_(std::move(name)), order_(order) {} + : name_(std::move(name)), order_(order) + {} DataArchiveOrigin() = default; }; @@ -72,18 +63,16 @@ class FileAlternative DataArchiveOrigin archive_; public: - OriginID originID() const { return originID_; } const DataArchiveOrigin& archive() const { return archive_; } - bool isFromArchive() const { - return archive_.isValid(); - } + bool isFromArchive() const { return archive_.isValid(); } FileAlternative() = default; - FileAlternative(OriginID originID, DataArchiveOrigin archive) - : originID_(originID), archive_(std::move(archive)) {} + FileAlternative(OriginID originID, DataArchiveOrigin archive) + : originID_(originID), archive_(std::move(archive)) + {} }; using AlternativesVector = std::vector<FileAlternative>; @@ -127,6 +116,6 @@ struct DirectoryStats std::string toCsv() const; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILEREGISTERFWD_INCLUDED +#endif // MO_REGISTER_FILEREGISTERFWD_INCLUDED diff --git a/src/shared/filesorigin.cpp b/src/shared/filesorigin.cpp index 049e4258..dd1aec61 100644 --- a/src/shared/filesorigin.cpp +++ b/src/shared/filesorigin.cpp @@ -1,12 +1,12 @@ #include "filesorigin.h" -#include "originconnection.h" -#include "fileregister.h" #include "fileentry.h" +#include "fileregister.h" +#include "originconnection.h" namespace MOShared { -std::wstring tail(const std::wstring &source, const size_t count) +std::wstring tail(const std::wstring& source, const size_t count) { if (count >= source.length()) { return source; @@ -15,28 +15,24 @@ std::wstring tail(const std::wstring &source, const size_t count) return source.substr(source.length() - count); } - FilesOrigin::FilesOrigin() - : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) -{ -} + : 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) -{ -} +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_Priority = priority; } -void FilesOrigin::setName(const std::wstring &name) +void FilesOrigin::setName(const std::wstring& name) { m_OriginConnection.lock()->changeNameLookup(m_Name, name); @@ -121,4 +117,4 @@ bool FilesOrigin::containsArchive(std::wstring archiveName) return false; } -} // namespace +} // namespace MOShared diff --git a/src/shared/filesorigin.h b/src/shared/filesorigin.h index 1aa4c645..92fe12ef 100644 --- a/src/shared/filesorigin.h +++ b/src/shared/filesorigin.h @@ -12,40 +12,26 @@ 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); + 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(const FilesOrigin&) = delete; FilesOrigin& operator=(const FilesOrigin&) = delete; // sets priority for this origin (does not automatically refresh // the structure) void setPriority(int priority); - int getPriority() const - { - return m_Priority; - } + int getPriority() const { return m_Priority; } - void setName(const std::wstring &name); - const std::wstring &getName() const - { - return m_Name; - } + void setName(const std::wstring& name); + const std::wstring& getName() const { return m_Name; } - OriginID getID() const - { - return m_ID; - } + OriginID getID() const { return m_ID; } - const std::wstring &getPath() const - { - return m_Path; - } + const std::wstring& getPath() const { return m_Path; } std::vector<FileEntryPtr> getFiles() const; FileEntryPtr findFile(FileIndex index) const; @@ -53,10 +39,7 @@ public: void enable(bool enabled, DirectoryStats& stats); void enable(bool enabled); - bool isDisabled() const - { - return m_Disabled; - } + bool isDisabled() const { return m_Disabled; } void addFile(FileIndex index) { @@ -80,6 +63,6 @@ private: mutable std::mutex m_Mutex; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILESORIGIN_INCLUDED +#endif // MO_REGISTER_FILESORIGIN_INCLUDED diff --git a/src/shared/originconnection.cpp b/src/shared/originconnection.cpp index 2830e8e3..e433891c 100644 --- a/src/shared/originconnection.cpp +++ b/src/shared/originconnection.cpp @@ -8,24 +8,20 @@ namespace MOShared using namespace MOBase; -OriginConnection::OriginConnection() - : m_NextID(0) -{ -} +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) + 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); + FilesOrigin& origin = createOriginNoLock(originName, directory, priority, + fileRegister, originConnection); return {origin, true}; } else { @@ -37,18 +33,19 @@ std::pair<FilesOrigin&, bool> OriginConnection::getOrCreate( } } -FilesOrigin& OriginConnection::createOrigin( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr<FileRegister> fileRegister, - boost::shared_ptr<OriginConnection> originConnection) +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); + return createOriginNoLock(originName, directory, priority, fileRegister, + originConnection); } -bool OriginConnection::exists(const std::wstring &name) +bool OriginConnection::exists(const std::wstring& name) { std::scoped_lock lock(m_Mutex); return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); @@ -73,7 +70,7 @@ const FilesOrigin* OriginConnection::findByID(OriginID ID) const } } -FilesOrigin& OriginConnection::getByName(const std::wstring &name) +FilesOrigin& OriginConnection::getByName(const std::wstring& name) { std::scoped_lock lock(m_Mutex); @@ -83,12 +80,14 @@ FilesOrigin& OriginConnection::getByName(const std::wstring &name) return m_Origins[iter->second]; } else { std::ostringstream stream; - stream << QObject::tr("invalid origin name: ").toStdString() << ToString(name, true); + stream << QObject::tr("invalid origin name: ").toStdString() + << ToString(name, true); throw std::runtime_error(stream.str()); } } -void OriginConnection::changeNameLookup(const std::wstring &oldName, const std::wstring &newName) +void OriginConnection::changeNameLookup(const std::wstring& oldName, + const std::wstring& newName) { std::scoped_lock lock(m_Mutex); @@ -99,7 +98,8 @@ void OriginConnection::changeNameLookup(const std::wstring &oldName, const std:: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), oldName, newName); + log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), + oldName, newName); } } @@ -109,23 +109,21 @@ OriginID OriginConnection::createID() } FilesOrigin& OriginConnection::createOriginNoLock( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr<FileRegister> fileRegister, - boost::shared_ptr<OriginConnection> originConnection) + 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; + 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}); return itor->second; } -} // namespace +} // namespace MOShared diff --git a/src/shared/originconnection.h b/src/shared/originconnection.h index ef8692bb..d8a67648 100644 --- a/src/shared/originconnection.h +++ b/src/shared/originconnection.h @@ -12,29 +12,29 @@ public: OriginConnection(); // noncopyable - OriginConnection(const OriginConnection&) = delete; + 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); + 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); + 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); + bool exists(const std::wstring& name); - FilesOrigin &getByID(OriginID ID); + FilesOrigin& getByID(OriginID ID); const FilesOrigin* findByID(OriginID ID) const; - FilesOrigin &getByName(const std::wstring &name); + FilesOrigin& getByName(const std::wstring& name); void changePriorityLookup(int oldPriority, int newPriority); - void changeNameLookup(const std::wstring &oldName, const std::wstring &newName); + void changeNameLookup(const std::wstring& oldName, const std::wstring& newName); private: std::atomic<OriginID> m_NextID; @@ -44,12 +44,12 @@ private: OriginID createID(); - FilesOrigin& createOriginNoLock( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr<FileRegister> fileRegister, - boost::shared_ptr<OriginConnection> originConnection); + FilesOrigin& createOriginNoLock(const std::wstring& originName, + const std::wstring& directory, int priority, + boost::shared_ptr<FileRegister> fileRegister, + boost::shared_ptr<OriginConnection> originConnection); }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_ORIGINCONNECTION_INCLUDED +#endif // MO_REGISTER_ORIGINCONNECTION_INCLUDED diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 54c6b841..c3b4d7aa 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1,444 +1,429 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "util.h"
-#include "windows_error.h"
-#include "../mainwindow.h"
-#include "../env.h"
-#include <log.h>
-#include <usvfs.h>
-#include <usvfs_version.h>
-
-using namespace MOBase;
-
-namespace MOShared
-{
-
-bool FileExists(const std::string &filename)
-{
- DWORD dwAttrib = ::GetFileAttributesA(filename.c_str());
-
- return (dwAttrib != INVALID_FILE_ATTRIBUTES);
-}
-
-bool FileExists(const std::wstring &filename)
-{
- DWORD dwAttrib = ::GetFileAttributesW(filename.c_str());
-
- return (dwAttrib != INVALID_FILE_ATTRIBUTES);
-}
-
-bool FileExists(const std::wstring &searchPath, const std::wstring &filename)
-{
- std::wstringstream stream;
- stream << searchPath << "\\" << filename;
- return FileExists(stream.str());
-}
-
-std::string ToString(const std::wstring &source, bool utf8)
-{
- std::string result;
- if (source.length() > 0) {
- UINT codepage = CP_UTF8;
- if (!utf8) {
- codepage = AreFileApisANSI() ? GetACP() : GetOEMCP();
- }
- int sizeRequired = ::WideCharToMultiByte(codepage, 0, &source[0], -1, nullptr, 0, nullptr, nullptr);
- if (sizeRequired == 0) {
- throw windows_error("failed to convert string to multibyte");
- }
- // the size returned by WideCharToMultiByte contains zero termination IF -1 is specified for the length.
- // we don't want that \0 in the string because then the length field would be wrong. Because madness
- result.resize(sizeRequired - 1, '\0');
- ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], sizeRequired, nullptr, nullptr);
- }
-
- return result;
-}
-
-std::wstring ToWString(const std::string &source, bool utf8)
-{
- std::wstring result;
- if (source.length() > 0) {
- UINT codepage = CP_UTF8;
- if (!utf8) {
- codepage = AreFileApisANSI() ? GetACP() : GetOEMCP();
- }
- int sizeRequired
- = ::MultiByteToWideChar(codepage, 0, source.c_str(),
- static_cast<int>(source.length()), nullptr, 0);
- if (sizeRequired == 0) {
- throw windows_error("failed to convert string to wide character");
- }
- result.resize(sizeRequired, L'\0');
- ::MultiByteToWideChar(codepage, 0, source.c_str(),
- static_cast<int>(source.length()), &result[0],
- sizeRequired);
- }
-
- return result;
-}
-
-static std::locale loc("");
-static auto locToLowerW = [] (wchar_t in) -> wchar_t {
- return std::tolower(in, loc);
-};
-
-static auto locToLower = [] (char in) -> char {
- return std::tolower(in, loc);
-};
-
-std::string& ToLowerInPlace(std::string& text)
-{
- CharLowerBuffA(const_cast<CHAR *>(text.c_str()), static_cast<DWORD>(text.size()));
- return text;
-}
-
-std::string ToLowerCopy(const std::string& text)
-{
- std::string result(text);
- CharLowerBuffA(const_cast<CHAR *>(result.c_str()), static_cast<DWORD>(result.size()));
- return result;
-}
-
-std::wstring& ToLowerInPlace(std::wstring& text)
-{
- CharLowerBuffW(const_cast<WCHAR *>(text.c_str()), static_cast<DWORD>(text.size()));
- return text;
-}
-
-std::wstring ToLowerCopy(const std::wstring& text)
-{
- std::wstring result(text);
- CharLowerBuffW(const_cast<WCHAR *>(result.c_str()), static_cast<DWORD>(result.size()));
- 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);
-}
-
-bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs)
-{
- return (lhs.length() == rhs.length())
- && std::equal(lhs.begin(), lhs.end(),
- rhs.begin(),
- [] (wchar_t lhs, wchar_t rhs) -> bool {
- return std::tolower(lhs, loc) == std::tolower(rhs, loc);
- });
-}
-
-VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName)
-{
- DWORD handle = 0UL;
- DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle);
- if (size == 0) {
- throw windows_error("failed to determine file version info size");
- }
-
- boost::scoped_array<char> buffer(new char[size]);
- try {
- handle = 0UL;
- if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) {
- throw windows_error("failed to determine file version info");
- }
-
- void *versionInfoPtr = nullptr;
- UINT versionInfoLength = 0;
- if (!::VerQueryValue(buffer.get(), L"\\", &versionInfoPtr, &versionInfoLength)) {
- throw windows_error("failed to determine file version");
- }
-
- VS_FIXEDFILEINFO result = *(VS_FIXEDFILEINFO*)versionInfoPtr;
- return result;
- } catch (...) {
- throw;
- }
-}
-
-std::wstring GetFileVersionString(const std::wstring &fileName)
-{
- DWORD handle = 0UL;
- DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle);
- if (size == 0) {
- throw windows_error("failed to determine file version info size");
- }
-
- boost::scoped_array<char> buffer(new char[size]);
- try {
- handle = 0UL;
- if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) {
- throw windows_error("failed to determine file version info");
- }
-
- LPVOID strBuffer = nullptr;
- UINT strLength = 0;
- if (!::VerQueryValue(buffer.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", &strBuffer, &strLength)) {
- throw windows_error("failed to determine file version");
- }
-
- return std::wstring((LPCTSTR)strBuffer);
- }
- catch (...) {
- throw;
- }
-}
-
-VersionInfo createVersionInfo()
-{
- VS_FIXEDFILEINFO version = GetFileVersion(env::thisProcessPath().native());
-
- if (version.dwFileFlags & VS_FF_PRERELEASE)
- {
- // Pre-release builds need annotating
- QString versionString = QString::fromStdWString(
- GetFileVersionString(env::thisProcessPath().native()));
-
- // The pre-release flag can be set without the string specifying what type of pre-release
- bool noLetters = true;
- for (QChar character : versionString)
- {
- if (character.isLetter())
- {
- noLetters = false;
- break;
- }
- }
-
- if (noLetters)
- {
- // Default to pre-alpha when release type is unspecified
- return VersionInfo(version.dwFileVersionMS >> 16,
- version.dwFileVersionMS & 0xFFFF,
- version.dwFileVersionLS >> 16,
- version.dwFileVersionLS & 0xFFFF,
- VersionInfo::RELEASE_PREALPHA);
- }
- else
- {
- // Trust the string to make sense
- return VersionInfo(versionString);
- }
- }
- else
- {
- // Non-pre-release builds just need their version numbers reading
- return VersionInfo(version.dwFileVersionMS >> 16,
- version.dwFileVersionMS & 0xFFFF,
- version.dwFileVersionLS >> 16,
- version.dwFileVersionLS & 0xFFFF);
- }
-}
-
-QString getUsvfsDLLVersion()
-{
- // once 2.2.2 is released, this can be changed to call USVFSVersionString()
- // directly; until then, using GetProcAddress() allows for mixing up devbuilds
- // and usvfs dlls
-
- using USVFSVersionStringType = const char* WINAPI ();
-
- QString s;
-
- const auto m = ::LoadLibraryW(L"usvfs_x64.dll");
-
- if (m) {
- auto* f = reinterpret_cast<USVFSVersionStringType*>(
- ::GetProcAddress(m, "USVFSVersionString"));
-
- if (f) {
- s = f();
- }
-
- ::FreeLibrary(m);
- }
-
- if (s.isEmpty()) {
- s = "?";
- }
-
- return s;
-}
-
-QString getUsvfsVersionString()
-{
- const QString dll = getUsvfsDLLVersion();
- const QString header = USVFS_VERSION_STRING;
-
- QString usvfsVersion;
-
- if (dll == header) {
- return dll;
- } else {
- return "dll is " + dll + ", compiled against " + header;
- }
-}
-
-void SetThisThreadName(const QString& s)
-{
- using SetThreadDescriptionType = HRESULT (
- HANDLE hThread,
- PCWSTR lpThreadDescription
- );
-
- static SetThreadDescriptionType* SetThreadDescription = [] {
- SetThreadDescriptionType* p = nullptr;
-
- env::LibraryPtr kernel32(LoadLibraryW(L"kernel32.dll"));
- if (!kernel32) {
- return p;
- }
-
- p = reinterpret_cast<SetThreadDescriptionType*>(
- GetProcAddress(kernel32.get(), "SetThreadDescription"));
-
- return p;
- }();
-
- if (SetThreadDescription) {
- SetThreadDescription(GetCurrentThread(), s.toStdWString().c_str());
- }
-}
-
-
-char shortcutChar(const QAction* a)
-{
- const auto text = a->text();
- char shortcut = 0;
-
- for (int i=0; i<text.size(); ++i) {
- const auto c = text[i];
- if (c == '&') {
- if (i >= (text.size() - 1)) {
- log::error("ampersand at the end");
- return 0;
- }
-
- return text[i + 1].toLatin1();
- }
- }
-
- log::error("action {} has no shortcut", text);
- return 0;
-}
-
-void checkDuplicateShortcuts(const QMenu& m)
-{
- const auto actions = m.actions();
-
- for (int i=0; i<actions.size(); ++i) {
- const auto* action1 = actions[i];
- if (action1->isSeparator()) {
- continue;
- }
-
- const char shortcut1 = shortcutChar(action1);
- if (shortcut1 == 0) {
- continue;
- }
-
- for (int j=i+1; j<actions.size(); ++j) {
- const auto* action2 = actions[j];
- if (action2->isSeparator()) {
- continue;
- }
-
- const char shortcut2 = shortcutChar(action2);
-
- if (shortcut1 == shortcut2) {
- log::error(
- "duplicate shortcut {} for {} and {}",
- shortcut1, action1->text(), action2->text());
-
- break;
- }
- }
- }
-}
-
-} // namespace MOShared
-
-
-static bool g_exiting = false;
-static bool g_canClose = false;
-
-MainWindow* findMainWindow()
-{
- for (auto* tl : qApp->topLevelWidgets()) {
- if (auto* mw=dynamic_cast<MainWindow*>(tl)) {
- return mw;
- }
- }
-
- return nullptr;
-}
-
-bool ExitModOrganizer(ExitFlags e)
-{
- if (g_exiting) {
- return true;
- }
-
- g_exiting = true;
- Guard g([&]{ g_exiting = false; });
-
- if (!e.testFlag(Exit::Force)) {
- if (auto* mw=findMainWindow()) {
- if (!mw->canExit()) {
- return false;
- }
- }
- }
-
- g_canClose = true;
-
- const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0);
- qApp->exit(code);
-
- return true;
-}
-
-bool ModOrganizerCanCloseNow()
-{
- return g_canClose;
-}
-
-bool ModOrganizerExiting()
-{
- return g_exiting;
-}
-
-void ResetExitFlag()
-{
- g_exiting = false;
-}
-
-
-bool isNxmLink(const QString& link)
-{
- return link.startsWith("nxm://", Qt::CaseInsensitive);
-}
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util.h" +#include "../env.h" +#include "../mainwindow.h" +#include "windows_error.h" +#include <log.h> +#include <usvfs.h> +#include <usvfs_version.h> + +using namespace MOBase; + +namespace MOShared +{ + +bool FileExists(const std::string& filename) +{ + DWORD dwAttrib = ::GetFileAttributesA(filename.c_str()); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES); +} + +bool FileExists(const std::wstring& filename) +{ + DWORD dwAttrib = ::GetFileAttributesW(filename.c_str()); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES); +} + +bool FileExists(const std::wstring& searchPath, const std::wstring& filename) +{ + std::wstringstream stream; + stream << searchPath << "\\" << filename; + return FileExists(stream.str()); +} + +std::string ToString(const std::wstring& source, bool utf8) +{ + std::string result; + if (source.length() > 0) { + UINT codepage = CP_UTF8; + if (!utf8) { + codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); + } + int sizeRequired = ::WideCharToMultiByte(codepage, 0, &source[0], -1, nullptr, 0, + nullptr, nullptr); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to multibyte"); + } + // the size returned by WideCharToMultiByte contains zero termination IF -1 is + // specified for the length. we don't want that \0 in the string because then the + // length field would be wrong. Because madness + result.resize(sizeRequired - 1, '\0'); + ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], + sizeRequired, nullptr, nullptr); + } + + return result; +} + +std::wstring ToWString(const std::string& source, bool utf8) +{ + std::wstring result; + if (source.length() > 0) { + UINT codepage = CP_UTF8; + if (!utf8) { + codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); + } + int sizeRequired = ::MultiByteToWideChar( + codepage, 0, source.c_str(), static_cast<int>(source.length()), nullptr, 0); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to wide character"); + } + result.resize(sizeRequired, L'\0'); + ::MultiByteToWideChar(codepage, 0, source.c_str(), + static_cast<int>(source.length()), &result[0], sizeRequired); + } + + return result; +} + +static std::locale loc(""); +static auto locToLowerW = [](wchar_t in) -> wchar_t { + return std::tolower(in, loc); +}; + +static auto locToLower = [](char in) -> char { + return std::tolower(in, loc); +}; + +std::string& ToLowerInPlace(std::string& text) +{ + CharLowerBuffA(const_cast<CHAR*>(text.c_str()), static_cast<DWORD>(text.size())); + return text; +} + +std::string ToLowerCopy(const std::string& text) +{ + std::string result(text); + CharLowerBuffA(const_cast<CHAR*>(result.c_str()), static_cast<DWORD>(result.size())); + return result; +} + +std::wstring& ToLowerInPlace(std::wstring& text) +{ + CharLowerBuffW(const_cast<WCHAR*>(text.c_str()), static_cast<DWORD>(text.size())); + return text; +} + +std::wstring ToLowerCopy(const std::wstring& text) +{ + std::wstring result(text); + CharLowerBuffW(const_cast<WCHAR*>(result.c_str()), static_cast<DWORD>(result.size())); + 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); +} + +bool CaseInsensitiveEqual(const std::wstring& lhs, const std::wstring& rhs) +{ + return (lhs.length() == rhs.length()) && + std::equal(lhs.begin(), lhs.end(), rhs.begin(), + [](wchar_t lhs, wchar_t rhs) -> bool { + return std::tolower(lhs, loc) == std::tolower(rhs, loc); + }); +} + +VS_FIXEDFILEINFO GetFileVersion(const std::wstring& fileName) +{ + DWORD handle = 0UL; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + boost::scoped_array<char> buffer(new char[size]); + try { + handle = 0UL; + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { + throw windows_error("failed to determine file version info"); + } + + void* versionInfoPtr = nullptr; + UINT versionInfoLength = 0; + if (!::VerQueryValue(buffer.get(), L"\\", &versionInfoPtr, &versionInfoLength)) { + throw windows_error("failed to determine file version"); + } + + VS_FIXEDFILEINFO result = *(VS_FIXEDFILEINFO*)versionInfoPtr; + return result; + } catch (...) { + throw; + } +} + +std::wstring GetFileVersionString(const std::wstring& fileName) +{ + DWORD handle = 0UL; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + boost::scoped_array<char> buffer(new char[size]); + try { + handle = 0UL; + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { + throw windows_error("failed to determine file version info"); + } + + LPVOID strBuffer = nullptr; + UINT strLength = 0; + if (!::VerQueryValue(buffer.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", + &strBuffer, &strLength)) { + throw windows_error("failed to determine file version"); + } + + return std::wstring((LPCTSTR)strBuffer); + } catch (...) { + throw; + } +} + +VersionInfo createVersionInfo() +{ + VS_FIXEDFILEINFO version = GetFileVersion(env::thisProcessPath().native()); + + if (version.dwFileFlags & VS_FF_PRERELEASE) { + // Pre-release builds need annotating + QString versionString = + QString::fromStdWString(GetFileVersionString(env::thisProcessPath().native())); + + // The pre-release flag can be set without the string specifying what type of + // pre-release + bool noLetters = true; + for (QChar character : versionString) { + if (character.isLetter()) { + noLetters = false; + break; + } + } + + if (noLetters) { + // Default to pre-alpha when release type is unspecified + return VersionInfo( + version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF, + VersionInfo::RELEASE_PREALPHA); + } else { + // Trust the string to make sense + return VersionInfo(versionString); + } + } else { + // Non-pre-release builds just need their version numbers reading + return VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF); + } +} + +QString getUsvfsDLLVersion() +{ + // once 2.2.2 is released, this can be changed to call USVFSVersionString() + // directly; until then, using GetProcAddress() allows for mixing up devbuilds + // and usvfs dlls + + using USVFSVersionStringType = const char* WINAPI(); + + QString s; + + const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); + + if (m) { + auto* f = reinterpret_cast<USVFSVersionStringType*>( + ::GetProcAddress(m, "USVFSVersionString")); + + if (f) { + s = f(); + } + + ::FreeLibrary(m); + } + + if (s.isEmpty()) { + s = "?"; + } + + return s; +} + +QString getUsvfsVersionString() +{ + const QString dll = getUsvfsDLLVersion(); + const QString header = USVFS_VERSION_STRING; + + QString usvfsVersion; + + if (dll == header) { + return dll; + } else { + return "dll is " + dll + ", compiled against " + header; + } +} + +void SetThisThreadName(const QString& s) +{ + using SetThreadDescriptionType = HRESULT(HANDLE hThread, PCWSTR lpThreadDescription); + + static SetThreadDescriptionType* SetThreadDescription = [] { + SetThreadDescriptionType* p = nullptr; + + env::LibraryPtr kernel32(LoadLibraryW(L"kernel32.dll")); + if (!kernel32) { + return p; + } + + p = reinterpret_cast<SetThreadDescriptionType*>( + GetProcAddress(kernel32.get(), "SetThreadDescription")); + + return p; + }(); + + if (SetThreadDescription) { + SetThreadDescription(GetCurrentThread(), s.toStdWString().c_str()); + } +} + +char shortcutChar(const QAction* a) +{ + const auto text = a->text(); + char shortcut = 0; + + for (int i = 0; i < text.size(); ++i) { + const auto c = text[i]; + if (c == '&') { + if (i >= (text.size() - 1)) { + log::error("ampersand at the end"); + return 0; + } + + return text[i + 1].toLatin1(); + } + } + + log::error("action {} has no shortcut", text); + return 0; +} + +void checkDuplicateShortcuts(const QMenu& m) +{ + const auto actions = m.actions(); + + for (int i = 0; i < actions.size(); ++i) { + const auto* action1 = actions[i]; + if (action1->isSeparator()) { + continue; + } + + const char shortcut1 = shortcutChar(action1); + if (shortcut1 == 0) { + continue; + } + + for (int j = i + 1; j < actions.size(); ++j) { + const auto* action2 = actions[j]; + if (action2->isSeparator()) { + continue; + } + + const char shortcut2 = shortcutChar(action2); + + if (shortcut1 == shortcut2) { + log::error("duplicate shortcut {} for {} and {}", shortcut1, action1->text(), + action2->text()); + + break; + } + } + } +} + +} // namespace MOShared + +static bool g_exiting = false; +static bool g_canClose = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw = dynamic_cast<MainWindow*>(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + g_exiting = true; + Guard g([&] { + g_exiting = false; + }); + + if (!e.testFlag(Exit::Force)) { + if (auto* mw = findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_canClose = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerCanCloseNow() +{ + return g_canClose; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} + +bool isNxmLink(const QString& link) +{ + return link.startsWith("nxm://", Qt::CaseInsensitive); +} diff --git a/src/shared/util.h b/src/shared/util.h index d8fdb3a3..1bc6bd47 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -1,89 +1,89 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 UTIL_H
-#define UTIL_H
-
-#include <log.h>
-#include <string>
-#include <filesystem>
-#include <versioninfo.h>
-
-class Executable;
-
-namespace MOShared {
-
-/// Test if a file (or directory) by the specified name exists
-bool FileExists(const std::string &filename);
-bool FileExists(const std::wstring &filename);
-
-bool FileExists(const std::wstring &searchPath, const std::wstring &filename);
-
-std::string ToString(const std::wstring &source, bool utf8);
-std::wstring ToWString(const std::string &source, bool utf8);
-
-std::string& ToLowerInPlace(std::string& text);
-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);
-
-MOBase::VersionInfo createVersionInfo();
-QString getUsvfsVersionString();
-
-void SetThisThreadName(const QString& s);
-void checkDuplicateShortcuts(const QMenu& m);
-
-inline FILETIME ToFILETIME(std::filesystem::file_time_type t)
-{
- FILETIME ft;
- static_assert(sizeof(t) == sizeof(ft));
-
- std::memcpy(&ft, &t, sizeof(FILETIME));
- return ft;
-}
-
-} // namespace MOShared
-
-
-enum class Exit
-{
- None = 0x00,
- Normal = 0x01,
- Restart = 0x02,
- Force = 0x04
-};
-
-const int RestartExitCode = INT_MAX;
-const int ReselectExitCode = INT_MAX - 1;
-
-using ExitFlags = QFlags<Exit>;
-Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags);
-
-bool ExitModOrganizer(ExitFlags e=Exit::Normal);
-bool ModOrganizerExiting();
-bool ModOrganizerCanCloseNow();
-void ResetExitFlag();
-
-bool isNxmLink(const QString& link);
-
-#endif // UTIL_H
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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 UTIL_H +#define UTIL_H + +#include <filesystem> +#include <log.h> +#include <string> +#include <versioninfo.h> + +class Executable; + +namespace MOShared +{ + +/// Test if a file (or directory) by the specified name exists +bool FileExists(const std::string& filename); +bool FileExists(const std::wstring& filename); + +bool FileExists(const std::wstring& searchPath, const std::wstring& filename); + +std::string ToString(const std::wstring& source, bool utf8); +std::wstring ToWString(const std::string& source, bool utf8); + +std::string& ToLowerInPlace(std::string& text); +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); + +MOBase::VersionInfo createVersionInfo(); +QString getUsvfsVersionString(); + +void SetThisThreadName(const QString& s); +void checkDuplicateShortcuts(const QMenu& m); + +inline FILETIME ToFILETIME(std::filesystem::file_time_type t) +{ + FILETIME ft; + static_assert(sizeof(t) == sizeof(ft)); + + std::memcpy(&ft, &t, sizeof(FILETIME)); + return ft; +} + +} // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; +const int ReselectExitCode = INT_MAX - 1; + +using ExitFlags = QFlags<Exit>; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e = Exit::Normal); +bool ModOrganizerExiting(); +bool ModOrganizerCanCloseNow(); +void ResetExitFlag(); + +bool isNxmLink(const QString& link); + +#endif // UTIL_H diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp index 97a58a20..f0add1da 100644 --- a/src/shared/windows_error.cpp +++ b/src/shared/windows_error.cpp @@ -1,49 +1,52 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "windows_error.h"
-#include <sstream>
-
-namespace MOShared {
-
-std::string windows_error::constructMessage(const std::string& input, int inErrorCode)
-{
- std::ostringstream finalMessage;
- finalMessage << input;
-
- LPSTR buffer = nullptr;
-
- DWORD errorCode = inErrorCode != -1 ? inErrorCode : ::GetLastError();
-
- // TODO: the message is not english?
- if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) {
- finalMessage << " (errorcode " << errorCode << ")";
- } else {
- LPSTR lastChar = buffer + strlen(buffer) - 2;
- *lastChar = '\0';
- finalMessage << " (" << buffer << " [" << errorCode << "])";
- LocalFree(buffer); // allocated by FormatMessage
- }
-
- ::SetLastError(errorCode); // restore error code because FormatMessage might have modified it
- return finalMessage.str();
-}
-
-} // namespace MOShared
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "windows_error.h" +#include <sstream> + +namespace MOShared +{ + +std::string windows_error::constructMessage(const std::string& input, int inErrorCode) +{ + std::ostringstream finalMessage; + finalMessage << input; + + LPSTR buffer = nullptr; + + DWORD errorCode = inErrorCode != -1 ? inErrorCode : ::GetLastError(); + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buffer, 0, nullptr) == 0) { + finalMessage << " (errorcode " << errorCode << ")"; + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; + finalMessage << " (" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + + ::SetLastError( + errorCode); // restore error code because FormatMessage might have modified it + return finalMessage.str(); +} + +} // namespace MOShared diff --git a/src/shared/windows_error.h b/src/shared/windows_error.h index 03ce0abd..2bdd6392 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -1,44 +1,47 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 WINDOWS_ERROR_H
-#define WINDOWS_ERROR_H
-
-
-#include <stdexcept>
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-namespace MOShared {
-
-class windows_error : public std::runtime_error {
-public:
- windows_error(const std::string& message, int errorcode = ::GetLastError())
- : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode)
- {}
- int getErrorCode() const { return m_ErrorCode; }
-private:
- std::string constructMessage(const std::string& input, int errorcode);
-private:
- int m_ErrorCode;
-};
-
-} // namespace MOShared
-
-#endif // WINDOWS_ERROR_H
+/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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 WINDOWS_ERROR_H +#define WINDOWS_ERROR_H + +#include <stdexcept> +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +namespace MOShared +{ + +class windows_error : public std::runtime_error +{ +public: + windows_error(const std::string& message, int errorcode = ::GetLastError()) + : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) + {} + int getErrorCode() const { return m_ErrorCode; } + +private: + std::string constructMessage(const std::string& input, int errorcode); + +private: + int m_ErrorCode; +}; + +} // namespace MOShared + +#endif // WINDOWS_ERROR_H |
