From 2ebc4280a01569e1b21e830b764a3b48e33db908 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 23 Sep 2021 13:18:43 +0200 Subject: [PATCH] Sort by release date, not just release year, fixes #128 --- src/libs/database/impl/Artist.cpp | 4 +- src/libs/database/impl/Release.cpp | 15 +- src/libs/database/impl/Session.cpp | 37 +- src/libs/database/impl/Track.cpp | 4 +- .../database/include/database/Release.hpp | 6 +- src/libs/database/include/database/Track.hpp | 44 +-- src/libs/metadata/CMakeLists.txt | 3 + src/libs/metadata/impl/AvFormatParser.cpp | 6 +- src/libs/metadata/impl/TagLibParser.cpp | 30 +- src/libs/metadata/impl/Utils.cpp | 60 +++ src/libs/metadata/impl/Utils.hpp | 27 ++ .../metadata/include/metadata/IParser.hpp | 21 +- src/libs/metadata/test/CMakeLists.txt | 18 + src/libs/metadata/test/Metadata.cpp | 27 ++ src/libs/metadata/test/Utils.cpp | 74 ++++ src/libs/scanner/impl/Scanner.cpp | 8 +- src/test/database/CMakeLists.txt | 1 + src/test/database/DatabaseTest.cpp | 278 -------------- src/test/database/Release.cpp | 354 ++++++++++++++++++ src/test/database/Track.cpp | 25 ++ src/tools/metadata/LmsMetadata.cpp | 8 +- 21 files changed, 701 insertions(+), 349 deletions(-) create mode 100644 src/libs/metadata/impl/Utils.cpp create mode 100644 src/libs/metadata/impl/Utils.hpp create mode 100644 src/libs/metadata/test/CMakeLists.txt create mode 100644 src/libs/metadata/test/Metadata.cpp create mode 100644 src/libs/metadata/test/Utils.cpp create mode 100644 src/test/database/Release.cpp diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 5c620561..a519f4c2 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -419,7 +419,7 @@ Artist::getReleases(const std::vector& clusterIds) const if (!clusterIds.empty()) oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size(); - oss << " ORDER BY t.year DESC, r.name COLLATE NOCASE"; + oss << " ORDER BY t.date DESC, r.name COLLATE NOCASE"; auto query {session()->query>(oss.str())}; @@ -447,7 +447,7 @@ Artist::getTracks(std::optional linkType) const auto query {session()->query>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id") .where("a.id = ?").bind(getId()) - .orderBy("t.year DESC,t.release_id,t.disc_number,t.track_number")}; + .orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number")}; if (linkType) query.where("t_a_l.type = ?").bind(*linkType); diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index a782b06a..a42d7c91 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -249,9 +249,9 @@ Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional> ("SELECT DISTINCT r from release r INNER JOIN track t ON r.id = t.release_id") - .where("t.year >= ?").bind(yearFrom) - .where("t.year <= ?").bind(yearTo) - .orderBy("t.year, r.name COLLATE NOCASE") + .where("t.date >= ?").bind(Wt::WDate {yearFrom, 1, 1}) + .where("t.date <= ?").bind(Wt::WDate {yearTo, 12, 31}) + .orderBy("t.date, r.name COLLATE NOCASE") .offset(range ? static_cast(range->offset) : -1) .limit(range ? static_cast(range->limit) : -1) .resultList()}; @@ -383,19 +383,20 @@ Release::getReleaseYear(bool original) const { assert(session()); - const char* field {original ? "original_year" : "year"}; + const char* field {original ? "original_date" : "date"}; - Wt::Dbo::collection dates = session()->query( + auto dates {session()->query( std::string {"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id") .where("r.id = ?") .groupBy(field) - .bind(getId()); + .bind(getId()) + .resultList()}; // various dates => no date if (dates.empty() || dates.size() > 1) return std::nullopt; - auto date {dates.front()}; + auto date {dates.front().year()}; if (date > 0) return date; diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 5141f6d6..56ec77d1 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -43,7 +43,7 @@ namespace Database { using Version = std::size_t; - static constexpr Version LMS_DATABASE_VERSION {30}; + static constexpr Version LMS_DATABASE_VERSION {31}; class VersionInfo { @@ -329,6 +329,37 @@ CREATE TABLE "user_backup" ( // Just increment the scan version of the settings to make the next scheduled scan rescan everything ScanSettings::get(*this).modify()->incScanVersion(); } + else if (version == 30) + { + // drop "year" and "original_year" (rescan needed to convert them into dates) + _session.execute(R"( +CREATE TABLE "track_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "scan_version" integer not null, + "track_number" integer not null, + "disc_number" integer not null, + "name" text not null, + "duration" integer, + "date" integer text, + "original_date" integer text, + "file_path" text not null, + "file_last_write" text, + "file_added" text, + "has_cover" boolean not null, + "mbid" text not null, + "copyright" text not null, + "copyright_url" text not null, + "release_id" bigint, total_disc INTEGER NOT NULL DEFAULT(0), total_track INTEGER NOT NULL DEFAULT(0), track_replay_gain REAL, release_replay_gain REAL, disc_subtitle TEXT NOT NULL DEFAULT '', recording_mbid TEXT, + constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred +))"); + _session.execute("INSERT INTO track_backup SELECT id, version, scan_version, track_number, disc_number, name, duration, \"1900-01-01\", \"1900-01-01\", file_path, file_last_write, file_added, has_cover, mbid, copyright, copyright_url, release_id, total_disc, total_track, track_replay_gain, release_replay_gain, disc_subtitle, recording_mbid FROM track"); + _session.execute("DROP TABLE track"); + _session.execute("ALTER TABLE track_backup RENAME TO track"); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + ScanSettings::get(*this).modify()->incScanVersion(); + } else { LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; @@ -442,8 +473,8 @@ Session::prepareTables() _session.execute("CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)"); - _session.execute("CREATE INDEX IF NOT EXISTS track_year_idx ON track(year)"); - _session.execute("CREATE INDEX IF NOT EXISTS track_original_year_idx ON track(original_year)"); + _session.execute("CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)"); + _session.execute("CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)"); _session.execute("CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS tracklist_user_idx ON tracklist(user_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)"); diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 91bdfd86..e8f3436b 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -483,13 +483,13 @@ Track::getTotalDisc() const std::optional Track::getYear() const { - return (_year > 0) ? std::make_optional(_year) : std::nullopt; + return (_date.isValid() ? std::make_optional(_date.year()) : std::nullopt); } std::optional Track::getOriginalYear() const { - return (_originalYear > 0) ? std::make_optional(_originalYear) : std::nullopt; + return (_originalDate.isValid() ? std::make_optional(_originalDate.year()) : std::nullopt); } std::optional diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index d39b1469..80c16eeb 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -81,9 +81,9 @@ class Release : public Object static pointer create(Session& session, const std::string& name, const std::optional& MBID = {}); // Utility functions - std::optional getReleaseYear(bool originalDate = false) const; // 0 if unknown or various - std::optional getCopyright() const; - std::optional getCopyrightURL() const; + std::optional getReleaseYear(bool originalDate = false) const; + std::optional getCopyright() const; + std::optional getCopyrightURL() const; // Accessors const std::string& getName() const { return _name; } diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index edfdbecc..1e193405 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -102,8 +102,8 @@ class Track : public Object void setDuration(std::chrono::milliseconds duration) { _duration = duration; } void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } void setAddedTime(Wt::WDateTime time) { _fileAdded = time; } - void setYear(int year) { _year = year; } - void setOriginalYear(int year) { _originalYear = year; } + void setDate(const Wt::WDate& date) { _date = date; } + void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } void setHasCover(bool hasCover) { _hasCover = hasCover; } void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } @@ -118,26 +118,26 @@ class Track : public Object void setFeatures(const ObjectPtr& features); std::size_t getScanVersion() const { return _scanVersion; } - std::optional getTrackNumber() const; - std::optional getTotalTrack() const; - std::optional getDiscNumber() const; - const std::string& getDiscSubtitle() const { return _discSubtitle; } - std::optional getTotalDisc() const; + std::optional getTrackNumber() const; + std::optional getTotalTrack() const; + std::optional getDiscNumber() const; + const std::string& getDiscSubtitle() const { return _discSubtitle; } + std::optional getTotalDisc() const; std::string getName() const { return _name; } - std::filesystem::path getPath() const { return _filePath; } - std::chrono::milliseconds getDuration() const { return _duration; } - const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } + std::filesystem::path getPath() const { return _filePath; } + std::chrono::milliseconds getDuration() const { return _duration; } + const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } std::optional getYear() const; std::optional getOriginalYear() const; Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; } Wt::WDateTime getAddedTime() const { return _fileAdded; } - bool hasCover() const { return _hasCover; } - std::optional getTrackMBID() const { return UUID::fromString(_trackMBID); } - std::optional getRecordingMBID() const { return UUID::fromString(_recordingMBID); } - std::optional getCopyright() const; - std::optional getCopyrightURL() const; - std::optional getTrackReplayGain() const { return _trackReplayGain; } - std::optional getReleaseReplayGain() const { return _releaseReplayGain; } + bool hasCover() const { return _hasCover; } + std::optional getTrackMBID() const { return UUID::fromString(_trackMBID); } + std::optional getRecordingMBID() const { return UUID::fromString(_recordingMBID); } + std::optional getCopyright() const; + std::optional getCopyrightURL() const; + std::optional getTrackReplayGain() const { return _trackReplayGain; } + std::optional getReleaseReplayGain() const { return _releaseReplayGain; } // no artistLinkTypes means get all std::vector> getArtists(EnumSet artistLinkTypes) const; @@ -160,10 +160,10 @@ class Track : public Object Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); Wt::Dbo::field(a, _totalTrack, "total_track"); Wt::Dbo::field(a, _totalDisc, "total_disc"); - Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _duration, "duration"); - Wt::Dbo::field(a, _year, "year"); - Wt::Dbo::field(a, _originalYear, "original_year"); + Wt::Dbo::field(a, _date, "date"); + Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _filePath, "file_path"); Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); Wt::Dbo::field(a, _fileAdded, "file_added"); @@ -198,8 +198,8 @@ class Track : public Object std::string _artistName; std::string _releaseName; std::chrono::duration _duration {}; - int _year {}; - int _originalYear {}; + Wt::WDate _date; + Wt::WDate _originalDate; std::string _filePath; Wt::WDateTime _fileLastWrite; Wt::WDateTime _fileAdded; diff --git a/src/libs/metadata/CMakeLists.txt b/src/libs/metadata/CMakeLists.txt index 40b56fc7..8ba7b93d 100644 --- a/src/libs/metadata/CMakeLists.txt +++ b/src/libs/metadata/CMakeLists.txt @@ -1,7 +1,10 @@ +add_subdirectory(test) + add_library(lmsmetadata SHARED impl/AvFormatParser.cpp impl/TagLibParser.cpp + impl/Utils.cpp ) target_include_directories(lmsmetadata INTERFACE diff --git a/src/libs/metadata/impl/AvFormatParser.cpp b/src/libs/metadata/impl/AvFormatParser.cpp index e35bdd99..9c588a2a 100644 --- a/src/libs/metadata/impl/AvFormatParser.cpp +++ b/src/libs/metadata/impl/AvFormatParser.cpp @@ -25,7 +25,7 @@ #include "av/IAudioFile.hpp" #include "utils/Logger.hpp" #include "utils/String.hpp" - +#include "Utils.hpp" namespace MetaData { @@ -186,12 +186,12 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) || tag == "YEAR" || tag == "WM/Year") { - track.year = StringUtils::readAs(value); + track.date = Utils::parseDate(value); } else if (tag == "TDOR" // Original release time (ID3v2 2.4) || tag == "TORY") // Original release year { - track.originalYear = StringUtils::readAs(value); + track.originalDate = Utils::parseDate(value); } else if (tag == "ACOUSTID ID") { diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index e383da71..456747cd 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -35,7 +35,7 @@ #include "utils/Logger.hpp" #include "utils/String.hpp" - +#include "Utils.hpp" namespace MetaData { @@ -214,18 +214,26 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str } } else if (tag == "DATE") - track.year = StringUtils::readAs(value); - else if (tag == "ORIGINALDATE" && !track.originalYear) { - // Lower priority than ORIGINALYEAR - track.originalYear = StringUtils::readAs(value); + // Higher priority than YEAR + if (const Wt::WDate date {Utils::parseDate(value)}; date.isValid()) + track.date = date; } - else if (tag == "ORIGINALYEAR") + else if (tag == "YEAR" && !track.date.isValid()) { - // Higher priority than ORIGINALDATE - auto originalYear = StringUtils::readAs(value); - if (originalYear) - track.originalYear = originalYear; + // lower priority than DATE + track.date = Utils::parseDate(value); + } + else if (tag == "ORIGINALDATE") + { + // Higher priority than ORIGINALYEAR + if (const Wt::WDate date {Utils::parseDate(value)}; date.isValid()) + track.originalDate = date; + } + else if (tag == "ORIGINALYEAR" && !track.originalDate.isValid()) + { + // Lower priority than ORIGINALDATE + track.originalDate = Utils::parseDate(value); } else if (tag == "METADATA_BLOCK_PICTURE") track.hasCover = true; @@ -260,7 +268,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) { TagLib::FileRef f {p.string().c_str(), true, // read audio properties - TagLib::AudioProperties::Fast}; + TagLib::AudioProperties::Fast}; // TODO parametrize this if (f.isNull()) { diff --git a/src/libs/metadata/impl/Utils.cpp b/src/libs/metadata/impl/Utils.cpp new file mode 100644 index 00000000..090db19c --- /dev/null +++ b/src/libs/metadata/impl/Utils.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include "Utils.hpp" +#include +#include +#include +#include + +namespace MetaData::Utils +{ + Wt::WDate + parseDate(const std::string& dateStr) + { + static constexpr const char* formats[] + { + "%Y-%m-%d", + "%Y/%m/%d", + }; + + for (const char* format : formats) + { + std::tm tm = {}; + std::stringstream ss {dateStr}; + ss >> std::get_time(&tm, format); + if (ss.fail()) + continue; + + const Wt::WDate res + { + tm.tm_year + 1900, // years since 1900 + tm.tm_mon + 1, // months since January – [0, 11] + tm.tm_mday ? tm.tm_mday : 1 // day of the month – [1, 31] + }; + if (!res.isValid()) + continue; + + return res; + } + + return {}; + } +} + diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp new file mode 100644 index 00000000..30025627 --- /dev/null +++ b/src/libs/metadata/impl/Utils.hpp @@ -0,0 +1,27 @@ + +/* + * Copyright (C) 2016 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include + +namespace MetaData::Utils +{ + Wt::WDate parseDate(const std::string& dateStr); +} + diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index 61244066..18db7f1d 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -27,6 +27,7 @@ #include #include +#include #include "utils/UUID.hpp" namespace MetaData @@ -56,21 +57,21 @@ namespace MetaData struct Track { - std::vector artists; - std::vector albumArtists; - std::string title; - std::optional trackMBID; - std::optional recordingMBID; - std::optional album; - Clusters clusters; + std::vector artists; + std::vector albumArtists; + std::string title; + std::optional trackMBID; + std::optional recordingMBID; + std::optional album; + Clusters clusters; std::chrono::milliseconds duration; std::optional trackNumber; std::optional totalTrack; std::optional discNumber; std::optional totalDisc; - std::optional year; - std::optional originalYear; - bool hasCover {}; + Wt::WDate date; + Wt::WDate originalDate; + bool hasCover {}; std::vector audioStreams; std::optional acoustID; std::string copyright; diff --git a/src/libs/metadata/test/CMakeLists.txt b/src/libs/metadata/test/CMakeLists.txt new file mode 100644 index 00000000..acd4aec2 --- /dev/null +++ b/src/libs/metadata/test/CMakeLists.txt @@ -0,0 +1,18 @@ +include(GoogleTest) + +add_executable(test-metadata + Metadata.cpp + Utils.cpp + ) + +target_include_directories(test-metadata PRIVATE + ../impl + ) + +target_link_libraries(test-metadata PRIVATE + lmsmetadata + GTest::GTest + ) + +gtest_discover_tests(test-metadata) + diff --git a/src/libs/metadata/test/Metadata.cpp b/src/libs/metadata/test/Metadata.cpp new file mode 100644 index 00000000..88325e8b --- /dev/null +++ b/src/libs/metadata/test/Metadata.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + diff --git a/src/libs/metadata/test/Utils.cpp b/src/libs/metadata/test/Utils.cpp new file mode 100644 index 00000000..97ff5b17 --- /dev/null +++ b/src/libs/metadata/test/Utils.cpp @@ -0,0 +1,74 @@ + +/* + * Copyright (C) 2019 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include + +#include "Utils.hpp" + +TEST(MetaData, parseDate) +{ + using namespace MetaData::Utils; + + struct TestCase + { + std::string str; + Wt::WDate result; + } testCases [] + { + { "1995-05-09", Wt::WDate {1995, 5, 9} }, + { "1995-01-01", Wt::WDate {1995, 1, 1} }, + { "1900-01-01", Wt::WDate {1900, 1, 1} }, + { "1899-01-01", Wt::WDate {1899, 1, 1} }, + { "1899-12-31", Wt::WDate {1899, 12, 31} }, + { "1899-11-30", Wt::WDate {1899, 11, 30} }, + { "1500-11-30", Wt::WDate {1500, 11, 30} }, + { "1000-11-30", Wt::WDate {1000, 11, 30} }, + { "1899-11-31", Wt::WDate {} }, // invalid day + { "1899-13-01", Wt::WDate {} }, // invalid month + { "1899-11", Wt::WDate {1899, 11, 1} }, // missing day + { "1899", Wt::WDate {1899, 1, 1} }, // missing month and days + { "1600", Wt::WDate {1600, 1, 1} }, // missing month and days + { "1995/05/09", Wt::WDate {1995, 5, 9} }, + { "1995/01/01", Wt::WDate {1995, 1, 1} }, + { "1900/01/01", Wt::WDate {1900, 1, 1} }, + { "1899/01/01", Wt::WDate {1899, 1, 1} }, + { "1899/12/31", Wt::WDate {1899, 12, 31} }, + { "1899/11/30", Wt::WDate {1899, 11, 30} }, + { "1500/11/30", Wt::WDate {1500, 11, 30} }, + { "1000/11/30", Wt::WDate {1000, 11, 30} }, + { "1899/11/31", Wt::WDate {} }, // invalid day + { "1899/13/01", Wt::WDate {} }, // invalid month + { "1899/11", Wt::WDate {1899, 11, 1} }, // missing day + { "1899", Wt::WDate {1899, 1, 1} }, // missing month and days + { "1600", Wt::WDate {1600, 1, 1} }, // missing month and days + { "1995/05-09", Wt::WDate {} }, // invalid mixup separators + { "1995-05/09", Wt::WDate {} }, // invalid mixup separators + }; + + + for (const TestCase& testCase : testCases) + { + const Wt::WDate parsed {parseDate(testCase.str)}; + + EXPECT_EQ(parsed.year(), testCase.result.year()) << " str was '" << testCase.str << "'"; + EXPECT_EQ(parsed.month(), testCase.result.month()) << " str was '" << testCase.str << "'"; + EXPECT_EQ(parsed.day(), testCase.result.day()) << " str was '" << testCase.str << "'"; + } +} diff --git a/src/libs/scanner/impl/Scanner.cpp b/src/libs/scanner/impl/Scanner.cpp index 6a8444be..bbc10fda 100644 --- a/src/libs/scanner/impl/Scanner.cpp +++ b/src/libs/scanner/impl/Scanner.cpp @@ -817,12 +817,12 @@ Scanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanSt track.modify()->setTotalDisc(trackInfo->totalDisc); if (!trackInfo->discSubtitle.empty()) track.modify()->setDiscSubtitle(trackInfo->discSubtitle); - track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0); - track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0); + track.modify()->setDate(trackInfo->date); + track.modify()->setOriginalDate(trackInfo->originalDate); // If a file has an OriginalYear but no Year, set it to ease filtering - if (!trackInfo->year && trackInfo->originalYear) - track.modify()->setYear(*trackInfo->originalYear); + if (!trackInfo->date.isValid() && trackInfo->originalDate.isValid()) + track.modify()->setDate(trackInfo->originalDate); track.modify()->setRecordingMBID(trackInfo->recordingMBID); track.modify()->setTrackMBID(trackInfo->trackMBID); diff --git a/src/test/database/CMakeLists.txt b/src/test/database/CMakeLists.txt index 30c59057..0ad6756d 100644 --- a/src/test/database/CMakeLists.txt +++ b/src/test/database/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(test-database Artist.cpp Cluster.cpp DatabaseTest.cpp + Release.cpp Track.cpp ) diff --git a/src/test/database/DatabaseTest.cpp b/src/test/database/DatabaseTest.cpp index c62df827..1bca6150 100644 --- a/src/test/database/DatabaseTest.cpp +++ b/src/test/database/DatabaseTest.cpp @@ -23,284 +23,6 @@ using namespace Database; -TEST_F(DatabaseFixture, SingleRelease) -{ - ScopedRelease release {session, "MyRelease"}; - - { - auto transaction {session.createSharedTransaction()}; - - auto releases {Release::getAllOrphans(session)}; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release.getId()); - - releases = Release::getAll(session); - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release.getId()); - EXPECT_EQ(release->getDuration(), std::chrono::seconds {0}); - } -} - -TEST_F(DatabaseFixture, SingleTrackSingleRelease) -{ - ScopedRelease release {session, "MyRelease"}; - - { - ScopedTrack track {session, "MyTrack"}; - { - auto transaction {session.createUniqueTransaction()}; - - track.get().modify()->setRelease(release.get()); - track.get().modify()->setName("MyTrackName"); - release.get().modify()->setName("MyReleaseName"); - } - - { - auto transaction {session.createSharedTransaction()}; - EXPECT_TRUE(Release::getAllOrphans(session).empty()); - - EXPECT_EQ(release->getTracksCount(), 1); - ASSERT_EQ(release->getTracks().size(), 1); - EXPECT_EQ(release->getTracks().front()->getId(), track.getId()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - ASSERT_TRUE(track->getRelease()); - EXPECT_EQ(track->getRelease()->getId(), release.getId()); - } - - { - auto transaction {session.createUniqueTransaction()}; - auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseName")}; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track.getId()); - } - { - auto transaction {session.createUniqueTransaction()}; - auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseFoo")}; - EXPECT_EQ(tracks.size(), 0); - } - { - auto transaction {session.createUniqueTransaction()}; - auto tracks {Track::getByNameAndReleaseName(session, "MyTrackFoo", "MyReleaseName")}; - EXPECT_EQ(tracks.size(), 0); - } - } - - { - auto transaction {session.createUniqueTransaction()}; - - EXPECT_TRUE(release->getTracks().empty()); - - auto releases {Release::getAllOrphans(session)}; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release.getId()); - } -} - -TEST_F(DatabaseFixture, MulitpleReleaseSearchByName) -{ - ScopedRelease release1 {session, "MyRelease"}; - ScopedRelease release2 {session, "MyRelease%"}; - ScopedRelease release3 {session, "%MyRelease"}; - ScopedRelease release4 {session, "MyRelease%Foo"}; - ScopedRelease release5 {session, "Foo%MyRelease"}; - ScopedRelease release6 {session, "_yRelease"}; - - // filters does not work on orphans - ScopedTrack track1 {session, "MyTrack"}; - ScopedTrack track2 {session, "MyTrack"}; - ScopedTrack track3 {session, "MyTrack"}; - ScopedTrack track4 {session, "MyTrack"}; - ScopedTrack track5 {session, "MyTrack"}; - ScopedTrack track6 {session, "MyTrack"}; - - { - auto transaction {session.createUniqueTransaction()}; - - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - track3.get().modify()->setRelease(release3.get()); - track4.get().modify()->setRelease(release4.get()); - track5.get().modify()->setRelease(release5.get()); - track6.get().modify()->setRelease(release6.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool more; - { - const auto releases {Release::getByFilter(session, {}, {"Release"}, std::nullopt, more)}; - EXPECT_EQ(releases.size(), 6); - } - - { - const auto releases {Release::getByFilter(session, {}, {"MyRelease"}, std::nullopt, more)}; - EXPECT_EQ(releases.size(), 5); - EXPECT_TRUE(std::none_of(std::cbegin(releases), std::cend(releases), [&](const Release::pointer& release) { return release->getId() == release6.getId(); })); - } - { - const auto releases {Release::getByFilter(session, {}, {"MyRelease%"}, std::nullopt, more)}; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release2.getId()); - EXPECT_EQ(releases[1]->getId(), release4.getId()); - } - { - const auto releases {Release::getByFilter(session, {}, {"%MyRelease"}, std::nullopt, more)}; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release3.getId()); - EXPECT_EQ(releases[1]->getId(), release5.getId()); - } - { - const auto releases {Release::getByFilter(session, {}, {"Foo%MyRelease"}, std::nullopt, more)}; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases[0]->getId(), release5.getId()); - } - { - const auto releases {Release::getByFilter(session, {}, {"MyRelease%Foo"}, std::nullopt, more)}; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases[0]->getId(), release4.getId()); - } - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack) -{ - ScopedRelease release1 {session, "MyRelease"}; - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_FALSE(release1->getTotalTrack()); - EXPECT_FALSE(release1->getTotalDisc()); - } - - ScopedTrack track1 {session, "MyTrack"}; - { - auto transaction {session.createUniqueTransaction()}; - - track1.get().modify()->setRelease(release1.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_FALSE(release1->getTotalTrack()); - EXPECT_FALSE(release1->getTotalDisc()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - track1.get().modify()->setTotalTrack(36); - track1.get().modify()->setTotalDisc(6); - } - - { - auto transaction {session.createSharedTransaction()}; - - ASSERT_TRUE(release1->getTotalTrack()); - EXPECT_EQ(*release1->getTotalTrack(), 36); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release1->getTotalDisc(), 6); - } - - ScopedTrack track2 {session, "MyTrack2"}; - { - auto transaction {session.createUniqueTransaction()}; - - track2.get().modify()->setRelease(release1.get()); - track2.get().modify()->setTotalTrack(37); - track2.get().modify()->setTotalDisc(67); - } - - { - auto transaction {session.createSharedTransaction()}; - - ASSERT_TRUE(release1->getTotalTrack()); - EXPECT_EQ(*release1->getTotalTrack(), 37); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release1->getTotalDisc(), 67); - } - - ScopedRelease release2 {session, "MyRelease2"}; - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_FALSE(release2->getTotalTrack()); - EXPECT_FALSE(release2->getTotalDisc()); - } - - ScopedTrack track3 {session, "MyTrack3"}; - { - auto transaction {session.createUniqueTransaction()}; - - track3.get().modify()->setRelease(release2.get()); - track3.get().modify()->setTotalTrack(7); - track3.get().modify()->setTotalDisc(5); - } - { - auto transaction {session.createSharedTransaction()}; - - ASSERT_TRUE(release1->getTotalTrack()); - EXPECT_EQ(*release1->getTotalTrack(), 37); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release1->getTotalDisc(), 67); - ASSERT_TRUE(release2->getTotalTrack()); - EXPECT_EQ(*release2->getTotalTrack(), 7); - ASSERT_TRUE(release2->getTotalDisc()); - EXPECT_EQ(*release2->getTotalDisc(), 5); - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) -{ - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; - - ScopedTrack track1A {session, "MyTrack1A"}; - ScopedTrack track1B {session, "MyTrack1B"}; - ScopedTrack track2A {session, "MyTrack2A"}; - ScopedTrack track2B {session, "MyTrack2B"}; - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_FALSE(release1->getFirstTrack()); - EXPECT_FALSE(release2->getFirstTrack()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - track1A.get().modify()->setRelease(release1.get()); - track1B.get().modify()->setRelease(release1.get()); - track2A.get().modify()->setRelease(release2.get()); - track2B.get().modify()->setRelease(release2.get()); - - track1A.get().modify()->setTrackNumber(1); - track1B.get().modify()->setTrackNumber(2); - - track2A.get().modify()->setDiscNumber(2); - track2A.get().modify()->setTrackNumber(1); - track2B.get().modify()->setTrackNumber(2); - track2B.get().modify()->setDiscNumber(1); - } - - { - auto transaction {session.createSharedTransaction()}; - - EXPECT_TRUE(release1->getFirstTrack()); - EXPECT_TRUE(release2->getFirstTrack()); - - EXPECT_EQ(release1->getFirstTrack()->getId(), track1A.getId()); - EXPECT_EQ(release2->getFirstTrack()->getId(), track2B.getId()); - } -} - TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) { constexpr std::size_t nbTracks {10}; diff --git a/src/test/database/Release.cpp b/src/test/database/Release.cpp new file mode 100644 index 00000000..a65db032 --- /dev/null +++ b/src/test/database/Release.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include "Common.hpp" + +using namespace Database; + +TEST_F(DatabaseFixture, SingleRelease) +{ + ScopedRelease release {session, "MyRelease"}; + + { + auto transaction {session.createSharedTransaction()}; + + auto releases {Release::getAllOrphans(session)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release.getId()); + + releases = Release::getAll(session); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release.getId()); + EXPECT_EQ(release->getDuration(), std::chrono::seconds {0}); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleRelease) +{ + ScopedRelease release {session, "MyRelease"}; + + { + ScopedTrack track {session, "MyTrack"}; + { + auto transaction {session.createUniqueTransaction()}; + + track.get().modify()->setRelease(release.get()); + track.get().modify()->setName("MyTrackName"); + release.get().modify()->setName("MyReleaseName"); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + + EXPECT_EQ(release->getTracksCount(), 1); + ASSERT_EQ(release->getTracks().size(), 1); + EXPECT_EQ(release->getTracks().front()->getId(), track.getId()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + ASSERT_TRUE(track->getRelease()); + EXPECT_EQ(track->getRelease()->getId(), release.getId()); + } + + { + auto transaction {session.createUniqueTransaction()}; + auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseName")}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front()->getId(), track.getId()); + } + { + auto transaction {session.createUniqueTransaction()}; + auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseFoo")}; + EXPECT_EQ(tracks.size(), 0); + } + { + auto transaction {session.createUniqueTransaction()}; + auto tracks {Track::getByNameAndReleaseName(session, "MyTrackFoo", "MyReleaseName")}; + EXPECT_EQ(tracks.size(), 0); + } + } + + { + auto transaction {session.createUniqueTransaction()}; + + EXPECT_TRUE(release->getTracks().empty()); + + auto releases {Release::getAllOrphans(session)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release.getId()); + } +} + +TEST_F(DatabaseFixture, MulitpleReleaseSearchByName) +{ + ScopedRelease release1 {session, "MyRelease"}; + ScopedRelease release2 {session, "MyRelease%"}; + ScopedRelease release3 {session, "%MyRelease"}; + ScopedRelease release4 {session, "MyRelease%Foo"}; + ScopedRelease release5 {session, "Foo%MyRelease"}; + ScopedRelease release6 {session, "_yRelease"}; + + // filters does not work on orphans + ScopedTrack track1 {session, "MyTrack"}; + ScopedTrack track2 {session, "MyTrack"}; + ScopedTrack track3 {session, "MyTrack"}; + ScopedTrack track4 {session, "MyTrack"}; + ScopedTrack track5 {session, "MyTrack"}; + ScopedTrack track6 {session, "MyTrack"}; + + { + auto transaction {session.createUniqueTransaction()}; + + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + track3.get().modify()->setRelease(release3.get()); + track4.get().modify()->setRelease(release4.get()); + track5.get().modify()->setRelease(release5.get()); + track6.get().modify()->setRelease(release6.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool more; + { + const auto releases {Release::getByFilter(session, {}, {"Release"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 6); + } + + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 5); + EXPECT_TRUE(std::none_of(std::cbegin(releases), std::cend(releases), [&](const Release::pointer& release) { return release->getId() == release6.getId(); })); + } + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease%"}, std::nullopt, more)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0]->getId(), release2.getId()); + EXPECT_EQ(releases[1]->getId(), release4.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"%MyRelease"}, std::nullopt, more)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0]->getId(), release3.getId()); + EXPECT_EQ(releases[1]->getId(), release5.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"Foo%MyRelease"}, std::nullopt, more)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0]->getId(), release5.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease%Foo"}, std::nullopt, more)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0]->getId(), release4.getId()); + } + } +} + +TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack) +{ + ScopedRelease release1 {session, "MyRelease"}; + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_FALSE(release1->getTotalTrack()); + EXPECT_FALSE(release1->getTotalDisc()); + } + + ScopedTrack track1 {session, "MyTrack"}; + { + auto transaction {session.createUniqueTransaction()}; + + track1.get().modify()->setRelease(release1.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_FALSE(release1->getTotalTrack()); + EXPECT_FALSE(release1->getTotalDisc()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + track1.get().modify()->setTotalTrack(36); + track1.get().modify()->setTotalDisc(6); + } + + { + auto transaction {session.createSharedTransaction()}; + + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 36); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 6); + } + + ScopedTrack track2 {session, "MyTrack2"}; + { + auto transaction {session.createUniqueTransaction()}; + + track2.get().modify()->setRelease(release1.get()); + track2.get().modify()->setTotalTrack(37); + track2.get().modify()->setTotalDisc(67); + } + + { + auto transaction {session.createSharedTransaction()}; + + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 37); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 67); + } + + ScopedRelease release2 {session, "MyRelease2"}; + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_FALSE(release2->getTotalTrack()); + EXPECT_FALSE(release2->getTotalDisc()); + } + + ScopedTrack track3 {session, "MyTrack3"}; + { + auto transaction {session.createUniqueTransaction()}; + + track3.get().modify()->setRelease(release2.get()); + track3.get().modify()->setTotalTrack(7); + track3.get().modify()->setTotalDisc(5); + } + { + auto transaction {session.createSharedTransaction()}; + + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 37); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 67); + ASSERT_TRUE(release2->getTotalTrack()); + EXPECT_EQ(*release2->getTotalTrack(), 7); + ASSERT_TRUE(release2->getTotalDisc()); + EXPECT_EQ(*release2->getTotalDisc(), 5); + } +} + +TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) +{ + ScopedRelease release1 {session, "MyRelease1"}; + ScopedRelease release2 {session, "MyRelease2"}; + + ScopedTrack track1A {session, "MyTrack1A"}; + ScopedTrack track1B {session, "MyTrack1B"}; + ScopedTrack track2A {session, "MyTrack2A"}; + ScopedTrack track2B {session, "MyTrack2B"}; + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_FALSE(release1->getFirstTrack()); + EXPECT_FALSE(release2->getFirstTrack()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + track1A.get().modify()->setRelease(release1.get()); + track1B.get().modify()->setRelease(release1.get()); + track2A.get().modify()->setRelease(release2.get()); + track2B.get().modify()->setRelease(release2.get()); + + track1A.get().modify()->setTrackNumber(1); + track1B.get().modify()->setTrackNumber(2); + + track2A.get().modify()->setDiscNumber(2); + track2A.get().modify()->setTrackNumber(1); + track2B.get().modify()->setTrackNumber(2); + track2B.get().modify()->setDiscNumber(1); + } + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_TRUE(release1->getFirstTrack()); + EXPECT_TRUE(release2->getFirstTrack()); + + EXPECT_EQ(release1->getFirstTrack()->getId(), track1A.getId()); + EXPECT_EQ(release2->getFirstTrack()->getId(), track2B.getId()); + } +} + +TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate) +{ + ScopedRelease release1 {session, "MyRelease1"}; + ScopedRelease release2 {session, "MyRelease2"}; + const Wt::WDate release1Date {Wt::WDate {1994, 2, 3}}; + const Wt::WDate release1OriginalDate {Wt::WDate {1993, 4, 5}}; + + ScopedTrack track1A {session, "MyTrack1A"}; + ScopedTrack track1B {session, "MyTrack1B"}; + ScopedTrack track2A {session, "MyTrack2A"}; + ScopedTrack track2B {session, "MyTrack2B"}; + + { + auto transaction {session.createSharedTransaction()}; + + const auto releases {Release::getByYear(session, 0, 3000)}; + EXPECT_EQ(releases.size(), 0); + } + + { + auto transaction {session.createUniqueTransaction()}; + + track1A.get().modify()->setRelease(release1.get()); + track1B.get().modify()->setRelease(release1.get()); + track2A.get().modify()->setRelease(release2.get()); + track2B.get().modify()->setRelease(release2.get()); + + + track1A.get().modify()->setDate(release1Date); + track1B.get().modify()->setDate(release1Date); + track1A.get().modify()->setOriginalDate(release1OriginalDate); + track1B.get().modify()->setOriginalDate(release1OriginalDate); + + EXPECT_EQ(release1.get()->getReleaseYear(), release1Date.year()); + EXPECT_EQ(release1.get()->getReleaseYear(true), release1OriginalDate.year()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto releases {Release::getByYear(session, 1950, 2000)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release1.getId()); + + releases = Release::getByYear(session, 1994, 1994); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release1.getId()); + + releases = Release::getByYear(session, 1993, 1993); + ASSERT_EQ(releases.size(), 0); + } +} + diff --git a/src/test/database/Track.cpp b/src/test/database/Track.cpp index cb1759a9..ac919bff 100644 --- a/src/test/database/Track.cpp +++ b/src/test/database/Track.cpp @@ -87,3 +87,28 @@ TEST_F(DatabaseFixture, MultipleTracksSearchByFilter) } } +TEST_F(DatabaseFixture, SingleTrackDate) +{ + ScopedTrack track {session, "MyTrack"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_EQ(track->getYear(), std::nullopt); + EXPECT_EQ(track->getOriginalYear(), std::nullopt); + } + + { + auto transaction {session.createUniqueTransaction()}; + track.get().modify()->setDate(Wt::WDate {1995, 5, 5}); + track.get().modify()->setOriginalDate(Wt::WDate {1994, 2, 2}); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_EQ(track->getYear(), 1995); + EXPECT_EQ(track->getOriginalYear(), 1994); + } +} + + + diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index 04d92a1f..2b25401d 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -134,11 +134,11 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file) if (track->totalDisc) std::cout << "TotalDisc: " << *track->totalDisc << std::endl; - if (track->year) - std::cout << "Year: " << *track->year << std::endl; + if (track->date.isValid()) + std::cout << "Date: " << track->date.toString("yyyy-MM-dd") << std::endl; - if (track->originalYear) - std::cout << "Original year: " << *track->originalYear << std::endl; + if (track->originalDate.isValid()) + std::cout << "Original date: " << track->originalDate.toString("yyyy-MM-dd") << std::endl; std::cout << "HasCover = " << std::boolalpha << track->hasCover << std::endl;