Skip to content

Commit

Permalink
Introduce ComparableFilename abstraction
Browse files Browse the repository at this point in the history
It's a typedef that's std::wstring on Windows and icu::UnicodeString on Linux, to simplify calling CompareFilenames() with cached pre-converted strings.
  • Loading branch information
Ortham committed Jan 21, 2025
1 parent c392149 commit b115f30
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 89 deletions.
19 changes: 12 additions & 7 deletions src/api/helpers/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,21 @@ std::string FromWinWide(const std::wstring& wstr) {
}
#endif

int CompareFilenames(const std::string& lhs, const std::string& rhs) {
ComparableFilename ToComparableFilename(const std::string filename) {
#ifdef _WIN32
return CompareFilenames(ToWinWide(lhs), ToWinWide(rhs));
return ToWinWide(filename);
#else
auto unicodeLhs = icu::UnicodeString::fromUTF8(lhs);
auto unicodeRhs = icu::UnicodeString::fromUTF8(rhs);
return unicodeLhs.caseCompare(unicodeRhs, U_FOLD_CASE_DEFAULT);
return icu::UnicodeString::fromUTF8(filename);
#endif
}

int CompareFilenames(const std::string& lhs, const std::string& rhs) {
return CompareFilenames(ToComparableFilename(lhs), ToComparableFilename(rhs));
}

int CompareFilenames(const ComparableFilename& lhs,
const ComparableFilename& rhs) {
#ifdef _WIN32
int CompareFilenames(const std::wstring& lhs, const std::wstring& rhs) {
// Use CompareStringOrdinal as that will perform case conversion
// using the operating system uppercase table information, which (I think)
// will give results that match the filesystem, and is not locale-dependent.
Expand All @@ -208,8 +211,10 @@ int CompareFilenames(const std::wstring& lhs, const std::wstring& rhs) {
throw std::invalid_argument(
"One of the filenames to compare was invalid.");
}
}
#else
return lhs.caseCompare(rhs, U_FOLD_CASE_DEFAULT);
#endif
}

std::string NormalizeFilename(const std::string& filename) {
#ifdef _WIN32
Expand Down
19 changes: 14 additions & 5 deletions src/api/helpers/text.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,35 @@

#include "loot/metadata/tag.h"

#ifndef _WIN32
#include <unicode/unistr.h>
#endif

namespace loot {
inline constexpr const char* GHOST_FILE_EXTENSION = ".ghost";
inline constexpr std::size_t GHOST_FILE_EXTENSION_LENGTH =
std::char_traits<char>::length(GHOST_FILE_EXTENSION);

#ifdef _WIN32
typedef std::wstring ComparableFilename;
#else
typedef icu::UnicodeString ComparableFilename;
#endif

std::vector<Tag> ExtractBashTags(const std::string& description);

std::optional<std::string> ExtractVersion(const std::string& text);

ComparableFilename ToComparableFilename(const std::string filename);

// Compare strings as if they're filenames, respecting filesystem case
// insensitivity on Windows. Returns -1 if lhs < rhs, 0 if lhs == rhs, and 1 if
// lhs > rhs. The comparison may give different results on Linux, but is still
// locale-invariant.
int CompareFilenames(const std::string& lhs, const std::string& rhs);

#ifdef _WIN32
std::wstring ToWinWide(const std::string& str);

int CompareFilenames(const std::wstring& lhs, const std::wstring& rhs);
#endif
int CompareFilenames(const ComparableFilename& lhs,
const ComparableFilename& rhs);

// Normalize the given filename in a way that is locale-invariant. On Windows,
// this uppercases the filename according to the same case mapping rules as used
Expand Down
43 changes: 20 additions & 23 deletions src/api/sorting/plugin_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,33 +659,35 @@ void PathsCache::CachePath(const vertex_t& fromVertex,
}
}

#if _WIN32
const std::wstring& WideStringsCache::Get(const std::string& narrowString) {
auto vertexNameIt = wideStringsCache_.find(narrowString);
if (vertexNameIt == wideStringsCache_.end()) {
const ComparableFilename& ComparableFilenamesCache::Get(
const std::string& narrowString) {
auto vertexNameIt = comparableFilenamesCache_.find(narrowString);
if (vertexNameIt == comparableFilenamesCache_.end()) {
throw std::invalid_argument("Given string was not already cached");
}

return vertexNameIt->second;
}

const std::wstring& WideStringsCache::GetOrInsert(
const ComparableFilename& ComparableFilenamesCache::GetOrInsert(
const std::string& narrowString) {
auto vertexNameIt = wideStringsCache_.find(narrowString);
if (vertexNameIt == wideStringsCache_.end()) {
auto vertexNameIt = comparableFilenamesCache_.find(narrowString);
if (vertexNameIt == comparableFilenamesCache_.end()) {
vertexNameIt =
wideStringsCache_.emplace(narrowString, ToWinWide(narrowString)).first;
comparableFilenamesCache_
.emplace(narrowString, ToComparableFilename(narrowString))
.first;
}

return vertexNameIt->second;
}

void WideStringsCache::Insert(const std::string& narrowString) {
if (!wideStringsCache_.contains(narrowString)) {
wideStringsCache_.emplace(narrowString, ToWinWide(narrowString));
void ComparableFilenamesCache::Insert(const std::string& narrowString) {
if (!comparableFilenamesCache_.contains(narrowString)) {
comparableFilenamesCache_.emplace(narrowString,
ToComparableFilename(narrowString));
}
}
#endif

size_t PluginGraph::CountVertices() const {
return boost::num_vertices(graph_);
Expand All @@ -696,19 +698,14 @@ std::pair<vertex_it, vertex_it> PluginGraph::GetVertices() const {
}

std::optional<vertex_t> PluginGraph::GetVertexByName(
const std::string& name) const {
const std::string& name) {
for (const auto& vertex : boost::make_iterator_range(GetVertices())) {
#if _WIN32
const auto& vertexName = GetPlugin(vertex).GetName();
wideStringCache_.Insert(vertexName);
auto& wideName = wideStringCache_.GetOrInsert(name);
auto& wideVertexName = wideStringCache_.Get(vertexName);

int comparison = CompareFilenames(wideVertexName, wideName);
#else
int comparison = CompareFilenames(GetPlugin(vertex).GetName(), name);
#endif
if (comparison == 0) {
comparableFilenamesCache_.Insert(vertexName);
const auto& comparableName = comparableFilenamesCache_.GetOrInsert(name);
const auto& comparableVertexName = comparableFilenamesCache_.Get(vertexName);

if (CompareFilenames(comparableVertexName, comparableName) == 0) {
return vertex;
}
}
Expand Down
18 changes: 8 additions & 10 deletions src/api/sorting/plugin_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <boost/unordered/unordered_flat_set.hpp>
#include <map>

#include "api/helpers/text.h"
#include "api/sorting/group_sort.h"
#include "api/sorting/plugin_sorting_data.h"
#include "loot/enum/edge_type.h"
Expand Down Expand Up @@ -58,23 +59,22 @@ class PathsCache {
pathsCache_;
};

#if _WIN32
class WideStringsCache {
class ComparableFilenamesCache {
public:
void Insert(const std::string& narrowString);
const std::wstring& Get(const std::string& narrowString);
const std::wstring& GetOrInsert(const std::string& narrowString);
const ComparableFilename& Get(const std::string& narrowString);
const ComparableFilename& GetOrInsert(const std::string& narrowString);

private:
boost::unordered_flat_map<std::string, std::wstring> wideStringsCache_;
boost::unordered_flat_map<std::string, ComparableFilename>
comparableFilenamesCache_;
};
#endif

class PluginGraph {
public:
size_t CountVertices() const;
std::pair<vertex_it, vertex_it> GetVertices() const;
std::optional<vertex_t> GetVertexByName(const std::string& name) const;
std::optional<vertex_t> GetVertexByName(const std::string& name);

const PluginSortingData& GetPlugin(const vertex_t& vertex) const;

Expand Down Expand Up @@ -112,9 +112,7 @@ class PluginGraph {
private:
RawPluginGraph graph_;
PathsCache pathsCache_;
#if _WIN32
mutable WideStringsCache wideStringCache_;
#endif
ComparableFilenamesCache comparableFilenamesCache_;
};
}

Expand Down
13 changes: 3 additions & 10 deletions src/api/sorting/plugin_sort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,10 @@ std::vector<PluginSortingData> GetPluginsSortingData(
std::vector<PluginSortingData> pluginsSortingData;
pluginsSortingData.reserve(loadedPluginInterfaces.size());

#ifdef _WIN32
std::vector<std::wstring> wideLoadOrder;
std::vector<ComparableFilename> comparableLoadOrder;
for (const auto& pluginName : loadOrder) {
wideLoadOrder.push_back(ToWinWide(pluginName));
comparableLoadOrder.push_back(ToComparableFilename(pluginName));
}
#endif

for (const auto& pluginInterface : loadedPluginInterfaces) {
if (!pluginInterface) {
Expand All @@ -64,13 +62,8 @@ std::vector<PluginSortingData> GetPluginsSortingData(
const auto userMetadata = db.GetPluginUserMetadata(plugin->GetName(), true)
.value_or(PluginMetadata(plugin->GetName()));

#ifdef _WIN32
const auto pluginSortingData = PluginSortingData(
plugin, masterlistMetadata, userMetadata, wideLoadOrder);
#else
const auto pluginSortingData =
PluginSortingData(plugin, masterlistMetadata, userMetadata, loadOrder);
#endif
plugin, masterlistMetadata, userMetadata, comparableLoadOrder);

pluginsSortingData.push_back(pluginSortingData);
}
Expand Down
17 changes: 3 additions & 14 deletions src/api/sorting/plugin_sorting_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ PluginSortingData::PluginSortingData(
const PluginSortingInterface* plugin,
const PluginMetadata& masterlistMetadata,
const PluginMetadata& userMetadata,
#ifdef _WIN32
const std::vector<std::wstring>& loadOrder) :
#else
const std::vector<std::string>& loadOrder) :
#endif
const std::vector<ComparableFilename>& loadOrder) :
plugin_(plugin),
name_(plugin == nullptr ? std::string() : plugin->GetName()),
isMaster_(plugin != nullptr && plugin->IsMaster()),
Expand All @@ -72,17 +68,10 @@ PluginSortingData::PluginSortingData(
return;
}

#ifdef _WIN32
auto wideName = ToWinWide(GetName());
#endif
const auto comparableName = ToComparableFilename(GetName());

for (size_t i = 0; i < loadOrder.size(); i++) {
#ifdef _WIN32
int comparison = CompareFilenames(wideName, loadOrder.at(i));
#else
int comparison = CompareFilenames(GetName(), loadOrder.at(i));
#endif
if (comparison == 0) {
if (CompareFilenames(comparableName, loadOrder.at(i)) == 0) {
loadOrderIndex_ = i;
break;
}
Expand Down
7 changes: 2 additions & 5 deletions src/api/sorting/plugin_sorting_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <unordered_set>

#include "api/plugin.h"
#include "api/helpers/text.h"
#include "loot/metadata/plugin_metadata.h"

namespace loot {
Expand All @@ -43,11 +44,7 @@ class PluginSortingData {
explicit PluginSortingData(const PluginSortingInterface* plugin,
const PluginMetadata& masterlistMetadata,
const PluginMetadata& userMetadata,
#ifdef _WIN32
const std::vector<std::wstring>& loadOrder);
#else
const std::vector<std::string>& loadOrder);
#endif
const std::vector<ComparableFilename>& loadOrder);

const std::string& GetName() const;
bool IsMaster() const;
Expand Down
11 changes: 3 additions & 8 deletions src/tests/api/internals/sorting/plugin_sort_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,13 @@ class PluginSortTest : public CommonGameTestFixture {
const std::vector<std::string>& loadOrder = {}) {
const auto plugin = GetPlugin(name);

#ifdef _WIN32
std::vector<std::wstring> wideLoadOrder;
std::vector<ComparableFilename> comparableLoadOrder;
for (const auto& pluginName : loadOrder) {
wideLoadOrder.push_back(ToWinWide(pluginName));
comparableLoadOrder.push_back(ToComparableFilename(pluginName));
}

return PluginSortingData(
plugin, PluginMetadata(), PluginMetadata(), wideLoadOrder);
#else
return PluginSortingData(
plugin, PluginMetadata(), PluginMetadata(), loadOrder);
#endif
plugin, PluginMetadata(), PluginMetadata(), comparableLoadOrder);
}

plugingraph::TestPlugin* GetPlugin(const std::string& name) {
Expand Down
10 changes: 3 additions & 7 deletions src/tests/api/internals/sorting/plugin_sorting_data_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,14 @@ class PluginSortingDataTest : public CommonGameTestFixture {
return loadedPluginInterfaces;
}

#ifdef _WIN32
std::vector<std::wstring> getNativeLoadOrder() {
std::vector<std::wstring> wideLoadOrder;
std::vector<ComparableFilename> getNativeLoadOrder() {
std::vector<ComparableFilename> wideLoadOrder;
for (const auto &pluginName : getLoadOrder()) {
wideLoadOrder.push_back(ToWinWide(pluginName));
wideLoadOrder.push_back(ToComparableFilename(pluginName));
}

return wideLoadOrder;
}
#else
std::vector<std::string> getNativeLoadOrder() { return getLoadOrder(); }
#endif

Game game_;
const std::string blankEslEsp;
Expand Down

0 comments on commit b115f30

Please sign in to comment.