Subsonic API: added bookmark support. Fixes #24
This commit is contained in:
@@ -46,6 +46,8 @@ lms_SOURCES = \
|
|||||||
$(srcdir)/database/SqlQuery.hpp \
|
$(srcdir)/database/SqlQuery.hpp \
|
||||||
$(srcdir)/database/Track.cpp \
|
$(srcdir)/database/Track.cpp \
|
||||||
$(srcdir)/database/Track.hpp \
|
$(srcdir)/database/Track.hpp \
|
||||||
|
$(srcdir)/database/TrackBookmark.cpp \
|
||||||
|
$(srcdir)/database/TrackBookmark.hpp \
|
||||||
$(srcdir)/database/User.cpp \
|
$(srcdir)/database/User.cpp \
|
||||||
$(srcdir)/database/User.hpp \
|
$(srcdir)/database/User.hpp \
|
||||||
$(srcdir)/image/Image.cpp \
|
$(srcdir)/image/Image.cpp \
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
#include "database/TrackBookmark.hpp"
|
||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "similarity/SimilaritySearcher.hpp"
|
#include "similarity/SimilaritySearcher.hpp"
|
||||||
@@ -48,6 +49,8 @@ using namespace Database;
|
|||||||
|
|
||||||
static const std::string genreClusterName {"GENRE"};
|
static const std::string genreClusterName {"GENRE"};
|
||||||
static const std::string reportedStarredDate {"2000-01-01T00:00:00"};
|
static const std::string reportedStarredDate {"2000-01-01T00:00:00"};
|
||||||
|
static const std::string reportedCreatedBookmarkDate {"2000-01-01T00:00:00"};
|
||||||
|
static const std::string reportedChangedBookmarkDate {"2000-01-01T00:00:00"};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<API::Subsonic::Id>
|
std::optional<API::Subsonic::Id>
|
||||||
@@ -412,6 +415,22 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
|
|||||||
return trackResponse;
|
return trackResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Response::Node
|
||||||
|
trackBookmarkToResponseNode(const TrackBookmark::pointer& trackBookmark)
|
||||||
|
{
|
||||||
|
Response::Node trackBookmarkNode;
|
||||||
|
|
||||||
|
trackBookmarkNode.setAttribute("position", std::to_string(trackBookmark->getOffset().count()));
|
||||||
|
if (!trackBookmark->getComment().empty())
|
||||||
|
trackBookmarkNode.setAttribute("comment", trackBookmark->getComment());
|
||||||
|
trackBookmarkNode.setAttribute("created", reportedCreatedBookmarkDate);
|
||||||
|
trackBookmarkNode.setAttribute("changed", reportedChangedBookmarkDate);
|
||||||
|
trackBookmarkNode.setAttribute("username", trackBookmark->getUser()->getLoginName());
|
||||||
|
|
||||||
|
return trackBookmarkNode;
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
Response::Node
|
Response::Node
|
||||||
releaseToResponseNode(const Release::pointer& release, Session& dbSession, const User::pointer& user, bool id3)
|
releaseToResponseNode(const Release::pointer& release, Session& dbSession, const User::pointer& user, bool id3)
|
||||||
@@ -1656,6 +1675,94 @@ handleUpdatePlaylistRequest(RequestContext& context)
|
|||||||
return Response::createOkResponse();
|
return Response::createOkResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Response
|
||||||
|
handleGetBookmarks(RequestContext& context)
|
||||||
|
{
|
||||||
|
auto transaction {context.dbSession.createSharedTransaction()};
|
||||||
|
|
||||||
|
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
|
||||||
|
if (!user)
|
||||||
|
throw UserNotAuthorizedError {};
|
||||||
|
|
||||||
|
const auto bookmarks {TrackBookmark::getByUser(context.dbSession, user)};
|
||||||
|
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
Response::Node& bookmarksNode {response.createNode("bookmarks")};
|
||||||
|
|
||||||
|
for (const TrackBookmark::pointer& bookmark : bookmarks)
|
||||||
|
{
|
||||||
|
Response::Node bookmarkNode {trackBookmarkToResponseNode(bookmark)};
|
||||||
|
bookmarkNode.addArrayChild("entry", trackToResponseNode(bookmark->getTrack(), context.dbSession, user));
|
||||||
|
|
||||||
|
bookmarksNode.addArrayChild("bookmark", std::move(bookmarkNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Response
|
||||||
|
handleCreateBookmark(RequestContext& context)
|
||||||
|
{
|
||||||
|
// Mandatory params
|
||||||
|
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
|
||||||
|
if (id.type != Id::Type::Track)
|
||||||
|
throw BadParameterGenericError {"id"};
|
||||||
|
|
||||||
|
unsigned long position {getMandatoryParameterAs<unsigned long>(context.parameters, "position")};
|
||||||
|
const std::optional<std::string> comment {getParameterAs<std::string>(context.parameters, "comment")};
|
||||||
|
|
||||||
|
auto transaction {context.dbSession.createUniqueTransaction()};
|
||||||
|
|
||||||
|
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
|
||||||
|
if (!user)
|
||||||
|
throw UserNotAuthorizedError {};
|
||||||
|
|
||||||
|
const Track::pointer track {Track::getById(context.dbSession, id.value)};
|
||||||
|
if (!track)
|
||||||
|
throw RequestedDataNotFoundError {};
|
||||||
|
|
||||||
|
// Replace any existing bookmark
|
||||||
|
auto bookmark {TrackBookmark::getByUser(context.dbSession, user, track)};
|
||||||
|
if (!bookmark)
|
||||||
|
bookmark = TrackBookmark::create(context.dbSession, user, track);
|
||||||
|
|
||||||
|
bookmark.modify()->setOffset(std::chrono::milliseconds {position});
|
||||||
|
if (comment)
|
||||||
|
bookmark.modify()->setComment(*comment);
|
||||||
|
|
||||||
|
return Response::createOkResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Response
|
||||||
|
handleDeleteBookmark(RequestContext& context)
|
||||||
|
{
|
||||||
|
// Mandatory params
|
||||||
|
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
|
||||||
|
if (id.type != Id::Type::Track)
|
||||||
|
throw BadParameterGenericError {"id"};
|
||||||
|
|
||||||
|
auto transaction {context.dbSession.createUniqueTransaction()};
|
||||||
|
|
||||||
|
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
|
||||||
|
if (!user)
|
||||||
|
throw UserNotAuthorizedError {};
|
||||||
|
|
||||||
|
const Track::pointer track {Track::getById(context.dbSession, id.value)};
|
||||||
|
if (!track)
|
||||||
|
throw RequestedDataNotFoundError {};
|
||||||
|
|
||||||
|
auto bookmark {TrackBookmark::getByUser(context.dbSession, user, track)};
|
||||||
|
if (!bookmark)
|
||||||
|
throw RequestedDataNotFoundError {};
|
||||||
|
|
||||||
|
bookmark.remove();
|
||||||
|
|
||||||
|
return Response::createOkResponse();
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
Response
|
Response
|
||||||
handleNotImplemented(RequestContext&)
|
handleNotImplemented(RequestContext&)
|
||||||
@@ -1898,9 +2005,9 @@ static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
|
|||||||
{"changePassword", {handleChangePassword, false}},
|
{"changePassword", {handleChangePassword, false}},
|
||||||
|
|
||||||
// Bookmarks
|
// Bookmarks
|
||||||
{"getBookmarks", {handleNotImplemented, false}},
|
{"getBookmarks", {handleGetBookmarks, false}},
|
||||||
{"createBookmarks", {handleNotImplemented, false}},
|
{"createBookmark", {handleCreateBookmark, false}},
|
||||||
{"deleteBookmarks", {handleNotImplemented, false}},
|
{"deleteBookmark", {handleDeleteBookmark, false}},
|
||||||
{"getPlayQueue", {handleNotImplemented, false}},
|
{"getPlayQueue", {handleNotImplemented, false}},
|
||||||
{"savePlayQueue", {handleNotImplemented, false}},
|
{"savePlayQueue", {handleNotImplemented, false}},
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ Response::Node::setValue(const std::string& value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Response::Node::setAttribute(const std::string& key, const std::string& value)
|
Response::Node::setAttribute(std::string_view key, std::string_view value)
|
||||||
{
|
{
|
||||||
_attributes[key] = value;
|
_attributes[std::string {key}] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +179,7 @@ class Response
|
|||||||
class Node
|
class Node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void setAttribute(const std::string& key, const std::string& value);
|
void setAttribute(std::string_view key, std::string_view value);
|
||||||
|
|
||||||
// A Node has either a value or some children
|
// A Node has either a value or some children
|
||||||
void setValue(const std::string& value);
|
void setValue(const std::string& value);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "Release.hpp"
|
#include "Release.hpp"
|
||||||
#include "ScanSettings.hpp"
|
#include "ScanSettings.hpp"
|
||||||
#include "Track.hpp"
|
#include "Track.hpp"
|
||||||
|
#include "TrackBookmark.hpp"
|
||||||
#include "TrackArtistLink.hpp"
|
#include "TrackArtistLink.hpp"
|
||||||
#include "TrackList.hpp"
|
#include "TrackList.hpp"
|
||||||
#include "TrackFeatures.hpp"
|
#include "TrackFeatures.hpp"
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
#define LMS_DATABASE_VERSION 9
|
#define LMS_DATABASE_VERSION 10
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
|
|
||||||
@@ -125,6 +126,20 @@ Session::doDatabaseMigrationIfNeeded()
|
|||||||
// 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 scheduled scan rescan everything
|
||||||
ScanSettings::get(*this).modify()->incScanVersion();
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
else if (version == 9)
|
||||||
|
{
|
||||||
|
_session.execute(R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
||||||
|
"id" integer primary key autoincrement,
|
||||||
|
"version" integer not null,
|
||||||
|
"offset" integer,
|
||||||
|
"comment" text not null,
|
||||||
|
"track_id" bigint,
|
||||||
|
"user_id" bigint,
|
||||||
|
constraint "fk_track_bookmark_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
||||||
|
constraint "fk_track_bookmark_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
||||||
|
);)");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
|
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
|
||||||
@@ -150,6 +165,7 @@ Session::Session(Db& db)
|
|||||||
_session.mapClass<Release>("release");
|
_session.mapClass<Release>("release");
|
||||||
_session.mapClass<ScanSettings>("scan_settings");
|
_session.mapClass<ScanSettings>("scan_settings");
|
||||||
_session.mapClass<Track>("track");
|
_session.mapClass<Track>("track");
|
||||||
|
_session.mapClass<TrackBookmark>("track_bookmark");
|
||||||
_session.mapClass<TrackArtistLink>("track_artist_link");
|
_session.mapClass<TrackArtistLink>("track_artist_link");
|
||||||
_session.mapClass<TrackFeatures>("track_features");
|
_session.mapClass<TrackFeatures>("track_features");
|
||||||
_session.mapClass<TrackList>("tracklist");
|
_session.mapClass<TrackList>("tracklist");
|
||||||
@@ -264,6 +280,8 @@ Session::prepareTables()
|
|||||||
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_name_idx ON track_artist_link(name)");
|
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_name_idx ON track_artist_link(name)");
|
||||||
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_track_idx ON track_artist_link(track_id)");
|
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_track_idx ON track_artist_link(track_id)");
|
||||||
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)");
|
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)");
|
||||||
|
_session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)");
|
||||||
|
_session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial settings tables
|
// Initial settings tables
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 "TrackBookmark.hpp"
|
||||||
|
|
||||||
|
#include "Session.hpp"
|
||||||
|
#include "Track.hpp"
|
||||||
|
#include "User.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
TrackBookmark::TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
|
||||||
|
: _user {user},
|
||||||
|
_track {track}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TrackBookmark::pointer
|
||||||
|
TrackBookmark::create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
|
||||||
|
{
|
||||||
|
session.checkUniqueLocked();
|
||||||
|
|
||||||
|
TrackBookmark::pointer res {session.getDboSession().add(std::make_unique<TrackBookmark>(user, track))};
|
||||||
|
session.getDboSession().flush();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TrackBookmark::pointer>
|
||||||
|
TrackBookmark::getAll(Session& session)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
Wt::Dbo::collection<TrackBookmark::pointer> res {session.getDboSession().find<TrackBookmark>()};
|
||||||
|
|
||||||
|
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TrackBookmark::pointer>
|
||||||
|
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
Wt::Dbo::collection<TrackBookmark::pointer> res
|
||||||
|
{
|
||||||
|
session.getDboSession().find<TrackBookmark>()
|
||||||
|
.where("user_id = ?").bind(user.id())
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackBookmark::pointer
|
||||||
|
TrackBookmark::getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
return session.getDboSession().find<TrackBookmark>()
|
||||||
|
.where("user_id = ?").bind(user.id())
|
||||||
|
.where("track_id = ?").bind(track.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackBookmark::pointer
|
||||||
|
TrackBookmark::getById(Session& session, IdType id)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
return session.getDboSession().find<TrackBookmark>()
|
||||||
|
.where("id = ?").bind(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 <chrono>
|
||||||
|
|
||||||
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
|
||||||
|
#include "Types.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
class Session;
|
||||||
|
class Track;
|
||||||
|
class User;
|
||||||
|
|
||||||
|
class TrackBookmark : public Wt::Dbo::Dbo<TrackBookmark>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using pointer = Wt::Dbo::ptr<TrackBookmark>;
|
||||||
|
|
||||||
|
TrackBookmark () = default;
|
||||||
|
TrackBookmark(Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
|
||||||
|
|
||||||
|
// utility
|
||||||
|
static pointer create(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
|
||||||
|
|
||||||
|
// Find utility functions
|
||||||
|
static std::vector<pointer> getAll(Session& session);
|
||||||
|
static std::vector<pointer> getByUser(Session& session, Wt::Dbo::ptr<User> user);
|
||||||
|
static pointer getByUser(Session& session, Wt::Dbo::ptr<User> user, Wt::Dbo::ptr<Track> track);
|
||||||
|
static pointer getById(Session& session, IdType id);
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
void setOffset(std::chrono::milliseconds offset) { _offset = offset; }
|
||||||
|
void setComment(std::string_view comment) { _comment = comment; }
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
std::chrono::milliseconds getOffset() const { return _offset; }
|
||||||
|
std::string_view getComment() const { return _comment; }
|
||||||
|
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
|
||||||
|
Wt::Dbo::ptr<User> getUser() const { return _user; }
|
||||||
|
|
||||||
|
template<class Action>
|
||||||
|
void persist(Action& a)
|
||||||
|
{
|
||||||
|
Wt::Dbo::field(a, _offset, "offset");
|
||||||
|
Wt::Dbo::field(a, _comment, "comment");
|
||||||
|
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||||
|
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
|
||||||
|
static const std::size_t _maxCommentLength = 128;
|
||||||
|
|
||||||
|
std::chrono::duration<int, std::milli> _offset;
|
||||||
|
std::string _comment;
|
||||||
|
|
||||||
|
Wt::Dbo::ptr<User> _user;
|
||||||
|
Wt::Dbo::ptr<Track> _track;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace database
|
||||||
|
|
||||||
|
|
||||||
@@ -164,6 +164,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
|
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
|
||||||
std::vector<Wt::Dbo::ptr<Release>> getStarredReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
|
std::vector<Wt::Dbo::ptr<Release>> getStarredReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
|
||||||
|
|
||||||
|
// Stars
|
||||||
void starTrack(Wt::Dbo::ptr<Track> track);
|
void starTrack(Wt::Dbo::ptr<Track> track);
|
||||||
void unstarTrack(Wt::Dbo::ptr<Track> track);
|
void unstarTrack(Wt::Dbo::ptr<Track> track);
|
||||||
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
|
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ test_database_SOURCES = \
|
|||||||
$(top_srcdir)/src/database/Session.cpp \
|
$(top_srcdir)/src/database/Session.cpp \
|
||||||
$(top_srcdir)/src/database/SqlQuery.cpp \
|
$(top_srcdir)/src/database/SqlQuery.cpp \
|
||||||
$(top_srcdir)/src/database/Track.cpp \
|
$(top_srcdir)/src/database/Track.cpp \
|
||||||
|
$(top_srcdir)/src/database/TrackBookmark.cpp \
|
||||||
$(top_srcdir)/src/database/User.cpp \
|
$(top_srcdir)/src/database/User.cpp \
|
||||||
$(top_srcdir)/src/utils/Logger.cpp \
|
$(top_srcdir)/src/utils/Logger.cpp \
|
||||||
$(top_srcdir)/src/utils/StreamLogger.cpp \
|
$(top_srcdir)/src/utils/StreamLogger.cpp \
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
#include "database/TrackBookmark.hpp"
|
||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ using ScopedCluster = ScopedEntity<Cluster>;
|
|||||||
using ScopedClusterType = ScopedEntity<ClusterType>;
|
using ScopedClusterType = ScopedEntity<ClusterType>;
|
||||||
using ScopedRelease = ScopedEntity<Release>;
|
using ScopedRelease = ScopedEntity<Release>;
|
||||||
using ScopedTrack = ScopedEntity<Track>;
|
using ScopedTrack = ScopedEntity<Track>;
|
||||||
|
using ScopedTrackBookmark = ScopedEntity<TrackBookmark>;
|
||||||
using ScopedTrackList = ScopedEntity<TrackList>;
|
using ScopedTrackList = ScopedEntity<TrackList>;
|
||||||
using ScopedUser = ScopedEntity<User>;
|
using ScopedUser = ScopedEntity<User>;
|
||||||
|
|
||||||
@@ -1266,6 +1268,42 @@ testMultipleTracksMultipleReleasesMultiClusters(Session& session)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleUserSingleBookmark(Session& session)
|
||||||
|
{
|
||||||
|
ScopedTrack track {session, "MyTrack"};
|
||||||
|
ScopedUser user {session, "MyUser", User::PasswordHash {}};
|
||||||
|
ScopedTrackBookmark bookmark {session, user.lockAndGet(), track.lockAndGet()};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
bookmark.get().modify()->setComment("MyComment");
|
||||||
|
bookmark.get().modify()->setOffset(std::chrono::milliseconds {5});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(TrackBookmark::getAll(session).size() == 1);
|
||||||
|
|
||||||
|
const auto bookmarks {TrackBookmark::getByUser(session, user.get())};
|
||||||
|
CHECK(bookmarks.size() == 1);
|
||||||
|
CHECK(bookmarks.back() == bookmark.get());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
auto userBookmark {TrackBookmark::getByUser(session, user.get(), track.get())};
|
||||||
|
CHECK(userBookmark);
|
||||||
|
CHECK(userBookmark == bookmark.get());
|
||||||
|
|
||||||
|
CHECK(userBookmark->getOffset() == std::chrono::milliseconds {5});
|
||||||
|
CHECK(userBookmark->getComment() == "MyComment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
testDatabaseEmpty(Session& session)
|
testDatabaseEmpty(Session& session)
|
||||||
@@ -1277,6 +1315,7 @@ testDatabaseEmpty(Session& session)
|
|||||||
CHECK(ClusterType::getAll(session).empty());
|
CHECK(ClusterType::getAll(session).empty());
|
||||||
CHECK(Release::getAll(session).empty());
|
CHECK(Release::getAll(session).empty());
|
||||||
CHECK(Track::getAll(session).empty());
|
CHECK(Track::getAll(session).empty());
|
||||||
|
CHECK(TrackBookmark::getAll(session).empty());
|
||||||
CHECK(TrackList::getAll(session).empty());
|
CHECK(TrackList::getAll(session).empty());
|
||||||
CHECK(User::getAll(session).empty());
|
CHECK(User::getAll(session).empty());
|
||||||
}
|
}
|
||||||
@@ -1353,6 +1392,8 @@ int main()
|
|||||||
RUN_TEST(testSingleTrackListMultipleTrackMultiClusters);
|
RUN_TEST(testSingleTrackListMultipleTrackMultiClusters);
|
||||||
RUN_TEST(testMultipleTracksMultipleArtistsMultiClusters);
|
RUN_TEST(testMultipleTracksMultipleArtistsMultiClusters);
|
||||||
RUN_TEST(testMultipleTracksMultipleReleasesMultiClusters);
|
RUN_TEST(testMultipleTracksMultipleReleasesMultiClusters);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleUserSingleBookmark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
|
|||||||
Reference in New Issue
Block a user