Can now browse by directorie in the Subsonic API, ref #474
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user