mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-09-22 03:13:15 -04:00
Merge branch 'vfs_normalized_path_24' into 'master'
Use normalized path in LuaUi::TextureData and to implement correctResourcePath (#8138) See merge request OpenMW/openmw!4920
This commit is contained in:
commit
699191e819
@ -3,82 +3,166 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
namespace Misc::ResourceHelpers
|
||||
{
|
||||
using namespace Misc::ResourceHelpers;
|
||||
TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView path("sound/bar.wav");
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({ { path, nullptr } });
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav");
|
||||
}
|
||||
|
||||
TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3");
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({ { mp3, nullptr } });
|
||||
constexpr VFS::Path::NormalizedView wav("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(wav, *mVFS), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs)
|
||||
{
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({});
|
||||
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
auto correctESM4SoundPath = [](auto path, auto* vfs) {
|
||||
return Misc::ResourceHelpers::correctResourcePath({ { "sound" } }, path, vfs, ".mp3");
|
||||
};
|
||||
|
||||
EXPECT_EQ(correctESM4SoundPath("foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("SOUND/foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("DATA\\SOUND\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("\\Data/Sound\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string checkChangeExtensionToDds(std::string path)
|
||||
using namespace ::testing;
|
||||
|
||||
TEST(MiscResourceHelpersCorrectSoundPath, shouldKeepWavExtensionIfExistsInVfs)
|
||||
{
|
||||
changeExtensionToDds(path);
|
||||
return path;
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({ { path, nullptr } });
|
||||
EXPECT_EQ(correctSoundPath(path, *vfs), "sound/foo.wav");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectSoundPath, shouldFallbackToMp3IfWavDoesNotExistInVfs)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *vfs), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectSoundPath, shouldKeepWavExtensionIfBothExistsInVfs)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView wav("sound/foo.wav");
|
||||
constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({
|
||||
{ wav, nullptr },
|
||||
{ mp3, nullptr },
|
||||
});
|
||||
EXPECT_EQ(correctSoundPath(wav, *vfs), "sound/foo.wav");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfDoesNotExistInVfs)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfBothExistInVfs)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView wav("sound/foo.wav");
|
||||
constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({
|
||||
{ wav, nullptr },
|
||||
{ mp3, nullptr },
|
||||
});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldKeepExtentionIfExistInVfs)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView wav("sound/foo.wav");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({
|
||||
{ wav, nullptr },
|
||||
});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), "mp3"), "sound/foo.wav");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldPrefixWithGivenTopDirectory)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndKeepExtensionIfOriginalExistInVfs)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView a("textures/foo.a");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({
|
||||
{ a, nullptr },
|
||||
});
|
||||
EXPECT_EQ(
|
||||
correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), "b"), "textures/foo.a");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndChangeExtensionIfFallbackExistInVfs)
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView b("textures/foo.b");
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({
|
||||
{ b, nullptr },
|
||||
});
|
||||
EXPECT_EQ(
|
||||
correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), "b"), "textures/foo.b");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldLowerCase)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveLeadingSlash)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveDuplicateSlashes)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersCorrectResourcePath, shouldConvertToForwardSlash)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
struct MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix : TestWithParam<std::string>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, shouldMatchExpected)
|
||||
{
|
||||
const std::unique_ptr<const VFS::Manager> vfs = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), "mp3"), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
const std::vector<std::string> pathsWithPrefix = {
|
||||
"data/sound/foo.mp3",
|
||||
"data/notsound/sound/foo.mp3",
|
||||
"data/soundnot/sound/foo.mp3",
|
||||
"data/notsoundnot/sound/foo.mp3",
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
PathsWithPrefix, MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, ValuesIn(pathsWithPrefix));
|
||||
|
||||
TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_with_same_size_as_dds)
|
||||
{
|
||||
std::string path = "texture/bar.tga";
|
||||
ASSERT_TRUE(changeExtensionToDds(path));
|
||||
EXPECT_EQ(path, "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_greater_than_dds)
|
||||
{
|
||||
std::string path = "texture/bar.jpeg";
|
||||
ASSERT_TRUE(changeExtensionToDds(path));
|
||||
EXPECT_EQ(path, "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_smaller_than_dds)
|
||||
{
|
||||
std::string path = "texture/bar.xx";
|
||||
ASSERT_TRUE(changeExtensionToDds(path));
|
||||
EXPECT_EQ(path, "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersChangeExtensionToDds, does_not_change_dds_extension)
|
||||
{
|
||||
std::string path = "texture/bar.dds";
|
||||
EXPECT_FALSE(changeExtensionToDds(path));
|
||||
EXPECT_EQ(path, "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(MiscResourceHelpersChangeExtensionToDds, does_not_change_when_no_extension)
|
||||
{
|
||||
std::string path = "texture/bar";
|
||||
EXPECT_FALSE(changeExtensionToDds(path));
|
||||
EXPECT_EQ(path, "texture/bar");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, original_extension_with_same_size_as_dds)
|
||||
{
|
||||
EXPECT_EQ(checkChangeExtensionToDds("texture/bar.tga"), "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, original_extension_greater_than_dds)
|
||||
{
|
||||
EXPECT_EQ(checkChangeExtensionToDds("texture/bar.jpeg"), "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, original_extension_smaller_than_dds)
|
||||
{
|
||||
EXPECT_EQ(checkChangeExtensionToDds("texture/bar.xx"), "texture/bar.dds");
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, does_not_change_dds_extension)
|
||||
{
|
||||
std::string path = "texture/bar.dds";
|
||||
EXPECT_FALSE(changeExtensionToDds(path));
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, does_not_change_when_no_extension)
|
||||
{
|
||||
std::string path = "texture/bar";
|
||||
EXPECT_FALSE(changeExtensionToDds(path));
|
||||
}
|
||||
|
||||
TEST(ChangeExtensionToDds, change_when_there_is_an_extension)
|
||||
{
|
||||
std::string path = "texture/bar.jpeg";
|
||||
EXPECT_TRUE(changeExtensionToDds(path));
|
||||
}
|
||||
}
|
||||
|
@ -10,54 +10,97 @@ namespace VFS::Path
|
||||
{
|
||||
using namespace testing;
|
||||
|
||||
TEST(NormalizedTest, shouldSupportDefaultConstructor)
|
||||
struct VFSPathIsNormalizedTest : TestWithParam<std::pair<std::string_view, bool>>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(VFSPathIsNormalizedTest, shouldReturnExpectedResult)
|
||||
{
|
||||
EXPECT_EQ(isNormalized(GetParam().first), GetParam().second);
|
||||
}
|
||||
|
||||
const std::pair<std::string_view, bool> isNormalizedTestParams[] = {
|
||||
{ std::string_view(), true },
|
||||
{ "foo", true },
|
||||
{ "foo/bar", true },
|
||||
{ "foo/bar/baz", true },
|
||||
{ "/foo", false },
|
||||
{ "foo//", false },
|
||||
{ "foo\\", false },
|
||||
{ "Foo", false },
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(IsNormalizedTestParams, VFSPathIsNormalizedTest, ValuesIn(isNormalizedTestParams));
|
||||
|
||||
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveLeadingSeparators)
|
||||
{
|
||||
std::string value("//foo");
|
||||
normalizeFilenameInPlace(value);
|
||||
EXPECT_EQ(value, "foo");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedSeparators)
|
||||
{
|
||||
std::string value("foo//bar///baz");
|
||||
normalizeFilenameInPlace(value);
|
||||
EXPECT_EQ(value, "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedLeadingSeparator)
|
||||
{
|
||||
std::string value("//foo");
|
||||
normalizeFilenameInPlace(value);
|
||||
EXPECT_EQ(value, "foo");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedTest, shouldSupportDefaultConstructor)
|
||||
{
|
||||
const Normalized value;
|
||||
EXPECT_EQ(value.value(), "");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportConstructorFromString)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportConstructorFromString)
|
||||
{
|
||||
const std::string string("Foo\\Bar/baz");
|
||||
const Normalized value(string);
|
||||
EXPECT_EQ(value.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportConstructorFromConstCharPtr)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportConstructorFromConstCharPtr)
|
||||
{
|
||||
const char* const ptr = "Foo\\Bar/baz";
|
||||
const Normalized value(ptr);
|
||||
EXPECT_EQ(value.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportConstructorFromStringView)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportConstructorFromStringView)
|
||||
{
|
||||
const std::string_view view = "Foo\\Bar/baz";
|
||||
const Normalized value(view);
|
||||
EXPECT_EQ(value.view(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportConstructorFromNormalizedView)
|
||||
{
|
||||
const NormalizedView view("foo/bar/baz");
|
||||
const Normalized value(view);
|
||||
EXPECT_EQ(value.view(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, supportMovingValueOut)
|
||||
TEST(VFSPathNormalizedTest, supportMovingValueOut)
|
||||
{
|
||||
Normalized value("Foo\\Bar/baz");
|
||||
EXPECT_EQ(std::move(value).value(), "foo/bar/baz");
|
||||
EXPECT_EQ(value.value(), "");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, isNotEqualToNotNormalized)
|
||||
TEST(VFSPathNormalizedTest, isNotEqualToNotNormalized)
|
||||
{
|
||||
const Normalized value("Foo\\Bar/baz");
|
||||
EXPECT_NE(value.value(), "Foo\\Bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportOperatorLeftShiftToOStream)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportOperatorLeftShiftToOStream)
|
||||
{
|
||||
const Normalized value("Foo\\Bar/baz");
|
||||
std::stringstream stream;
|
||||
@ -65,68 +108,86 @@ namespace VFS::Path
|
||||
EXPECT_EQ(stream.str(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportOperatorDivEqual)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportOperatorDivEqual)
|
||||
{
|
||||
Normalized value("foo/bar");
|
||||
value /= NormalizedView("baz");
|
||||
EXPECT_EQ(value.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, shouldSupportOperatorDivEqualWithStringView)
|
||||
TEST(VFSPathNormalizedTest, shouldSupportOperatorDivEqualWithStringView)
|
||||
{
|
||||
Normalized value("foo/bar");
|
||||
value /= std::string_view("BAZ");
|
||||
EXPECT_EQ(value.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot)
|
||||
TEST(VFSPathNormalizedTest, operatorDivShouldNormalizeSuffix)
|
||||
{
|
||||
Normalized value("foo/bar");
|
||||
value /= std::string_view("\\A\\\\B");
|
||||
EXPECT_EQ(value.value(), "foo/bar/a/b");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldReplaceAfterLastDot)
|
||||
{
|
||||
Normalized value("foo/bar.a");
|
||||
ASSERT_TRUE(value.changeExtension("so"));
|
||||
EXPECT_EQ(value.value(), "foo/bar.so");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldNormalizeExtension)
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnNotNormalizedExtension)
|
||||
{
|
||||
Normalized value("foo/bar.a");
|
||||
ASSERT_TRUE(value.changeExtension("SO"));
|
||||
EXPECT_EQ(value.value(), "foo/bar.so");
|
||||
EXPECT_THROW(value.changeExtension("\\SO"), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldIgnorePathWithoutADot)
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithoutADot)
|
||||
{
|
||||
Normalized value("foo/bar");
|
||||
ASSERT_FALSE(value.changeExtension("so"));
|
||||
EXPECT_EQ(value.value(), "foo/bar");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator)
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator)
|
||||
{
|
||||
Normalized value("foo.bar/baz");
|
||||
ASSERT_FALSE(value.changeExtension("so"));
|
||||
EXPECT_EQ(value.value(), "foo.bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot)
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot)
|
||||
{
|
||||
Normalized value("foo.a");
|
||||
EXPECT_THROW(value.changeExtension(".so"), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator)
|
||||
TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator)
|
||||
{
|
||||
Normalized value("foo.a");
|
||||
EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument);
|
||||
EXPECT_THROW(value.changeExtension("so/"), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedTest, filenameShouldReturnLastComponentOfThePath)
|
||||
{
|
||||
const Normalized value("foo/bar");
|
||||
EXPECT_EQ(value.filename(), "bar");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedTest, filenameShouldReturnSameValueForPathWithSingleComponent)
|
||||
{
|
||||
const Normalized value("foo");
|
||||
EXPECT_EQ(value.filename(), "foo");
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct NormalizedOperatorsTest : Test
|
||||
struct VFSPathNormalizedOperatorsTest : Test
|
||||
{
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(NormalizedOperatorsTest);
|
||||
TYPED_TEST_SUITE_P(VFSPathNormalizedOperatorsTest);
|
||||
|
||||
TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual)
|
||||
TYPED_TEST_P(VFSPathNormalizedOperatorsTest, supportsEqual)
|
||||
{
|
||||
using Type0 = typename TypeParam::Type0;
|
||||
using Type1 = typename TypeParam::Type1;
|
||||
@ -139,7 +200,7 @@ namespace VFS::Path
|
||||
EXPECT_NE(otherNotEqual, normalized);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(NormalizedOperatorsTest, supportsLess)
|
||||
TYPED_TEST_P(VFSPathNormalizedOperatorsTest, supportsLess)
|
||||
{
|
||||
using Type0 = typename TypeParam::Type0;
|
||||
using Type1 = typename TypeParam::Type1;
|
||||
@ -155,7 +216,7 @@ namespace VFS::Path
|
||||
EXPECT_FALSE(otherGreater < normalized);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess);
|
||||
REGISTER_TYPED_TEST_SUITE_P(VFSPathNormalizedOperatorsTest, supportsEqual, supportsLess);
|
||||
|
||||
template <class T0, class T1>
|
||||
struct TypePair
|
||||
@ -170,32 +231,44 @@ namespace VFS::Path
|
||||
TypePair<NormalizedView, const char*>, TypePair<NormalizedView, std::string>,
|
||||
TypePair<NormalizedView, std::string_view>, TypePair<NormalizedView, NormalizedView>>;
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Typed, VFSPathNormalizedOperatorsTest, TypePairs);
|
||||
|
||||
TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized)
|
||||
TEST(VFSPathNormalizedViewTest, shouldSupportConstructorFromNormalized)
|
||||
{
|
||||
const Normalized value("Foo\\Bar/baz");
|
||||
const NormalizedView view(value);
|
||||
EXPECT_EQ(view.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral)
|
||||
TEST(VFSPathNormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral)
|
||||
{
|
||||
constexpr NormalizedView view("foo/bar/baz");
|
||||
EXPECT_EQ(view.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized)
|
||||
TEST(VFSPathNormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized)
|
||||
{
|
||||
EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(NormalizedView, shouldSupportOperatorDiv)
|
||||
TEST(VFSPathNormalizedViewTest, shouldSupportOperatorDiv)
|
||||
{
|
||||
const NormalizedView a("foo/bar");
|
||||
const NormalizedView b("baz");
|
||||
const Normalized result = a / b;
|
||||
EXPECT_EQ(result.value(), "foo/bar/baz");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedViewTest, filenameShouldReturnLastComponentOfThePath)
|
||||
{
|
||||
const NormalizedView value("foo/bar");
|
||||
EXPECT_EQ(value.filename(), "bar");
|
||||
}
|
||||
|
||||
TEST(VFSPathNormalizedViewTest, filenameShouldReturnSameValueForPathWithSingleComponent)
|
||||
{
|
||||
const NormalizedView value("foo");
|
||||
EXPECT_EQ(value.filename(), "foo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ namespace MWLua
|
||||
LuaUi::TextureData data;
|
||||
sol::object path = LuaUtil::getFieldOrNil(options, "path");
|
||||
if (path.is<std::string>())
|
||||
data.mPath = path.as<std::string>();
|
||||
data.mPath = VFS::Path::Normalized(path.as<std::string>());
|
||||
if (data.mPath.empty())
|
||||
throw std::logic_error("Invalid texture path");
|
||||
sol::object offset = LuaUtil::getFieldOrNil(options, "offset");
|
||||
|
@ -201,7 +201,7 @@ namespace MWSound
|
||||
SoundBuffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::Sound& sound)
|
||||
{
|
||||
std::string path = Misc::ResourceHelpers::correctResourcePath(
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3");
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), "mp3");
|
||||
float volume = 1, min = 1, max = 255; // TODO: needs research
|
||||
SoundBuffer& sfx = mSoundBuffers.emplace_back(VFS::Path::Normalized(std::move(path)), volume, min, max);
|
||||
mBufferNameMap.emplace(soundId, &sfx);
|
||||
@ -211,7 +211,7 @@ namespace MWSound
|
||||
SoundBuffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::SoundReference& sound)
|
||||
{
|
||||
std::string path = Misc::ResourceHelpers::correctResourcePath(
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3");
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), "mp3");
|
||||
float volume = 1, min = 1, max = 255; // TODO: needs research
|
||||
// TODO: sound.mSoundId can link to another SoundReference, probably we will need to add additional lookups to
|
||||
// ESMStore.
|
||||
|
@ -1,20 +0,0 @@
|
||||
#include "resources.hpp"
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
namespace LuaUi
|
||||
{
|
||||
std::shared_ptr<TextureResource> ResourceManager::registerTexture(TextureData data)
|
||||
{
|
||||
VFS::Path::normalizeFilenameInPlace(data.mPath);
|
||||
|
||||
TextureResources& list = mTextures[data.mPath];
|
||||
list.push_back(std::make_shared<TextureResource>(data));
|
||||
return list.back();
|
||||
}
|
||||
|
||||
void ResourceManager::clear()
|
||||
{
|
||||
mTextures.clear();
|
||||
}
|
||||
}
|
@ -2,12 +2,13 @@
|
||||
#define OPENMW_LUAUI_RESOURCES
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
@ -17,7 +18,7 @@ namespace LuaUi
|
||||
{
|
||||
struct TextureData
|
||||
{
|
||||
std::string mPath;
|
||||
VFS::Path::Normalized mPath;
|
||||
osg::Vec2f mOffset;
|
||||
osg::Vec2f mSize;
|
||||
};
|
||||
@ -28,12 +29,18 @@ namespace LuaUi
|
||||
class ResourceManager
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<TextureResource> registerTexture(TextureData data);
|
||||
void clear();
|
||||
std::shared_ptr<TextureResource> registerTexture(TextureData data)
|
||||
{
|
||||
TextureResources& list = mTextures[data.mPath];
|
||||
list.push_back(std::make_shared<TextureResource>(std::move(data)));
|
||||
return list.back();
|
||||
}
|
||||
|
||||
void clear() { mTextures.clear(); }
|
||||
|
||||
private:
|
||||
using TextureResources = std::vector<std::shared_ptr<TextureResource>>;
|
||||
std::unordered_map<std::string, TextureResources> mTextures;
|
||||
std::unordered_map<VFS::Path::Normalized, TextureResources, VFS::Path::Hash> mTextures;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "resourcehelpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
@ -26,6 +25,38 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t findDirectory(VFS::Path::NormalizedView path, std::string_view directory)
|
||||
{
|
||||
const std::string_view pathValue = path.value();
|
||||
const std::size_t directorySize = directory.size();
|
||||
|
||||
for (std::size_t offset = 0, pathSize = pathValue.size(); offset < pathSize;)
|
||||
{
|
||||
const std::size_t position = pathValue.find(directory, offset);
|
||||
|
||||
if (position == std::string_view::npos)
|
||||
return std::string_view::npos;
|
||||
|
||||
if (position + directorySize > pathSize)
|
||||
return std::string_view::npos;
|
||||
|
||||
if ((position == 0 || pathValue[position - 1] == VFS::Path::separator)
|
||||
&& pathValue[position + directorySize] == VFS::Path::separator)
|
||||
return position;
|
||||
|
||||
offset = position + directorySize;
|
||||
}
|
||||
|
||||
return std::string_view::npos;
|
||||
}
|
||||
|
||||
VFS::Path::Normalized withPrefix(VFS::Path::NormalizedView path, std::string_view prefix)
|
||||
{
|
||||
VFS::Path::Normalized prefixed(prefix);
|
||||
prefixed /= path;
|
||||
return prefixed;
|
||||
}
|
||||
}
|
||||
|
||||
bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path)
|
||||
@ -37,47 +68,29 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path)
|
||||
std::string Misc::ResourceHelpers::correctResourcePath(std::span<const std::string_view> topLevelDirectories,
|
||||
std::string_view resPath, const VFS::Manager* vfs, std::string_view ext)
|
||||
{
|
||||
std::string correctedPath = Misc::StringUtils::lowerCase(resPath);
|
||||
|
||||
// Flatten slashes
|
||||
std::replace(correctedPath.begin(), correctedPath.end(), '/', '\\');
|
||||
auto bothSeparators = [](char a, char b) { return a == '\\' && b == '\\'; };
|
||||
correctedPath.erase(std::unique(correctedPath.begin(), correctedPath.end(), bothSeparators), correctedPath.end());
|
||||
|
||||
// Remove leading separator
|
||||
if (!correctedPath.empty() && correctedPath[0] == '\\')
|
||||
correctedPath.erase(0, 1);
|
||||
VFS::Path::Normalized correctedPath(resPath);
|
||||
|
||||
// Handle top level directory
|
||||
bool needsPrefix = true;
|
||||
for (std::string_view potentialTopLevelDirectory : topLevelDirectories)
|
||||
|
||||
for (const std::string_view potentialTopLevelDirectory : topLevelDirectories)
|
||||
{
|
||||
if (correctedPath.starts_with(potentialTopLevelDirectory)
|
||||
&& correctedPath.size() > potentialTopLevelDirectory.size()
|
||||
&& correctedPath[potentialTopLevelDirectory.size()] == '\\')
|
||||
if (const std::size_t topLevelPos = findDirectory(correctedPath, potentialTopLevelDirectory);
|
||||
topLevelPos != std::string::npos)
|
||||
{
|
||||
correctedPath = VFS::Path::Normalized(correctedPath.value().substr(topLevelPos));
|
||||
needsPrefix = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + '\\';
|
||||
size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix);
|
||||
if (topLevelPos != std::string::npos)
|
||||
{
|
||||
correctedPath.erase(0, topLevelPos + 1);
|
||||
needsPrefix = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needsPrefix)
|
||||
correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath;
|
||||
|
||||
std::string origExt = correctedPath;
|
||||
if (needsPrefix)
|
||||
correctedPath = withPrefix(correctedPath, topLevelDirectories.front());
|
||||
|
||||
const VFS::Path::Normalized origExt = correctedPath;
|
||||
|
||||
// replace extension if `ext` is specified (used for .tga -> .dds, .wav -> .mp3)
|
||||
bool isExtChanged = !ext.empty() && changeExtension(correctedPath, ext);
|
||||
const bool isExtChanged = !ext.empty() && correctedPath.changeExtension(ext);
|
||||
|
||||
if (vfs->exists(correctedPath))
|
||||
return correctedPath;
|
||||
@ -87,18 +100,15 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span<const std::stri
|
||||
return origExt;
|
||||
|
||||
// fall back to a resource in the top level directory if it exists
|
||||
std::string fallback{ topLevelDirectories.front() };
|
||||
fallback += '\\';
|
||||
fallback += Misc::getFileName(correctedPath);
|
||||
|
||||
if (vfs->exists(fallback))
|
||||
return fallback;
|
||||
{
|
||||
const VFS::Path::Normalized fallback = withPrefix(correctedPath.filename(), topLevelDirectories.front());
|
||||
if (vfs->exists(fallback))
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (isExtChanged)
|
||||
{
|
||||
fallback = topLevelDirectories.front();
|
||||
fallback += '\\';
|
||||
fallback += Misc::getFileName(origExt);
|
||||
const VFS::Path::Normalized fallback = withPrefix(origExt.filename(), topLevelDirectories.front());
|
||||
if (vfs->exists(fallback))
|
||||
return fallback;
|
||||
}
|
||||
@ -112,17 +122,17 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span<const std::stri
|
||||
|
||||
std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs, ".dds");
|
||||
return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs, "dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "icons" } }, resPath, vfs, ".dds");
|
||||
return correctResourcePath({ { "icons" } }, resPath, vfs, "dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs, ".dds");
|
||||
return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs, "dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctBookartPath(
|
||||
@ -200,9 +210,9 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(
|
||||
VFS::Path::NormalizedView resPath, const VFS::Manager& vfs)
|
||||
{
|
||||
// Note: likely should be replaced with
|
||||
// return correctResourcePath({ { "sound" } }, resPath, vfs, ".mp3");
|
||||
// return correctResourcePath({ { "sound" } }, resPath, vfs, "mp3");
|
||||
// but there is a slight difference in behaviour:
|
||||
// - `correctResourcePath(..., ".mp3")` first checks `.mp3`, then tries the original extension
|
||||
// - `correctResourcePath(..., "mp3")` first checks `.mp3`, then tries the original extension
|
||||
// - the implementation below first tries the original extension, then falls back to `.mp3`.
|
||||
|
||||
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
||||
|
@ -14,24 +14,59 @@ namespace VFS::Path
|
||||
inline constexpr char separator = '/';
|
||||
inline constexpr char extensionSeparator = '.';
|
||||
|
||||
inline constexpr char normalize(char c)
|
||||
[[nodiscard]] inline constexpr char normalize(char c)
|
||||
{
|
||||
return c == '\\' ? separator : Misc::StringUtils::toLower(c);
|
||||
}
|
||||
|
||||
inline constexpr bool isNormalized(std::string_view name)
|
||||
[[nodiscard]] inline constexpr bool isNormalized(std::string_view name)
|
||||
{
|
||||
return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); });
|
||||
if (name.empty())
|
||||
return true;
|
||||
|
||||
if (name.front() != normalize(name.front()))
|
||||
return false;
|
||||
|
||||
if (name.front() == separator)
|
||||
return false;
|
||||
|
||||
for (std::size_t i = 1, n = name.size(); i < n; ++i)
|
||||
{
|
||||
if (name[i] != normalize(name[i]))
|
||||
return false;
|
||||
|
||||
if (name[i] == separator && name[i - 1] == name[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void normalizeFilenameInPlace(auto begin, auto end)
|
||||
[[nodiscard]] inline auto removeDuplicatedSeparators(auto begin, auto end)
|
||||
{
|
||||
return std::unique(begin, end, [](char a, char b) { return a == separator && b == separator; });
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto removeLeadingSeparator(auto begin, auto end)
|
||||
{
|
||||
if (begin != end && *begin == separator)
|
||||
return begin + 1;
|
||||
return begin;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto normalizeFilenameInPlace(auto begin, auto end)
|
||||
{
|
||||
std::transform(begin, end, begin, normalize);
|
||||
end = removeDuplicatedSeparators(begin, end);
|
||||
begin = removeLeadingSeparator(begin, end);
|
||||
return std::pair(begin, end);
|
||||
}
|
||||
|
||||
inline void normalizeFilenameInPlace(std::string& name)
|
||||
{
|
||||
normalizeFilenameInPlace(name.begin(), name.end());
|
||||
const auto [begin, end] = normalizeFilenameInPlace(name.begin(), name.end());
|
||||
name.erase(end, name.end());
|
||||
name.erase(name.begin(), begin);
|
||||
}
|
||||
|
||||
/// Normalize the given filename, making slashes/backslashes consistent, and lower-casing.
|
||||
@ -148,6 +183,14 @@ namespace VFS::Path
|
||||
return stem;
|
||||
}
|
||||
|
||||
NormalizedView filename() const
|
||||
{
|
||||
NormalizedView result(*this);
|
||||
if (const std::size_t position = mValue.find_last_of(separator); position != std::string_view::npos)
|
||||
result.mValue.remove_prefix(position + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string_view mValue;
|
||||
};
|
||||
@ -197,6 +240,8 @@ namespace VFS::Path
|
||||
|
||||
bool changeExtension(std::string_view extension)
|
||||
{
|
||||
if (!isNormalized(extension))
|
||||
throw std::invalid_argument("Not normalized extension: " + std::string(extension));
|
||||
if (findSeparatorOrExtensionSeparator(extension.begin(), extension.end()) != extension.end())
|
||||
throw std::invalid_argument("Invalid extension: " + std::string(extension));
|
||||
const auto it = findSeparatorOrExtensionSeparator(mValue.rbegin(), mValue.rend());
|
||||
@ -204,7 +249,7 @@ namespace VFS::Path
|
||||
return false;
|
||||
const std::string::difference_type pos = mValue.rend() - it;
|
||||
mValue.replace(pos, mValue.size(), extension);
|
||||
normalizeFilenameInPlace(mValue.begin() + pos, mValue.end());
|
||||
std::transform(mValue.begin() + pos, mValue.end(), mValue.begin() + pos, normalize);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -230,7 +275,9 @@ namespace VFS::Path
|
||||
mValue += separator;
|
||||
const std::size_t offset = mValue.size();
|
||||
mValue += value;
|
||||
normalizeFilenameInPlace(mValue.begin() + offset, mValue.end());
|
||||
const auto [begin, end] = normalizeFilenameInPlace(mValue.begin() + offset, mValue.end());
|
||||
std::copy(begin, end, mValue.begin() + offset);
|
||||
mValue.resize(offset + (end - begin));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -290,6 +337,11 @@ namespace VFS::Path
|
||||
return NormalizedView(*this).stem();
|
||||
}
|
||||
|
||||
NormalizedView filename() const
|
||||
{
|
||||
return NormalizedView(*this).filename();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mValue;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user