From c0ef664061bff5516031607676022cfb9d4941be Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 27 Apr 2020 14:50:18 +0200 Subject: [PATCH 1/8] Fixed a bug when the playqueue is emptied (by a rescan for example) and repeat all is active --- src/lms/ui/PlayQueueView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lms/ui/PlayQueueView.cpp b/src/lms/ui/PlayQueueView.cpp index b634429d..34c82186 100644 --- a/src/lms/ui/PlayQueueView.cpp +++ b/src/lms/ui/PlayQueueView.cpp @@ -224,7 +224,7 @@ PlayQueue::loadTrack(std::size_t pos, bool play) // If out of range, stop playing if (pos >= tracklist->getCount()) { - if (!_repeatAll) + if (!_repeatAll || tracklist->getCount() == 0) { stop(); return; From 6c1a7863ebc8a9803c7d2859fd5b83dc9c4b8459 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 27 Apr 2020 14:58:12 +0200 Subject: [PATCH 2/8] Recycled the default cover cache to cache any cover (cache is still small to fit on devices that have small RAM constraints) --- src/libs/cover/impl/CoverArtGrabber.cpp | 105 +++++++++++------- src/libs/cover/impl/CoverArtGrabber.hpp | 65 +++++++++-- .../cover/include/cover/ICoverArtGrabber.hpp | 2 + src/lms/main.cpp | 9 +- 4 files changed, 132 insertions(+), 49 deletions(-) diff --git a/src/libs/cover/impl/CoverArtGrabber.cpp b/src/libs/cover/impl/CoverArtGrabber.cpp index 8646d8bf..efd1c73c 100644 --- a/src/libs/cover/impl/CoverArtGrabber.cpp +++ b/src/libs/cover/impl/CoverArtGrabber.cpp @@ -26,6 +26,7 @@ #include "database/Track.hpp" #include "utils/Logger.hpp" +#include "utils/Random.hpp" namespace { @@ -52,37 +53,11 @@ Grabber::Grabber(const std::filesystem::path& execPath) void Grabber::setDefaultCover(const std::filesystem::path& p) { - std::unique_lock lock {_mutex}; - _defaultCover = std::make_unique(); if (!_defaultCover->load(p)) throw LmsException("Cannot read default cover file '" + p.string() + "'"); } -Image -Grabber::getDefaultCover(std::size_t size) -{ - LMS_LOG(COVER, DEBUG) << "Getting a default cover using size = " << size; - std::unique_lock lock {_mutex}; - - auto it = _defaultCovers.find(size); - if (it == _defaultCovers.end()) - { - Image cover = *_defaultCover; - - LMS_LOG(COVER, DEBUG) << "default cover size = " << cover.getSize().width << " x " << cover.getSize().height; - - LMS_LOG(COVER, DEBUG) << "Scaling cover to size = " << size; - cover.scale(Geometry{size, size}); - LMS_LOG(COVER, DEBUG) << "Scaling DONE"; - auto res = _defaultCovers.insert(std::make_pair(size, cover)); - assert(res.second); - it = res.first; - } - - return it->second; -} - static std::optional getFromAvMediaFile(const Av::MediaFile& input) { @@ -173,7 +148,11 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st { using namespace Database; - std::optional cover; + const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Track, trackId, size}; + std::optional cover {loadFromCache(cacheEntryDesc)}; + + if (cover) + return *cover; bool hasCover {}; bool isMultiDisc {}; @@ -182,7 +161,7 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st { auto transaction {dbSession.createSharedTransaction()}; - Track::pointer track = Track::getById(dbSession, trackId); + const Track::pointer track {Track::getById(dbSession, trackId)}; if (track) { hasCover = track->hasCover(); @@ -207,9 +186,11 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st } if (!cover) - cover = getDefaultCover(size); - else - cover->scale(Geometry {size, size}); + cover = *_defaultCover; + + cover->scale(Geometry {size, size}); + + saveToCache(cacheEntryDesc, *cover); return *cover; } @@ -218,32 +199,53 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st Image Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, std::size_t size) { - std::optional cover; + const CacheEntryDesc cacheEntryDesc {CacheEntryDesc::Type::Release, releaseId, size}; + std::optional cover {loadFromCache(cacheEntryDesc)}; + + if (cover) + return *cover; std::optional trackId; { auto transaction {session.createSharedTransaction()}; - auto release {Database::Release::getById(session, releaseId)}; + const auto release {Database::Release::getById(session, releaseId)}; if (release) { - auto tracks {release->getTracks()}; + const auto tracks {release->getTracks()}; if (!tracks.empty()) trackId = tracks.front().id(); } } if (trackId) - return getFromTrack(session, *trackId, size); - - if (!cover) - cover = getDefaultCover(size); + { + cover = getFromTrack(session, *trackId, size); + } else + { + if (!cover) + cover = *_defaultCover; + cover->scale(Geometry {size, size}); + } + + saveToCache(cacheEntryDesc, *cover); return *cover; } +void +Grabber::flushCache() +{ + std::unique_lock lock {_cacheMutex}; + + LMS_LOG(COVER, DEBUG) << "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses; + _cacheHits = 0; + _cacheMisses = 0; + _cache.clear(); +} + std::vector Grabber::getFromTrack(Database::Session& session, Database::IdType trackId, Format format, std::size_t width) { @@ -262,5 +264,32 @@ Grabber::getFromRelease(Database::Session& session, Database::IdType releaseId, return cover.save(format); } +void +Grabber::saveToCache(const CacheEntryDesc& entryDesc, const Image& image) +{ + std::unique_lock lock {_cacheMutex}; + + if (_cache.size() >= _maxCacheEntries) + _cache.erase(Random::pickRandom(_cache)); + + _cache[entryDesc] = image; +} + +std::optional +Grabber::loadFromCache(const CacheEntryDesc& entryDesc) +{ + std::shared_lock lock {_cacheMutex}; + + auto it {_cache.find(entryDesc)}; + if (it == std::cend(_cache)) + { + ++_cacheMisses; + return std::nullopt; + } + + ++_cacheHits; + return it->second; +} + } // namespace CoverArt diff --git a/src/libs/cover/impl/CoverArtGrabber.hpp b/src/libs/cover/impl/CoverArtGrabber.hpp index df932315..1e6df791 100644 --- a/src/libs/cover/impl/CoverArtGrabber.hpp +++ b/src/libs/cover/impl/CoverArtGrabber.hpp @@ -20,10 +20,10 @@ #pragma once #include -#include -#include +#include #include #include +#include #include "cover/ICoverArtGrabber.hpp" #include "database/Types.hpp" @@ -36,7 +36,48 @@ namespace Database namespace CoverArt { + struct CacheEntryDesc + { + enum class Type + { + Track, + Release, + }; + Type type; + Database::IdType id; + std::size_t size; + + bool operator==(const CacheEntryDesc& other) const + { + return type == other.type + && id == other.id + && size == other.size; + } + }; + +} // ns CoverArt + +namespace std +{ + + template<> + class hash + { + public: + size_t operator()(const CoverArt::CacheEntryDesc& e) const + { + size_t h = std::hash()(static_cast(e.type)); + h ^= std::hash()(e.id) << 1; + h ^= std::hash()(e.size) << 1; + return h; + } + }; + +} // ns std + +namespace CoverArt +{ class Grabber : public IGrabber { public: @@ -47,12 +88,14 @@ namespace CoverArt Grabber(Grabber&&) = delete; Grabber& operator=(Grabber&&) = delete; + private: + void setDefaultCover(const std::filesystem::path& defaultCoverPath) override; std::vector getFromTrack(Database::Session& dbSession, Database::IdType trackId, Format format, std::size_t width) override; std::vector getFromRelease(Database::Session& dbSession, Database::IdType releaseId, Format format, std::size_t width) override; + void flushCache() override; - private: Image getFromTrack(Database::Session& dbSession, Database::IdType trackId, std::size_t size); Image getFromRelease(Database::Session& dbSession, Database::IdType releaseId, std::size_t size); @@ -60,14 +103,20 @@ namespace CoverArt std::optional getFromTrack(const std::filesystem::path& path) const; std::vector getCoverPaths(const std::filesystem::path& directoryPath) const; std::optional getFromDirectory(const std::filesystem::path& path) const; - Image getDefaultCover(std::size_t size); - std::mutex _mutex; - std::unique_ptr _defaultCover; - std::map _defaultCovers; + std::unique_ptr _defaultCover; // unique_ptr to defer initializing + std::shared_mutex _cacheMutex; + std::unordered_map _cache; + std::size_t _cacheMisses {}; + std::size_t _cacheHits {}; + + void saveToCache(const CacheEntryDesc& entryDesc, const Image& image); + std::optional loadFromCache(const CacheEntryDesc& entryDesc); + + static inline constexpr std::size_t _maxCacheEntries {300}; static inline const std::vector _fileExtensions {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize - static inline const std::size_t _maxFileSize {10000000}; + static inline constexpr std::size_t _maxFileSize {10000000}; static inline const std::vector _preferredFileNames {"cover", "front"}; // TODO parametrize }; diff --git a/src/libs/cover/include/cover/ICoverArtGrabber.hpp b/src/libs/cover/include/cover/ICoverArtGrabber.hpp index c62e3955..e9cda763 100644 --- a/src/libs/cover/include/cover/ICoverArtGrabber.hpp +++ b/src/libs/cover/include/cover/ICoverArtGrabber.hpp @@ -40,6 +40,8 @@ class IGrabber virtual std::vector getFromTrack(Database::Session& dbSession, Database::IdType trackId, Format format, std::size_t width) = 0; virtual std::vector getFromRelease(Database::Session& dbSession, Database::IdType releaseId, Format format, std::size_t width) = 0; + + virtual void flushCache() = 0; }; std::unique_ptr createGrabber(const std::filesystem::path& execPath); diff --git a/src/lms/main.cpp b/src/lms/main.cpp index bbaa1985..0911e8db 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -148,6 +148,9 @@ int main(int argc, char* argv[]) Scanner::IMediaScanner& mediaScanner {ServiceProvider::assign(Scanner::createMediaScanner(database))}; Recommendation::IEngine& recommendationEngine {ServiceProvider::assign(Recommendation::createEngine(database))}; + CoverArt::IGrabber& coverArtGrabber {ServiceProvider::assign(CoverArt::createGrabber(argv[0]))}; + coverArtGrabber.setDefaultCover(server.appRoot() + "/images/unknown-cover.jpg"); + mediaScanner.scanComplete().connect([&]() { auto status = mediaScanner.getStatus(); @@ -161,11 +164,11 @@ int main(int argc, char* argv[]) { LMS_LOG(MAIN, INFO) << "Scanner did not change files, not reloading the recommendation engine..."; } + // Flush cover cache even if no changes: + // covers may be external files that changed and we don't keep track of them + coverArtGrabber.flushCache(); }); - CoverArt::IGrabber& coverArtGrabber {ServiceProvider::assign(CoverArt::createGrabber(argv[0]))}; - coverArtGrabber.setDefaultCover(server.appRoot() + "/images/unknown-cover.jpg"); - API::Subsonic::SubsonicResource subsonicResource {database}; // bind API resources From 9edad1314ee4fbd5c56768396cdf24406544d433 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 2 May 2020 14:34:40 +0200 Subject: [PATCH 3/8] Use the last modified file attribute in order to display recently added items. fixes #51 --- src/libs/database/impl/Artist.cpp | 6 +++--- src/libs/database/impl/Release.cpp | 6 +++--- src/libs/database/impl/Track.cpp | 6 +++--- src/libs/database/include/database/Artist.hpp | 2 +- src/libs/database/include/database/Release.hpp | 2 +- src/libs/database/include/database/Track.hpp | 2 +- src/libs/subsonic/impl/SubsonicResource.cpp | 2 +- src/lms/ui/explore/ArtistsInfoView.cpp | 4 ++-- src/lms/ui/explore/ReleasesInfoView.cpp | 4 ++-- src/lms/ui/explore/TracksInfoView.cpp | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 8f310180..cba62539 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -230,13 +230,13 @@ Artist::getByFilter(Session& session, } std::vector -Artist::getLastAdded(Session& session, Wt::WDateTime after, std::optional limit) +Artist::getLastWritten(Session& session, Wt::WDateTime after, std::optional limit) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().query("SELECT a from artist a INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN track t ON t.id = t_a_l.track_id") - .where("t.file_added > ?").bind(after) + .where("t.file_last_write > ?").bind(after) .groupBy("a.id") - .orderBy("t.file_added DESC") + .orderBy("t.file_last_write DESC") .limit(limit ? static_cast(*limit) : -1); return std::vector(res.begin(), res.end()); diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index a8535f49..d7c8491b 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -145,14 +145,14 @@ Release::getAllOrphans(Session& session) } std::vector -Release::getLastAdded(Session& session, const Wt::WDateTime& after, std::optional offset, std::optional limit) +Release::getLastWritten(Session& session, const Wt::WDateTime& after, std::optional offset, std::optional limit) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().query("SELECT r from release r INNER JOIN track t ON r.id = t.release_id") - .where("t.file_added > ?").bind(after) + .where("t.file_last_write > ?").bind(after) .groupBy("r.id") - .orderBy("t.file_added DESC") + .orderBy("t.file_last_write DESC") .offset(offset ? static_cast(*offset) : -1) .limit(limit ? static_cast(*limit) : -1); diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index f0deedab..cc286ee0 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -147,13 +147,13 @@ Track::getMBIDDuplicates(Session& session) } std::vector -Track::getLastAdded(Session& session, const Wt::WDateTime& after, std::optional limit) +Track::getLastWritten(Session& session, const Wt::WDateTime& after, std::optional limit) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().find() - .where("file_added > ?").bind(after) - .orderBy("file_added DESC") + .where("file_last_write > ?").bind(after) + .orderBy("file_last_write DESC") .limit(limit ? static_cast(*limit) : -1); return std::vector(res.begin(), res.end()); diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index f6f5df7b..f29d4637 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -77,7 +77,7 @@ class Artist : public Wt::Dbo::Dbo static std::vector getAll(Session& session, SortMethod sortMethod, std::optional offset = {}, std::optional size = {}); static std::vector getAllIds(Session& session); static std::vector getAllOrphans(Session& session); // No track related - static std::vector getLastAdded(Session& session, Wt::WDateTime after, std::optional size = {}); + static std::vector getLastWritten(Session& session, Wt::WDateTime after, std::optional size = {}); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); // Accessors diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 29b93c58..7b61148b 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -56,7 +56,7 @@ class Release : public Wt::Dbo::Dbo static std::vector getAllIds(Session& session); static std::vector getAllOrderedByArtist(Session& session, std::optional offset = {}, std::optional size = {}); static std::vector getAllRandom(Session& session, std::optional size = {}); - static std::vector getLastAdded(Session& session, const Wt::WDateTime& after, std::optional offset = {}, std::optional size = {}); + static std::vector getLastWritten(Session& session, const Wt::WDateTime& after, std::optional offset = {}, std::optional size = {}); static std::vector getByYear(Session& session, int yearFrom, int yearTo, std::optional offset = {}, std::optional size = {}); static std::vector getByClusters(Session& session, const std::set& clusters); diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index 9dbd4083..4a83f560 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -77,7 +77,7 @@ class Track : public Wt::Dbo::Dbo static std::vector getAllIds(Session& session); static std::vector> getAllPaths(Session& session, std::optional offset = std::nullopt, std::optional size = std::nullopt); static std::vector getMBIDDuplicates(Session& session); - static std::vector getLastAdded(Session& session, const Wt::WDateTime& after, std::optional size = 1); + static std::vector getLastWritten(Session& session, const Wt::WDateTime& after, std::optional size = 1); static std::vector getAllWithMBIDAndMissingFeatures(Session& session); static std::vector getAllIdsWithFeatures(Session& session, std::optional limit = {}); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index b34211a9..bab3fa94 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -696,7 +696,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3) else if (type == "newest") { auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6)}; - releases = Release::getLastAdded(context.dbSession, after, offset, size); + releases = Release::getLastWritten(context.dbSession, after, offset, size); } else if (type == "alphabeticalByName") { diff --git a/src/lms/ui/explore/ArtistsInfoView.cpp b/src/lms/ui/explore/ArtistsInfoView.cpp index e9181a64..ac0dd65d 100644 --- a/src/lms/ui/explore/ArtistsInfoView.cpp +++ b/src/lms/ui/explore/ArtistsInfoView.cpp @@ -57,10 +57,10 @@ ArtistsInfo::ArtistsInfo() void ArtistsInfo::refreshRecentlyAdded() { - auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); + auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6); auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const std::vector artists {Artist::getLastAdded(LmsApp->getDbSession(), after, 5)}; + const std::vector artists {Artist::getLastWritten(LmsApp->getDbSession(), after, 5)}; _recentlyAddedContainer->clear(); for (const Database::Artist::pointer& artist : artists) diff --git a/src/lms/ui/explore/ReleasesInfoView.cpp b/src/lms/ui/explore/ReleasesInfoView.cpp index 0693cc32..f5690ec8 100644 --- a/src/lms/ui/explore/ReleasesInfoView.cpp +++ b/src/lms/ui/explore/ReleasesInfoView.cpp @@ -58,11 +58,11 @@ ReleasesInfo::ReleasesInfo() void ReleasesInfo::refreshRecentlyAdded() { - auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); + auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6); auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const auto releases {Release::getLastAdded(LmsApp->getDbSession(), after, 0, 5)}; + const auto releases {Release::getLastWritten(LmsApp->getDbSession(), after, 0, 5)}; _recentlyAddedContainer->clear(); for (const Database::Release::pointer& release : releases) diff --git a/src/lms/ui/explore/TracksInfoView.cpp b/src/lms/ui/explore/TracksInfoView.cpp index d05cc200..b4907003 100644 --- a/src/lms/ui/explore/TracksInfoView.cpp +++ b/src/lms/ui/explore/TracksInfoView.cpp @@ -74,10 +74,10 @@ TracksInfo::TracksInfo() void TracksInfo::refreshRecentlyAdded() { - const auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)}; + const auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6)}; auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const auto tracks {Track::getLastAdded(LmsApp->getDbSession(), after, 5)}; + const auto tracks {Track::getLastWritten(LmsApp->getDbSession(), after, 5)}; _recentlyAddedContainer->clear(); addEntries(_recentlyAddedContainer, tracks); From d6a60c662870e9decda1a547e427fcf525c50270 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 2 May 2020 15:43:37 +0200 Subject: [PATCH 4/8] Cleanup (spotted using codefactor.io) --- README.md | 2 +- docroot/css/lms.css | 64 ++++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 40145c8a..e293642b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LMS - Lightweight Music Server -[![Build Status](https://travis-ci.org/epoupon/lms.svg?branch=master)](https://travis-ci.org/epoupon/lms) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/epoupon/lms) +[![Build Status](https://travis-ci.org/epoupon/lms.svg?branch=master)](https://travis-ci.org/epoupon/lms) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/epoupon/lms) [![CodeFactor](https://www.codefactor.io/repository/github/epoupon/lms/badge/master)](https://www.codefactor.io/repository/github/epoupon/lms/overview/master) _LMS_ is a self-hosted music streaming software: access your music collection from anywhere using a web interface! diff --git a/docroot/css/lms.css b/docroot/css/lms.css index b0735192..938203f8 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -27,7 +27,7 @@ a, a:link, a:active, a:visited { min-height: 48px; padding-top: 8px; padding-bottom: 8px; - margin: 0px; + margin: 0; } .Lms-admin-users-add-btn { @@ -75,24 +75,24 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-cover-smaller { width: 64px; /* some cover may not be square */ border-radius: 3px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4); } .Lms-cover-small { width: 96px; /* some cover may not be square */ border-radius: 3px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4); } .Lms-cover-medium { width: 128px; border-radius: 3px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4); } .Lms-cover-large { border-radius: 3px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4); } .Lms-entry-playing { @@ -155,7 +155,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { } .Lms-explore-releaselink { - margin: 0px; + margin: 0; padding-top: 4px; padding-bottom: 4px; } @@ -179,7 +179,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-explore-artistlink { min-height: 28px; - margin: 0px; + margin: 0; } .Lms-explore-tracks-entry { @@ -212,11 +212,11 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-explore-tracksinfo-entry { min-height: 28px; - margin: 0px; + margin: 0; } .Lms-navbar { - border-radius: 0px; + border-radius: 0; } .Lms-playqueue-btn { @@ -270,7 +270,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-playhistory-entry { min-height: 32px; - margin-bottom: 0px; + margin-bottom: 0; } .Lms-playhistory-entry-name { @@ -284,8 +284,8 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { bottom: 0; width: 100%; height: 70px; - padding: 0px; - margin: 0px; + padding: 0; + margin: 0; background-color: var(--player-background-color); } @@ -329,8 +329,8 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-player-seek::-webkit-slider-thumb { -webkit-appearance: none; -moz-appearance: none; - height: 0px; - width: 0px; + height: 0; + width: 0; } .Lms-player-seek-container { @@ -344,8 +344,8 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-player-seek-common { position: absolute; width:100%; - top: 0px; - left: 0px; + top: 0; + left: 0; height: 8px; border-radius: 3px; } @@ -353,8 +353,8 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { .Lms-player-seek::-moz-range-thumb { -webkit-appearance: none; -moz-appearance: none; - height: 0px; - width: 0px; + height: 0; + width: 0; } .Lms-player-text-center { @@ -375,22 +375,6 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { display: flex; } -.Lms-player-volume-slider::-webkit-slider-thumb { - background-color: var(--body-color); -} - -.Lms-player-volume-slider::-webkit-slider-thumb:hover { - background-color: var(--hover-color); -} - -.Lms-player-volume-slider::-moz-range-thumb { - background-color: var(--body-color); -} - -.Lms-player-volume-slider::-moz-range-thumb:hover { - background-color: var(--hover-color); -} - .Lms-player-volume-slider { -webkit-appearance: none; appearance: none; @@ -405,19 +389,29 @@ a.Lms-artistname:hover, a.Lms-artistname:focus { } .Lms-player-volume-slider::-webkit-slider-thumb { - -webkit-appearance: none; /* Override default look */ + -webkit-appearance: none; + background-color: var(--body-color); appearance: none; width: 14px; height: 14px; border-radius: 7px; } +.Lms-player-volume-slider::-webkit-slider-thumb:hover { + background-color: var(--hover-color); +} + .Lms-player-volume-slider::-moz-range-thumb { + background-color: var(--body-color); width: 14px; height: 14px; border-radius: 7px; } +.Lms-player-volume-slider::-moz-range-thumb:hover { + background-color: var(--hover-color); +} + .Lms-player-volume-slider-container { display: inline-block; width: 85px; From e69c4038ac5d8ab60d025f8981f44db802d16127 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 2 May 2020 16:03:29 +0200 Subject: [PATCH 5/8] Add a hint for files that are going to be transcoded. ref #53 --- src/libs/subsonic/impl/SubsonicResource.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index bab3fa94..df58215f 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -263,6 +263,22 @@ getTrackPath(const Track::pointer& track) return path; } +static +std::string_view +formatToSuffix(AudioFormat format) +{ + switch (format) + { + case AudioFormat::MP3: return "mp3"; + case AudioFormat::OGG_OPUS: return "opus"; + case AudioFormat::MATROSKA_OPUS: return "mka"; + case AudioFormat::OGG_VORBIS: return "ogg"; + case AudioFormat::WEBM_VORBIS: return "webm"; + } + + return ""; +} + static Response::Node trackToResponseNode(const Track::pointer& track, Session& dbSession, const User::pointer& user) @@ -293,6 +309,9 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User: trackResponse.setAttribute("suffix", extension.string().substr(1)); } + if (user->getSubsonicTranscodeEnable()) + trackResponse.setAttribute("transcodeSuffix", formatToSuffix(user->getSubsonicTranscodeFormat())); + trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()})); auto artists {track->getArtists()}; From 8277b9c82ba03154242185867fb7860e59c5d7df Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 2 May 2020 20:46:18 +0200 Subject: [PATCH 6/8] Corrected typo. fixes #53 --- src/libs/subsonic/impl/SubsonicResource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index df58215f..39e38f40 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -310,7 +310,7 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User: } if (user->getSubsonicTranscodeEnable()) - trackResponse.setAttribute("transcodeSuffix", formatToSuffix(user->getSubsonicTranscodeFormat())); + trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicTranscodeFormat())); trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()})); From 74fc4e058e3b88faa13844c40b735aee20d9fa4b Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 10 May 2020 13:11:43 +0200 Subject: [PATCH 7/8] New UI layout and UI improvements. Fixes #50 --- approot/admin-users.xml | 2 +- approot/artist.xml | 20 +- approot/artistinfo.xml | 10 - approot/artistlink.xml | 13 - approot/artists.xml | 23 +- approot/artistsinfo.xml | 12 - approot/explore.xml | 13 +- approot/messages.xml | 32 +- approot/messages_fr.xml | 29 +- approot/playhistory.xml | 45 -- approot/playqueue.xml | 4 +- approot/release.xml | 33 +- approot/releaseinfo.xml | 15 - approot/releaselink.xml | 23 - approot/releases.xml | 30 +- approot/releasesinfo.xml | 12 - approot/search.xml | 42 ++ approot/templates.xml | 49 +- approot/tracks.xml | 13 +- docroot/css/lms.css | 84 ++- docroot/js/collapse.js | 211 ++++++++ docroot/js/dropdown.js | 165 ++++++ docroot/js/transition.js | 59 +++ src/libs/cover/impl/CoverArtGrabber.hpp | 6 +- src/libs/database/impl/Artist.cpp | 218 +++++--- src/libs/database/impl/Db.cpp | 20 +- src/libs/database/impl/Release.cpp | 158 +++--- src/libs/database/impl/Session.cpp | 1 + src/libs/database/impl/Track.cpp | 155 +++--- src/libs/database/impl/TrackList.cpp | 273 ++++++++-- src/libs/database/include/database/Artist.hpp | 15 +- src/libs/database/include/database/Db.hpp | 26 +- .../database/include/database/Release.hpp | 10 +- src/libs/database/include/database/Track.hpp | 10 +- .../database/include/database/TrackList.hpp | 12 +- src/libs/database/include/database/Types.hpp | 9 +- src/libs/subsonic/impl/SubsonicResource.cpp | 26 +- src/libs/utils/impl/Random.cpp | 7 +- src/libs/utils/include/utils/Random.hpp | 2 + src/lms/CMakeLists.txt | 11 +- src/lms/main.cpp | 15 +- src/lms/ui/LmsApplication.cpp | 136 ++--- src/lms/ui/LmsTheme.cpp | 29 +- src/lms/ui/LmsTheme.hpp | 1 - src/lms/ui/MediaPlayer.cpp | 10 + src/lms/ui/PlayHistoryView.cpp | 132 ----- src/lms/ui/common/ValueStringModel.hpp | 4 +- src/lms/ui/explore/ArtistInfoView.cpp | 81 --- src/lms/ui/explore/ArtistInfoView.hpp | 39 -- .../{ArtistLink.cpp => ArtistListHelpers.cpp} | 30 +- .../{ArtistLink.hpp => ArtistListHelpers.hpp} | 21 +- src/lms/ui/explore/ArtistView.cpp | 162 +++--- src/lms/ui/explore/ArtistView.hpp | 19 +- src/lms/ui/explore/ArtistsInfoView.cpp | 83 --- src/lms/ui/explore/ArtistsInfoView.hpp | 41 -- src/lms/ui/explore/ArtistsView.cpp | 162 ++++-- src/lms/ui/explore/ArtistsView.hpp | 49 +- src/lms/ui/explore/Explore.cpp | 69 +-- src/lms/ui/explore/Explore.hpp | 4 +- src/lms/ui/explore/Filters.cpp | 6 +- src/lms/ui/explore/ReleaseInfoView.cpp | 116 ----- src/lms/ui/explore/ReleaseInfoView.hpp | 39 -- src/lms/ui/explore/ReleaseLink.cpp | 63 --- src/lms/ui/explore/ReleaseListHelpers.cpp | 68 +++ ...ReleaseLink.hpp => ReleaseListHelpers.hpp} | 16 +- src/lms/ui/explore/ReleaseView.cpp | 99 +++- src/lms/ui/explore/ReleaseView.hpp | 38 +- src/lms/ui/explore/ReleasesInfoView.cpp | 85 ---- src/lms/ui/explore/ReleasesInfoView.hpp | 41 -- src/lms/ui/explore/ReleasesView.cpp | 233 ++++++--- src/lms/ui/explore/ReleasesView.hpp | 44 +- src/lms/ui/explore/SearchView.cpp | 183 +++++++ .../SearchView.hpp} | 25 +- src/lms/ui/explore/TracksInfoView.cpp | 97 ---- src/lms/ui/explore/TracksInfoView.hpp | 41 -- src/lms/ui/explore/TracksView.cpp | 286 +++++++---- src/lms/ui/explore/TracksView.hpp | 45 +- src/test/database/DatabaseTest.cpp | 478 +++++++++++++++--- src/tools/metadata/LmsMetadata.cpp | 2 +- 79 files changed, 3003 insertions(+), 1987 deletions(-) delete mode 100644 approot/artistinfo.xml delete mode 100644 approot/artistlink.xml delete mode 100644 approot/artistsinfo.xml delete mode 100644 approot/playhistory.xml delete mode 100644 approot/releaseinfo.xml delete mode 100644 approot/releaselink.xml delete mode 100644 approot/releasesinfo.xml create mode 100644 approot/search.xml create mode 100755 docroot/js/collapse.js create mode 100755 docroot/js/dropdown.js create mode 100755 docroot/js/transition.js delete mode 100644 src/lms/ui/PlayHistoryView.cpp delete mode 100644 src/lms/ui/explore/ArtistInfoView.cpp delete mode 100644 src/lms/ui/explore/ArtistInfoView.hpp rename src/lms/ui/explore/{ArtistLink.cpp => ArtistListHelpers.cpp} (52%) rename src/lms/ui/explore/{ArtistLink.hpp => ArtistListHelpers.hpp} (68%) delete mode 100644 src/lms/ui/explore/ArtistsInfoView.cpp delete mode 100644 src/lms/ui/explore/ArtistsInfoView.hpp delete mode 100644 src/lms/ui/explore/ReleaseInfoView.cpp delete mode 100644 src/lms/ui/explore/ReleaseInfoView.hpp delete mode 100644 src/lms/ui/explore/ReleaseLink.cpp create mode 100644 src/lms/ui/explore/ReleaseListHelpers.cpp rename src/lms/ui/explore/{ReleaseLink.hpp => ReleaseListHelpers.hpp} (77%) delete mode 100644 src/lms/ui/explore/ReleasesInfoView.cpp delete mode 100644 src/lms/ui/explore/ReleasesInfoView.hpp create mode 100644 src/lms/ui/explore/SearchView.cpp rename src/lms/ui/{PlayHistoryView.hpp => explore/SearchView.hpp} (68%) delete mode 100644 src/lms/ui/explore/TracksInfoView.cpp delete mode 100644 src/lms/ui/explore/TracksInfoView.hpp diff --git a/approot/admin-users.xml b/approot/admin-users.xml index 3e27948c..8407e944 100644 --- a/approot/admin-users.xml +++ b/approot/admin-users.xml @@ -2,7 +2,7 @@ -