Can now browse by directorie in the Subsonic API, ref #474

This commit is contained in:
emeric
2024-07-13 14:22:19 +02:00
parent 9292b05732
commit 980709dbcf
20 changed files with 613 additions and 224 deletions
+33 -5
View File
@@ -33,10 +33,25 @@ namespace lms::db
{
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Directory>>("SELECT d FROM directory d") };
for (std::string_view keyword : params.keywords)
query.where("d.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%");
if (params.artist.isValid()
|| params.release.isValid())
{
query.join("track t ON t.directory_id = d.id");
query.groupBy("d.id");
}
if (params.parentDirectory.isValid())
query.where("d.parent_directory_id = ?").bind(params.parentDirectory);
if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release);
if (params.artist.isValid())
{
query.join("track t ON t.directory_id = d.id")
.join("artist a ON a.id = t_a_l.artist_id")
query.join("artist a ON a.id = t_a_l.artist_id")
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?")
.bind(params.artist);
@@ -57,10 +72,11 @@ namespace lms::db
}
query.where(oss.str());
}
query.groupBy("d.id");
}
if (params.withNoTrack)
query.where("NOT EXISTS (SELECT 1 FROM track t WHERE t.directory_id = d.id)");
return query;
}
} // namespace
@@ -108,10 +124,16 @@ namespace lms::db
});
}
RangeResults<Directory::pointer> Directory::find(Session& session, const FindParameters& params)
{
auto query{ createQuery(session, params) };
return utils::execRangeQuery<Directory::pointer>(query, params.range);
}
void Directory::find(Session& session, const FindParameters& params, const std::function<void(const Directory::pointer&)>& func)
{
auto query{ createQuery(session, params) };
utils::forEachQueryResult(query, [&func](const Directory::pointer& dir) {
utils::forEachQueryRangeResult(query, params.range, [&func](const Directory::pointer& dir) {
func(dir);
});
}
@@ -131,6 +153,12 @@ namespace lms::db
return utils::execRangeQuery<DirectoryId>(query, range);
}
RangeResults<Directory::pointer> Directory::findRootDirectories(Session& session, std::optional<Range> range)
{
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Directory>>("SELECT d from directory d").where("d.parent_directory_id IS NULL") };
return utils::execRangeQuery<Directory::pointer>(query, range);
}
void Directory::setAbsolutePath(const std::filesystem::path& p)
{
assert(p.is_absolute());
+5 -1
View File
@@ -53,7 +53,8 @@ namespace lms::db
|| params.dateRange
|| params.artist.isValid()
|| params.clusters.size() == 1
|| params.mediaLibrary.isValid())
|| params.mediaLibrary.isValid()
|| params.directory.isValid())
{
query.join("track t ON t.release_id = r.id");
}
@@ -61,6 +62,9 @@ namespace lms::db
if (params.mediaLibrary.isValid())
query.where("t.media_library_id = ?").bind(params.mediaLibrary);
if (params.directory.isValid())
query.where("t.directory_id = ?").bind(params.directory);
if (!params.releaseType.empty())
{
query.join("release_release_type r_r_t ON r_r_t.release_id = r.id");
+3
View File
@@ -154,6 +154,9 @@ namespace lms::db
if (params.mediaLibrary.isValid())
query.where("t.media_library_id = ?").bind(params.mediaLibrary);
if (params.directory.isValid())
query.where("t.directory_id = ?").bind(params.directory);
switch (params.sortMethod)
{
case TrackSortMethod::None:
@@ -21,12 +21,16 @@
#include <filesystem>
#include <functional>
#include <vector>
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include "core/EnumSet.hpp"
#include "database/ArtistId.hpp"
#include "database/DirectoryId.hpp"
#include "database/ReleaseId.hpp"
#include "database/Object.hpp"
#include "database/Types.hpp"
@@ -42,20 +46,44 @@ namespace lms::db
struct FindParameters
{
std::optional<Range> range;
ArtistId artist; // only tracks that involve this artist
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
ArtistId artist; // only directory that involve this artist
ReleaseId release; // only releases that involve this artist
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
DirectoryId parentDirectory; // If set, directories that have this parent
bool withNoTrack{}; // If set, directories that do not contain any track
FindParameters& setRange(std::optional<Range> _range)
{
range = _range;
return *this;
}
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords)
{
keywords = _keywords;
return *this;
}
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
{
artist = _artist;
trackArtistLinkTypes = _trackArtistLinkTypes;
return *this;
}
FindParameters& setRelease(ReleaseId _release)
{
release = _release;
return *this;
}
FindParameters& setParentDirectory(DirectoryId _parentDirectory)
{
parentDirectory = _parentDirectory;
return *this;
}
FindParameters& setWithNoTrack(bool _withNoTrack)
{
withNoTrack = _withNoTrack;
return *this;
}
};
// find
@@ -63,13 +91,15 @@ namespace lms::db
static pointer find(Session& session, DirectoryId id);
static pointer find(Session& session, const std::filesystem::path& path);
static void find(Session& session, DirectoryId& lastRetrievedDirectory, std::size_t count, const std::function<void(const Directory::pointer&)>& func);
static RangeResults<Directory::pointer> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& parameters, const std::function<void(const Directory::pointer&)>& func);
static RangeResults<DirectoryId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<pointer> findRootDirectories(Session& session, std::optional<Range> range = std::nullopt);
// getters
const std::filesystem::path& getAbsolutePath() const { return _absolutePath; }
std::string_view getName() const { return _name; }
ObjectPtr<Directory> getParent() const { return _parent; }
ObjectPtr<Directory> getParentDirectory() const { return _parent; }
// setters
void setAbsolutePath(const std::filesystem::path& p);
@@ -33,6 +33,7 @@
#include "core/UUID.hpp"
#include "database/ArtistId.hpp"
#include "database/ClusterId.hpp"
#include "database/DirectoryId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/Object.hpp"
#include "database/ReleaseId.hpp"
@@ -96,6 +97,7 @@ namespace lms::db
core::EnumSet<TrackArtistLinkType> excludedTrackArtistLinkTypes; // but not for these link types
std::string releaseType; // If set, albums that has this release type
MediaLibraryId mediaLibrary; // If set, releases that has at least a track in this library
DirectoryId directory; // if set, tracks in this directory
FindParameters& setClusters(std::span<const ClusterId> _clusters)
{
@@ -150,6 +152,11 @@ namespace lms::db
mediaLibrary = _mediaLibrary;
return *this;
}
FindParameters& setDirectory(DirectoryId _directory)
{
directory = _directory;
return *this;
}
};
Release() = default;
@@ -37,6 +37,7 @@
#include "core/UUID.hpp"
#include "database/ArtistId.hpp"
#include "database/ClusterId.hpp"
#include "database/DirectoryId.hpp"
#include "database/MediaLibraryId.hpp"
#include "database/Object.hpp"
#include "database/ReleaseId.hpp"
@@ -81,6 +82,7 @@ namespace lms::db
std::optional<int> trackNumber; // matching this track number
std::optional<int> discNumber; // matching this disc number
MediaLibraryId mediaLibrary; // If set, tracks in this library
DirectoryId directory; // if set, tracks in this directory
FindParameters& setClusters(std::span<const ClusterId> _clusters)
{
@@ -165,6 +167,11 @@ namespace lms::db
mediaLibrary = _mediaLibrary;
return *this;
}
FindParameters& setDirectory(DirectoryId _directory)
{
directory = _directory;
return *this;
}
};
struct PathResult
+104 -4
View File
@@ -107,23 +107,35 @@ namespace lms::db::tests
{
auto transaction{ session.createReadTransaction() };
auto dir{ child->getParent() };
auto dir{ child->getParentDirectory() };
EXPECT_EQ(dir, Directory::pointer{});
}
{
auto transaction{ session.createWriteTransaction() };
child.get().modify()->setParent(parent.lockAndGet());
child.get().modify()->setParent(parent.get());
}
{
auto transaction{ session.createReadTransaction() };
auto dir{ child->getParent() };
auto dir{ child->getParentDirectory() };
ASSERT_NE(dir, Directory::pointer{});
EXPECT_EQ(dir->getId(), parent.getId());
}
{
auto transaction{ session.createReadTransaction() };
Directory::pointer foundDir;
Directory::find(session, Directory::FindParameters{}.setParentDirectory(parent->getId()), [&](const Directory::pointer& dir) {
ASSERT_EQ(foundDir, Directory::pointer{});
foundDir = dir;
});
ASSERT_NE(foundDir, Directory::pointer{});
EXPECT_EQ(foundDir->getId(), child.getId());
}
}
TEST_F(DatabaseFixture, Directory_orphaned)
@@ -141,7 +153,7 @@ namespace lms::db::tests
{
auto transaction{ session.createWriteTransaction() };
child.get().modify()->setParent(parent.lockAndGet());
child.get().modify()->setParent(parent.get());
}
{
@@ -152,4 +164,92 @@ namespace lms::db::tests
EXPECT_EQ(directories.front(), child.getId());
}
}
TEST_F(DatabaseFixture, Directory_findRootDirectories)
{
ScopedDirectory parent1{ session, "/root1" };
ScopedDirectory child{ session, "/root1/child" };
ScopedDirectory parent2{ session, "/root2" };
{
auto transaction{ session.createWriteTransaction() };
child.get().modify()->setParent(parent1.get());
}
{
auto transaction{ session.createReadTransaction() };
const auto directories{ Directory::findRootDirectories(session).results };
ASSERT_EQ(directories.size(), 2);
EXPECT_EQ(directories[0]->getId(), parent1.getId());
EXPECT_EQ(directories[1]->getId(), parent2.getId());
}
}
TEST_F(DatabaseFixture, Directory_findNonTrackDirectories)
{
ScopedDirectory parent{ session, "/root" };
ScopedDirectory child1{ session, "/root/child1" };
ScopedDirectory child2{ session, "/root/child2" };
ScopedTrack track{ session };
{
auto transaction{ session.createWriteTransaction() };
child1.get().modify()->setParent(parent.get());
child2.get().modify()->setParent(parent.get());
}
{
auto transaction{ session.createReadTransaction() };
Directory::FindParameters params;
params.setWithNoTrack(true);
auto res{ Directory::find(session, params).results };
ASSERT_EQ(res.size(), 3);
EXPECT_EQ(res[0]->getId(), parent.getId());
EXPECT_EQ(res[1]->getId(), child1.getId());
EXPECT_EQ(res[2]->getId(), child2.getId());
}
{
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setDirectory(child2.get());
}
{
auto transaction{ session.createReadTransaction() };
Directory::FindParameters params;
params.setWithNoTrack(true);
auto res{ Directory::find(session, params).results };
ASSERT_EQ(res.size(), 2);
EXPECT_EQ(res[0]->getId(), parent.getId());
EXPECT_EQ(res[1]->getId(), child1.getId());
}
}
TEST_F(DatabaseFixture, Directory_findWithKeywords)
{
ScopedDirectory parent{ session, "/root" };
ScopedDirectory child1{ session, "/root/foo" };
ScopedDirectory child2{ session, "/root/bar/foo" };
{
auto transaction{ session.createReadTransaction() };
Directory::FindParameters params;
params.setKeywords({ "foo" });
auto res{ Directory::find(session, params).results };
ASSERT_EQ(res.size(), 2);
EXPECT_EQ(res[0]->getId(), child1.getId());
EXPECT_EQ(res[1]->getId(), child2.getId());
}
}
} // namespace lms::db::tests