${name}
diff --git a/approot/templates.xml b/approot/templates.xml
index 321420a5..bc95730e 100644
--- a/approot/templates.xml
+++ b/approot/templates.xml
@@ -6,7 +6,7 @@
${contents class="Lms-contents"}
- ${player class="Lms-player well well-sm"}
+ ${player class="Lms-player"}
diff --git a/docroot/css/lms-darkly.css b/docroot/css/lms-darkly.css
index 8cbb660a..904cc8c1 100644
--- a/docroot/css/lms-darkly.css
+++ b/docroot/css/lms-darkly.css
@@ -1,8 +1,9 @@
:root {
+ --body-color: #ffffff;
--hover-color: #00bc8c;
- --body-color: #cacaca;
--playing-background-color: #375a7f4f;
--slider-background-color: #5a5a5a;
+ --player-background-color: #303030;
}
.Lms-artistname.text-muted {
diff --git a/docroot/css/lms-flatly.css b/docroot/css/lms-flatly.css
index 9e9e817a..9848fd7e 100644
--- a/docroot/css/lms-flatly.css
+++ b/docroot/css/lms-flatly.css
@@ -3,6 +3,7 @@
--hover-color: #18bc9c;
--playing-background-color: #2c3e5029;
--slider-background-color: #2c3e5029;
+ --player-background-color: #ecf0f1;
}
.Lms-artistname.text-muted {
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index d2b1fbff..5d89d714 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -1,3 +1,4 @@
+/* Stylized for flaty/darkly themes */
a, a:link, a:active, a:visited {
text-decoration: none;
cursor: pointer;
@@ -51,66 +52,17 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
}
.Lms-cluster {
- display: inline;
+ display: inline-block;
cursor: pointer;
- line-height: 32px;
- margin: 4px;
+ line-height: 1.3;
+ margin-right: 4px;
+ margin-bottom: 4px;
}
.Lms-cluster:hover {
filter: brightness(130%);
}
-.Lms-cluster-type-0 {
- background-color: darkslategrey;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-1 {
- background-color: darkblue;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-2 {
- background-color: darkgrey;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-3 {
- background-color: darkorchid;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-4 {
- background-color: darkgoldenrod;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-5 {
- background-color: darkkhaki;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-6 {
- background-color: darkolivegreen;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-7 {
- background-color: darkred;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-8 {
- background-color: darkviolet;
- margin-bottom: 4px;
-}
-
-.Lms-cluster-type-9 {
- background-color: darkturquoise;
- margin-bottom: 4px;
-}
-
.Lms-control-btn:hover {
color: var(--hover-color);
}
@@ -121,24 +73,24 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
.Lms-cover-smaller {
width: 64px; /* some cover may not be square */
- border-radius: 10px;
+ border-radius: 3px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
.Lms-cover-small {
width: 96px; /* some cover may not be square */
- border-radius: 12px;
+ border-radius: 3px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
.Lms-cover-medium {
width: 128px;
- border-radius: 16px;
+ border-radius: 3px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
.Lms-cover-large {
- border-radius: 16px;
+ border-radius: 3px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
@@ -162,7 +114,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
}
.Lms-explore-release-entry-tracknumber {
- text-align: center;
+ text-align: right;
white-space: nowrap;
}
@@ -333,6 +285,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
height: 70px;
padding: 0px;
margin: 0px;
+ background-color: var(--player-background-color);
}
.Lms-player-btn {
@@ -397,7 +350,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
top: 0px;
left: 0px;
height: 8px;
- border-radius: 4px;
+ border-radius: 3px;
}
.Lms-player-seek::-moz-range-thumb {
@@ -448,7 +401,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
height: 8px;
outline: none;
cursor: pointer;
- border-radius: 4px;
+ border-radius: 3px;
border-style: solid;
border-width: thin;
background-color: var(--slider-background-color);
diff --git a/src/libs/cover/impl/CoverArtGrabber.cpp b/src/libs/cover/impl/CoverArtGrabber.cpp
index 38781cbc..8646d8bf 100644
--- a/src/libs/cover/impl/CoverArtGrabber.cpp
+++ b/src/libs/cover/impl/CoverArtGrabber.cpp
@@ -189,7 +189,7 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st
trackPath = track->getPath();
auto release {track->getRelease()};
- if (release && release->getTotalDiscNumber() > 1)
+ if (release && release->getTotalDisc() > 1)
isMultiDisc = true;
}
}
diff --git a/src/libs/database/impl/Db.cpp b/src/libs/database/impl/Db.cpp
index c3ec1d60..dd6b8d0e 100644
--- a/src/libs/database/impl/Db.cpp
+++ b/src/libs/database/impl/Db.cpp
@@ -33,9 +33,9 @@ Db::Db(const std::filesystem::path& dbPath)
LMS_LOG(DB, INFO) << "Creating connection pool on file " << dbPath.string();
std::unique_ptr
connection {std::make_unique(dbPath.string())};
+// connection->setProperty("show-queries", "true");
connection->executeSql("pragma journal_mode=WAL");
connection->executeSql("pragma synchronous=normal");
-// connection->setProperty("show-queries", "true");
auto connectionPool = std::make_unique(std::move(connection), 10);
connectionPool->setTimeout(std::chrono::seconds(10));
@@ -43,6 +43,14 @@ Db::Db(const std::filesystem::path& dbPath)
_connectionPool = std::move(connectionPool);
}
+void
+Db::executeSql(const std::string& sql)
+{
+ auto connection {_connectionPool->getConnection()};
+ connection->executeSql(sql);
+ _connectionPool->returnConnection(std::move(connection));
+}
+
} // namespace Database
diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp
index 7fdd639a..a8535f49 100644
--- a/src/libs/database/impl/Release.cpp
+++ b/src/libs/database/impl/Release.cpp
@@ -268,15 +268,29 @@ Release::getAllIdsWithClusters(Session& session, std::optional limi
std::optional
-Release::getTotalTrackNumber(void) const
+Release::getTotalTrack(void) const
{
- return (_totalTrackNumber > 0) ? std::make_optional(_totalTrackNumber) : std::nullopt;
+ assert(session());
+ assert(IdIsValid(self()->id()));
+
+ int res = session()->query("SELECT COALESCE(MAX(total_track),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
+ .where("r.id = ?")
+ .bind(this->id());
+
+ return (res > 0) ? std::make_optional(res) : std::nullopt;
}
std::optional
-Release::getTotalDiscNumber(void) const
+Release::getTotalDisc(void) const
{
- return (_totalDiscNumber > 0) ? std::make_optional(_totalDiscNumber) : std::nullopt;
+ assert(session());
+ assert(IdIsValid(self()->id()));
+
+ int res = session()->query("SELECT COALESCE(MAX(total_disc),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
+ .where("r.id = ?")
+ .bind(this->id());
+
+ return (res > 0) ? std::make_optional(res) : std::nullopt;
}
std::optional
@@ -287,7 +301,7 @@ Release::getReleaseYear(bool original) const
const std::string field {original ? "original_year" : "year"};
Wt::Dbo::collection dates = session()->query(
- std::string{"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
+ std::string {"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy(field)
.bind(this->id());
diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp
index 6270c7df..70ebabea 100644
--- a/src/libs/database/impl/Session.cpp
+++ b/src/libs/database/impl/Session.cpp
@@ -40,7 +40,7 @@
namespace Database {
-#define LMS_DATABASE_VERSION 16
+#define LMS_DATABASE_VERSION 18
using Version = std::size_t;
@@ -83,26 +83,31 @@ class VersionInfo
void
Session::doDatabaseMigrationIfNeeded()
{
- auto uniqueTransaction {createUniqueTransaction()};
-
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
- Version version;
- try
- {
- version = VersionInfo::getOrCreate(*this)->getVersion();
- LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
- if (version == LMS_DATABASE_VERSION)
- return;
- }
- catch (std::exception& e)
- {
- LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
- throw LmsException {outdatedMsg};
- }
+ Db::ScopedNoForeignKeys noPragmaKeys {_db};
- while (version < LMS_DATABASE_VERSION)
+ while (1)
{
+ auto uniqueTransaction {createUniqueTransaction()};
+
+ Version version;
+ try
+ {
+ version = VersionInfo::getOrCreate(*this)->getVersion();
+ LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
+ if (version == LMS_DATABASE_VERSION)
+ {
+ LMS_LOG(DB, DEBUG) << "Lms database version " << LMS_DATABASE_VERSION << ": up to date!";
+ return;
+ }
+ }
+ catch (std::exception& e)
+ {
+ LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
+ throw LmsException {outdatedMsg};
+ }
+
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
if (version == 5)
@@ -173,15 +178,41 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
{
_session.execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(User::defaultUITheme)) + ")");
}
+ else if (version == 16)
+ {
+ _session.execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)");
+ _session.execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)");
+
+ // Just increment the scan version of the settings to make the next scheduled scan rescan everything
+ ScanSettings::get(*this).modify()->incScanVersion();
+ }
+ else if (version == 17)
+ {
+ // Drop colums total_disc/total_track from release
+ _session.execute(R"(
+CREATE TABLE "release_backup" (
+ "id" integer primary key autoincrement,
+ "version" integer not null,
+ "name" text not null,
+ "mbid" text not null
+))");
+ _session.execute("INSERT INTO release_backup SELECT id,version,name,mbid FROM release");
+ _session.execute("DROP TABLE release;");
+ _session.execute("ALTER TABLE release_backup RENAME TO release");
+ _session.execute("CREATE INDEX release_name_idx ON release(name)");
+ _session.execute("CREATE INDEX release_name_nocase_idx ON release(name COLLATE NOCASE)");
+ _session.execute("CREATE INDEX release_mbid_idx ON release(mbid)");
+
+ // 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";
throw LmsException { LMS_DATABASE_VERSION > version ? outdatedMsg : "Server binary outdated, please upgrade it to handle this database"};
}
- ++version;
-
- VersionInfo::get(*this).modify()->setVersion(LMS_DATABASE_VERSION);
+ VersionInfo::get(*this).modify()->setVersion(++version);
}
}
diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp
index 2acff171..e62f875f 100644
--- a/src/libs/database/impl/Track.cpp
+++ b/src/libs/database/impl/Track.cpp
@@ -177,7 +177,7 @@ Track::getAllIdsWithClusters(Session& session, std::optional limit)
}
std::vector
-Track::getClusters(void) const
+Track::getClusters() const
{
std::vector< Cluster::pointer > clusters;
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
@@ -185,7 +185,7 @@ Track::getClusters(void) const
}
std::vector
-Track::getClusterIds(void) const
+Track::getClusterIds() const
{
assert(self());
assert(IdIsValid(self()->id()));
@@ -355,17 +355,29 @@ Track::setFeatures(const Wt::Dbo::ptr& features)
}
std::optional
-Track::getTrackNumber(void) const
+Track::getTrackNumber() const
{
return (_trackNumber > 0) ? std::make_optional(_trackNumber) : std::nullopt;
}
std::optional
-Track::getDiscNumber(void) const
+Track::getTotalTrack() const
+{
+ return (_totalTrack > 0) ? std::make_optional(_totalTrack) : std::nullopt;
+}
+
+std::optional
+Track::getDiscNumber() const
{
return (_discNumber > 0) ? std::make_optional(_discNumber) : std::nullopt;
}
+std::optional
+Track::getTotalDisc() const
+{
+ return (_totalDisc > 0) ? std::make_optional(_totalDisc) : std::nullopt;
+}
+
std::optional
Track::getYear() const
{
diff --git a/src/libs/database/include/database/Db.hpp b/src/libs/database/include/database/Db.hpp
index fa07af26..17470c19 100644
--- a/src/libs/database/include/database/Db.hpp
+++ b/src/libs/database/include/database/Db.hpp
@@ -39,6 +39,25 @@ class Db
std::shared_mutex& getMutex() { return _sharedMutex; }
Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; }
+ class ScopedNoForeignKeys
+ {
+ public:
+ ScopedNoForeignKeys(Db& db) : _db {db}
+ {
+ _db.executeSql("PRAGMA foreign_keys=OFF");
+ }
+ ~ScopedNoForeignKeys()
+ {
+ _db.executeSql("PRAGMA foreign_keys=ON");
+ }
+
+ private:
+ Db& _db;
+
+ };
+
+ void executeSql(const std::string& sql);
+
std::shared_mutex _sharedMutex;
std::unique_ptr _connectionPool;
};
diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp
index 6360b90e..29b93c58 100644
--- a/src/libs/database/include/database/Release.hpp
+++ b/src/libs/database/include/database/Release.hpp
@@ -84,15 +84,11 @@ class Release : public Wt::Dbo::Dbo
std::optional getCopyright() const;
std::optional getCopyrightURL() const;
- // Modifiers
- void setTotalDiscNumber(std::size_t num) { _totalDiscNumber = static_cast(num); }
- void setTotalTrackNumber(std::size_t num) { _totalTrackNumber = static_cast(num); }
-
// Accessors
const std::string& getName() const { return _name; }
std::optional getMBID() const { return UUID::fromString(_MBID); }
- std::optional getTotalTrackNumber() const;
- std::optional getTotalDiscNumber() const;
+ std::optional getTotalTrack() const;
+ std::optional getTotalDisc() const;
std::chrono::milliseconds getDuration() const;
// Get the artists of this release
@@ -109,8 +105,6 @@ class Release : public Wt::Dbo::Dbo
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
- Wt::Dbo::field(a, _totalDiscNumber, "total_disc_number");
- Wt::Dbo::field(a, _totalTrackNumber, "total_track_number");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
Wt::Dbo::hasMany(a, _starringUsers, Wt::Dbo::ManyToMany, "user_release_starred", "", Wt::Dbo::OnDeleteCascade);
@@ -121,8 +115,6 @@ class Release : public Wt::Dbo::Dbo
std::string _name;
std::string _MBID;
- int _totalDiscNumber {};
- int _totalTrackNumber {};
Wt::Dbo::collection> _tracks; // Tracks in the release
Wt::Dbo::collection> _starringUsers; // Users that starred this release
diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp
index 24113f21..2bbeb9ee 100644
--- a/src/libs/database/include/database/Track.hpp
+++ b/src/libs/database/include/database/Track.hpp
@@ -88,6 +88,8 @@ class Track : public Wt::Dbo::Dbo