Rework DB session + transactions. Now can handle multiple read only transactions in parallel
This commit is contained in:
+12
-12
@@ -34,21 +34,21 @@ namespace UserInterface {
|
||||
Auth::Auth()
|
||||
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template"))
|
||||
{
|
||||
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDb().getAuthService(), LmsApp->getDb().getUserDatabase());
|
||||
_model->addPasswordAuth(&Database::Handler::getPasswordService());
|
||||
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDbSession().getAuthService(), LmsApp->getDbSession().getUserDatabase());
|
||||
_model->addPasswordAuth(&Database::Session::getPasswordService());
|
||||
|
||||
// LoginName
|
||||
setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>());
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto demoUser = Database::User::getDemo(LmsApp->getDboSession());
|
||||
auto demoUser = Database::User::getDemo(LmsApp->getDbSession());
|
||||
if (demoUser)
|
||||
{
|
||||
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(std::to_string(demoUser.id()));
|
||||
_model->setValue(Wt::Auth::AuthModel::LoginNameField, authUser.identity(Wt::Auth::Identity::LoginName));
|
||||
_model->setValue(Wt::Auth::AuthModel::PasswordField, authUser.identity(Wt::Auth::Identity::LoginName));
|
||||
const std::string userName {LmsApp->getDbSession().getUserLoginName(demoUser)};
|
||||
_model->setValue(Wt::Auth::AuthModel::LoginNameField, userName );
|
||||
_model->setValue(Wt::Auth::AuthModel::PasswordField, userName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ Auth::Auth()
|
||||
Wt::WPushButton* loginBtn = bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login"));
|
||||
loginBtn->clicked().connect(this, &Auth::processAuth);
|
||||
|
||||
LmsApp->getDb().getLogin().changed().connect(std::bind([=]
|
||||
LmsApp->getDbSession().getLogin().changed().connect(std::bind([=]
|
||||
{
|
||||
if (LmsApp->getDb().getLogin().loggedIn())
|
||||
if (LmsApp->getDbSession().getLogin().loggedIn())
|
||||
this->setHidden(true);
|
||||
}));
|
||||
|
||||
@@ -76,7 +76,7 @@ Auth::Auth()
|
||||
if (user.isValid())
|
||||
{
|
||||
LMS_LOG(UI, DEBUG) << "Valid user found from auth token (id = " << user.id() << ")";
|
||||
_model->loginUser(LmsApp->getDb().getLogin(), user, Wt::Auth::LoginState::Weak);
|
||||
_model->loginUser(LmsApp->getDbSession().getLogin(), user, Wt::Auth::LoginState::Weak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ Auth::processAuth()
|
||||
updateModel(_model.get());
|
||||
|
||||
if (_model->validate())
|
||||
_model->login(LmsApp->getDb().getLogin());
|
||||
_model->login(LmsApp->getDbSession().getLogin());
|
||||
else
|
||||
updateView(_model.get());
|
||||
}
|
||||
@@ -94,7 +94,7 @@ Auth::processAuth()
|
||||
void
|
||||
Auth::logout()
|
||||
{
|
||||
_model->logout(LmsApp->getDb().getLogin());
|
||||
_model->logout(LmsApp->getDbSession().getLogin());
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
+47
-45
@@ -55,9 +55,9 @@
|
||||
namespace UserInterface {
|
||||
|
||||
std::unique_ptr<Wt::WApplication>
|
||||
LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups)
|
||||
LmsApplication::create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups)
|
||||
{
|
||||
return std::make_unique<LmsApplication>(env, connectionPool, appGroups);
|
||||
return std::make_unique<LmsApplication>(env, db.createSession(), appGroups);
|
||||
}
|
||||
|
||||
LmsApplication*
|
||||
@@ -67,11 +67,11 @@ LmsApplication::instance()
|
||||
}
|
||||
|
||||
LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool,
|
||||
std::unique_ptr<Database::Session> dbSession,
|
||||
LmsApplicationGroupContainer& appGroups)
|
||||
: Wt::WApplication(env),
|
||||
_db(connectionPool),
|
||||
_appGroups(appGroups)
|
||||
: Wt::WApplication {env},
|
||||
_dbSession {std::move(dbSession)},
|
||||
_appGroups {appGroups}
|
||||
{
|
||||
auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>();
|
||||
bootstrapTheme->setVersion(Wt::BootstrapVersion::v3);
|
||||
@@ -116,12 +116,14 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
||||
|
||||
setTitle("LMS");
|
||||
|
||||
// If here is no account in the database, launch the first connection wizard
|
||||
bool firstConnection;
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
// Handle Media Scanner events and other session events
|
||||
enableUpdates(true);
|
||||
|
||||
firstConnection = (Database::User::getAll(LmsApp->getDboSession()).size() == 0);
|
||||
// If here is no account in the database, launch the first connection wizard
|
||||
bool firstConnection {};
|
||||
{
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
firstConnection = Database::User::getAll(*_dbSession).empty();
|
||||
}
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << firstConnection;
|
||||
@@ -132,7 +134,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
||||
}
|
||||
else
|
||||
{
|
||||
LmsApp->getDb().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
|
||||
LmsApp->getDbSession().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
|
||||
_auth = root()->addNew<Auth>();
|
||||
}
|
||||
}
|
||||
@@ -260,7 +262,7 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
|
||||
|
||||
for (auto& view : views)
|
||||
for (const auto& view : views)
|
||||
{
|
||||
if (wApp->internalPathMatches(view.path))
|
||||
{
|
||||
@@ -284,46 +286,46 @@ LmsApplication::getApplicationGroup()
|
||||
void
|
||||
LmsApplication::handleAuthEvent()
|
||||
{
|
||||
if (!getDbSession().getLogin().loggedIn())
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
|
||||
|
||||
goHomeAndQuit();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!getDb().getLogin().loggedIn())
|
||||
// post([this]
|
||||
// {
|
||||
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
|
||||
const LmsApplicationInfo info {LmsApplicationInfo::fromEnvironment(environment())};
|
||||
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
|
||||
getApplicationGroup().join(info);
|
||||
|
||||
getApplicationGroup().postOthers([info]
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
|
||||
LmsApp->getEvents().appOpen(info);
|
||||
});
|
||||
|
||||
goHomeAndQuit();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
|
||||
LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment());
|
||||
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
|
||||
getApplicationGroup().join(info);
|
||||
|
||||
getApplicationGroup().postOthers([info]
|
||||
{
|
||||
LmsApp->getEvents().appOpen(info);
|
||||
});
|
||||
|
||||
createHome();
|
||||
}
|
||||
createHome();
|
||||
triggerUpdate();
|
||||
// });
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LMS_LOG(UI, ERROR) << "Error while handling auth event: " << e.what();
|
||||
throw LmsException("Internal error"); // Do not put details here at it appears on the user rendered html
|
||||
throw LmsException {"Internal error"}; // Do not put details here at it appears on the user rendered html
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LmsApplication::createHome()
|
||||
{
|
||||
// Handle Media Scanner events and other session events
|
||||
enableUpdates(true);
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
_isAdmin = LmsApp->getUser()->isAdmin();
|
||||
}
|
||||
|
||||
@@ -332,14 +334,14 @@ LmsApplication::createHome()
|
||||
|
||||
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
|
||||
|
||||
Wt::WTemplate* main = root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")));
|
||||
Wt::WTemplate* main {root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")))};
|
||||
|
||||
// Navbar
|
||||
Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top");
|
||||
navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
|
||||
navbar->setResponsive(true);
|
||||
|
||||
Wt::WMenu* menu = navbar->addMenu(std::make_unique<Wt::WMenu>());
|
||||
Wt::WMenu* menu {navbar->addMenu(std::make_unique<Wt::WMenu>())};
|
||||
{
|
||||
auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists"));
|
||||
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
|
||||
@@ -417,14 +419,14 @@ LmsApplication::createHome()
|
||||
mainStack->addNew<UserView>();
|
||||
}
|
||||
|
||||
explore->tracksAdd.connect([=] (std::vector<Database::Track::pointer> tracks)
|
||||
explore->tracksAdd.connect([=] (const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
playqueue->addTracks(tracks);
|
||||
playqueue->addTracks(trackIds);
|
||||
});
|
||||
|
||||
explore->tracksPlay.connect([=] (std::vector<Database::Track::pointer> tracks)
|
||||
explore->tracksPlay.connect([=] (const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
playqueue->playTracks(tracks);
|
||||
playqueue->playTracks(trackIds);
|
||||
});
|
||||
|
||||
|
||||
@@ -459,7 +461,7 @@ LmsApplication::createHome()
|
||||
|
||||
// Events from MediaScanner
|
||||
{
|
||||
std::string sessionId = LmsApp->sessionId();
|
||||
const std::string sessionId {LmsApp->sessionId()};
|
||||
getService<Scanner::MediaScanner>()->scanComplete().connect(this, [=] (Scanner::MediaScanner::Stats stats)
|
||||
{
|
||||
Wt::WServer::instance()->post(sessionId, [=]
|
||||
@@ -551,7 +553,7 @@ static std::string msgTypeToString(MsgType type)
|
||||
void
|
||||
LmsApplication::post(std::function<void()> func)
|
||||
{
|
||||
Wt::WServer::instance()->post(LmsApp->sessionId(), func);
|
||||
Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func));
|
||||
}
|
||||
|
||||
static std::string escape(std::string str)
|
||||
|
||||
+16
-18
@@ -23,9 +23,8 @@
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <Wt/WApplication.h>
|
||||
#include <Wt/Dbo/SqlConnectionPool.h>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/Database.hpp"
|
||||
#include "scanner/MediaScanner.hpp"
|
||||
|
||||
#include "LmsApplicationGroup.hpp"
|
||||
@@ -35,6 +34,7 @@ namespace Database {
|
||||
class Artist;
|
||||
class Cluster;
|
||||
class Release;
|
||||
class User;
|
||||
}
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -73,20 +73,18 @@ enum class MsgType
|
||||
class LmsApplication : public Wt::WApplication
|
||||
{
|
||||
public:
|
||||
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups);
|
||||
LmsApplication(const Wt::WEnvironment& env, std::unique_ptr<Database::Session> dbSession, LmsApplicationGroupContainer& appGroups);
|
||||
|
||||
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool, LmsApplicationGroupContainer& appGroups);
|
||||
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Database& db, LmsApplicationGroupContainer& appGroups);
|
||||
static LmsApplication* instance();
|
||||
|
||||
// Session application data
|
||||
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
|
||||
std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; }
|
||||
Database::Handler& getDb() { return _db;}
|
||||
Wt::Dbo::Session& getDboSession() { return _db.getSession();}
|
||||
Database::Session& getDbSession() { return *_dbSession.get();}
|
||||
|
||||
const Wt::Auth::User& getAuthUser() { return _db.getLogin().user(); }
|
||||
Database::User::pointer getUser() { return _db.getCurrentUser(); }
|
||||
const Wt::Auth::User& getAuthUser() { return getDbSession().getLogin().user(); }
|
||||
Wt::Dbo::ptr<Database::User> getUser() { return getDbSession().getLoggedUser(); }
|
||||
Wt::WString getUserIdentity() { return _userIdentity; }
|
||||
|
||||
Events& getEvents() { return _events; }
|
||||
@@ -118,15 +116,15 @@ class LmsApplication : public Wt::WApplication
|
||||
|
||||
void createHome();
|
||||
|
||||
Wt::Signal<> _preQuit;
|
||||
Database::Handler _db;
|
||||
LmsApplicationGroupContainer& _appGroups;
|
||||
Events _events;
|
||||
Wt::WString _userIdentity;
|
||||
Auth* _auth = nullptr;
|
||||
std::shared_ptr<ImageResource> _imageResource;
|
||||
std::shared_ptr<AudioResource> _audioResource;
|
||||
bool _isAdmin = false;
|
||||
Wt::Signal<> _preQuit;
|
||||
std::unique_ptr<Database::Session> _dbSession;
|
||||
LmsApplicationGroupContainer& _appGroups;
|
||||
Events _events;
|
||||
Wt::WString _userIdentity;
|
||||
Auth* _auth {};
|
||||
std::shared_ptr<ImageResource> _imageResource;
|
||||
std::shared_ptr<AudioResource> _audioResource;
|
||||
bool _isAdmin {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play)
|
||||
{
|
||||
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto track = Database::Track::getById(LmsApp->getDboSession(), trackId);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||
|
||||
try
|
||||
{
|
||||
Av::MediaFile mediaFile(track->getPath());
|
||||
const Av::MediaFile mediaFile {track->getPath()};
|
||||
|
||||
auto resource = LmsApp->getAudioResource()->getUrl(trackId);
|
||||
auto imgResource = LmsApp->getImageResource()->getTrackUrl(trackId, 64);
|
||||
|
||||
@@ -70,7 +70,7 @@ std::unique_ptr<Wt::WTemplate> createEntry(Database::Track::pointer track)
|
||||
namespace UserInterface {
|
||||
|
||||
PlayHistory::PlayHistory()
|
||||
: Wt::WTemplate(Wt::WString::tr("Lms.PlayHistory.template"))
|
||||
: Wt::WTemplate {Wt::WString::tr("Lms.PlayHistory.template")}
|
||||
{
|
||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
@@ -86,10 +86,14 @@ PlayHistory::PlayHistory()
|
||||
|
||||
LmsApp->getEvents().trackLoaded.connect([=](Database::IdType trackId, bool /* play */)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
auto trackEntry = LmsApp->getUser()->getPlayedTrackList().modify()->add(trackId);
|
||||
_entriesContainer->insertWidget(0, createEntry(trackEntry->getTrack()));
|
||||
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||
if (track)
|
||||
{
|
||||
Database::TrackListEntry::create(LmsApp->getDbSession(), track, LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession()));
|
||||
_entriesContainer->insertWidget(0, createEntry(track));
|
||||
}
|
||||
});
|
||||
|
||||
addSome();
|
||||
@@ -98,11 +102,11 @@ PlayHistory::PlayHistory()
|
||||
void
|
||||
PlayHistory::addSome()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto trackList = LmsApp->getUser()->getPlayedTrackList();
|
||||
auto trackEntries = trackList->getEntriesReverse(_entriesContainer->count(), 50);
|
||||
for (auto trackEntry : trackEntries)
|
||||
const Database::TrackList::pointer trackList {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())};
|
||||
auto trackEntries {trackList->getEntriesReverse(_entriesContainer->count(), 50)};
|
||||
for (const auto& trackEntry : trackEntries)
|
||||
_entriesContainer->addWidget(createEntry(trackEntry->getTrack()));
|
||||
|
||||
_showMore->setHidden(static_cast<std::size_t>(_entriesContainer->count()) >= trackList->getCount());
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WTemplate.h>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class PlayHistory : public Wt::WTemplate
|
||||
|
||||
+85
-80
@@ -19,12 +19,15 @@
|
||||
|
||||
#include "PlayQueueView.hpp"
|
||||
|
||||
#include <Wt/WText.h>
|
||||
#include <Wt/WText.h>
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "similarity/SimilaritySearcher.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -35,7 +38,7 @@ PlayQueue::PlayQueue()
|
||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
_repeatAll = LmsApp->getUser()->isRepeatAllSet();
|
||||
_radioMode = LmsApp->getUser()->isRadioSet();
|
||||
}
|
||||
@@ -62,9 +65,15 @@ PlayQueue::PlayQueue()
|
||||
shuffleBtn->clicked().connect([=]
|
||||
{
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
getTrackList().modify()->shuffle();
|
||||
Database::TrackList::pointer trackList {getTrackList()};
|
||||
auto entries {trackList->getEntries()};
|
||||
shuffleContainer(entries);
|
||||
|
||||
getTrackList().modify()->clear();
|
||||
for (const auto& entry : entries)
|
||||
Database::TrackListEntry::create(LmsApp->getDbSession(), entry->getTrack(), trackList);
|
||||
}
|
||||
_entriesContainer->clear();
|
||||
addSome();
|
||||
@@ -77,7 +86,7 @@ PlayQueue::PlayQueue()
|
||||
_repeatAll = !_repeatAll;
|
||||
updateRepeatBtn();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
if (!LmsApp->getUser()->isDemo())
|
||||
LmsApp->getUser().modify()->setRepeatAll(_repeatAll);
|
||||
@@ -91,7 +100,7 @@ PlayQueue::PlayQueue()
|
||||
_radioMode = !_radioMode;
|
||||
updateRadioBtn();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
if (!LmsApp->getUser()->isDemo())
|
||||
LmsApp->getUser().modify()->setRadio(_radioMode);
|
||||
@@ -102,23 +111,21 @@ PlayQueue::PlayQueue()
|
||||
|
||||
LmsApp->preQuit().connect([=]
|
||||
{
|
||||
if (_tracklistId)
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
if (LmsApp->getUser()->isDemo())
|
||||
{
|
||||
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << *_tracklistId;
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
auto tracklist = Database::TrackList::getById(LmsApp->getDboSession(), *_tracklistId);
|
||||
LMS_LOG(UI, DEBUG) << "Removing tracklist id " << _tracklistId;
|
||||
auto tracklist = Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
|
||||
if (tracklist)
|
||||
tracklist.remove();
|
||||
}
|
||||
});
|
||||
|
||||
updateInfo();
|
||||
addSome();
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
Database::TrackList::pointer trackList;
|
||||
|
||||
if (!LmsApp->getUser()->isDemo())
|
||||
{
|
||||
@@ -126,8 +133,19 @@ PlayQueue::PlayQueue()
|
||||
{
|
||||
load(LmsApp->getUser()->getCurPlayingTrackPos(), false);
|
||||
});
|
||||
trackList = LmsApp->getUser()->getQueuedTrackList(LmsApp->getDbSession());
|
||||
}
|
||||
else
|
||||
{
|
||||
static const std::string currentPlayQueueName {"__current__playqueue__"};
|
||||
trackList = Database::TrackList::create(LmsApp->getDbSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
|
||||
}
|
||||
|
||||
_tracklistId = trackList.id();
|
||||
}
|
||||
|
||||
updateInfo();
|
||||
addSome();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -145,32 +163,17 @@ PlayQueue::updateRadioBtn()
|
||||
Database::TrackList::pointer
|
||||
PlayQueue::getTrackList()
|
||||
{
|
||||
Database::TrackList::pointer res;
|
||||
|
||||
if (LmsApp->getUser()->isDemo())
|
||||
{
|
||||
static const std::string currentPlayQueueName = "__current__playqueue__";
|
||||
|
||||
if (!_tracklistId)
|
||||
{
|
||||
res = Database::TrackList::create(LmsApp->getDboSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
|
||||
LmsApp->getDboSession().flush();
|
||||
_tracklistId = res.id();
|
||||
return res;
|
||||
}
|
||||
|
||||
return Database::TrackList::getById(LmsApp->getDboSession(), *_tracklistId);
|
||||
}
|
||||
|
||||
return LmsApp->getUser()->getQueuedTrackList();
|
||||
return Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::clearTracks()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
getTrackList().modify()->clear();
|
||||
}
|
||||
|
||||
getTrackList().modify()->clear();
|
||||
_showMore->setHidden(true);
|
||||
_entriesContainer->clear();
|
||||
updateInfo();
|
||||
@@ -189,11 +192,12 @@ PlayQueue::load(std::size_t pos, bool play)
|
||||
{
|
||||
updateCurrentTrack(false);
|
||||
|
||||
Database::IdType trackId;
|
||||
Database::IdType trackId {};
|
||||
bool addRadioTrack {};
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto tracklist = getTrackList();
|
||||
Database::TrackList::pointer tracklist {getTrackList()};
|
||||
|
||||
// If out of range, stop playing
|
||||
if (pos >= tracklist->getCount())
|
||||
@@ -209,19 +213,22 @@ PlayQueue::load(std::size_t pos, bool play)
|
||||
|
||||
// If last and radio mode, fill the next song
|
||||
if (_radioMode && pos == tracklist->getCount() - 1)
|
||||
addRadioTrack();
|
||||
addRadioTrack = true;
|
||||
|
||||
_trackPos = pos;
|
||||
auto track = tracklist->getEntry(*_trackPos)->getTrack();
|
||||
|
||||
trackId = track.id();
|
||||
|
||||
updateCurrentTrack(true);
|
||||
|
||||
if (!LmsApp->getUser()->isDemo())
|
||||
LmsApp->getUser().modify()->setCurPlayingTrackPos(pos);
|
||||
}
|
||||
|
||||
if (addRadioTrack)
|
||||
enqueueRadioTrack();
|
||||
|
||||
updateCurrentTrack(true);
|
||||
|
||||
loadTrack.emit(trackId, play);
|
||||
}
|
||||
|
||||
@@ -252,7 +259,7 @@ PlayQueue::playNext()
|
||||
void
|
||||
PlayQueue::updateInfo()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
_nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(static_cast<unsigned>(getTrackList()->getCount())));
|
||||
}
|
||||
@@ -274,55 +281,58 @@ PlayQueue::updateCurrentTrack(bool selected)
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::enqueueTracks(const std::vector<Database::Track::pointer>& tracks)
|
||||
PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
// Use a "session" playqueue in order to store the current playqueue
|
||||
// so that the user can disconnect and get its playqueue back
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
auto tracklist = getTrackList();
|
||||
auto tracklist = getTrackList();
|
||||
for (Database::IdType trackId : trackIds)
|
||||
{
|
||||
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
for (auto track : tracks)
|
||||
Database::TrackListEntry::create(LmsApp->getDboSession(), track, tracklist);
|
||||
Database::TrackListEntry::create(LmsApp->getDbSession(), track, tracklist);
|
||||
}
|
||||
}
|
||||
|
||||
updateInfo();
|
||||
addSome();
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::enqueueTrack(Database::Track::pointer track)
|
||||
PlayQueue::enqueueTrack(Database::IdType trackId)
|
||||
{
|
||||
enqueueTracks(std::vector<Database::Track::pointer>(1, track));
|
||||
enqueueTracks({trackId});
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
|
||||
PlayQueue::addTracks(const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
enqueueTracks(tracks);
|
||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", tracks.size()).arg(tracks.size()), std::chrono::milliseconds(2000));
|
||||
enqueueTracks(trackIds);
|
||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::playTracks(const std::vector<Database::Track::pointer>& tracks)
|
||||
PlayQueue::playTracks(const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
clearTracks();
|
||||
enqueueTracks(tracks);
|
||||
enqueueTracks(trackIds);
|
||||
load(0, true);
|
||||
|
||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", tracks.size()).arg(tracks.size()), std::chrono::milliseconds(2000));
|
||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PlayQueue::addSome()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto tracklist = getTrackList();
|
||||
|
||||
auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50);
|
||||
for (auto tracklistEntry : tracklistEntries)
|
||||
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
||||
{
|
||||
auto tracklistEntryId = tracklistEntry.id();
|
||||
auto track = tracklistEntry->getTrack();
|
||||
@@ -367,15 +377,15 @@ PlayQueue::addSome()
|
||||
{
|
||||
// Remove the entry n both the widget tree and the playqueue
|
||||
{
|
||||
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
auto entryToRemove = Database::TrackListEntry::getById(LmsApp->getDboSession(), tracklistEntryId);
|
||||
Database::TrackListEntry::pointer entryToRemove {Database::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId)};
|
||||
entryToRemove.remove();
|
||||
}
|
||||
|
||||
if (_trackPos)
|
||||
{
|
||||
auto pos = _entriesContainer->indexOf(entry);
|
||||
auto pos {_entriesContainer->indexOf(entry)};
|
||||
if (pos > 0 && *_trackPos >= static_cast<std::size_t>(pos))
|
||||
(*_trackPos)--;
|
||||
}
|
||||
@@ -391,28 +401,23 @@ PlayQueue::addSome()
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::addRadioTrack()
|
||||
PlayQueue::enqueueRadioTrack()
|
||||
{
|
||||
auto tracklist = getTrackList();
|
||||
std::vector<Database::IdType> trackIds;
|
||||
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
Database::TrackList::pointer tracklist {getTrackList()};
|
||||
|
||||
trackIds = getTrackList()->getTrackIds();
|
||||
}
|
||||
|
||||
std::vector<Database::IdType> trackIds = getTrackList()->getTrackIds();
|
||||
if (trackIds.empty())
|
||||
return;
|
||||
|
||||
auto res = getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDboSession(), std::set<Database::IdType>(trackIds.begin(), trackIds.end()), 1);
|
||||
for (auto trackId : res)
|
||||
{
|
||||
auto trackToAdd = Database::Track::getById(LmsApp->getDboSession(), trackId);
|
||||
enqueueTrack(trackToAdd);
|
||||
}
|
||||
|
||||
const std::vector<Database::IdType> trackToAddIds {getService<Similarity::Searcher>()->getSimilarTracks(LmsApp->getDbSession(), std::set<Database::IdType>(std::cbegin(trackIds), std::cend(trackIds)), 1)};
|
||||
enqueueTracks(trackToAddIds);
|
||||
}
|
||||
|
||||
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Searcher> similaritySearcher)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
|
||||
+20
-20
@@ -19,23 +19,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Wt/WCheckBox.h>
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WSignal.h>
|
||||
#include <Wt/WTemplate.h>
|
||||
#include <Wt/WText.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace Similarity
|
||||
{
|
||||
namespace Similarity {
|
||||
class Finder;
|
||||
}
|
||||
|
||||
namespace Database {
|
||||
class TrackList;
|
||||
}
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class PlayQueue : public Wt::WTemplate
|
||||
@@ -43,8 +43,8 @@ class PlayQueue : public Wt::WTemplate
|
||||
public:
|
||||
PlayQueue();
|
||||
|
||||
void addTracks(const std::vector<Database::Track::pointer>& tracks);
|
||||
void playTracks(const std::vector<Database::Track::pointer>& tracks);
|
||||
void addTracks(const std::vector<Database::IdType>& trackIds);
|
||||
void playTracks(const std::vector<Database::IdType>& trackIds);
|
||||
|
||||
// play the next track in the queue
|
||||
void playNext();
|
||||
@@ -59,13 +59,13 @@ class PlayQueue : public Wt::WTemplate
|
||||
Wt::Signal<> trackUnload;
|
||||
|
||||
private:
|
||||
Database::TrackList::pointer getTrackList();
|
||||
Wt::Dbo::ptr<Database::TrackList> getTrackList();
|
||||
|
||||
void clearTracks();
|
||||
void enqueueTracks(const std::vector<Database::Track::pointer>& tracks);
|
||||
void enqueueTrack(Database::Track::pointer track);
|
||||
void enqueueTracks(const std::vector<Database::IdType>& trackIds);
|
||||
void enqueueTrack(Database::IdType trackId);
|
||||
void addSome();
|
||||
void addRadioTrack();
|
||||
void enqueueRadioTrack();
|
||||
void updateInfo();
|
||||
void updateCurrentTrack(bool selected);
|
||||
void updateRepeatBtn();
|
||||
@@ -77,14 +77,14 @@ class PlayQueue : public Wt::WTemplate
|
||||
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder);
|
||||
void addRadioTrackFromClusters();
|
||||
|
||||
bool _repeatAll = false;
|
||||
bool _radioMode = false;
|
||||
boost::optional<Database::IdType> _tracklistId;
|
||||
Wt::WContainerWidget* _entriesContainer = nullptr;
|
||||
Wt::WPushButton* _showMore = nullptr;
|
||||
Wt::WText* _nbTracks = nullptr;
|
||||
Wt::WText* _repeatBtn = nullptr;
|
||||
Wt::WText* _radioBtn = nullptr;
|
||||
bool _repeatAll {};
|
||||
bool _radioMode {};
|
||||
Database::IdType _tracklistId {};
|
||||
Wt::WContainerWidget* _entriesContainer {};
|
||||
Wt::WPushButton* _showMore {};
|
||||
Wt::WText* _nbTracks {};
|
||||
Wt::WText* _repeatBtn {};
|
||||
Wt::WText* _radioBtn {};
|
||||
boost::optional<std::size_t> _trackPos; // current track position, if set
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class SettingsModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
|
||||
|
||||
@@ -82,12 +82,12 @@ class SettingsModel : public Wt::WFormModel
|
||||
LmsApp->getUser().modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow));
|
||||
|
||||
if (!valueText(PasswordField).empty())
|
||||
Handler::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
|
||||
Session::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
|
||||
}
|
||||
|
||||
void loadData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
|
||||
if (!LmsApp->getUser()->getAudioTranscodeEnable())
|
||||
@@ -116,7 +116,7 @@ class SettingsModel : public Wt::WFormModel
|
||||
if (!valueText(PasswordField).empty())
|
||||
{
|
||||
// Evaluate the strength of the password
|
||||
auto res = Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), LmsApp->getUserIdentity(), "");
|
||||
auto res = Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), LmsApp->getUserIdentity(), "");
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
@@ -148,12 +148,12 @@ class SettingsModel : public Wt::WFormModel
|
||||
{
|
||||
Bitrate maxAudioBitrate;
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate();
|
||||
}
|
||||
|
||||
_transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
|
||||
for (Bitrate bitrate : User::audioTranscodeAllowedBitrates)
|
||||
for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates)
|
||||
{
|
||||
if (bitrate > maxAudioBitrate)
|
||||
break;
|
||||
@@ -243,7 +243,7 @@ SettingsView::refreshView()
|
||||
{
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
if (LmsApp->getUser()->isDemo())
|
||||
{
|
||||
|
||||
@@ -85,10 +85,10 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
|
||||
void loadData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())};
|
||||
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())};
|
||||
const ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
|
||||
const SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
|
||||
|
||||
setValue(MediaDirectoryField, scanSettings->getMediaDirectory().string());
|
||||
|
||||
@@ -108,17 +108,17 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
if (!clusterTypes.empty())
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
std::transform(clusterTypes.begin(), clusterTypes.end(),std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
|
||||
std::transform(clusterTypes.begin(), clusterTypes.end(), std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
|
||||
setValue(TagsField, joinStrings(names, " "));
|
||||
}
|
||||
}
|
||||
|
||||
void saveData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
auto scanSettings {ScanSettings::get(LmsApp->getDboSession())};
|
||||
auto similaritySettings {SimilaritySettings::get(LmsApp->getDboSession())};
|
||||
ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
|
||||
SimilaritySettings::pointer similaritySettings {SimilaritySettings::get(LmsApp->getDbSession())};
|
||||
|
||||
scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8());
|
||||
|
||||
@@ -135,7 +135,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
similaritySettings.modify()->setEngineType(_similarityEngineTypeModel->getValue(*similarityEngineTypeRow));
|
||||
|
||||
auto clusterTypes {splitString(valueText(TagsField).toUTF8(), " ")};
|
||||
scanSettings.modify()->setClusterTypes(std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||
scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -54,21 +54,14 @@ class InitWizardModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction(LmsApp->getDbSession().createUniqueTransaction());
|
||||
|
||||
// Check if a user already exist
|
||||
// If it's the case, just do nothing
|
||||
if (!Database::User::getAll(LmsApp->getDboSession()).empty())
|
||||
if (!Database::User::getAll(LmsApp->getDbSession()).empty())
|
||||
throw LmsException("Admin user already created");
|
||||
|
||||
// Create user
|
||||
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
|
||||
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
|
||||
|
||||
// Account
|
||||
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(AdminLoginField));
|
||||
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
|
||||
|
||||
Database::User::pointer user {LmsApp->getDbSession().createUser(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8())};
|
||||
user.modify()->setType(Database::User::Type::ADMIN);
|
||||
}
|
||||
|
||||
@@ -81,7 +74,7 @@ class InitWizardModel : public Wt::WFormModel
|
||||
if (!valueText(PasswordField).empty())
|
||||
{
|
||||
// Evaluate the strength of the password
|
||||
auto res = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField),
|
||||
auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField),
|
||||
valueText(AdminLoginField), "");
|
||||
|
||||
if (!res.isValid())
|
||||
|
||||
+26
-32
@@ -78,17 +78,16 @@ class UserModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
if (_userId)
|
||||
{
|
||||
// Update user
|
||||
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
|
||||
Database::User::pointer user = LmsApp->getDb().getUser( authUser );
|
||||
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
|
||||
|
||||
// Account
|
||||
if (!valueText(PasswordField).empty())
|
||||
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
|
||||
LmsApp->getDbSession().updateUserPassword(user, valueText(PasswordField).toUTF8());
|
||||
|
||||
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
|
||||
if (transcodeBitrateLimitRow)
|
||||
@@ -97,12 +96,7 @@ class UserModel : public Wt::WFormModel
|
||||
else
|
||||
{
|
||||
// Create user
|
||||
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
|
||||
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
|
||||
|
||||
// Account
|
||||
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
|
||||
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
|
||||
Database::User::pointer user = LmsApp->getDbSession().createUser(valueText(LoginField).toUTF8(), valueText(PasswordField).toUTF8());
|
||||
|
||||
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
|
||||
if (transcodeBitrateLimitRow )
|
||||
@@ -120,11 +114,9 @@ class UserModel : public Wt::WFormModel
|
||||
if (!_userId)
|
||||
return;
|
||||
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
|
||||
auto authUser {LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) )};
|
||||
auto user {LmsApp->getDb().getUser(authUser)};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
|
||||
if (user == LmsApp->getUser())
|
||||
throw LmsException("Cannot edit ourselves");
|
||||
|
||||
@@ -133,12 +125,14 @@ class UserModel : public Wt::WFormModel
|
||||
setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow));
|
||||
}
|
||||
|
||||
Wt::WString getLogin() const
|
||||
Wt::WString getLoginName() const
|
||||
{
|
||||
if (_userId)
|
||||
{
|
||||
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
|
||||
return authUser.identity(Wt::Auth::Identity::LoginName);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
|
||||
return LmsApp->getDbSession().getUserLoginName(user);
|
||||
}
|
||||
else
|
||||
return valueText(LoginField);
|
||||
@@ -150,8 +144,8 @@ class UserModel : public Wt::WFormModel
|
||||
|
||||
if (field == LoginField)
|
||||
{
|
||||
auto user = LmsApp->getDb().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
|
||||
if (user.isValid())
|
||||
const Database::User::pointer user {LmsApp->getDbSession().getUser(valueText(LoginField).toUTF8())};
|
||||
if (user)
|
||||
error = Wt::WString::tr("Lms.Admin.User.user-already-exists");
|
||||
}
|
||||
else if (field == PasswordField)
|
||||
@@ -161,13 +155,13 @@ class UserModel : public Wt::WFormModel
|
||||
if (Wt::asNumber(value(DemoField)))
|
||||
{
|
||||
//Demo account: password must be the same as the login name
|
||||
if (valueText(PasswordField) != getLogin())
|
||||
if (valueText(PasswordField) != getLoginName())
|
||||
error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Evaluate the strength of the password for non demo accounts
|
||||
auto res = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), getLogin(), "");
|
||||
auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), getLoginName(), "");
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
@@ -176,9 +170,9 @@ class UserModel : public Wt::WFormModel
|
||||
}
|
||||
else if (field == DemoField)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDboSession()))
|
||||
if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDbSession()))
|
||||
error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists");
|
||||
}
|
||||
|
||||
@@ -226,23 +220,23 @@ UserView::refreshView()
|
||||
|
||||
auto userId = readAs<Database::IdType>(wApp->internalPathNextPart("/admin/user/"));
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "userId = " << (userId ? std::to_string(*userId) : "none");
|
||||
|
||||
clear();
|
||||
|
||||
Wt::WTemplateFormView* t = addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.User.template"));
|
||||
Wt::WTemplateFormView* t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.User.template"))};
|
||||
|
||||
auto model = std::make_shared<UserModel>(userId);
|
||||
auto model {std::make_shared<UserModel>(userId)};
|
||||
|
||||
if (userId)
|
||||
{
|
||||
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*userId) );
|
||||
auto name = authUser.identity(Wt::Auth::Identity::LoginName);
|
||||
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::TextFormat::Plain);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *userId)};
|
||||
const std::string loginName {LmsApp->getDbSession().getUserLoginName(user)};
|
||||
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(loginName), Wt::TextFormat::Plain);
|
||||
t->setCondition("if-has-last-login-attempt", true);
|
||||
|
||||
Wt::WLineEdit *lastLoginAttempt = t->bindNew<Wt::WLineEdit>("last-login-attempt");
|
||||
lastLoginAttempt->setText(authUser.lastLoginAttempt().toString());
|
||||
Wt::WLineEdit *lastLoginAttempt {t->bindNew<Wt::WLineEdit>("last-login-attempt")};
|
||||
lastLoginAttempt->setText(LmsApp->getDbSession().getUserLastLoginAttempt(user).toString());
|
||||
lastLoginAttempt->setEnabled(false);
|
||||
}
|
||||
else
|
||||
|
||||
+13
-17
@@ -59,22 +59,16 @@ UsersView::refreshView()
|
||||
|
||||
_container->clear();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto users = Database::User::getAll(LmsApp->getDboSession());
|
||||
for (auto user : users)
|
||||
auto users = Database::User::getAll(LmsApp->getDbSession());
|
||||
for (const auto& user : users)
|
||||
{
|
||||
auto userId = std::to_string(user.id());
|
||||
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"));
|
||||
const Database::IdType userId {user.id()};
|
||||
|
||||
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
|
||||
if (!authUser.isValid()) {
|
||||
LMS_LOG(UI, ERROR) << "Skipping invalid userId = " << user.id();
|
||||
continue;
|
||||
}
|
||||
Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"))};
|
||||
|
||||
auto login = authUser.identity(Wt::Auth::Identity::LoginName);
|
||||
entry->bindString("name", login, Wt::TextFormat::Plain);
|
||||
entry->bindString("name", LmsApp->getDbSession().getUserLoginName(user), Wt::TextFormat::Plain);
|
||||
|
||||
// Create tag
|
||||
if (user->isAdmin() || user->isDemo())
|
||||
@@ -106,16 +100,18 @@ UsersView::refreshView()
|
||||
{
|
||||
if (btn == Wt::StandardButton::Yes)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), userId)};
|
||||
if (user)
|
||||
LmsApp->getDbSession().removeUser(user);
|
||||
|
||||
auto authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
|
||||
auto user = LmsApp->getDb().getUser(authUser);
|
||||
LmsApp->getDb().getUserDatabase().deleteUser( authUser );
|
||||
user.remove();
|
||||
_container->removeWidget(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
delBtn->removeChild(msgBox);
|
||||
}
|
||||
});
|
||||
|
||||
msgBox->show();
|
||||
|
||||
@@ -63,21 +63,18 @@ ArtistInfo::refresh()
|
||||
if (!artistId)
|
||||
return;
|
||||
|
||||
auto artistsIds = getService<Similarity::Searcher>()->getSimilarArtists(LmsApp->getDboSession(), *artistId, 5);
|
||||
const std::vector<Database::IdType> artistsIds {getService<Similarity::Searcher>()->getSimilarArtists(LmsApp->getDbSession(), *artistId, 5)};
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
std::vector<Database::Artist::pointer> artists;
|
||||
for (auto artistId : artistsIds)
|
||||
for (Database::IdType artistId : artistsIds)
|
||||
{
|
||||
auto artist = Database::Artist::getById(LmsApp->getDboSession(), artistId);
|
||||
Database::Artist::pointer artist {Database::Artist::getById(LmsApp->getDbSession(), artistId)};
|
||||
if (!artist)
|
||||
continue;
|
||||
|
||||
if (artist)
|
||||
artists.push_back(artist);
|
||||
}
|
||||
|
||||
for (auto artist : artists)
|
||||
_similarArtistsContainer->addNew<ArtistLink>(artist);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -66,22 +66,22 @@ Artist::refresh()
|
||||
if (!artistId)
|
||||
return;
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto artist = Database::Artist::getById(LmsApp->getDboSession(), *artistId);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::Artist::pointer artist = Database::Artist::getById(LmsApp->getDbSession(), *artistId);
|
||||
if (!artist)
|
||||
{
|
||||
LmsApp->goHome();
|
||||
return;
|
||||
}
|
||||
|
||||
Wt::WTemplate* t = addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template"));
|
||||
Wt::WTemplate* t {addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template"))};
|
||||
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
Wt::WContainerWidget* clusterContainers = t->bindNew<Wt::WContainerWidget>("clusters");
|
||||
|
||||
{
|
||||
auto clusterTypes = ScanSettings::get(LmsApp->getDboSession())->getClusterTypes();
|
||||
auto clusterTypes = ScanSettings::get(LmsApp->getDbSession())->getClusterTypes();
|
||||
auto clusterGroups = artist->getClusterGroups(clusterTypes, 3);
|
||||
|
||||
for (auto clusters : clusterGroups)
|
||||
|
||||
@@ -58,22 +58,23 @@ ArtistsInfo::refreshRecentlyAdded()
|
||||
{
|
||||
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto artists = Artist::getLastAdded(LmsApp->getDboSession(), after, 5);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
const std::vector<Database::Artist::pointer> artists {Artist::getLastAdded(LmsApp->getDbSession(), after, 5)};
|
||||
|
||||
_recentlyAddedContainer->clear();
|
||||
for (auto artist : artists)
|
||||
for (const Database::Artist::pointer& artist : artists)
|
||||
_recentlyAddedContainer->addNew<ArtistLink>(artist);
|
||||
}
|
||||
|
||||
void
|
||||
ArtistsInfo::refreshMostPlayed()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto artists = LmsApp->getUser()->getPlayedTrackList()->getTopArtists(5);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const std::vector<Database::Artist::pointer> artists {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopArtists(5)};
|
||||
|
||||
_mostPlayedContainer->clear();
|
||||
for (auto artist : artists)
|
||||
for (const Database::Artist::pointer& artist : artists)
|
||||
_mostPlayedContainer->addNew<ArtistLink>(artist);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,17 +73,17 @@ Artists::addSome()
|
||||
|
||||
auto clusterIds = _filters->getClusterIds();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
bool moreResults;
|
||||
auto artists = Artist::getByFilter(LmsApp->getDboSession(),
|
||||
bool moreResults {};
|
||||
const std::vector<Artist::pointer> artists {Artist::getByFilter(LmsApp->getDbSession(),
|
||||
clusterIds,
|
||||
searchKeywords,
|
||||
_container->count(), 20, moreResults);
|
||||
_container->count(), 20, moreResults)};
|
||||
|
||||
for (auto artist : artists)
|
||||
for (const auto& artist : artists)
|
||||
{
|
||||
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"));
|
||||
Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"))};
|
||||
|
||||
entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
|
||||
}
|
||||
|
||||
+43
-42
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
@@ -66,9 +67,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
|
||||
{ "/tracks", IdxTracks },
|
||||
};
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
|
||||
|
||||
for (auto index : indexes)
|
||||
for (const auto& index : indexes)
|
||||
{
|
||||
if (wApp->internalPathMatches(index.first))
|
||||
{
|
||||
@@ -182,102 +181,104 @@ Explore::Explore()
|
||||
handleInfoPathChange(infoStack);
|
||||
}
|
||||
|
||||
// TODO SQL this?
|
||||
static std::vector<Database::Track::pointer> getArtistTracks(Wt::Dbo::Session& session, Database::IdType artistId, std::set<Database::IdType> clusters)
|
||||
static
|
||||
std::vector<Database::IdType>
|
||||
getArtistTracks(Database::Session& session, Database::IdType artistId, const std::set<Database::IdType>& clusters)
|
||||
{
|
||||
std::vector<Database::Track::pointer> res;
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto artist = Database::Artist::getById(session, artistId);
|
||||
Database::Artist::pointer artist {Database::Artist::getById(session, artistId)};
|
||||
if (!artist)
|
||||
return res;
|
||||
return {};
|
||||
|
||||
res = artist->getTracks();
|
||||
// TODO handle clusters here
|
||||
const std::vector<Database::Track::pointer> tracks {artist->getTracks()};
|
||||
|
||||
std::vector<Database::IdType> res;
|
||||
res.reserve(tracks.size());
|
||||
|
||||
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::vector<Database::Track::pointer> getReleaseTracks(Wt::Dbo::Session& session, Database::IdType releaseId, std::set<Database::IdType> clusters)
|
||||
static
|
||||
std::vector<Database::IdType>
|
||||
getReleaseTracks(Database::Session& session, Database::IdType releaseId, std::set<Database::IdType> clusters)
|
||||
{
|
||||
std::vector<Database::Track::pointer> res;
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto release = Database::Release::getById(session, releaseId);
|
||||
Database::Release::pointer release {Database::Release::getById(session, releaseId)};
|
||||
if (!release)
|
||||
return res;
|
||||
return {};
|
||||
|
||||
res = release->getTracks(clusters);
|
||||
const std::vector<Database::Track::pointer> tracks {release->getTracks(clusters)};
|
||||
|
||||
std::vector<Database::IdType> res;
|
||||
res.reserve(tracks.size());
|
||||
|
||||
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::vector<Database::Track::pointer> getTrack(Wt::Dbo::Session& session, Database::IdType trackId)
|
||||
static
|
||||
std::vector<Database::IdType>
|
||||
getTrack(Database::Session& session, Database::IdType trackId)
|
||||
{
|
||||
std::vector<Database::Track::pointer> res;
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto track = Database::Track::getById(session, trackId);
|
||||
if (track)
|
||||
res.push_back(track);
|
||||
Database::Track::pointer track {Database::Track::getById(session, trackId)};
|
||||
if (!track)
|
||||
return {};
|
||||
|
||||
return res;
|
||||
return {track.id()};
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleArtistAdd(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksAdd.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
|
||||
tracksAdd.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleArtistPlay(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksPlay.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
|
||||
tracksPlay.emit(getArtistTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleReleaseAdd(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksAdd.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
|
||||
tracksAdd.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleReleasePlay(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksPlay.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
|
||||
tracksPlay.emit(getReleaseTracks(LmsApp->getDbSession(), id, _filters->getClusterIds()));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleTrackAdd(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksAdd.emit(getTrack(LmsApp->getDboSession(), id));
|
||||
tracksAdd.emit(getTrack(LmsApp->getDbSession(), id));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleTrackPlay(Database::IdType id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
tracksPlay.emit(getTrack(LmsApp->getDboSession(), id));
|
||||
tracksPlay.emit(getTrack(LmsApp->getDbSession(), id));
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleTracksAdd(std::vector<Database::Track::pointer> tracks)
|
||||
Explore::handleTracksAdd(const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
tracksAdd.emit(tracks);
|
||||
tracksAdd.emit(trackIds);
|
||||
}
|
||||
|
||||
void
|
||||
Explore::handleTracksPlay(std::vector<Database::Track::pointer> tracks)
|
||||
Explore::handleTracksPlay(const std::vector<Database::IdType>& trackIds)
|
||||
{
|
||||
tracksPlay.emit(tracks);
|
||||
tracksPlay.emit(trackIds);
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
+10
-11
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <Wt/WTemplate.h>
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -33,19 +32,19 @@ class Explore : public Wt::WTemplate
|
||||
public:
|
||||
Explore();
|
||||
|
||||
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
|
||||
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
|
||||
Wt::Signal<std::vector<Database::IdType>> tracksAdd;
|
||||
Wt::Signal<std::vector<Database::IdType>> tracksPlay;
|
||||
|
||||
private:
|
||||
|
||||
void handleArtistAdd(Database::IdType id);
|
||||
void handleArtistPlay(Database::IdType id);
|
||||
void handleReleaseAdd(Database::IdType id);
|
||||
void handleReleasePlay(Database::IdType id);
|
||||
void handleTrackAdd(Database::IdType id);
|
||||
void handleTrackPlay(Database::IdType id);
|
||||
void handleTracksAdd(std::vector<Database::Track::pointer> tracks);
|
||||
void handleTracksPlay(std::vector<Database::Track::pointer> tracks);
|
||||
void handleArtistAdd(Database::IdType artistId);
|
||||
void handleArtistPlay(Database::IdType artistId);
|
||||
void handleReleaseAdd(Database::IdType releaseId);
|
||||
void handleReleasePlay(Database::IdType releaseId);
|
||||
void handleTrackAdd(Database::IdType trackId);
|
||||
void handleTrackPlay(Database::IdType trackId);
|
||||
void handleTracksAdd(const std::vector<Database::IdType>& trackIds);
|
||||
void handleTracksPlay(const std::vector<Database::IdType>& trackIds);
|
||||
|
||||
Filters* _filters;
|
||||
};
|
||||
|
||||
+23
-25
@@ -49,43 +49,41 @@ Filters::showDialog()
|
||||
|
||||
// Populate data
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto types = Database::ClusterType::getAll(LmsApp->getDboSession());
|
||||
|
||||
for (auto type : types)
|
||||
const auto types {Database::ClusterType::getAll(LmsApp->getDbSession())};
|
||||
for (const Database::ClusterType::pointer& type : types)
|
||||
typeCombo->addItem(Wt::WString::fromUTF8(type->getName()));
|
||||
|
||||
if (!types.empty())
|
||||
{
|
||||
auto values = types.front()->getClusters();
|
||||
const auto values {types.front()->getClusters()};
|
||||
|
||||
for (auto value : values)
|
||||
for (const Database::Cluster::pointer& value : values)
|
||||
{
|
||||
if (_filterIds.find(value.id()) == _filterIds.end())
|
||||
valueCombo->addItem(Wt::WString::fromUTF8(value->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typeCombo->changed().connect(std::bind([=]
|
||||
typeCombo->changed().connect([=]
|
||||
{
|
||||
auto name = typeCombo->valueText().toUTF8();
|
||||
const std::string name {typeCombo->valueText().toUTF8()};
|
||||
|
||||
valueCombo->clear();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), name);
|
||||
auto clusterType = Database::ClusterType::getByName(LmsApp->getDbSession(), name);
|
||||
|
||||
auto values = clusterType->getClusters();
|
||||
for (auto value : values)
|
||||
const auto values = clusterType->getClusters();
|
||||
for (const Database::Cluster::pointer& value : values)
|
||||
{
|
||||
if (_filterIds.find(value.id()) == _filterIds.end())
|
||||
valueCombo->addItem(Wt::WString::fromUTF8(value->getName()));
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
dialog->setModal(true);
|
||||
dialog->setMovable(false);
|
||||
@@ -93,26 +91,26 @@ Filters::showDialog()
|
||||
dialog->setResizable(false);
|
||||
dialog->setClosable(false);
|
||||
|
||||
dialog->finished().connect(std::bind([=]
|
||||
dialog->finished().connect([=]
|
||||
{
|
||||
if (dialog->result() != Wt::DialogCode::Accepted)
|
||||
return;
|
||||
|
||||
auto type = typeCombo->valueText().toUTF8();
|
||||
auto value = valueCombo->valueText().toUTF8();
|
||||
const std::string type {typeCombo->valueText().toUTF8()};
|
||||
const std::string value {valueCombo->valueText().toUTF8()};
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), type);
|
||||
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(LmsApp->getDbSession(), type)};
|
||||
if (!clusterType)
|
||||
return;
|
||||
|
||||
auto cluster = clusterType->getCluster(value);
|
||||
Database::Cluster::pointer cluster {clusterType->getCluster(value)};
|
||||
if (!cluster)
|
||||
return;
|
||||
|
||||
add(cluster.id());
|
||||
}));
|
||||
});
|
||||
|
||||
dialog->show();
|
||||
}
|
||||
@@ -120,17 +118,17 @@ Filters::showDialog()
|
||||
void
|
||||
Filters::add(Database::IdType clusterId)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto cluster = Database::Cluster::getById(LmsApp->getDboSession(), clusterId);
|
||||
Database::Cluster::pointer cluster {Database::Cluster::getById(LmsApp->getDbSession(), clusterId)};
|
||||
if (!cluster)
|
||||
return;
|
||||
|
||||
auto res = _filterIds.insert(clusterId);
|
||||
auto res {_filterIds.insert(clusterId)};
|
||||
if (!res.second)
|
||||
return;
|
||||
|
||||
auto filter = _filters->addWidget(LmsApp->createCluster(cluster, true));
|
||||
auto filter {_filters->addWidget(LmsApp->createCluster(cluster, true))};
|
||||
filter->clicked().connect(std::bind([=]
|
||||
{
|
||||
_filters->removeWidget(filter);
|
||||
|
||||
@@ -68,11 +68,11 @@ ReleaseInfo::refresh()
|
||||
if (!releaseId)
|
||||
return;
|
||||
|
||||
std::vector<Database::IdType> releasesIds {getService<Similarity::Searcher>()->getSimilarReleases(LmsApp->getDboSession(), *releaseId, 5)};
|
||||
const std::vector<Database::IdType> releasesIds {getService<Similarity::Searcher>()->getSimilarReleases(LmsApp->getDbSession(), *releaseId, 5)};
|
||||
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
Database::Release::pointer release {Database::Release::getById(LmsApp->getDboSession(), *releaseId)};
|
||||
Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), *releaseId)};
|
||||
if (!release)
|
||||
return;
|
||||
|
||||
@@ -102,13 +102,13 @@ ReleaseInfo::refresh()
|
||||
std::vector<Database::Release::pointer> similarReleases;
|
||||
for (Database::IdType id : releasesIds)
|
||||
{
|
||||
Database::Release::pointer similarRelease {Database::Release::getById(LmsApp->getDboSession(), id)};
|
||||
Database::Release::pointer similarRelease {Database::Release::getById(LmsApp->getDbSession(), id)};
|
||||
|
||||
if (similarRelease)
|
||||
similarReleases.emplace_back(similarRelease);
|
||||
}
|
||||
|
||||
for (const auto& similarRelease : similarReleases)
|
||||
for (const Database::Release::pointer& similarRelease : similarReleases)
|
||||
_similarReleasesContainer->addNew<ReleaseLink>(similarRelease);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ Release::refresh()
|
||||
if (!releaseId)
|
||||
return;
|
||||
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto release {Database::Release::getById(LmsApp->getDboSession(), *releaseId)};
|
||||
const Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), *releaseId)};
|
||||
if (!release)
|
||||
{
|
||||
LmsApp->goHome();
|
||||
@@ -118,12 +118,12 @@ Release::refresh()
|
||||
|
||||
Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")};
|
||||
{
|
||||
auto clusterTypes {ScanSettings::get(LmsApp->getDboSession())->getClusterTypes()};
|
||||
auto clusterGroups {release->getClusterGroups(clusterTypes, 3)};
|
||||
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
|
||||
const auto clusterGroups {release->getClusterGroups(clusterTypes, 3)};
|
||||
|
||||
for (auto clusters : clusterGroups)
|
||||
for (const auto& clusters : clusterGroups)
|
||||
{
|
||||
for (auto cluster : clusters)
|
||||
for (const auto& cluster : clusters)
|
||||
{
|
||||
auto clusterId {cluster.id()};
|
||||
auto entry {clusterContainers->addWidget(LmsApp->createCluster(cluster))};
|
||||
|
||||
@@ -58,24 +58,24 @@ ReleasesInfo::refreshRecentlyAdded()
|
||||
{
|
||||
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto releases {Release::getLastAdded(LmsApp->getDboSession(), after, 0, 5)};
|
||||
const auto releases {Release::getLastAdded(LmsApp->getDbSession(), after, 0, 5)};
|
||||
|
||||
_recentlyAddedContainer->clear();
|
||||
for (auto release : releases)
|
||||
for (const Database::Release::pointer& release : releases)
|
||||
_recentlyAddedContainer->addNew<ReleaseLink>(release);
|
||||
}
|
||||
|
||||
void
|
||||
ReleasesInfo::refreshMostPlayed()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto releases = LmsApp->getUser()->getPlayedTrackList()->getTopReleases(5);
|
||||
const auto releases {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopReleases(5)};
|
||||
|
||||
_mostPlayedContainer->clear();
|
||||
for (auto release : releases)
|
||||
for (const Database::Release::pointer& release : releases)
|
||||
_mostPlayedContainer->addNew<ReleaseLink>(release);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,18 +71,17 @@ Releases::refresh()
|
||||
void
|
||||
Releases::addSome()
|
||||
{
|
||||
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
|
||||
const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
|
||||
const auto clusterIds {_filters->getClusterIds()};
|
||||
|
||||
auto clusterIds = _filters->getClusterIds();
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
bool moreResults;
|
||||
auto releases = Release::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, _container->count(), 20, moreResults);
|
||||
const auto releases {Release::getByFilter(LmsApp->getDbSession(), clusterIds, searchKeywords, _container->count(), 20, moreResults)};
|
||||
|
||||
for (auto release : releases)
|
||||
for (const Database::Release::pointer& release : releases)
|
||||
{
|
||||
auto releaseId = release.id();
|
||||
const Database::IdType releaseId {release.id()};
|
||||
|
||||
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
|
||||
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
|
||||
|
||||
@@ -74,10 +74,10 @@ TracksInfo::TracksInfo()
|
||||
void
|
||||
TracksInfo::refreshRecentlyAdded()
|
||||
{
|
||||
auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
|
||||
const auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)};
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto tracks = Track::getLastAdded(LmsApp->getDboSession(), after, 5);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
const auto tracks {Track::getLastAdded(LmsApp->getDbSession(), after, 5)};
|
||||
|
||||
_recentlyAddedContainer->clear();
|
||||
addEntries(_recentlyAddedContainer, tracks);
|
||||
@@ -86,8 +86,8 @@ TracksInfo::refreshRecentlyAdded()
|
||||
void
|
||||
TracksInfo::refreshMostPlayed()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
auto tracks = LmsApp->getUser()->getPlayedTrackList()->getTopTracks(5);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
const auto tracks {LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTopTracks(5)};
|
||||
|
||||
_mostPlayedContainer->clear();
|
||||
addEntries(_mostPlayedContainer, tracks);
|
||||
|
||||
@@ -52,14 +52,12 @@ _filters(filters)
|
||||
Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
|
||||
playBtn->clicked().connect(std::bind([=]
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
tracksPlay.emit(getTracks());
|
||||
}));
|
||||
|
||||
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
||||
addBtn->clicked().connect(std::bind([=]
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
tracksAdd.emit(getTracks());
|
||||
}));
|
||||
|
||||
@@ -76,18 +74,24 @@ _filters(filters)
|
||||
filters->updated().connect(this, &Tracks::refresh);
|
||||
}
|
||||
|
||||
std::vector<Database::Track::pointer>
|
||||
std::vector<Database::IdType>
|
||||
Tracks::getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults)
|
||||
{
|
||||
auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
|
||||
auto clusterIds {_filters->getClusterIds()};
|
||||
const auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
|
||||
const auto clusterIds {_filters->getClusterIds()};
|
||||
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const auto tracks {Track::getByFilter(LmsApp->getDbSession(), clusterIds, searchKeywords, offset, size, moreResults)};
|
||||
std::vector<Database::IdType> res;
|
||||
res.reserve(tracks.size());
|
||||
std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track.id(); });
|
||||
|
||||
return res;
|
||||
|
||||
return Track::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, offset, size, moreResults);
|
||||
}
|
||||
|
||||
std::vector<Database::Track::pointer>
|
||||
std::vector<Database::IdType>
|
||||
Tracks::getTracks()
|
||||
{
|
||||
bool moreResults;
|
||||
@@ -104,14 +108,15 @@ Tracks::refresh()
|
||||
void
|
||||
Tracks::addSome()
|
||||
{
|
||||
Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
|
||||
|
||||
bool moreResults;
|
||||
auto tracks {getTracks(_tracksContainer->count(), 20, moreResults)};
|
||||
const std::vector<Database::IdType> trackIds {getTracks(_tracksContainer->count(), 20, moreResults)};
|
||||
|
||||
for (auto track : tracks)
|
||||
for (const Database::IdType trackId : trackIds)
|
||||
{
|
||||
auto trackId {track.id()};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||
|
||||
Wt::WTemplate* entry {_tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
|
||||
|
||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WTemplate.h>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
@@ -38,15 +39,15 @@ class Tracks : public Wt::WTemplate
|
||||
Wt::Signal<Database::IdType> trackAdd;
|
||||
Wt::Signal<Database::IdType> trackPlay;
|
||||
|
||||
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
|
||||
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
|
||||
Wt::Signal<std::vector<Database::IdType>> tracksAdd;
|
||||
Wt::Signal<std::vector<Database::IdType>> tracksPlay;
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void addSome();
|
||||
|
||||
std::vector<Database::Track::pointer> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults);
|
||||
std::vector<Database::Track::pointer> getTracks();
|
||||
std::vector<Database::IdType> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults);
|
||||
std::vector<Database::IdType> getTracks();
|
||||
|
||||
Wt::WContainerWidget* _tracksContainer;
|
||||
Wt::WPushButton* _showMore;
|
||||
|
||||
@@ -38,9 +38,7 @@ AudioResource:: ~AudioResource()
|
||||
std::string
|
||||
AudioResource::getUrl(Database::IdType trackId) const
|
||||
{
|
||||
std::string res = url()+ "&trackid=" + std::to_string(trackId);
|
||||
|
||||
return res;
|
||||
return url()+ "&trackid=" + std::to_string(trackId);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -82,14 +80,12 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
|
||||
return;
|
||||
}
|
||||
|
||||
// transactions are not thread safe
|
||||
// DbSession are not thread safe
|
||||
{
|
||||
Wt::WApplication::UpdateLock lock(LmsApp);
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById(LmsApp->getDboSession(), trackId);
|
||||
|
||||
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||
if (!track)
|
||||
{
|
||||
LMS_LOG(UI, ERROR) << "Missing track";
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
||||
if (!sizeStr)
|
||||
return;
|
||||
|
||||
auto size = readAs<std::size_t>(*sizeStr);
|
||||
const auto size {readAs<std::size_t>(*sizeStr)};
|
||||
if (!size || *size > maxSize)
|
||||
return;
|
||||
|
||||
@@ -73,26 +73,26 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
||||
|
||||
if (trackIdStr)
|
||||
{
|
||||
auto trackId = readAs<Database::IdType>(*trackIdStr);
|
||||
const auto trackId {readAs<Database::IdType>(*trackIdStr)};
|
||||
if (!trackId)
|
||||
return;
|
||||
|
||||
// transactions are not thread safe
|
||||
// DbSession are not thread safe
|
||||
{
|
||||
Wt::WApplication::UpdateLock lock(LmsApp);
|
||||
cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDboSession(), *trackId, Image::Format::JPEG, *size);
|
||||
Wt::WApplication::UpdateLock lock {LmsApp};
|
||||
cover = getService<CoverArt::Grabber>()->getFromTrack(LmsApp->getDbSession(), *trackId, Image::Format::JPEG, *size);
|
||||
}
|
||||
}
|
||||
else if (releaseIdStr)
|
||||
{
|
||||
auto releaseId = readAs<Database::IdType>(*releaseIdStr);
|
||||
const auto releaseId {readAs<Database::IdType>(*releaseIdStr)};
|
||||
if (!releaseId)
|
||||
return;
|
||||
|
||||
// transactions are not thread safe
|
||||
// DbSession are not thread safe
|
||||
{
|
||||
Wt::WApplication::UpdateLock lock(LmsApp);
|
||||
cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDboSession(), *releaseId, Image::Format::JPEG, *size);
|
||||
Wt::WApplication::UpdateLock lock {LmsApp};
|
||||
cover = getService<CoverArt::Grabber>()->getFromRelease(LmsApp->getDbSession(), *releaseId, Image::Format::JPEG, *size);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <Wt/WResource.h>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "image/Image.hpp"
|
||||
@@ -35,7 +34,7 @@ namespace UserInterface {
|
||||
class ImageResource : public Wt::WResource
|
||||
{
|
||||
public:
|
||||
static const std::size_t maxSize = 512;
|
||||
static const std::size_t maxSize {512};
|
||||
|
||||
~ImageResource();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user