Support unsigned integers in TagValue

This commit is contained in:
Martchus 2022-06-19 17:38:54 +02:00
parent dcda6b673b
commit 08570aea32
3 changed files with 150 additions and 8 deletions

View File

@ -266,8 +266,6 @@ bool TagValue::compareTo(const TagValue &other, TagValueComparisionFlags options
}
case TagDataType::PositionInSet:
return toPositionInSet() == other.toPositionInSet();
case TagDataType::Integer:
return toInteger() == other.toInteger();
case TagDataType::StandardGenreIndex:
return toStandardGenreIndex() == other.toStandardGenreIndex();
case TagDataType::TimeSpan:
@ -298,7 +296,11 @@ bool TagValue::compareTo(const TagValue &other, TagValueComparisionFlags options
}
// handle types where an implicit conversion to the specific type can be done
if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
return toInteger() == other.toInteger();
} else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
return toUnsignedInteger() == other.toUnsignedInteger();
} else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
if (options & TagValueComparisionFlags::CaseInsensitive) {
const auto lhs = toPopularity(), rhs = other.toPopularity();
return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && compareData(lhs.user, rhs.user, true);
@ -391,6 +393,51 @@ std::int32_t TagValue::toInteger() const
throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
case TagDataType::Popularity:
return static_cast<std::int32_t>(toPopularity().rating);
case TagDataType::UnsignedInteger: {
const auto unsignedInteger = toUnsignedInteger();
if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
}
return static_cast<std::int32_t>(unsignedInteger);
}
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
}
}
std::uint64_t TagValue::toUnsignedInteger() const
{
if (isEmpty()) {
return 0;
}
switch (m_type) {
case TagDataType::Text:
switch (m_encoding) {
case TagTextEncoding::Utf16LittleEndian:
case TagTextEncoding::Utf16BigEndian: {
auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
ensureHostByteOrder(u16str, m_encoding);
return stringToNumber<std::uint64_t>(u16str);
}
default:
return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
}
case TagDataType::PositionInSet:
case TagDataType::Integer:
case TagDataType::StandardGenreIndex: {
const auto integer = toInteger();
if (integer < 0) {
throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
}
return static_cast<std::uint64_t>(integer);
}
case TagDataType::Popularity:
return static_cast<std::uint64_t>(toPopularity().rating);
case TagDataType::UnsignedInteger:
if (m_size == sizeof(std::uint64_t)) {
return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
}
throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
}
@ -423,10 +470,19 @@ int TagValue::toStandardGenreIndex() const
}
case TagDataType::StandardGenreIndex:
case TagDataType::Integer:
if (m_size != sizeof(std::int32_t)) {
case TagDataType::UnsignedInteger:
if (m_size == sizeof(std::int32_t)) {
index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
} else if (m_size == sizeof(std::uint64_t)) {
const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
if (unsignedInt <= std::numeric_limits<int>::max()) {
index = static_cast<int>(unsignedInt);
} else {
index = Id3Genres::genreCount();
}
} else {
throw ConversionException("The assigned index/integer is of unappropriate size.");
}
index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
break;
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
@ -470,6 +526,17 @@ PositionInSet TagValue::toPositionInSet() const
default:
throw ConversionException("The size of the assigned data is not appropriate.");
}
case TagDataType::UnsignedInteger:
switch (m_size) {
case sizeof(std::uint64_t): {
const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
if (track < std::numeric_limits<std::int32_t>::max()) {
return PositionInSet(static_cast<std::int32_t>(track));
}
}
default:;
}
throw ConversionException("The size of the assigned data is not appropriate.");
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
}
@ -498,6 +565,17 @@ TimeSpan TagValue::toTimeSpan() const
default:
throw ConversionException("The size of the assigned integer is not appropriate for conversion to time span.");
}
case TagDataType::UnsignedInteger:
switch (m_size) {
case sizeof(std::uint64_t): {
const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
if (ticks < std::numeric_limits<std::int64_t>::max()) {
return TimeSpan(static_cast<std::int64_t>(ticks));
}
}
default:;
}
throw ConversionException("The size of the assigned data is not appropriate.");
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
}
@ -524,9 +602,10 @@ DateTime TagValue::toDateTime() const
}
case TagDataType::Integer:
case TagDataType::DateTime:
case TagDataType::UnsignedInteger:
if (m_size == sizeof(std::int32_t)) {
return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
} else if (m_size == sizeof(std::int64_t)) {
} else if (m_size == sizeof(std::uint64_t)) {
return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
} else {
throw ConversionException("The size of the assigned integer is not appropriate for conversion to date time.");
@ -568,6 +647,9 @@ Popularity TagValue::toPopularity() const
}
break;
}
case TagDataType::UnsignedInteger:
popularity.rating = static_cast<double>(toUnsignedInteger());
break;
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
}
@ -751,6 +833,9 @@ void TagValue::toString(string &result, TagTextEncoding encoding) const
case TagDataType::Popularity:
result = toPopularity().toString();
break;
case TagDataType::UnsignedInteger:
result = numberToString(toUnsignedInteger());
break;
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
}
@ -838,6 +923,9 @@ void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
case TagDataType::Popularity:
regularStrRes = toPopularity().toString();
break;
case TagDataType::UnsignedInteger:
regularStrRes = numberToString(toUnsignedInteger());
break;
default:
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
}
@ -918,6 +1006,19 @@ void TagValue::assignInteger(int value)
m_encoding = TagTextEncoding::Latin1;
}
/*!
* \brief Assigns the given unsigned integer \a value.
* \param value Specifies the unsigned integer to be assigned.
*/
void TagValue::assignUnsignedInteger(std::uint64_t value)
{
m_size = sizeof(value);
m_ptr = make_unique<char[]>(m_size);
std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
m_type = TagDataType::UnsignedInteger;
m_encoding = TagTextEncoding::Latin1;
}
/*!
* \brief Assigns a copy of the given \a data.
* \param data Specifies the data to be assigned.

View File

@ -102,6 +102,7 @@ enum class TagDataType : unsigned int {
Binary, /**< unspecified binary data */
Undefined, /**< undefined/invalid data type */
Popularity, /**< rating with user info and play counter (as in ID3v2's "Popularimeter") */
UnsignedInteger, /**< unsigned integer */
};
/*!
@ -126,6 +127,7 @@ public:
explicit TagValue(
std::string_view text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified);
explicit TagValue(int value);
explicit TagValue(std::uint64_t value);
explicit TagValue(
const char *data, std::size_t length, TagDataType type = TagDataType::Undefined, TagTextEncoding encoding = TagTextEncoding::Latin1);
explicit TagValue(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type = TagDataType::Binary,
@ -158,6 +160,7 @@ public:
std::u16string toWString(TagTextEncoding encoding = TagTextEncoding::Unspecified) const;
void toWString(std::u16string &result, TagTextEncoding encoding = TagTextEncoding::Unspecified) const;
std::int32_t toInteger() const;
std::uint64_t toUnsignedInteger() const;
int toStandardGenreIndex() const;
PositionInSet toPositionInSet() const;
CppUtilities::TimeSpan toTimeSpan() const;
@ -194,6 +197,7 @@ public:
void assignText(
std::string_view text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified);
void assignInteger(int value);
void assignUnsignedInteger(std::uint64_t value);
void assignStandardGenreIndex(int index);
void assignData(const char *data, std::size_t length, TagDataType type = TagDataType::Binary, TagTextEncoding encoding = TagTextEncoding::Latin1);
void assignData(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type = TagDataType::Binary,
@ -318,6 +322,14 @@ inline TagValue::TagValue(int value)
{
}
/*!
* \brief Constructs a new TagValue holding the given unsigned integer \a value.
*/
inline TagParser::TagValue::TagValue(std::uint64_t value)
: TagValue(reinterpret_cast<const char *>(&value), sizeof(value), TagDataType::UnsignedInteger)
{
}
/*!
* \brief Constructs a new TagValue with a copy of the given \a data.
*

View File

@ -24,6 +24,7 @@ class TagValueTests : public TestFixture {
CPPUNIT_TEST(testBasics);
CPPUNIT_TEST(testBinary);
CPPUNIT_TEST(testInteger);
CPPUNIT_TEST(testUnsignedInteger);
CPPUNIT_TEST(testPositionInSet);
CPPUNIT_TEST(testTimeSpan);
CPPUNIT_TEST(testDateTime);
@ -39,6 +40,7 @@ public:
void testBasics();
void testBinary();
void testInteger();
void testUnsignedInteger();
void testPositionInSet();
void testTimeSpan();
void testDateTime();
@ -77,10 +79,11 @@ void TagValueTests::testBinary()
void TagValueTests::testInteger()
{
// positive number
TagValue integer(42);
auto integer = TagValue(42);
CPPUNIT_ASSERT(!integer.isEmpty());
CPPUNIT_ASSERT_EQUAL(TagDataType::Integer, integer.type());
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(42), integer.toInteger());
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(42), integer.toUnsignedInteger());
CPPUNIT_ASSERT_EQUAL("42"s, integer.toString());
integer.assignInteger(2);
CPPUNIT_ASSERT_EQUAL("Country"s, string(Id3Genres::stringFromIndex(integer.toStandardGenreIndex())));
@ -107,16 +110,41 @@ void TagValueTests::testInteger()
CPPUNIT_ASSERT_MESSAGE("cleared vale considered empty", integer.isEmpty());
CPPUNIT_ASSERT_EQUAL_MESSAGE("only date (but not type) cleared"s, TagDataType::Integer, integer.type());
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(0), integer.toInteger());
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(0), integer.toUnsignedInteger());
CPPUNIT_ASSERT_EQUAL(string(), integer.toString());
CPPUNIT_ASSERT_EQUAL(DateTime(), integer.toDateTime());
CPPUNIT_ASSERT_EQUAL(TimeSpan(), integer.toTimeSpan());
}
void TagValueTests::testUnsignedInteger()
{
auto unsignedInteger = TagValue(static_cast<std::uint64_t>(42ul));
CPPUNIT_ASSERT(!unsignedInteger.isEmpty());
CPPUNIT_ASSERT_EQUAL(TagDataType::UnsignedInteger, unsignedInteger.type());
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(42), unsignedInteger.toInteger());
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(42), unsignedInteger.toUnsignedInteger());
CPPUNIT_ASSERT_EQUAL("42"s, unsignedInteger.toString());
unsignedInteger.assignUnsignedInteger(2);
CPPUNIT_ASSERT_EQUAL("Country"s, string(Id3Genres::stringFromIndex(unsignedInteger.toStandardGenreIndex())));
unsignedInteger.assignInteger(Id3Genres::emptyGenreIndex());
CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
unsignedInteger.clearData();
CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
// zero
unsignedInteger.assignInteger(0);
CPPUNIT_ASSERT_MESSAGE("explicitly assigned zero not considered empty", !unsignedInteger.isEmpty());
CPPUNIT_ASSERT_EQUAL("0"s, unsignedInteger.toString());
CPPUNIT_ASSERT_EQUAL(DateTime(), unsignedInteger.toDateTime());
CPPUNIT_ASSERT_EQUAL(TimeSpan(), unsignedInteger.toTimeSpan());
}
void TagValueTests::testPositionInSet()
{
const TagValue test(PositionInSet(4, 23));
CPPUNIT_ASSERT_EQUAL(PositionInSet(4, 23), test.toPositionInSet());
CPPUNIT_ASSERT_EQUAL(test.toInteger(), 4);
CPPUNIT_ASSERT_EQUAL(4, test.toInteger());
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(4), test.toUnsignedInteger());
CPPUNIT_ASSERT_EQUAL("4/23"s, test.toString());
CPPUNIT_ASSERT_THROW(test.toStandardGenreIndex(), ConversionException);
CPPUNIT_ASSERT_THROW(test.toDateTime(), ConversionException);
@ -170,6 +198,7 @@ void TagValueTests::testString()
CPPUNIT_ASSERT_EQUAL("\x31\0\x35\0"s, TagValue(15).toString(TagTextEncoding::Utf16LittleEndian));
CPPUNIT_ASSERT_EQUAL("\0\x31\0\x35"s, TagValue(15).toString(TagTextEncoding::Utf16BigEndian));
CPPUNIT_ASSERT_EQUAL(15, TagValue("\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toInteger());
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(15), TagValue("\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toUnsignedInteger());
CPPUNIT_ASSERT_EQUAL_MESSAGE(
"original encoding preserved", "15ä"s, TagValue("15ä", 4, TagTextEncoding::Utf8).toString(TagTextEncoding::Unspecified));
CPPUNIT_ASSERT_EQUAL_MESSAGE("original encoding preserved", "\0\x31\0\x35"s,