Switched the file added info back to a full date time, ref #601

This commit is contained in:
emeric
2025-01-26 15:08:31 +01:00
parent e553e840d3
commit 814eaf0a3e
12 changed files with 96 additions and 55 deletions
+10
View File
@@ -465,17 +465,27 @@ namespace lms::core::stringUtils
} }
std::string toISO8601String(const Wt::WDateTime& dateTime) std::string toISO8601String(const Wt::WDateTime& dateTime)
{
if (dateTime.isValid())
{ {
// assume UTC // assume UTC
return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8(); return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8();
} }
return "";
}
std::string toISO8601String(const Wt::WDate& date) std::string toISO8601String(const Wt::WDate& date)
{
if (date.isValid())
{ {
// assume UTC // assume UTC
return date.toString("yyyy-MM-dd").toUTF8(); return date.toString("yyyy-MM-dd").toUTF8();
} }
return "";
}
Wt::WDateTime fromISO8601String(std::string_view dateTime) Wt::WDateTime fromISO8601String(std::string_view dateTime)
{ {
// assume UTC // assume UTC
+16 -11
View File
@@ -43,11 +43,26 @@ namespace lms::core
static PartialDateTime fromWtDateTime(const Wt::WDateTime& dateTime); static PartialDateTime fromWtDateTime(const Wt::WDateTime& dateTime);
std::string toISO8601String() const; std::string toISO8601String() const;
bool isValid() const { return _precision != Precision::Invalid; } constexpr bool isValid() const { return _precision != Precision::Invalid; }
enum class Precision : std::uint8_t
{
Invalid,
Year,
Month,
Day,
Hour,
Min,
Sec,
};
constexpr Precision getPrecision() const { return _precision; }
constexpr std::optional<int> getYear() const { return (_precision >= Precision::Year ? std::make_optional(_year) : std::nullopt); } constexpr std::optional<int> getYear() const { return (_precision >= Precision::Year ? std::make_optional(_year) : std::nullopt); }
constexpr std::optional<int> getMonth() const { return (_precision >= Precision::Month ? std::make_optional(_month) : std::nullopt); } constexpr std::optional<int> getMonth() const { return (_precision >= Precision::Month ? std::make_optional(_month) : std::nullopt); }
constexpr std::optional<int> getDay() const { return (_precision >= Precision::Day ? std::make_optional(_day) : std::nullopt); } constexpr std::optional<int> getDay() const { return (_precision >= Precision::Day ? std::make_optional(_day) : std::nullopt); }
constexpr std::optional<int> getHour() const { return (_precision >= Precision::Hour ? std::make_optional(_hour) : std::nullopt); }
constexpr std::optional<int> getMin() const { return (_precision >= Precision::Min ? std::make_optional(_min) : std::nullopt); }
constexpr std::optional<int> getSec() const { return (_precision >= Precision::Sec ? std::make_optional(_sec) : std::nullopt); }
constexpr auto operator<=>(const PartialDateTime& other) const = default; constexpr auto operator<=>(const PartialDateTime& other) const = default;
@@ -60,16 +75,6 @@ namespace lms::core
std::uint8_t _hour{}; // 0 to 23 std::uint8_t _hour{}; // 0 to 23
std::uint8_t _min{}; // 0 to 59 std::uint8_t _min{}; // 0 to 59
std::uint8_t _sec{}; // 0 to 59 std::uint8_t _sec{}; // 0 to 59
enum class Precision : std::uint8_t
{
Invalid,
Year,
Month,
Day,
Hour,
Min,
Sec,
};
Precision _precision{ Precision::Invalid }; Precision _precision{ Precision::Invalid };
}; };
} // namespace lms::core } // namespace lms::core
+19
View File
@@ -300,17 +300,36 @@ namespace lms::core::stringUtils::tests
} }
TEST(Stringutils, DateToString) TEST(Stringutils, DateToString)
{
{ {
const Wt::WDate date{ 2020, 01, 03 }; const Wt::WDate date{ 2020, 01, 03 };
EXPECT_EQ(toISO8601String(date), "2020-01-03"); EXPECT_EQ(toISO8601String(date), "2020-01-03");
} }
{
const Wt::WDate date;
EXPECT_EQ(toISO8601String(date), "");
}
}
TEST(Stringutils, DateTimeToString) TEST(Stringutils, DateTimeToString)
{
{ {
const Wt::WDateTime dateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } }; const Wt::WDateTime dateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } };
EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075"); EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075");
} }
{
const Wt::WDateTime dateTime{ Wt::WDate{ 2020, 01, 03 } };
EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T00:00:00.000");
}
{
const Wt::WDateTime dateTime;
EXPECT_EQ(toISO8601String(dateTime), "");
}
}
TEST(Stringutils, DateTimeFromString) TEST(Stringutils, DateTimeFromString)
{ {
EXPECT_EQ(fromISO8601String("2020-01-03T09:08:11.075"), (Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } })); EXPECT_EQ(fromISO8601String("2020-01-03T09:08:11.075"), (Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } }));
+3 -3
View File
@@ -704,18 +704,18 @@ namespace lms::db
return utils::fetchQuerySingleResult(session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t").where("t.release_id = ?").bind(getId())); return utils::fetchQuerySingleResult(session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t").where("t.release_id = ?").bind(getId()));
} }
Wt::WDateTime Release::getLastWritten() const Wt::WDateTime Release::getLastWrittenTime() const
{ {
assert(session()); assert(session());
return utils::fetchQuerySingleResult(session()->query<Wt::WDateTime>("SELECT MAX(file_last_write) FROM track t").where("t.release_id = ?").bind(getId())); return utils::fetchQuerySingleResult(session()->query<Wt::WDateTime>("SELECT MAX(file_last_write) FROM track t").where("t.release_id = ?").bind(getId()));
} }
core::PartialDateTime Release::getAddedTime() const Wt::WDateTime Release::getAddedTime() const
{ {
assert(session()); assert(session());
return utils::fetchQuerySingleResult(session()->query<core::PartialDateTime>("SELECT MAX(file_added) FROM track t").where("t.release_id = ?").bind(getId())); return utils::fetchQuerySingleResult(session()->query<Wt::WDateTime>("SELECT MAX(file_added) FROM track t").where("t.release_id = ?").bind(getId()));
} }
std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
@@ -244,8 +244,8 @@ namespace lms::db
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example) std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
std::vector<DiscInfo> getDiscs() const; std::vector<DiscInfo> getDiscs() const;
std::chrono::milliseconds getDuration() const; std::chrono::milliseconds getDuration() const;
core::PartialDateTime getAddedTime() const; Wt::WDateTime getAddedTime() const;
Wt::WDateTime getLastWritten() const; Wt::WDateTime getLastWrittenTime() const;
std::string_view getArtistDisplayName() const { return _artistDisplayName; } std::string_view getArtistDisplayName() const { return _artistDisplayName; }
bool isCompilation() const { return _isCompilation; } bool isCompilation() const { return _isCompilation; }
std::size_t getTrackCount() const; std::size_t getTrackCount() const;
+4 -4
View File
@@ -223,8 +223,8 @@ namespace lms::db
void setAbsoluteFilePath(const std::filesystem::path& filePath); void setAbsoluteFilePath(const std::filesystem::path& filePath);
void setRelativeFilePath(const std::filesystem::path& filePath); void setRelativeFilePath(const std::filesystem::path& filePath);
void setFileSize(std::size_t fileSize) { _fileSize = fileSize; } void setFileSize(std::size_t fileSize) { _fileSize = fileSize; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } void setLastWriteTime(const Wt::WDateTime& time) { _fileLastWrite = time; }
void setAddedTime(core::PartialDateTime time) { _fileAdded = time; } void setAddedTime(const Wt::WDateTime& time) { _fileAdded = time; }
void setBitrate(std::size_t bitrate) { _bitrate = bitrate; } void setBitrate(std::size_t bitrate) { _bitrate = bitrate; }
void setBitsPerSample(std::size_t bitsPerSample) { _bitsPerSample = bitsPerSample; } void setBitsPerSample(std::size_t bitsPerSample) { _bitsPerSample = bitsPerSample; }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; } void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
@@ -272,7 +272,7 @@ namespace lms::db
const core::PartialDateTime& getOriginalDate() const { return _originalDate; } const core::PartialDateTime& getOriginalDate() const { return _originalDate; }
std::optional<int> getOriginalYear() const; std::optional<int> getOriginalYear() const;
const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; } const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; }
const core::PartialDateTime& getAddedTime() const { return _fileAdded; } const Wt::WDateTime& getAddedTime() const { return _fileAdded; }
bool hasCover() const { return _hasCover; } bool hasCover() const { return _hasCover; }
bool hasLyrics() const; bool hasLyrics() const;
std::optional<core::UUID> getTrackMBID() const { return core::UUID::fromString(_trackMBID); } std::optional<core::UUID> getTrackMBID() const { return core::UUID::fromString(_trackMBID); }
@@ -367,7 +367,7 @@ namespace lms::db
std::filesystem::path _fileName; std::filesystem::path _fileName;
long long _fileSize{}; long long _fileSize{};
Wt::WDateTime _fileLastWrite; Wt::WDateTime _fileLastWrite;
core::PartialDateTime _fileAdded; Wt::WDateTime _fileAdded;
bool _hasCover{}; bool _hasCover{};
std::string _trackMBID; std::string _trackMBID;
std::string _recordingMBID; std::string _recordingMBID;
+5 -5
View File
@@ -751,11 +751,11 @@ namespace lms::db::tests
{ {
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
trackA1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2 }); trackA1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 } });
trackB1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 1 }); trackB1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 1 } });
trackD1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2, 15, 36, 24 }); trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 }, Wt::WTime{ 15, 36, 24 } });
trackD1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 3 }); trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
trackA2.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 4 }); trackA2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist); TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer); TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
+11 -11
View File
@@ -1138,11 +1138,11 @@ namespace lms::db::tests
{ {
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
trackA1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2 }); trackA1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 } });
trackB1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 1 }); trackB1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 1 } });
trackD1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2, 15, 36, 24 }); trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 }, Wt::WTime{ 15, 36, 24 } });
trackD1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 3 }); trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
trackA2.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 4 }); trackA2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
trackA1.get().modify()->setRelease(releaseA.get()); trackA1.get().modify()->setRelease(releaseA.get());
trackA2.get().modify()->setRelease(releaseA.get()); trackA2.get().modify()->setRelease(releaseA.get());
@@ -1212,7 +1212,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
Wt::WDateTime lastWritten{ release.get()->getLastWritten() }; Wt::WDateTime lastWritten{ release.get()->getLastWrittenTime() };
EXPECT_FALSE(lastWritten.isValid()); EXPECT_FALSE(lastWritten.isValid());
} }
@@ -1227,7 +1227,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
Wt::WDateTime lastWritten{ release.get()->getLastWritten() }; Wt::WDateTime lastWritten{ release.get()->getLastWrittenTime() };
ASSERT_TRUE(lastWritten.isValid()); ASSERT_TRUE(lastWritten.isValid());
EXPECT_EQ(lastWritten, track2.get()->getLastWriteTime()); EXPECT_EQ(lastWritten, track2.get()->getLastWriteTime());
} }
@@ -1243,14 +1243,14 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
core::PartialDateTime addedTime{ release.get()->getAddedTime() }; const Wt::WDateTime addedTime{ release.get()->getAddedTime() };
EXPECT_FALSE(addedTime.isValid()); EXPECT_FALSE(addedTime.isValid());
} }
{ {
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2 }); track1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 } });
track2.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2, 15, 36, 24 }); track2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 }, Wt::WTime{ 15, 36, 24 } });
track1.get().modify()->setRelease(release.get()); track1.get().modify()->setRelease(release.get());
track2.get().modify()->setRelease(release.get()); track2.get().modify()->setRelease(release.get());
} }
@@ -1258,7 +1258,7 @@ namespace lms::db::tests
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
core::PartialDateTime addedTime{ release.get()->getAddedTime() }; const Wt::WDateTime addedTime{ release.get()->getAddedTime() };
ASSERT_TRUE(addedTime.isValid()); ASSERT_TRUE(addedTime.isValid());
EXPECT_EQ(addedTime, track2.get()->getAddedTime()); EXPECT_EQ(addedTime, track2.get()->getAddedTime());
} }
+3 -3
View File
@@ -399,9 +399,9 @@ namespace lms::db::tests
{ {
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2 }); track1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 } });
track2.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 1 }); track2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 1 } });
track4.get().modify()->setAddedTime(core::PartialDateTime{ 2021, 1, 2, 15, 36, 24 }); track4.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 2 }, Wt::WTime{ 15, 36, 24 } });
} }
{ {
@@ -482,11 +482,7 @@ namespace lms::scanner
added = true; added = true;
track.modify()->setAbsoluteFilePath(_file); track.modify()->setAbsoluteFilePath(_file);
if (!_parsedTrack->encodingTime.isValid()) track.modify()->setAddedTime(_mediaLibrary.firstScan ? fileInfo->lastWriteTime : Wt::WDateTime::currentDateTime()); // may be erased by encodingTime
{
const core::PartialDateTime addedTime{ core::PartialDateTime::fromWtDateTime(_mediaLibrary.firstScan ? fileInfo->lastWriteTime : Wt::WDateTime::currentDateTime()) };
track.modify()->setAddedTime(addedTime); // may be erased by encodingTime
}
} }
// Track related data // Track related data
@@ -504,7 +500,18 @@ namespace lms::scanner
track.modify()->setLastWriteTime(fileInfo->lastWriteTime); track.modify()->setLastWriteTime(fileInfo->lastWriteTime);
if (_parsedTrack->encodingTime.isValid()) if (_parsedTrack->encodingTime.isValid())
track.modify()->setAddedTime(_parsedTrack->encodingTime); {
const core::PartialDateTime& encodingTime{ _parsedTrack->encodingTime };
Wt::WDate date;
Wt::WTime time;
if (encodingTime.getPrecision() >= core::PartialDateTime::Precision::Day)
date = Wt::WDate{ *encodingTime.getYear(), *encodingTime.getMonth(), *encodingTime.getDay() };
if (encodingTime.getPrecision() >= core::PartialDateTime::Precision::Sec)
time = Wt::WTime{ *encodingTime.getHour(), *encodingTime.getMin(), *encodingTime.getSec() };
if (date.isValid())
track.modify()->setAddedTime(Wt::WDateTime{ date, time });
}
db::MediaLibrary::pointer mediaLibrary{ db::MediaLibrary::find(dbSession, _mediaLibrary.id) }; // may be null if settings are updated in // => next scan will correct this db::MediaLibrary::pointer mediaLibrary{ db::MediaLibrary::find(dbSession, _mediaLibrary.id) }; // may be null if settings are updated in // => next scan will correct this
track.modify()->setMediaLibrary(mediaLibrary); track.modify()->setMediaLibrary(mediaLibrary);
+1 -1
View File
@@ -85,7 +85,7 @@ namespace lms::api::subsonic
albumNode.setAttribute("isDir", true); albumNode.setAttribute("isDir", true);
} }
albumNode.setAttribute("created", release->getAddedTime().toISO8601String()); albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getAddedTime()));
if (const auto image{ release->getImage() }) if (const auto image{ release->getImage() })
{ {
const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
+1 -1
View File
@@ -145,7 +145,7 @@ namespace lms::api::subsonic
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count()); trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
trackResponse.setAttribute("bitRate", (track->getBitrate() / 1000)); trackResponse.setAttribute("bitRate", (track->getBitrate() / 1000));
trackResponse.setAttribute("type", "music"); trackResponse.setAttribute("type", "music");
trackResponse.setAttribute("created", track->getAddedTime().toISO8601String()); trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getAddedTime()));
trackResponse.setAttribute("contentType", av::getMimeType(track->getAbsoluteFilePath().extension())); trackResponse.setAttribute("contentType", av::getMimeType(track->getAbsoluteFilePath().extension()));
if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), track->getId()) }) if (const auto rating{ core::Service<feedback::IFeedbackService>::get()->getRating(context.user->getId(), track->getId()) })
trackResponse.setAttribute("userRating", *rating); trackResponse.setAttribute("userRating", *rating);