/*
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);
}
static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs)
{
return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0;
}
DirectoryStats::DirectoryStats()
{
std::memset(this, 0, sizeof(DirectoryStats));
}
DirectoryStats& DirectoryStats::operator+=(const DirectoryStats& o)
{
dirTimes += o.dirTimes;
fileTimes += o.fileTimes;
sortTimes += o.sortTimes;
subdirLookupTimes += o.subdirLookupTimes;
addDirectoryTimes += o.addDirectoryTimes;
filesLookupTimes += o.filesLookupTimes;
addFileTimes += o.addFileTimes;
addOriginToFileTimes += o.addOriginToFileTimes;
addFileToOriginTimes += o.addFileToOriginTimes;
addFileToRegisterTimes += o.addFileToRegisterTimes;
originExists += o.originExists;
originCreate += o.originCreate;
originsNeededEnabled += o.originsNeededEnabled;
subdirExists += o.subdirExists;
subdirCreate += o.subdirCreate;
fileExists += o.fileExists;
fileCreate += o.fileCreate;
filesInsertedInRegister += o.filesInsertedInRegister;
filesAssignedInRegister += o.filesAssignedInRegister;
return *this;
}
std::string DirectoryStats::csvHeader()
{
QStringList sl = {
"dirTimes",
"fileTimes",
"sortTimes",
"subdirLookupTimes",
"addDirectoryTimes",
"filesLookupTimes",
"addFileTimes",
"addOriginToFileTimes",
"addFileToOriginTimes",
"addFileToRegisterTimes",
"originExists",
"originCreate",
"originsNeededEnabled",
"subdirExists",
"subdirCreate",
"fileExists",
"fileCreate",
"filesInsertedInRegister",
"filesAssignedInRegister"};
return sl.join(",").toStdString();
}
std::string DirectoryStats::toCsv() const
{
QStringList oss;
auto s = [](auto ns) {
return ns.count() / 1000.0 / 1000.0 / 1000.0;
};
oss
<< QString::number(s(dirTimes))
<< QString::number(s(fileTimes))
<< QString::number(s(sortTimes))
<< QString::number(s(subdirLookupTimes))
<< QString::number(s(addDirectoryTimes))
<< QString::number(s(filesLookupTimes))
<< QString::number(s(addFileTimes))
<< QString::number(s(addOriginToFileTimes))
<< QString::number(s(addFileToOriginTimes))
<< QString::number(s(addFileToRegisterTimes))
<< QString::number(originExists)
<< QString::number(originCreate)
<< QString::number(originsNeededEnabled)
<< QString::number(subdirExists)
<< QString::number(subdirCreate)
<< QString::number(fileExists)
<< QString::number(fileCreate)
<< QString::number(filesInsertedInRegister)
<< QString::number(filesAssignedInRegister);
return oss.join(",").toStdString();
}
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);
}
});
elapsed(stats.sortTimes, [&]{
std::sort(
m_SubDirectories.begin(),
m_SubDirectories.end(),
&DirCompareByName);
});
m_Populated = true;
}
void DirectoryEntry::addFromBSA(
const std::wstring &originName, std::wstring &directory,
const std::wstring &fileName, int priority, int order)
{
DirectoryStats dummy;
FilesOrigin &origin = createOrigin(originName, directory, priority, dummy);
WIN32_FILE_ATTRIBUTE_DATA fileData;
if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) {
throw windows_error(QObject::tr("failed to determine file time").toStdString());
}
FILETIME now;
::GetSystemTimeAsFileTime(&now);
const double clfSecondsPer100ns = 100. * 1.E-9;
((ULARGE_INTEGER *)&now)->QuadPart -= ((double)5) / clfSecondsPer100ns;
size_t namePos = fileName.find_last_of(L"\\/");
if (namePos == std::wstring::npos) {
namePos = 0;
}
else {
++namePos;
}
if (!containsArchive(fileName.substr(namePos)) || ::CompareFileTime(&fileData.ftLastWriteTime, &now) > 0) {
BSA::Archive archive;
BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false);
if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
std::ostringstream stream;
stream
<< QObject::tr("invalid bsa file: ").toStdString()
<< ToString(fileName, false)
<< " error code " << res << " - " << ::GetLastError();
throw std::runtime_error(stream.str());
}
addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos), order);
m_Populated = true;
}
}
void DirectoryEntry::propagateOrigin(int origin)
{
{
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 != -1){
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)
{
return getSubDirectoryRecursive(path, false, -1);
}
const FileEntryPtr DirectoryEntry::findFile(
const std::wstring &name, bool alreadyLowerCase) const
{
FilesLookup::const_iterator iter;
if (alreadyLowerCase) {
iter = m_FilesLookup.find(FileKey(name));
} else {
iter = m_FilesLookup.find(FileKey(ToLowerCopy(name)));
}
if (iter != m_FilesLookup.end()) {
return m_FileRegister->getFile(iter->second);
} else {
return FileEntryPtr();
}
}
const FileEntryPtr DirectoryEntry::findFile(const FileKey& 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(FileEntry::Index 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);
DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
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);
DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
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;
FileKey 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)
{
onFile((Context*)pcx, path, ft);
}
);
{
std::scoped_lock lock(m_SubDirMutex);
std::sort(
m_SubDirectories.begin(),
m_SubDirectories.end(),
&DirCompareByName);
}
}
void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path)
{
elapsed(cx->stats.dirTimes, [&] {
auto* sd = cx->current.top()->getSubDirectory(
path, true, cx->stats, cx->origin.getID());
cx->current.push(sd);
});
}
void DirectoryEntry::onDirectoryEnd(Context* cx, std::wstring_view path)
{
elapsed(cx->stats.dirTimes, [&] {
auto* current = cx->current.top();
{
std::scoped_lock lock(current->m_SubDirMutex);
std::sort(
current->m_SubDirectories.begin(),
current->m_SubDirectories.end(),
&DirCompareByName);
}
cx->current.pop();
});
}
void DirectoryEntry::onFile(Context* cx, std::wstring_view path, FILETIME ft)
{
elapsed(cx->stats.fileTimes, [&]{
cx->current.top()->insert(path, cx->origin, ft, L"", -1, cx->stats);
});
}
void DirectoryEntry::addFiles(
FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime,
const std::wstring &archiveName, int order)
{
DirectoryStats dummy;
// add files
for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) {
BSA::File::Ptr file = archiveFolder->getFile(fileIdx);
auto f = insert(
ToWString(file->getName(), true), origin, fileTime,
archiveName, order, dummy);
if (f) {
if (file->getUncompressedFileSize() > 0) {
f->setFileSize(file->getFileSize(), file->getUncompressedFileSize());
} else {
f->setFileSize(file->getFileSize(), FileEntry::NoFileSize);
}
}
}
// recurse into subdirectories
for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) {
BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx);
DirectoryEntry *folderEntry = getSubDirectoryRecursive(
ToWString(folder->getName(), true), true, origin.getID());
folderEntry->addFiles(origin, folder, fileTime, archiveName, order);
}
}
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, int originID)
{
if (path.length() == 0) {
// path ended with a backslash?
return this;
}
const size_t pos = path.find_first_of(L"\\/");
DirectoryStats dummy;
if (pos == std::wstring::npos) {
return getSubDirectory(path, create, dummy);
} else {
DirectoryEntry *nextChild = getSubDirectory(
path.substr(0, pos), create, dummy, originID);
if (nextChild == nullptr) {
return nullptr;
} else {
return nextChild->getSubDirectoryRecursive(
path.substr(pos + 1), create, 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.push_back(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(FileEntry::Index 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, FileEntry::Index 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