diff options
Diffstat (limited to 'src/shared')
| -rw-r--r-- | src/shared/appconfig.cpp | 66 | ||||
| -rw-r--r-- | src/shared/appconfig.h | 80 | ||||
| -rw-r--r-- | src/shared/directoryentry.cpp | 1956 | ||||
| -rw-r--r-- | src/shared/directoryentry.h | 642 | ||||
| -rw-r--r-- | src/shared/util.cpp | 888 | ||||
| -rw-r--r-- | src/shared/util.h | 178 | ||||
| -rw-r--r-- | src/shared/windows_error.cpp | 98 | ||||
| -rw-r--r-- | src/shared/windows_error.h | 88 |
8 files changed, 1998 insertions, 1998 deletions
diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 49edcbcc..d2fc386c 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -1,33 +1,33 @@ -/*
-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 diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h index b32043e5..cca5b0f5 100644 --- a/src/shared/appconfig.h +++ b/src/shared/appconfig.h @@ -1,40 +1,40 @@ -/*
-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 + +#endif // APPCONFIG_H diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index badb5636..48ff7aa3 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -1,978 +1,978 @@ -/*
-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 "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 diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 0ee3c919..f7ab2b90 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -1,321 +1,321 @@ -/*
-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 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 diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 54c6b841..e2faaec5 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1,444 +1,444 @@ -/*
-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 "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); +} diff --git a/src/shared/util.h b/src/shared/util.h index d8fdb3a3..87ec5bf2 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 <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 diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp index 97a58a20..49fb9bc7 100644 --- a/src/shared/windows_error.cpp +++ b/src/shared/windows_error.cpp @@ -1,49 +1,49 @@ -/*
-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..ea506dc1 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -1,44 +1,44 @@ -/*
-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 |
