Added playlist import (and sync)from m3u files, ref #391

This commit is contained in:
emeric
2024-12-18 14:15:25 +01:00
parent ec20c9bfa6
commit 3661eaccb6
95 changed files with 3439 additions and 1634 deletions
+1
View File
@@ -8,6 +8,7 @@ add_library(lmsdatabase SHARED
impl/Listen.cpp
impl/MediaLibrary.cpp
impl/Migration.cpp
impl/PlayListFile.cpp
impl/PlayQueue.cpp
impl/TrackArtistLink.cpp
impl/TrackFeatures.cpp
+2
View File
@@ -170,10 +170,12 @@ namespace lms::db
query.leftJoin("track t ON d.id = t.directory_id");
query.leftJoin("image i ON d.id = i.directory_id");
query.leftJoin("track_lyrics l_lrc ON d.id = l_lrc.directory_id");
query.leftJoin("playlist_file pl_f ON d.id = pl_f.directory_id");
query.where("d_child.id IS NULL");
query.where("t.directory_id IS NULL");
query.where("i.directory_id IS NULL");
query.where("l_lrc.directory_id IS NULL");
query.where("pl_f.directory_id IS NULL");
return utils::execRangeQuery<DirectoryId>(query, range);
}
+88 -26
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 76 };
static constexpr Version LMS_DATABASE_VERSION{ 77 };
}
VersionInfo::VersionInfo()
@@ -127,7 +127,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
void migrateFromV36(Session& session)
{
// Increased precision for track durations (now in milliseconds instead of secodns)
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -136,7 +136,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
// Support Performer tags (via subtypes)
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_artist_link ADD subtype TEXT");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -176,7 +176,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "DROP TABLE track");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_backup RENAME TO track");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -186,7 +186,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD primary_type INTEGER");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD secondary_types INTEGER");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -196,7 +196,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD artist_display_name TEXT NOT NULL DEFAULT ''");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD artist_display_name TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -241,7 +241,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "ALTER TABLE cluster ADD track_count INTEGER");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE cluster ADD release_count INTEGER");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -250,7 +250,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
// add bitrate
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD bitrate INTEGER NOT NULL DEFAULT 0");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -274,7 +274,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN extra_tags_to_scan TEXT");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -299,14 +299,14 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_release_type_release_type" on "release_release_type" ("release_type_id"))");
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_release_type_release" on "release_release_type" ("release_id"))");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
void migrateFromV48(Session& session)
{
// Regression for the extra tags not being parsed
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -316,7 +316,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD year INTEGER");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD original_year INTEGER");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -423,7 +423,7 @@ SELECT
// Add sort name for releases
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD sort_name TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -432,7 +432,7 @@ SELECT
// Add release group mbid
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD group_mbid TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -443,7 +443,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD file_size BIGINT NOT NULL DEFAULT(0)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD relative_file_path TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -454,7 +454,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD channel_count INTEGER NOT NULL DEFAULT(0)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD sample_rate INTEGER NOT NULL DEFAULT(0)");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -494,7 +494,7 @@ SELECT
constraint "fk_image_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred
))");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -625,7 +625,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), "DROP TABLE image");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE image_backup RENAME TO image");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -660,7 +660,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), "DROP TABLE directory");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE directory_backup RENAME TO directory");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -669,7 +669,7 @@ SELECT
// Add a new column comment
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD comment TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -732,7 +732,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_label_label" on "release_label" ("label_id"))");
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_label_release" on "release_label" ("release_id"))");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -740,7 +740,7 @@ SELECT
{
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD is_compilation BOOLEAN NOT NULL DEFAULT(false)");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -805,7 +805,7 @@ SELECT
for (const auto& indexName : indexeNames)
utils::executeCommand(*session.getDboSession(), "DROP INDEX " + indexName);
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -868,7 +868,7 @@ SELECT
utils::executeCommand(*session.getDboSession(), "DROP TABLE artist");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE artist_backup RENAME TO artist");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -903,7 +903,7 @@ SELECT
constraint "fk_track_lyrics_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred
))");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -937,7 +937,7 @@ SELECT
// Add catalog number
utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD barcode TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
@@ -968,6 +968,67 @@ SELECT
utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD bcrypt_round_count INTEGER NOT NULL DEFAULT(7)");
}
void migrateFromV76(Session& session)
{
// Rename public -> visibility (false is Private(0) and true is Public(1))
utils::executeCommand(*session.getDboSession(), "ALTER TABLE tracklist RENAME COLUMN public TO visibility");
// Supported extensions are now runtime
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings DROP COLUMN audio_file_extensions");
// Add PlayListFile
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "playlist_file" (
"id" integer primary key autoincrement,
"version" integer not null,
"absolute_file_path" text not null,
"file_stem" text not null,
"file_size" bigint not null,
"file_last_write" text,
"name" text not null,
"entries" text not null,
"media_library_id" bigint,
"directory_id" bigint,
constraint "fk_playlist_file_media_library" foreign key ("media_library_id") references "media_library" ("id") on delete set null deferrable initially deferred,
constraint "fk_playlist_file_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred
))");
// Add a link into tracklist to ease cleanup
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "tracklist_backup" (
"id" integer primary key autoincrement,
"version" integer not null,
"name" text not null,
"type" integer not null,
"visibility" integer not null,
"creation_date_time" text,
"last_modified_date_time" text,
"user_id" bigint,
"playlist_file_id" bigint,
constraint "fk_tracklist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred,
constraint "fk_tracklist_playlist_file" foreign key ("playlist_file_id") references "playlist_file" ("id") on delete cascade deferrable initially deferred))");
utils::executeCommand(*session.getDboSession(), R"(INSERT INTO tracklist_backup
SELECT
id,
version,
name,
type,
visibility,
creation_date_time,
last_modified_date_time,
user_id,
NULL AS playlist_file_id
FROM tracklist)");
utils::executeCommand(*session.getDboSession(), R"(DROP TABLE tracklist)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE tracklist_backup RENAME TO tracklist)");
// Add a file name in tracks
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD COLUMN file_name TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1020,6 +1081,7 @@ SELECT
{ 73, migrateFromV73 },
{ 74, migrateFromV74 },
{ 75, migrateFromV75 },
{ 76, migrateFromV76 },
};
bool migrationPerformed{};
+142
View File
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*/
#include "database/PlayListFile.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Directory.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
namespace lms::db
{
PlayListFile::PlayListFile(const std::filesystem::path& file)
{
setAbsoluteFilePath(file);
}
PlayListFile::pointer PlayListFile::create(Session& session, const std::filesystem::path& file)
{
return session.getDboSession()->add(std::unique_ptr<PlayListFile>{ new PlayListFile{ file } });
}
std::size_t PlayListFile::getCount(Session& session)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM playlist_file"));
}
PlayListFile::pointer PlayListFile::find(Session& session, const std::filesystem::path& p)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<PlayListFile>>("SELECT pl_f from playlist_file pl_f").where("pl_f.absolute_file_path = ?").bind(p.string()));
}
void PlayListFile::find(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function<void(const pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<PlayListFile>>("SELECT pl_f from playlist_file pl_f").orderBy("pl_f.id").where("pl_f.id > ?").bind(lastRetrievedId).limit(static_cast<int>(count)) };
utils::forEachQueryResult(query, [&](const PlayListFile::pointer& playList) {
func(playList);
lastRetrievedId = playList->getId();
});
}
PlayListFile::pointer PlayListFile::find(Session& session, PlayListFileId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<PlayListFile>>("SELECT pl_f from playlist_file pl_f").where("pl_f.id = ?").bind(id));
}
std::vector<std::filesystem::path> PlayListFile::getFiles() const
{
std::vector<std::filesystem::path> files;
{
Wt::Json::Object root;
Wt::Json::parse(_entries, root);
assert(root.type("files") == Wt::Json::Type::Array);
const Wt::Json::Array& filesArray = root.get("files");
for (const Wt::Json::Value& file : filesArray)
files.push_back(static_cast<std::string>(file.toString()));
}
return files;
}
TrackList::pointer PlayListFile::getTrackList() const
{
return _trackList.lock();
}
Directory::pointer PlayListFile::getDirectory() const
{
return _directory;
}
void PlayListFile::setAbsoluteFilePath(const std::filesystem::path& filePath)
{
assert(filePath.is_absolute());
_absoluteFilePath = filePath;
_fileStem = filePath.stem();
}
void PlayListFile::setDirectory(ObjectPtr<Directory> directory)
{
_directory = getDboPtr(directory);
}
void PlayListFile::setTrackList(ObjectPtr<TrackList> trackList)
{
_trackList = getDboPtr(trackList);
}
void PlayListFile::setName(std::string_view name)
{
_name = std::string{ name, 0, _maxNameLength };
if (name.size() > _maxNameLength)
LMS_LOG(DB, WARNING, "PlaylistFile name too long, truncated to '" << _name << "'");
}
void PlayListFile::setFiles(std::span<const std::filesystem::path> files)
{
Wt::Json::Object root;
Wt::Json::Array fileArray;
for (const auto& file : files)
fileArray.push_back(Wt::Json::Value{ file.string() });
root["files"] = std::move(fileArray);
_entries = Wt::Json::serialize(root);
}
} // namespace lms::db
-11
View File
@@ -46,17 +46,6 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->find<ScanSettings>());
}
std::vector<std::filesystem::path> ScanSettings::getAudioFileExtensions() const
{
const auto extensions{ core::stringUtils::splitString(_audioFileExtensions, ' ') };
std::vector<std::filesystem::path> res(std::cbegin(extensions), std::cend(extensions));
std::sort(std::begin(res), std::end(res));
res.erase(std::unique(std::begin(res), std::end(res)), std::end(res));
return res;
}
std::vector<std::string_view> ScanSettings::getExtraTagsToScan() const
{
std::vector<std::string_view> tags{ core::stringUtils::splitString(_extraTagsToScan, ';') };
+85 -74
View File
@@ -30,6 +30,7 @@
#include "database/Image.hpp"
#include "database/Listen.hpp"
#include "database/MediaLibrary.hpp"
#include "database/PlayListFile.hpp"
#include "database/PlayQueue.hpp"
#include "database/RatedArtist.hpp"
#include "database/RatedRelease.hpp"
@@ -105,6 +106,7 @@ namespace lms::db
_session.mapClass<Label>("label");
_session.mapClass<Listen>("listen");
_session.mapClass<MediaLibrary>("media_library");
_session.mapClass<PlayListFile>("playlist_file");
_session.mapClass<PlayQueue>("playqueue");
_session.mapClass<RatedArtist>("rated_artist");
_session.mapClass<RatedRelease>("rated_release");
@@ -181,98 +183,107 @@ namespace lms::db
LMS_SCOPED_TRACE_OVERVIEW("Database", "IndexCreation");
LMS_LOG(DB, INFO, "Creating indexes... This may take a while...");
auto transaction{ createWriteTransaction() };
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_id_idx ON artist(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_image_idx ON artist(image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)");
{
auto transaction{ createWriteTransaction() };
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_id_idx ON artist(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_image_idx ON artist(image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_user_domain_idx ON auth_token(user_id, domain)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_domain_expiry_idx ON auth_token(domain, expiry)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_domain_value_idx ON auth_token(domain, value)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_user_domain_idx ON auth_token(user_id, domain)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_domain_expiry_idx ON auth_token(domain, expiry)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_domain_value_idx ON auth_token(domain, value)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS cluster_cluster_type_idx ON cluster(cluster_type_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS cluster_cluster_type_idx ON cluster(cluster_type_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_id_idx ON directory(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_parent_directory_idx ON directory(parent_directory_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_path_idx ON directory(absolute_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_media_library_idx ON directory(media_library_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_id_idx ON directory(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_parent_directory_idx ON directory(parent_directory_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_path_idx ON directory(absolute_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_media_library_idx ON directory(media_library_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS label_name_idx ON label(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS label_name_idx ON label(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_id_idx ON listen(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_backend_date_time_idx ON listen(user_id, backend, date_time DESC)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_track_user_backend_idx ON listen(track_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_track_backend_date_time_idx ON listen(user_id,track_id,backend,date_time)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_id_idx ON listen(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_backend_date_time_idx ON listen(user_id, backend, date_time DESC)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_track_user_backend_idx ON listen(track_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS listen_user_track_backend_date_time_idx ON listen(user_id,track_id,backend,date_time)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS media_library_id_idx ON media_library(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS media_library_id_idx ON media_library(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_artist_user_artist_idx ON rated_artist(user_id,artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_release_user_release_idx ON rated_release(user_id,release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_track_user_track_idx ON rated_track(user_id,track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_id_idx ON playlist_file(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_absolute_file_path_idx ON playlist_file(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_id_idx ON release(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_image_idx ON release(image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_mbid_idx ON release(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_name_nocase_idx ON release(name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_type_name_idx ON release_type(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_artist_user_artist_idx ON rated_artist(user_id,artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_release_user_release_idx ON rated_release(user_id,release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_track_user_track_idx ON rated_track(user_id,track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_id_idx ON track(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_absolute_path_idx ON track(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_release_idx ON track(directory_id, release_id);");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_file_stem_idx ON track(directory_id, file_stem);");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_last_write_idx ON track(file_last_write)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_idx ON track(media_library_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_release_idx ON track(media_library_id, release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_idx ON track(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_nocase_idx ON track(name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_year_idx ON track(original_year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_last_write_idx ON track(release_id, file_last_write)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_year_idx ON track(release_id, year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_year_idx ON track(year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_id_idx ON release(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_image_idx ON release(image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_mbid_idx ON release(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_name_nocase_idx ON release(name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS release_type_name_idx ON release_type(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_user_idx ON tracklist(user_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_id_idx ON track(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_absolute_path_idx ON track(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_release_idx ON track(directory_id, release_id);");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_file_stem_idx ON track(directory_id, file_stem);");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_last_write_idx ON track(file_last_write)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_idx ON track(media_library_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_release_idx ON track(media_library_id, release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_idx ON track(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_nocase_idx ON track(name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_year_idx ON track(original_year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_last_write_idx ON track(release_id, file_last_write)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_year_idx ON track(release_id, year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_year_idx ON track(year)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_idx ON track_artist_link(artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_track_idx ON track_artist_link(artist_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_artist_idx ON track_artist_link(track_id, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_type_idx ON track_artist_link(track_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_type_track_artist_idx ON track_artist_link(type, track_id, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_user_type_idx ON tracklist(user_id, type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_last_modified_date_time_idx ON tracklist(last_modified_date_time)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_entry_idx ON tracklist_entry(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_entry_tracklist_track_idx ON tracklist_entry(tracklist_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_id_idx ON track_lyrics(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_absolute_file_path_idx ON track_lyrics(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_track_idx ON track_lyrics(track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_idx ON track_artist_link(artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_track_idx ON track_artist_link(artist_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_artist_idx ON track_artist_link(track_id, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_type_idx ON track_artist_link(track_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_type_track_artist_idx ON track_artist_link(type, track_id, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_artist_user_backend_idx ON starred_artist(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_artist_artist_user_backend_idx ON starred_artist(artist_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_id_idx ON track_lyrics(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_absolute_file_path_idx ON track_lyrics(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_track_idx ON track_lyrics(track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_release_user_backend_idx ON starred_release(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_release_release_user_backend_idx ON starred_release(release_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_track_user_backend_idx ON starred_track(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_track_track_user_backend_idx ON starred_track(track_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_artist_user_backend_idx ON starred_artist(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_artist_artist_user_backend_idx ON starred_artist(artist_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_release_user_backend_idx ON starred_release(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_release_release_user_backend_idx ON starred_release(release_id,user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_track_user_backend_idx ON starred_track(user_id,backend)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS starred_track_track_user_backend_idx ON starred_track(track_id,user_id,backend)");
}
LMS_LOG(DB, INFO, "Indexes created!");
}
+8 -2
View File
@@ -54,8 +54,11 @@ namespace lms::db
for (std::string_view keyword : params.keywords)
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%");
if (!params.stem.empty())
query.where("t.file_stem = ?").bind(params.stem);
if (!params.fileStem.empty())
query.where("t.file_stem = ?").bind(params.fileStem);
if (!params.fileName.empty())
query.where("t.file_name = ?").bind(params.fileName);
if (!params.name.empty())
query.where("t.name = ?").bind(params.name);
@@ -375,8 +378,10 @@ namespace lms::db
void Track::setAbsoluteFilePath(const std::filesystem::path& filePath)
{
assert(filePath.is_absolute());
_absoluteFilePath = filePath;
_fileStem = filePath.stem();
_fileName = filePath.filename();
}
void Track::setRelativeFilePath(const std::filesystem::path& filePath)
@@ -385,6 +390,7 @@ namespace lms::db
assert(_absoluteFilePath.filename() == filePath.filename()); // must be compatible with previous setAbsoluteFilePath call
_fileStem = filePath.stem(); // lazy migration (_fileStem added later, could be set only with setAbsoluteFilePath)
_fileName = filePath.filename(); // lazy migration (_fileName added later, could be set only with setAbsoluteFilePath)
_relativeFilePath = filePath;
}
+25 -20
View File
@@ -20,10 +20,8 @@
#include <cassert>
#include "core/ILogger.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
#include "database/PlayListFile.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
@@ -40,10 +38,13 @@ namespace lms::db
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const TrackList::FindParameters& params)
{
auto query{ session.getDboSession()->query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " FROM tracklist t_l") };
auto query{ session.getDboSession()->query<ResultType>("SELECT " + std::string{ itemToSelect } + " FROM tracklist t_l") };
if (!params.clusters.empty() || params.mediaLibrary.isValid())
{
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
query.groupBy("t_l.id");
}
if (params.mediaLibrary.isValid())
query.join("track t ON t.id = t_l_e.track_id");
@@ -57,6 +58,9 @@ namespace lms::db
if (params.type)
query.where("t_l.type = ?").bind(*params.type);
if (params.visibility)
query.where("t_l.visibility = ?").bind(*params.visibility);
if (!params.clusters.empty())
{
std::ostringstream oss;
@@ -108,20 +112,17 @@ namespace lms::db
}
} // namespace
TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
TrackList::TrackList(std::string_view name, TrackListType type)
: _name{ name }
, _type{ type }
, _isPublic{ isPublic }
, _creationDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) }
, _lastModifiedDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) }
, _user{ getDboPtr(user) }
{
assert(user);
}
TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type)
{
return session.getDboSession()->add(std::unique_ptr<TrackList>{ new TrackList{ name, type, isPublic, user } });
return session.getDboSession()->add(std::unique_ptr<TrackList>{ new TrackList{ name, type } });
}
std::size_t TrackList::getCount(Session& session)
@@ -311,20 +312,24 @@ namespace lms::db
return session.getDboSession()->add(std::unique_ptr<TrackListEntry>{ new TrackListEntry{ track, tracklist, dateTime } });
}
void TrackListEntry::onPostCreated()
{
_tracklist.modify()->setLastModifiedDateTime(utils::normalizeDateTime(Wt::WDateTime::currentDateTime()));
}
void TrackListEntry::onPreRemove()
{
_tracklist.modify()->setLastModifiedDateTime(utils::normalizeDateTime(Wt::WDateTime::currentDateTime()));
}
TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackListEntry>().where("id = ?").bind(id));
}
void TrackListEntry::find(Session& session, const FindParameters& params, const std::function<void(const TrackListEntry::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackListEntry>>("SELECT t_l_e FROM tracklist_entry t_l_e") };
if (params.trackList.isValid())
query.where("t_l_e.tracklist_id = ?").bind(params.trackList);
query.orderBy("t_l_e.id");
utils::forEachQueryRangeResult(query, params.range, func);
}
} // namespace lms::db
@@ -57,8 +57,6 @@ namespace lms::db
TransactionChecker::checkWriteTransaction(*_obj.session());
#endif
if (_obj->hasOnPreRemove())
_obj.modify()->onPreRemove();
_obj.remove();
}
@@ -87,12 +85,6 @@ namespace lms::db
template<typename>
friend class ObjectPtr;
virtual bool hasOnPreRemove() const { return false; }
virtual void onPreRemove() {}
virtual bool hasOnPostCreated() const { return false; }
virtual void onPostCreated() {}
// Can get raw dbo ptr only from Objects
template<typename SomeObject>
static Wt::Dbo::ptr<SomeObject> getDboPtr(const ObjectPtr<SomeObject>& ptr)
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <filesystem>
#include <span>
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "database/DirectoryId.hpp"
#include "database/Object.hpp"
LMS_DECLARE_IDTYPE(PlayListFileId)
namespace lms::db
{
class Session;
class Directory;
class MediaLibrary;
class TrackList;
class PlayListFile final : public Object<PlayListFile, PlayListFileId>
{
public:
PlayListFile() = default;
// find
static std::size_t getCount(Session& session);
static pointer find(Session& session, PlayListFileId id);
static pointer find(Session& session, const std::filesystem::path& path);
static void find(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function<void(const pointer&)>& func);
// getters
const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; }
const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; }
std::size_t getFileSize() const { return _fileSize; }
std::string_view getName() const { return _name; }
std::vector<std::filesystem::path> getFiles() const;
ObjectPtr<TrackList> getTrackList() const;
ObjectPtr<Directory> getDirectory() const;
DirectoryId getDirectoryId() const { return _directory.id(); }
// setters
void setAbsoluteFilePath(const std::filesystem::path& filePath);
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setFileSize(std::size_t fileSize) { _fileSize = fileSize; }
void setMediaLibrary(ObjectPtr<MediaLibrary> mediaLibrary) { _mediaLibrary = getDboPtr(mediaLibrary); }
void setDirectory(ObjectPtr<Directory> directory);
void setTrackList(ObjectPtr<TrackList> trackList);
void setName(std::string_view name);
void setFiles(std::span<const std::filesystem::path> files);
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _absoluteFilePath, "absolute_file_path");
Wt::Dbo::field(a, _fileStem, "file_stem");
Wt::Dbo::field(a, _fileSize, "file_size");
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _entries, "entries");
Wt::Dbo::belongsTo(a, _mediaLibrary, "media_library", Wt::Dbo::OnDeleteSetNull); // don't delete playlist on media library removal, we want to wait for the next scan to have a chance to migrate files
Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasOne(a, _trackList, "playlist_file");
}
private:
friend class Session;
PlayListFile(const std::filesystem::path& file);
static pointer create(Session& session, const std::filesystem::path& file);
static constexpr std::size_t _maxNameLength{ 512 };
std::filesystem::path _absoluteFilePath;
std::string _fileStem;
Wt::WDateTime _fileLastWrite;
Wt::WDateTime _fileAdded;
long long _fileSize{};
std::string _name;
std::string _entries; // A json encoded list of files
Wt::Dbo::ptr<MediaLibrary> _mediaLibrary;
Wt::Dbo::ptr<Directory> _directory;
Wt::Dbo::weak_ptr<TrackList> _trackList;
};
} // namespace lms::db
@@ -67,7 +67,6 @@ namespace lms::db
Wt::WTime getUpdateStartTime() const { return _startTime; }
UpdatePeriod getUpdatePeriod() const { return _updatePeriod; }
std::vector<std::string_view> getExtraTagsToScan() const;
std::vector<std::filesystem::path> getAudioFileExtensions() const;
SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; }
std::vector<std::string> getArtistTagDelimiters() const;
std::vector<std::string> getDefaultTagDelimiters() const;
@@ -87,7 +86,6 @@ namespace lms::db
Wt::Dbo::field(a, _scanVersion, "scan_version");
Wt::Dbo::field(a, _startTime, "start_time");
Wt::Dbo::field(a, _updatePeriod, "update_period");
Wt::Dbo::field(a, _audioFileExtensions, "audio_file_extensions");
Wt::Dbo::field(a, _similarityEngineType, "similarity_engine_type");
Wt::Dbo::field(a, _extraTagsToScan, "extra_tags_to_scan");
Wt::Dbo::field(a, _artistTagDelimiters, "artist_tag_delimiters");
@@ -99,7 +97,6 @@ namespace lms::db
Wt::WTime _startTime = Wt::WTime{ 0, 0, 0 };
UpdatePeriod _updatePeriod{ UpdatePeriod::Never };
SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters };
std::string _audioFileExtensions{ ".alac .mp3 .ogg .oga .aac .m4a .m4b .flac .wav .wma .aif .aiff .ape .mpc .shn .opus .wv .dsf" };
std::string _extraTagsToScan;
std::string _artistTagDelimiters;
std::string _defaultTagDelimiters;
@@ -27,7 +27,6 @@
#include "core/ITraceLogger.hpp"
#include "core/RecursiveSharedMutex.hpp"
#include "database/Object.hpp"
#include "database/TransactionChecker.hpp"
namespace lms::db
@@ -69,7 +68,10 @@ namespace lms::db
class Session
{
public:
Session(Db& database);
Session(Db& db);
~Session() = default;
Session(const Session&) = delete;
Session& operator=(const Session&) = delete;
[[nodiscard]] WriteTransaction createWriteTransaction();
[[nodiscard]] ReadTransaction createReadTransaction();
@@ -119,16 +121,10 @@ namespace lms::db
typename Object::pointer res{ Object::create(*this, std::forward<Args>(args)...) };
getDboSession()->flush();
if (res->hasOnPostCreated())
res.modify()->onPostCreated();
return res;
}
private:
Session(const Session&) = delete;
Session& operator=(const Session&) = delete;
Db& _db;
Wt::Dbo::Session _session;
};
+13 -5
View File
@@ -26,7 +26,6 @@
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <Wt/Dbo/Dbo.h>
@@ -67,8 +66,9 @@ namespace lms::db
{
std::vector<ClusterId> clusters; // if non empty, tracks that belong to these clusters
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
std::string name; // if non empty, must match this name
std::string stem; // if non empty, must match this stem
std::string name; // if non empty, must match this name (title)
std::string fileStem; // if non empty, must match this file stem
std::string fileName; // if non empty, must match this file name
TrackSortMethod sortMethod{ TrackSortMethod::None };
std::optional<Range> range;
Wt::WDateTime writtenAfter;
@@ -102,11 +102,17 @@ namespace lms::db
name = _name;
return *this;
}
FindParameters& setStem(std::string_view _stem)
FindParameters& setFileStem(std::string_view _fileStem)
{
stem = _stem;
fileStem = _fileStem;
return *this;
}
FindParameters& setFileName(std::string_view _fileName)
{
fileName = _fileName;
return *this;
}
FindParameters& setSortMethod(TrackSortMethod _method)
{
sortMethod = _method;
@@ -317,6 +323,7 @@ namespace lms::db
Wt::Dbo::field(a, _absoluteFilePath, "absolute_file_path");
Wt::Dbo::field(a, _relativeFilePath, "relative_file_path");
Wt::Dbo::field(a, _fileStem, "file_stem");
Wt::Dbo::field(a, _fileName, "file_name");
Wt::Dbo::field(a, _fileSize, "file_size");
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
Wt::Dbo::field(a, _fileAdded, "file_added");
@@ -364,6 +371,7 @@ namespace lms::db
std::filesystem::path _absoluteFilePath; // full path
std::filesystem::path _relativeFilePath; // relative to root (that may be deleted)
std::filesystem::path _fileStem;
std::filesystem::path _fileName;
long long _fileSize{};
Wt::WDateTime _fileLastWrite;
Wt::WDateTime _fileAdded;
@@ -41,6 +41,7 @@ namespace lms::db
class Artist;
class Cluster;
class ClusterType;
class PlayListFile;
class Release;
class Session;
class Track;
@@ -52,6 +53,12 @@ namespace lms::db
public:
TrackList() = default;
enum class Visibility
{
Private = 0,
Public = 1,
};
// Search utility
struct FindParameters
{
@@ -61,6 +68,7 @@ namespace lms::db
UserId user; // only tracklists owned by this user
MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library
TrackListSortMethod sortMethod{ TrackListSortMethod::None };
std::optional<Visibility> visibility;
FindParameters& setClusters(std::span<const ClusterId> _clusters)
{
@@ -92,6 +100,11 @@ namespace lms::db
sortMethod = _sortMethod;
return *this;
}
FindParameters& setVisibility(std::optional<Visibility> _visibility)
{
visibility = _visibility;
return *this;
}
};
static std::size_t getCount(Session& session);
static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
@@ -101,13 +114,17 @@ namespace lms::db
// Accessors
std::string_view getName() const { return _name; }
bool isPublic() const { return _isPublic; }
Visibility getVisibility() const { return _visibility; }
TrackListType getType() const { return _type; }
ObjectPtr<User> getUser() const { return _user; }
UserId getUserId() const { return _user.id(); }
Wt::WDateTime getLastModifiedDateTime() const { return _lastModifiedDateTime; }
Wt::WDateTime getCreationDateTime() const { return _creationDateTime; }
// Modifiers
void setName(const std::string& name) { _name = name; }
void setIsPublic(bool isPublic) { _isPublic = isPublic; }
void setUser(ObjectPtr<User> user) { _user = getDboPtr(user); }
void setName(std::string_view name) { _name = name; }
void setVisibility(Visibility visibility) { _visibility = visibility; }
void clear() { _entries.clear(); }
// Get tracks, ordered by position
@@ -134,26 +151,29 @@ namespace lms::db
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _isPublic, "public");
Wt::Dbo::field(a, _visibility, "visibility");
Wt::Dbo::field(a, _creationDateTime, "creation_date_time");
Wt::Dbo::field(a, _lastModifiedDateTime, "last_modified_date_time");
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); // optional
Wt::Dbo::belongsTo(a, _playListFile, "playlist_file", Wt::Dbo::OnDeleteCascade); // optional
Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist");
}
private:
friend class Session;
TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
TrackList(std::string_view name, TrackListType type);
static pointer create(Session& session, std::string_view name, TrackListType type);
std::string _name;
TrackListType _type{ TrackListType::Playlist };
bool _isPublic{ false };
TrackListType _type{ TrackListType::PlayList };
Visibility _visibility{ Visibility::Private };
Wt::WDateTime _creationDateTime;
Wt::WDateTime _lastModifiedDateTime;
Wt::Dbo::ptr<User> _user;
Wt::Dbo::ptr<PlayListFile> _playListFile;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _entries;
};
@@ -162,16 +182,29 @@ namespace lms::db
public:
TrackListEntry() = default;
bool hasOnPostCreated() const override { return true; }
void onPostCreated() override;
bool hasOnPreRemove() const override { return true; }
void onPreRemove() override;
// find utility
// Search utility
struct FindParameters
{
TrackListId trackList;
std::optional<Range> range;
FindParameters& setTrackList(TrackListId _trackList)
{
trackList = _trackList;
return *this;
}
FindParameters& setRange(std::optional<Range> _range)
{
range = _range;
return *this;
}
};
static pointer getById(Session& session, TrackListEntryId id);
static void find(Session& session, const FindParameters& params, const std::function<void(const TrackListEntry::pointer&)>& func);
// Accessors
TrackId getTrackId() const { return _track.id(); }
ObjectPtr<Track> getTrack() const { return _track; }
const Wt::WDateTime& getDateTime() const { return _dateTime; }
+2 -2
View File
@@ -246,7 +246,7 @@ namespace lms::db
enum class TrackListType
{
Playlist, // user controlled playlists
Internal, // internal usage (current playqueue, history, ...)
PlayList = 0, // user controlled playlists
Internal = 1, // internal usage (current playqueue, history, ...)
};
} // namespace lms::db
+1
View File
@@ -9,6 +9,7 @@ add_executable(test-database
Image.cpp
Listen.cpp
Migration.cpp
PlayListFile.cpp
RatedArtist.cpp
RatedRelease.cpp
RatedTrack.cpp
+3 -5
View File
@@ -718,8 +718,7 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::PlayList };
ScopedClusterType clusterType{ session, "MyClusterType" };
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
std::list<ScopedTrack> tracks;
@@ -743,15 +742,14 @@ namespace lms::db::tests
const auto similarTracks{ trackList->getSimilarTracks() };
EXPECT_EQ(similarTracks.size(), 5);
for (auto similarTrack : similarTracks)
for (const auto& similarTrack : similarTracks)
EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); }));
}
}
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::PlayList };
ScopedClusterType clusterType{ session, "MyClusterType" };
ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" };
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" };
+2
View File
@@ -24,6 +24,7 @@
#include "database/Db.hpp"
#include "database/Directory.hpp"
#include "database/Image.hpp"
#include "database/PlayListFile.hpp"
#include "database/PlayQueue.hpp"
#include "database/RatedArtist.hpp"
#include "database/RatedRelease.hpp"
@@ -345,6 +346,7 @@ VALUES
EXPECT_FALSE(Image::find(session, ImageId{}));
EXPECT_FALSE(Label::find(session, LabelId{}));
EXPECT_FALSE(Listen::find(session, ListenId{}));
EXPECT_FALSE(PlayListFile::find(session, PlayListFileId{}));
EXPECT_FALSE(PlayQueue::find(session, PlayQueueId{}));
EXPECT_FALSE(RatedArtist::find(session, RatedArtistId{}));
EXPECT_FALSE(RatedRelease::find(session, RatedReleaseId{}));
+114
View File
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*/
#include "Common.hpp"
#include "database/PlayListFile.hpp"
#include "database/TrackList.hpp"
namespace lms::db::tests
{
using ScopedPlayListFile = ScopedEntity<db::PlayListFile>;
TEST_F(DatabaseFixture, PlayListFile)
{
{
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(PlayListFile::getCount(session), 0);
}
ScopedPlayListFile playlist{ session, "/tmp/foo.m3u" };
{
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(PlayListFile::getCount(session), 1);
}
{
auto transaction{ session.createReadTransaction() };
const PlayListFile::pointer dbPlayList{ playlist.get() };
EXPECT_EQ(dbPlayList->getAbsoluteFilePath(), "/tmp/foo.m3u");
EXPECT_EQ(dbPlayList->getLastWriteTime(), Wt::WDateTime{});
EXPECT_EQ(dbPlayList->getFileSize(), 0);
EXPECT_EQ(dbPlayList->getName(), "");
EXPECT_EQ(dbPlayList->getTrackList(), TrackList::pointer{});
}
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::PlayList };
// Now change some values
{
auto transaction{ session.createWriteTransaction() };
PlayListFile::pointer dbPlayList{ playlist.get() };
dbPlayList.modify()->setAbsoluteFilePath("/tmp/bar.m3u");
dbPlayList.modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2024, 30, 1 }, Wt::WTime{ 12, 58, 29 } });
dbPlayList.modify()->setFileSize(1234);
dbPlayList.modify()->setName("My playlist");
dbPlayList.modify()->setTrackList(trackList.get());
dbPlayList.modify()->setFiles({ { std::filesystem::path{ "/foo/foo.mp3" }, std::filesystem::path{ "/foo/bar.mp3" } } });
}
// Check values are reflected
{
auto transaction{ session.createReadTransaction() };
const PlayListFile::pointer dbPlayList{ playlist.get() };
EXPECT_EQ(dbPlayList->getAbsoluteFilePath(), "/tmp/bar.m3u");
EXPECT_EQ(dbPlayList->getLastWriteTime(), (Wt::WDateTime{ Wt::WDate{ 2024, 30, 1 }, Wt::WTime{ 12, 58, 29 } }));
EXPECT_EQ(dbPlayList->getFileSize(), 1234);
EXPECT_EQ(dbPlayList->getName(), "My playlist");
EXPECT_EQ(dbPlayList->getTrackList(), trackList.get());
const auto files{ dbPlayList->getFiles() };
ASSERT_EQ(files.size(), 2);
EXPECT_EQ(files[0], "/foo/foo.mp3");
EXPECT_EQ(files[1], "/foo/bar.mp3");
}
}
TEST_F(DatabaseFixture, PlayListFile_deleteTrackList)
{
{
ScopedPlayListFile playlist{ session, "/tmp/foo.m3u" };
{
auto transaction{ session.createWriteTransaction() };
TrackList::pointer trackList{ session.create<TrackList>("MyTrackList", TrackListType::PlayList) };
playlist.get().modify()->setTrackList(trackList);
}
{
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(TrackList::getCount(session), 1);
}
}
{
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(PlayListFile::getCount(session), 0);
}
{
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(TrackList::getCount(session), 0);
}
}
} // namespace lms::db::tests
+37 -18
View File
@@ -25,13 +25,12 @@ namespace lms::db::tests
{
TEST_F(DatabaseFixture, SingleTrackList)
{
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(TrackList::getCount(session), 0);
}
ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList{ session, "MytrackList", TrackListType::PlayList };
{
auto transaction{ session.createReadTransaction() };
@@ -41,9 +40,8 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::PlayList };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::PlayList };
ScopedTrack track{ session };
{
@@ -76,9 +74,8 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, TrackList_SortMethod)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::PlayList };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::PlayList };
ScopedTrack track{ session };
{
@@ -125,8 +122,7 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList{ session, "MytrackList", TrackListType::PlayList };
std::list<ScopedTrack> tracks;
for (std::size_t i{}; i < 10; ++i)
@@ -153,9 +149,8 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, TrackList_MediaLibrary)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1{ session, "MytrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2{ session, "MytrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList1{ session, "MytrackList1", TrackListType::PlayList };
ScopedTrackList trackList2{ session, "MytrackList2", TrackListType::PlayList };
ScopedTrack track1{ session };
ScopedTrack track2{ session };
ScopedMediaLibrary library{ session, "MyLibrary", "/root" };
@@ -191,9 +186,8 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::PlayList };
ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::PlayList };
ScopedClusterType clusterType{ session, "MyClusterType" };
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
ScopedTrack track{ session };
@@ -223,8 +217,7 @@ namespace lms::db::tests
TEST_F(DatabaseFixture, SingleTrackList_getEntries)
{
ScopedUser user{ session, "MyUser" };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() };
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::PlayList };
ScopedTrack track1{ session };
ScopedTrack track2{ session };
@@ -260,4 +253,30 @@ namespace lms::db::tests
EXPECT_EQ(entries.results[0]->getTrack()->getId(), track2.getId());
}
}
TEST_F(DatabaseFixture, SingleTrackList_visitEntries)
{
ScopedTrackList trackList{ session, "MyTrackList", TrackListType::PlayList };
ScopedTrack track1{ session };
ScopedTrack track2{ session };
{
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track1.get(), trackList.get());
session.create<TrackListEntry>(track2.get(), trackList.get());
}
{
auto transaction{ session.createReadTransaction() };
std::vector<TrackId> visitedTrackIds;
TrackListEntry::find(session, TrackListEntry::FindParameters{}.setTrackList(trackList.getId()), [&](const TrackListEntry::pointer& entry) {
visitedTrackIds.push_back(entry->getTrackId());
});
ASSERT_EQ(visitedTrackIds.size(), 2);
EXPECT_EQ(visitedTrackIds[0], track1.getId());
EXPECT_EQ(visitedTrackIds[1], track2.getId());
}
}
} // namespace lms::db::tests