diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 95dd0e16..f5ca6a76 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -39,10 +39,11 @@ #include "database/TrackFeatures.hpp" #include "database/User.hpp" -namespace Database { +namespace Database +{ using Version = std::size_t; - static constexpr Version LMS_DATABASE_VERSION {29}; + static constexpr Version LMS_DATABASE_VERSION {30}; class VersionInfo { @@ -78,7 +79,7 @@ namespace Database { private: int _version {LMS_DATABASE_VERSION}; -}; + }; void Session::doDatabaseMigrationIfNeeded() @@ -315,6 +316,11 @@ CREATE TABLE "user_backup" ( _session.execute("DROP TABLE user"); _session.execute("ALTER TABLE user_backup RENAME TO user"); } + else if (version == 29) + { + // new field data_time in tracklist_entry (used by async scrobble or to make stats) + _session.execute("ALTER TABLE tracklist_entry ADD date_time TEXT"); + } else { LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; diff --git a/src/libs/database/include/database/TrackList.hpp b/src/libs/database/include/database/TrackList.hpp index b890391e..a7ba1a8c 100644 --- a/src/libs/database/include/database/TrackList.hpp +++ b/src/libs/database/include/database/TrackList.hpp @@ -22,8 +22,10 @@ #include #include #include +#include #include +#include #include "Types.hpp" @@ -142,13 +144,16 @@ class TrackListEntry : public Wt::Dbo::Dbo template void persist(Action& a) { - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::field(a, _dateTime, "date_time"); + + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _tracklist, "tracklist", Wt::Dbo::OnDeleteCascade); } private: - Wt::Dbo::ptr _track; + Wt::WDateTime _dateTime; + Wt::Dbo::ptr _track; Wt::Dbo::ptr _tracklist; }; diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index a058abfb..d81ae7b4 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(lms main.cpp ui/Auth.cpp ui/LmsApplication.cpp - ui/LmsApplicationGroup.cpp + ui/LmsApplicationManager.cpp ui/LmsTheme.cpp ui/MediaPlayer.cpp ui/PlayQueue.cpp diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 71b9b95d..38a450d8 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -34,6 +34,7 @@ #include "recommendation/IEngine.hpp" #include "subsonic/SubsonicResource.hpp" #include "ui/LmsApplication.hpp" +#include "ui/LmsApplicationManager.hpp" #include "utils/IChildProcessManager.hpp" #include "utils/IConfig.hpp" #include "utils/Service.hpp" @@ -221,7 +222,7 @@ int main(int argc, char* argv[]) session.optimize(); } - UserInterface::LmsApplicationGroupContainer appGroups; + UserInterface::LmsApplicationManager appManager; // Service initialization order is important (reverse-order for deinit) Service childProcessManagerService {createChildProcessManager()}; @@ -268,7 +269,7 @@ int main(int argc, char* argv[]) server.addEntryPoint(Wt::EntryPointType::Application, [&](const Wt::WEnvironment &env) { - return UserInterface::LmsApplication::create(env, database, appGroups); + return UserInterface::LmsApplication::create(env, database, appManager); }); proxyScannerEventsToApplication(*scannerService, server); diff --git a/src/lms/ui/Auth.cpp b/src/lms/ui/Auth.cpp index 4e84eaa6..11a27a0b 100644 --- a/src/lms/ui/Auth.cpp +++ b/src/lms/ui/Auth.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index f86906ee..a778c796 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -53,6 +53,7 @@ #include "resource/CoverResource.hpp" #include "Auth.hpp" #include "LmsApplicationException.hpp" +#include "LmsApplicationManager.hpp" #include "LmsTheme.hpp" #include "MediaPlayer.hpp" #include "PlayQueue.hpp" @@ -63,7 +64,7 @@ namespace UserInterface { static constexpr const char* defaultPath {"/releases"}; std::unique_ptr -LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups) +LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager) { if (auto *authEnvService {Service<::Auth::IEnvService>::get()}) { @@ -75,10 +76,10 @@ LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplica return std::make_unique(env); } - return std::make_unique(env, db, appGroups, checkResult.userId); + return std::make_unique(env, db, appManager, checkResult.userId); } - return std::make_unique(env, db, appGroups); + return std::make_unique(env, db, appManager); } LmsApplication* @@ -102,6 +103,12 @@ LmsApplication::getUser() return Database::User::getById(getDbSession(), _authenticatedUser->userId); } +Database::IdType +LmsApplication::getUserId() +{ + return _authenticatedUser->userId; +} + bool LmsApplication::isUserAuthStrong() const { @@ -134,11 +141,11 @@ LmsApplication::getUserLoginName() LmsApplication::LmsApplication(const Wt::WEnvironment& env, Database::Db& db, - LmsApplicationGroupContainer& appGroups, + LmsApplicationManager& appManager, std::optional userId) : Wt::WApplication {env} , _db {db} -, _appGroups {appGroups} +, _appManager {appManager} , _authenticatedUser {userId ? std::make_optional(UserAuthInfo {*userId, false}) : std::nullopt} { try @@ -261,16 +268,7 @@ void LmsApplication::finalize() { if (_authenticatedUser) - { - LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment()); - - getApplicationGroup().postOthers([info] - { - LmsApp->getEvents().appClosed(info); - }); - - getApplicationGroup().leave(); - } + _appManager.unregisterApplication(*this); preQuit().emit(); } @@ -427,12 +425,6 @@ handlePathChange(Wt::WStackedWidget& stack, bool isAdmin) wApp->setInternalPath(defaultPath, true); } -LmsApplicationGroup& -LmsApplication::getApplicationGroup() -{ - return _appGroups.get(_authenticatedUser->userId); -} - void LmsApplication::logoutUser() { @@ -451,14 +443,19 @@ LmsApplication::onUserLoggedIn() setTheme(); root()->clear(); - const LmsApplicationInfo info {LmsApplicationInfo::fromEnvironment(environment())}; - LMS_LOG(UI, INFO) << "User '" << getUserLoginName() << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent(); - getApplicationGroup().join(info); - getApplicationGroup().postOthers([info] + _appManager.registerApplication(*this); + _appManager.applicationRegistered.connect(this, [this] (LmsApplication& otherApplication) { - LmsApp->getEvents().appOpen(info); + // Only one active session by user + if (otherApplication.getUserId() == getUserId()) + { + if (!LmsApp->isUserDemo()) + { + quit(Wt::WString::tr("Lms.quit-other-session")); + } + } }); createHome(); @@ -576,16 +573,6 @@ LmsApplication::createHome() }); } - // Events from Application group - _events.appOpen.connect([=] - { - // Only one active session by user - if (!LmsApp->isUserDemo()) - { - quit(Wt::WString::tr("Lms.quit-other-session")); - } - }); - internalPathChanged().connect([=] { handlePathChange(*mainStack, isUserAdmin()); diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index f6b5217b..37d7cbf7 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -25,8 +25,6 @@ #include "scanner/ScannerEvents.hpp" -#include "LmsApplicationGroup.hpp" - namespace Database { class Artist; @@ -47,23 +45,16 @@ class CoverResource; class LmsApplicationException; class MediaPlayer; class PlayQueue; - -// Events that can be listen from anywhere in the application -struct Events -{ - // Events relative to group - Wt::Signal appOpen; - Wt::Signal appClosed; -}; +class LmsApplicationManager; class LmsApplication : public Wt::WApplication { public: - LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups, std::optional userId = std::nullopt); + LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager, std::optional userId = std::nullopt); ~LmsApplication(); - static std::unique_ptr create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups); + static std::unique_ptr create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager); static LmsApplication* instance(); @@ -71,13 +62,14 @@ class LmsApplication : public Wt::WApplication std::shared_ptr getCoverResource() { return _coverResource; } Database::Session& getDbSession(); // always thread safe - Wt::Dbo::ptr getUser(); + Wt::Dbo::ptr getUser(); + Database::IdType getUserId(); bool isUserAuthStrong() const; // user must be logged in prior this call bool isUserAdmin(); // user must be logged in prior this call bool isUserDemo(); // user must be logged in prior this call std::string getUserLoginName(); // user must be logged in prior this call - Events& getEvents() { return _events; } + // Proxified scanner events Scanner::Events& getScannerEvents() { return _scannerEvents; } // Utils @@ -113,8 +105,6 @@ class LmsApplication : public Wt::WApplication void handleException(LmsApplicationException& e); void goHomeAndQuit(); - LmsApplicationGroup& getApplicationGroup(); - // Signal slots void logoutUser(); void onUserLoggedIn(); @@ -126,8 +116,7 @@ class LmsApplication : public Wt::WApplication Database::Db& _db; Wt::Signal<> _preQuit; - LmsApplicationGroupContainer& _appGroups; - Events _events; + LmsApplicationManager& _appManager; Scanner::Events _scannerEvents; struct UserAuthInfo { diff --git a/src/lms/ui/LmsApplicationGroup.cpp b/src/lms/ui/LmsApplicationGroup.cpp deleted file mode 100644 index d9c41199..00000000 --- a/src/lms/ui/LmsApplicationGroup.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2018 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 . - */ - -#include "LmsApplicationGroup.hpp" - -#include - -#include -#include - - -namespace UserInterface { - -LmsApplicationInfo -LmsApplicationInfo::fromEnvironment(const Wt::WEnvironment& env) -{ - LmsApplicationInfo info = {env.agent()}; - return info; -} - -void -LmsApplicationGroup::join(LmsApplicationInfo info) -{ - std::unique_lock lock {_mutex}; - - _apps.emplace(wApp->sessionId(), std::move(info)); -} - -void -LmsApplicationGroup::leave() -{ - std::unique_lock lock {_mutex}; - - _apps.erase(wApp->sessionId()); -} - -std::vector -LmsApplicationGroup::getOtherSessionIds() const -{ - std::vector res; - - std::unique_lock lock {_mutex}; - for (auto const& app : _apps) - { - if (app.first != wApp->sessionId()) - res.push_back(app.first); - } - - return res; -} - -void -LmsApplicationGroup::postOthers(std::function func) const -{ - for (auto const& sessionId : getOtherSessionIds()) - { - Wt::WServer::instance()->post(sessionId, [=] - { - func(); - wApp->triggerUpdate(); - }); - } -} - -LmsApplicationGroup& -LmsApplicationGroupContainer::get(Database::IdType userId) -{ - std::unique_lock lock {_mutex}; - - return _apps[userId]; -} - -} // UserInterface diff --git a/src/lms/ui/LmsApplicationGroup.hpp b/src/lms/ui/LmsApplicationGroup.hpp deleted file mode 100644 index ae579e91..00000000 --- a/src/lms/ui/LmsApplicationGroup.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2018 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 . - */ - -#pragma once - -#include -#include - -#include -#include - -#include "database/Types.hpp" - -namespace UserInterface { - - -struct LmsApplicationInfo -{ - Wt::UserAgent userAgent; - - static LmsApplicationInfo fromEnvironment(const Wt::WEnvironment& env); -}; - -// LmsApplication instances are grouped by User -class LmsApplicationGroup -{ - public: - void join(LmsApplicationInfo info); - void leave(); - - void postOthers(std::function func) const; - - private: - - mutable std::mutex _mutex; - - std::vector getOtherSessionIds() const; - - std::map _apps; -}; - -class LmsApplicationGroupContainer -{ - public: - LmsApplicationGroup& get(Database::IdType userId); - - private: - std::map _apps; - std::mutex _mutex; -}; - -} // UserInterface diff --git a/src/lms/ui/LmsApplicationManager.cpp b/src/lms/ui/LmsApplicationManager.cpp new file mode 100644 index 00000000..44390e06 --- /dev/null +++ b/src/lms/ui/LmsApplicationManager.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 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 . + */ + +#include "LmsApplicationManager.hpp" + +#include "LmsApplication.hpp" + +namespace UserInterface +{ + void + LmsApplicationManager::registerApplication(LmsApplication& application) + { + { + std::scoped_lock lock {_mutex}; + m_applications[application.getUserId()].insert(&application); + } + + applicationRegistered.emit(application); + } + + void + LmsApplicationManager::unregisterApplication(LmsApplication& application) + { + { + std::scoped_lock lock {_mutex}; + m_applications[application.getUserId()].erase(&application); + } + + applicationUnregistered.emit(application); + } + +} // UserInterface diff --git a/src/lms/ui/LmsApplicationManager.hpp b/src/lms/ui/LmsApplicationManager.hpp new file mode 100644 index 00000000..b67e339d --- /dev/null +++ b/src/lms/ui/LmsApplicationManager.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 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 . + */ + +#pragma once + +#include +#include +#include + +#include + +#include "database/Types.hpp" + +namespace UserInterface +{ + class LmsApplication; + class LmsApplicationManager + { + public: + Wt::Signal applicationRegistered; + Wt::Signal applicationUnregistered; + + private: + + friend class LmsApplication; + + void registerApplication(LmsApplication& application); + void unregisterApplication(LmsApplication& application); + + std::mutex _mutex; + std::unordered_map> m_applications; + }; +} // UserInterface diff --git a/src/lms/ui/common/PasswordValidator.cpp b/src/lms/ui/common/PasswordValidator.cpp index 64cfc2f6..20559f5d 100644 --- a/src/lms/ui/common/PasswordValidator.cpp +++ b/src/lms/ui/common/PasswordValidator.cpp @@ -19,6 +19,8 @@ #include "PasswordValidator.hpp" +#include + #include "auth/IPasswordService.hpp" #include "utils/Service.hpp" #include "LmsApplication.hpp"