From 86bb01ba9eac879d3685c439ac9da0028bc4bc80 Mon Sep 17 00:00:00 2001 From: Mikaƫl Capelle Date: Tue, 17 May 2022 11:37:19 +0200 Subject: Convert everything to CRLF. --- src/shared/directoryentry.cpp | 1956 ++++++++++++++++++++--------------------- 1 file changed, 978 insertions(+), 978 deletions(-) (limited to 'src/shared/directoryentry.cpp') 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 . -*/ - -#include "directoryentry.h" -#include "originconnection.h" -#include "filesorigin.h" -#include "fileentry.h" -#include "../envfs.h" -#include "util.h" -#include "windows_error.h" -#include -#include - -namespace MOShared -{ - -using namespace MOBase; -const int MAXPATH_UNICODE = 32767; - -template -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, - boost::shared_ptr 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& archives, - const std::set& enabledArchives, - const std::vector& 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 DirectoryEntry::getFiles() const -{ - std::vector 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 &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 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; igetFile(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; igetSubFolder(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& 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 . +*/ + +#include "directoryentry.h" +#include "originconnection.h" +#include "filesorigin.h" +#include "fileentry.h" +#include "../envfs.h" +#include "util.h" +#include "windows_error.h" +#include +#include + +namespace MOShared +{ + +using namespace MOBase; +const int MAXPATH_UNICODE = 32767; + +template +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, + boost::shared_ptr 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& archives, + const std::set& enabledArchives, + const std::vector& 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 DirectoryEntry::getFiles() const +{ + std::vector 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 &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 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; igetFile(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; igetSubFolder(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& 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 -- cgit v1.3.1