Added date/time field for tracklist, added a LmsApplicationManager to make scrobbler register for played tracks

This commit is contained in:
emeric
2021-03-23 14:01:09 +01:00
parent 260e5833d3
commit 2c4be7468a
12 changed files with 151 additions and 220 deletions
+9 -3
View File
@@ -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";
@@ -22,8 +22,10 @@
#include <optional>
#include <string>
#include <set>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "Types.hpp"
@@ -142,13 +144,16 @@ class TrackListEntry : public Wt::Dbo::Dbo<TrackListEntry>
template<class Action>
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> _track;
Wt::WDateTime _dateTime;
Wt::Dbo::ptr<Track> _track;
Wt::Dbo::ptr<TrackList> _tracklist;
};
+1 -1
View File
@@ -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
+3 -2
View File
@@ -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<IChildProcessManager> 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);
+1
View File
@@ -21,6 +21,7 @@
#include <iomanip>
#include <Wt/WEnvironment.h>
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WCheckBox.h>
+23 -36
View File
@@ -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<Wt::WApplication>
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<Wt::WApplication>(env);
}
return std::make_unique<LmsApplication>(env, db, appGroups, checkResult.userId);
return std::make_unique<LmsApplication>(env, db, appManager, checkResult.userId);
}
return std::make_unique<LmsApplication>(env, db, appGroups);
return std::make_unique<LmsApplication>(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<Database::IdType> userId)
: Wt::WApplication {env}
, _db {db}
, _appGroups {appGroups}
, _appManager {appManager}
, _authenticatedUser {userId ? std::make_optional<UserAuthInfo>(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());
+7 -18
View File
@@ -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<LmsApplicationInfo> appOpen;
Wt::Signal<LmsApplicationInfo> appClosed;
};
class LmsApplicationManager;
class LmsApplication : public Wt::WApplication
{
public:
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups, std::optional<Database::IdType> userId = std::nullopt);
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager, std::optional<Database::IdType> userId = std::nullopt);
~LmsApplication();
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
static std::unique_ptr<Wt::WApplication> 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<CoverResource> getCoverResource() { return _coverResource; }
Database::Session& getDbSession(); // always thread safe
Wt::Dbo::ptr<Database::User> getUser();
Wt::Dbo::ptr<Database::User> 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
{
-89
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "LmsApplicationGroup.hpp"
#include <algorithm>
#include <Wt/WServer.h>
#include <Wt/WApplication.h>
namespace UserInterface {
LmsApplicationInfo
LmsApplicationInfo::fromEnvironment(const Wt::WEnvironment& env)
{
LmsApplicationInfo info = {env.agent()};
return info;
}
void
LmsApplicationGroup::join(LmsApplicationInfo info)
{
std::unique_lock<std::mutex> lock {_mutex};
_apps.emplace(wApp->sessionId(), std::move(info));
}
void
LmsApplicationGroup::leave()
{
std::unique_lock<std::mutex> lock {_mutex};
_apps.erase(wApp->sessionId());
}
std::vector<std::string>
LmsApplicationGroup::getOtherSessionIds() const
{
std::vector<std::string> res;
std::unique_lock<std::mutex> lock {_mutex};
for (auto const& app : _apps)
{
if (app.first != wApp->sessionId())
res.push_back(app.first);
}
return res;
}
void
LmsApplicationGroup::postOthers(std::function<void()> func) const
{
for (auto const& sessionId : getOtherSessionIds())
{
Wt::WServer::instance()->post(sessionId, [=]
{
func();
wApp->triggerUpdate();
});
}
}
LmsApplicationGroup&
LmsApplicationGroupContainer::get(Database::IdType userId)
{
std::unique_lock<std::mutex> lock {_mutex};
return _apps[userId];
}
} // UserInterface
-68
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <mutex>
#include <Wt/WSignal.h>
#include <Wt/WEnvironment.h>
#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<void()> func) const;
private:
mutable std::mutex _mutex;
std::vector<std::string> getOtherSessionIds() const;
std::map<std::string, LmsApplicationInfo> _apps;
};
class LmsApplicationGroupContainer
{
public:
LmsApplicationGroup& get(Database::IdType userId);
private:
std::map<Database::IdType /* userId */, LmsApplicationGroup> _apps;
std::mutex _mutex;
};
} // UserInterface
+48
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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
+49
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <mutex>
#include <unordered_set>
#include <unordered_map>
#include <Wt/WSignal.h>
#include "database/Types.hpp"
namespace UserInterface
{
class LmsApplication;
class LmsApplicationManager
{
public:
Wt::Signal<LmsApplication&> applicationRegistered;
Wt::Signal<LmsApplication&> applicationUnregistered;
private:
friend class LmsApplication;
void registerApplication(LmsApplication& application);
void unregisterApplication(LmsApplication& application);
std::mutex _mutex;
std::unordered_map<Database::IdType /* user */, std::unordered_set<LmsApplication*>> m_applications;
};
} // UserInterface
+2
View File
@@ -19,6 +19,8 @@
#include "PasswordValidator.hpp"
#include <Wt/WEnvironment.h>
#include "auth/IPasswordService.hpp"
#include "utils/Service.hpp"
#include "LmsApplication.hpp"