diff --git a/Makefile.am b/Makefile.am index f40a0bf0..e0209bca 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,9 +7,6 @@ bin_PROGRAMS = lms lms_SOURCES = \ $(top_srcdir)/main/main.cpp \ - $(top_srcdir)/main/ServiceManager.cpp \ - $(top_srcdir)/main/DatabaseUpdateService.cpp \ - $(top_srcdir)/main/UserInterfaceService.cpp \ $(top_srcdir)/av/CodecContext.cpp \ $(top_srcdir)/av/Common.cpp \ $(top_srcdir)/av/Dictionary.cpp \ @@ -18,20 +15,6 @@ lms_SOURCES = \ $(top_srcdir)/av/Stream.cpp \ $(top_srcdir)/cover/CoverArt.cpp \ $(top_srcdir)/cover/CoverArtGrabber.cpp \ - $(top_srcdir)/ui/LmsApplication.cpp \ - $(top_srcdir)/ui/LmsHome.cpp \ - $(top_srcdir)/ui/audio/AudioWidget.cpp \ - $(top_srcdir)/ui/audio/AudioDatabaseWidget.cpp \ - $(top_srcdir)/ui/audio/AudioMediaPlayerWidget.cpp \ - $(top_srcdir)/ui/audio/SearchFilterWidget.cpp \ - $(top_srcdir)/ui/audio/TableFilterWidget.cpp \ - $(top_srcdir)/ui/audio/TrackWidget.cpp \ - $(top_srcdir)/ui/common/SessionData.cpp \ - $(top_srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ - $(top_srcdir)/ui/video/VideoWidget.cpp \ - $(top_srcdir)/ui/video/VideoDatabaseWidget.cpp \ - $(top_srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ - $(top_srcdir)/ui/video/VideoParametersDialog.cpp \ $(top_srcdir)/database/Artist.cpp \ $(top_srcdir)/database/Genre.cpp \ $(top_srcdir)/database/Release.cpp \ @@ -41,14 +24,11 @@ lms_SOURCES = \ $(top_srcdir)/database/SqlQuery.cpp \ $(top_srcdir)/database/Path.cpp \ $(top_srcdir)/database/Video.cpp \ + $(top_srcdir)/database/User.cpp \ $(top_srcdir)/database-updater/DatabaseUpdater.cpp \ $(top_srcdir)/database-updater/Checksum.cpp \ - $(top_srcdir)/transcode/AvConvTranscoder.cpp \ - $(top_srcdir)/transcode/Format.cpp \ - $(top_srcdir)/transcode/Parameters.cpp \ - $(top_srcdir)/transcode/InputMediaFile.cpp \ $(top_srcdir)/metadata/AvFormat.cpp \ - $(top_srcdir)/main/RemoteServerService.cpp \ + $(top_srcdir)/metadata/Utils.cpp \ $(top_srcdir)/remote/messages/auth.pb.cc \ $(top_srcdir)/remote/messages/collection.pb.cc \ $(top_srcdir)/remote/messages/common.pb.cc \ @@ -61,7 +41,32 @@ lms_SOURCES = \ $(top_srcdir)/remote/server/MediaRequestHandler.cpp \ $(top_srcdir)/remote/server/RequestHandler.cpp \ $(top_srcdir)/remote/server/Server.cpp \ - $(top_srcdir)/metadata/Utils.cpp + $(top_srcdir)/service/ServiceManager.cpp \ + $(top_srcdir)/service/DatabaseUpdateService.cpp \ + $(top_srcdir)/service/UserInterfaceService.cpp \ + $(top_srcdir)/service/RemoteServerService.cpp \ + $(top_srcdir)/transcode/AvConvTranscoder.cpp \ + $(top_srcdir)/transcode/Format.cpp \ + $(top_srcdir)/transcode/Parameters.cpp \ + $(top_srcdir)/transcode/InputMediaFile.cpp \ + $(top_srcdir)/ui/LmsApplication.cpp \ + $(top_srcdir)/ui/LmsHome.cpp \ + $(top_srcdir)/ui/auth/LmsAuth.cpp \ + $(top_srcdir)/ui/audio/AudioWidget.cpp \ + $(top_srcdir)/ui/audio/AudioDatabaseWidget.cpp \ + $(top_srcdir)/ui/audio/AudioMediaPlayerWidget.cpp \ + $(top_srcdir)/ui/audio/SearchFilterWidget.cpp \ + $(top_srcdir)/ui/audio/TableFilterWidget.cpp \ + $(top_srcdir)/ui/audio/TrackWidget.cpp \ + $(top_srcdir)/ui/common/DirectoryValidator.cpp \ + $(top_srcdir)/ui/common/SessionData.cpp \ + $(top_srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ + $(top_srcdir)/ui/video/VideoWidget.cpp \ + $(top_srcdir)/ui/video/VideoDatabaseWidget.cpp \ + $(top_srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ + $(top_srcdir)/ui/video/VideoParametersDialog.cpp \ + $(top_srcdir)/ui/settings/Settings.cpp \ + $(top_srcdir)/ui/settings/SettingsDatabase.cpp lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/boost/ -I$(top_srcdir)/ui -I$(top_srcdir)/remote diff --git a/TODO b/TODO index 1d4fb862..0607a5de 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,12 @@ [ServiceManager] - Rework the whole start/stop/try/cach/thread/interrupts things +- Rework the io_service thread pool thing + +[Services] +- [UI] generate argc/argv from a config file (crypto, port info, db path) +- [Remote] use config file to get crypto, port info, db path +- consider using external web server instead of builtin httpd (new FCGI service?) [Settings] - [admin] Add/remove users @@ -21,6 +27,7 @@ - Scaling: find something more "reliable" than GIL and its customs extensions (adobe work, io_new)? [Database] +- Remove Audio/Video distinction - When removing a track, make sure to remove genre/artist/release if last of it - Use Inotify like system to watch modified/added files? - Implement a video database cleanup diff --git a/configure.ac b/configure.ac index b12b3bbc..08235bca 100644 --- a/configure.ac +++ b/configure.ac @@ -67,10 +67,10 @@ AC_CHECK_LIB( [boost_thread], , [AC_MSG_ERROR([libboost_thread not found!])]) -#AC_CHECK_LIB( [boost_date_time], -# [main], -# , -# [AC_MSG_ERROR([libboost_date_time not found!])]) +AC_CHECK_LIB( [boost_date_time], + [main], + , + [AC_MSG_ERROR([libboost_date_time not found!])]) #AC_CHECK_LIB( [boost_signals], # [main], diff --git a/database-updater/DatabaseUpdater.hpp b/database-updater/DatabaseUpdater.hpp index e3fff663..61487dda 100644 --- a/database-updater/DatabaseUpdater.hpp +++ b/database-updater/DatabaseUpdater.hpp @@ -13,6 +13,12 @@ namespace DatabaseUpdater { class Updater { public: + Updater(boost::filesystem::path db, MetaData::Parser& parser); + + // Update database + void process(); + + private: struct Stats { @@ -30,19 +36,6 @@ class Updater Stats videoStats; }; - Updater(boost::filesystem::path db, MetaData::Parser& parser); - - // Update database - // TODO add a callback to get notifed once finished - void process(); - - const Result& getResult(void) const { return _result; } - - private: - - -// void refresh(const WatchedDirectory& directory); - // Video void refreshVideoDirectory( const boost::filesystem::path& directory ); void processVideoFile( const boost::filesystem::path& file); diff --git a/database/DatabaseHandler.cpp b/database/DatabaseHandler.cpp index 8452a078..262a5bc7 100644 --- a/database/DatabaseHandler.cpp +++ b/database/DatabaseHandler.cpp @@ -91,4 +91,29 @@ Handler::getUserDatabase() return *_users; } +User::pointer +Handler::getCurrentUser() +{ + if (_login.loggedIn()) + return getUser(_login.user()); + else + return User::pointer(); +} + +User::pointer +Handler::getUser(const Wt::Auth::User& authUser) +{ + Wt::Dbo::ptr authInfo = _users->find(authUser); + + User::pointer user = authInfo->user(); + + if (!user) { + user = _session.add(new User()); + authInfo.modify()->setUser(user); + } + + return user; +} + + } // namespace Database diff --git a/database/MediaDirectory.cpp b/database/MediaDirectory.cpp index 3542c8a2..39b0de37 100644 --- a/database/MediaDirectory.cpp +++ b/database/MediaDirectory.cpp @@ -1,3 +1,5 @@ +#include + #include "MediaDirectory.hpp" namespace Database { @@ -27,6 +29,14 @@ MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Typ return session.add( new MediaDirectory( p, type ) ); } +void +MediaDirectory::eraseAll(Wt::Dbo::Session& session) +{ + std::vector dirs = getAll(session); + BOOST_FOREACH(MediaDirectory::pointer dir, dirs) + dir.remove(); +} + std::vector MediaDirectory::getAll(Wt::Dbo::Session& session) { diff --git a/database/MediaDirectory.hpp b/database/MediaDirectory.hpp index 63b3c20f..f49b153e 100644 --- a/database/MediaDirectory.hpp +++ b/database/MediaDirectory.hpp @@ -24,15 +24,25 @@ class MediaDirectorySettings static pointer get(Wt::Dbo::Session& session); // write accessors + void setManualScanRequested(bool value) { _manualScanRequested = value;} + void setUpdatePeriod(boost::posix_time::time_duration dur) { _updatePeriod = dur;} + void setUpdateStartTime(boost::posix_time::time_duration dur) { _updateStartTime = dur;} void setLastUpdate(boost::posix_time::ptime time) { _lastUpdate = time; } void setLastScan(boost::posix_time::ptime time) { _lastScan = time; } // Read accessors + bool getManualScanRequested(void) const { return _manualScanRequested; } + boost::posix_time::time_duration getUpdatePeriod(void) const { return _updatePeriod; } + boost::posix_time::time_duration getUpdateStartTime(void) const { return _updateStartTime; } boost::posix_time::ptime getLastUpdated(void) const { return _lastUpdate; } + boost::posix_time::ptime getLastScan(void) const { return _lastScan; } template void persist(Action& a) { + Wt::Dbo::field(a, _manualScanRequested, "manual_scan_requested"); + Wt::Dbo::field(a, _updatePeriod, "update_period"); + Wt::Dbo::field(a, _updateStartTime, "update_start_time"); Wt::Dbo::field(a, _lastUpdate, "last_update"); Wt::Dbo::field(a, _lastScan, "last_scan"); Wt::Dbo::hasMany(a, _mediaDirectories, Wt::Dbo::ManyToOne, "media_directory_settings"); @@ -40,7 +50,9 @@ class MediaDirectorySettings private: - boost::posix_time::time_duration _updatePeriod; // TODO + bool _manualScanRequested; // Immadiate scan has been requested by user + boost::posix_time::time_duration _updatePeriod; // How long between updates + boost::posix_time::time_duration _updateStartTime; // Time of day to begin the update boost::posix_time::ptime _lastUpdate; // last time the database has changed boost::posix_time::ptime _lastScan; // last time the database has been scanned Wt::Dbo::collection< Wt::Dbo::ptr > _mediaDirectories; // list of media directories @@ -65,6 +77,8 @@ class MediaDirectory static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type); static std::vector getAll(Wt::Dbo::Session& session); + static void eraseAll(Wt::Dbo::Session& session); + Type getType(void) const { return _type; } boost::filesystem::path getPath(void) const { return boost::filesystem::path(_path); } diff --git a/database/User.cpp b/database/User.cpp new file mode 100644 index 00000000..e08a2566 --- /dev/null +++ b/database/User.cpp @@ -0,0 +1,22 @@ + +#include "User.hpp" + + +namespace Database { + +User::User() +: _maxAudioBitrate(maxAudioBitrate), + _maxVideoBitrate(maxVideoBitrate), +_isAdmin(false), +_audioBitrate(defaultAudioBitrate), +_videoBitrate(defaultVideoBitrate) +{ + +} + + + + +} // namespace Database + + diff --git a/database/User.hpp b/database/User.hpp index a97849bf..d075cdf6 100644 --- a/database/User.hpp +++ b/database/User.hpp @@ -13,17 +13,38 @@ class User { public: + User(); + typedef Wt::Dbo::ptr pointer; + bool isAdmin() const {return _isAdmin;} + template void persist(Action& a) { + Wt::Dbo::field(a, _maxAudioBitrate, "max_audio_bitrate"); + Wt::Dbo::field(a, _maxVideoBitrate, "max_video_bitrate"); Wt::Dbo::field(a, _isAdmin, "admin"); + Wt::Dbo::field(a, _audioBitrate, "audio_bitrate"); + Wt::Dbo::field(a, _videoBitrate, "video_bitrate"); } private: - bool _isAdmin; + static const std::size_t maxAudioBitrate = 320000; + static const std::size_t maxVideoBitrate = 7500000; + + static const std::size_t defaultAudioBitrate = 128000; + static const std::size_t defaultVideoBitrate = 1500000; + + // Admin defined settings + int _maxAudioBitrate; + int _maxVideoBitrate; + bool _isAdmin; + + // User defined settings + int _audioBitrate; + int _videoBitrate; }; diff --git a/main/main.cpp b/main/main.cpp index 7c85b0a9..391842c6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -7,10 +7,10 @@ #include "av/Common.hpp" #include "database/DatabaseHandler.hpp" -#include "ServiceManager.hpp" -#include "DatabaseUpdateService.hpp" -#include "UserInterfaceService.hpp" -#include "RemoteServerService.hpp" +#include "service/ServiceManager.hpp" +#include "service/DatabaseUpdateService.hpp" +#include "service/UserInterfaceService.hpp" +#include "service/RemoteServerService.hpp" #include "ui/LmsApplication.hpp" @@ -21,7 +21,7 @@ int main(int argc, char* argv[]) try { - ServiceManager serviceManager; + ServiceManager& serviceManager = ServiceManager::instance(); // TODO Retreive the database path in some config file const boost::filesystem::path dbPath("test.db"); @@ -34,7 +34,7 @@ int main(int argc, char* argv[]) std::cout << "Starting services..." << std::endl; -// serviceManager.startService( std::make_shared( serviceManager.getIoService(), dbPath) ); + serviceManager.startService( std::make_shared( serviceManager.getIoService(), dbPath) ); serviceManager.startService( std::make_shared( serviceManager.getIoService(), remoteListenEndpoint, dbPath) ); serviceManager.startService( std::make_shared(argc, argv, dbPath) ); diff --git a/main/DatabaseUpdateService.cpp b/service/DatabaseUpdateService.cpp similarity index 55% rename from main/DatabaseUpdateService.cpp rename to service/DatabaseUpdateService.cpp index c5fe0faf..35f44328 100644 --- a/main/DatabaseUpdateService.cpp +++ b/service/DatabaseUpdateService.cpp @@ -11,13 +11,16 @@ DatabaseUpdateService::DatabaseUpdateService(boost::asio::io_service& ioService, void DatabaseUpdateService::start(void) { - _thread = boost::thread(boost::bind(&DatabaseUpdater::Updater::process, &_databaseUpdater)); + // TODO + // Read database parameters and program a timer for the next scan +// _thread = boost::thread(boost::bind(&DatabaseUpdater::Updater::process, &_databaseUpdater)); } void DatabaseUpdateService::stop(void) { std::cout << "DatabaseUpdateService::stop, processing..." << std::endl; + // no effect if thread does not exist _thread.interrupt(); _thread.join(); std::cout << "DatabaseUpdateService::stop, process done" << std::endl; @@ -26,7 +29,16 @@ DatabaseUpdateService::stop(void) void DatabaseUpdateService::restart(void) { - std::cout << "DatabaseUpdateService::restart, not implemented" << std::endl; + std::cout << "DatabaseUpdateService::restart" << std::endl; + stop(); + start(); +} + +bool +DatabaseUpdateService::isScanning(void) const +{ + // scanning is active only if a thread is running the updater + return _thread.get_id() != boost::thread::id(); } diff --git a/main/DatabaseUpdateService.hpp b/service/DatabaseUpdateService.hpp similarity index 76% rename from main/DatabaseUpdateService.hpp rename to service/DatabaseUpdateService.hpp index 4f34f4fa..759bb21b 100644 --- a/main/DatabaseUpdateService.hpp +++ b/service/DatabaseUpdateService.hpp @@ -13,12 +13,18 @@ class DatabaseUpdateService : public Service { public: + typedef std::shared_ptr pointer; + DatabaseUpdateService(boost::asio::io_service& ioService, const boost::filesystem::path& p); + // Service interface void start(void); void stop(void); void restart(void); + // Specific interface + bool isScanning(void) const; //return if the service is currently scanning the db + private: boost::thread _thread; diff --git a/main/RemoteServerService.cpp b/service/RemoteServerService.cpp similarity index 100% rename from main/RemoteServerService.cpp rename to service/RemoteServerService.cpp diff --git a/main/RemoteServerService.hpp b/service/RemoteServerService.hpp similarity index 100% rename from main/RemoteServerService.hpp rename to service/RemoteServerService.hpp diff --git a/main/Service.hpp b/service/Service.hpp similarity index 93% rename from main/Service.hpp rename to service/Service.hpp index 0f88de1f..e70d4e9a 100644 --- a/main/Service.hpp +++ b/service/Service.hpp @@ -1,6 +1,7 @@ #ifndef SERVICE_HPP #define SERVICE_HPP +#include #include #include @@ -21,8 +22,6 @@ class Service virtual void stop(void) = 0; virtual void restart(void) = 0; - private: - }; #endif diff --git a/main/ServiceManager.cpp b/service/ServiceManager.cpp similarity index 95% rename from main/ServiceManager.cpp rename to service/ServiceManager.cpp index e7edebe9..67b9dead 100644 --- a/main/ServiceManager.cpp +++ b/service/ServiceManager.cpp @@ -6,6 +6,13 @@ #include "ServiceManager.hpp" +ServiceManager& +ServiceManager::instance() +{ + static ServiceManager instance; + return instance; +} + ServiceManager::ServiceManager() : _signalSet(_ioService) { diff --git a/main/ServiceManager.hpp b/service/ServiceManager.hpp similarity index 59% rename from main/ServiceManager.hpp rename to service/ServiceManager.hpp index b84609ce..a8212989 100644 --- a/main/ServiceManager.hpp +++ b/service/ServiceManager.hpp @@ -11,7 +11,7 @@ class ServiceManager { public: - ServiceManager(); + static ServiceManager& instance(); ~ServiceManager(); void stopService(Service::pointer service); @@ -25,8 +25,16 @@ class ServiceManager boost::asio::io_service& getIoService() {return _ioService;} const boost::asio::io_service& getIoService() const {return _ioService;} + template typename T::pointer getService(); + + boost::mutex& mutex() { return _mutex;} + private: + ServiceManager(); + ServiceManager(ServiceManager const&); // Don't Implement + void operator=(ServiceManager const&); // Don't implement + void restartServices(void); void stopServices(void); @@ -34,6 +42,8 @@ class ServiceManager void handleSignal(boost::system::error_code error, int signo); + boost::mutex _mutex; + boost::asio::io_service _ioService; // Listen for interesting signals @@ -43,5 +53,18 @@ class ServiceManager }; +template typename T::pointer +ServiceManager::getService() +{ + std::set::iterator it; + for (std::set::iterator it = _services.begin(); it != _services.end(); ++it) + { + if (typeid(*(*it)) == typeid(T)) { + return std::dynamic_pointer_cast(*it); + } + } + return std::shared_ptr(); +} + #endif diff --git a/main/UserInterfaceService.cpp b/service/UserInterfaceService.cpp similarity index 100% rename from main/UserInterfaceService.cpp rename to service/UserInterfaceService.cpp diff --git a/main/UserInterfaceService.hpp b/service/UserInterfaceService.hpp similarity index 100% rename from main/UserInterfaceService.hpp rename to service/UserInterfaceService.hpp diff --git a/test/Makefile.am b/test/Makefile.am index 90eb562b..033689b2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,6 +17,7 @@ remote_SOURCES = \ $(top_srcdir)/database/Track.cpp \ $(top_srcdir)/database/DatabaseHandler.cpp \ $(top_srcdir)/database/SqlQuery.cpp \ + $(top_srcdir)/database/User.cpp \ $(top_srcdir)/database/Path.cpp \ $(top_srcdir)/database/Video.cpp @@ -31,6 +32,7 @@ database_user_SOURCES = \ $(top_srcdir)/database/DatabaseHandler.cpp \ $(top_srcdir)/database/SqlQuery.cpp \ $(top_srcdir)/database/Path.cpp \ + $(top_srcdir)/database/User.cpp \ $(top_srcdir)/database/Video.cpp database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir) @@ -45,6 +47,7 @@ database_integrity_SOURCES = \ $(top_srcdir)/database/DatabaseHandler.cpp \ $(top_srcdir)/database/SqlQuery.cpp \ $(top_srcdir)/database/Path.cpp \ + $(top_srcdir)/database/User.cpp \ $(top_srcdir)/database/Video.cpp database_integrity_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir) @@ -59,6 +62,7 @@ database_basics_SOURCES = \ $(top_srcdir)/database/MediaDirectory.cpp \ $(top_srcdir)/database/SqlQuery.cpp \ $(top_srcdir)/database/Path.cpp \ + $(top_srcdir)/database/User.cpp \ $(top_srcdir)/database/Video.cpp database_basics_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir) diff --git a/ui/LmsApplication.cpp b/ui/LmsApplication.cpp index 9e8e364c..97a415d4 100644 --- a/ui/LmsApplication.cpp +++ b/ui/LmsApplication.cpp @@ -1,5 +1,5 @@ #include -#include "LmsAuth.hpp" +#include "auth/LmsAuth.hpp" #include "LmsHome.hpp" #include "LmsApplication.hpp" @@ -35,12 +35,10 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::p _sessionData.getDatabaseHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); - LmsAuth *authWidget = new LmsAuth(Database::Handler::getAuthService(), - _sessionData.getDatabaseHandler().getUserDatabase(), - _sessionData.getDatabaseHandler().getLogin()); + LmsAuth *authWidget = new LmsAuth(_sessionData.getDatabaseHandler()); authWidget->model()->addPasswordAuth(&Database::Handler::getPasswordService()); - authWidget->setRegistrationEnabled(true); + authWidget->setRegistrationEnabled(false); authWidget->processEnvironment(); diff --git a/ui/LmsAuth.hpp b/ui/LmsAuth.hpp deleted file mode 100644 index 0981c697..00000000 --- a/ui/LmsAuth.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include -#include -#include - -namespace UserInterface { - -class LmsAuth : public Wt::Auth::AuthWidget -{ - public: - - LmsAuth(const Wt::Auth::AuthService &baseAuth, - Wt::Auth::AbstractUserDatabase &users, - Wt::Auth::Login &login, - Wt::WContainerWidget *parent=0) - : Wt::Auth::AuthWidget(baseAuth, users, login, parent) {} - - // LoggedInView is delegated to LmsHome - void createLoggedInView () { hide();} - - void createLoginView () { show(); Wt::Auth::AuthWidget::createLoginView();} - - private: - - -}; - -} // namespace UserInterface diff --git a/ui/LmsHome.cpp b/ui/LmsHome.cpp index 56a167dd..3af31c7e 100644 --- a/ui/LmsHome.cpp +++ b/ui/LmsHome.cpp @@ -5,6 +5,8 @@ #include #include +#include "settings/Settings.hpp" + #include "LmsHome.hpp" namespace UserInterface { @@ -31,6 +33,7 @@ LmsHome::LmsHome(SessionData& sessionData) leftMenu->addItem("Audio", _audioWidget); leftMenu->addItem("Video", _videoWidget); + leftMenu->addItem("Settings", new Settings::Settings(_sessionData)); // Setup a Right-aligned menu. Wt::WMenu *rightMenu = new Wt::WMenu(); @@ -38,8 +41,6 @@ LmsHome::LmsHome(SessionData& sessionData) navigation->addMenu(rightMenu, Wt::AlignRight); Wt::WPopupMenu *popup = new Wt::WPopupMenu(); - popup->addItem("Parameters"); - popup->addSeparator(); popup->addItem("Logout"); popup->itemSelected().connect(this, &LmsHome::handleUserMenuSelected); diff --git a/ui/auth/LmsAuth.cpp b/ui/auth/LmsAuth.cpp new file mode 100644 index 00000000..dfd51e6f --- /dev/null +++ b/ui/auth/LmsAuth.cpp @@ -0,0 +1,27 @@ + +#include "LmsAuth.hpp" + +namespace UserInterface { + +LmsAuth::LmsAuth(Database::Handler& db) +: Wt::Auth::AuthWidget(db.getAuthService(), + db.getUserDatabase(), + db.getLogin()) +{ +} + + +void +LmsAuth::createLoggedInView() +{ + hide(); +} + +void +LmsAuth::createLoginView() +{ + show(); + Wt::Auth::AuthWidget::createLoginView(); +} + +} // namespace UserInterface diff --git a/ui/auth/LmsAuth.hpp b/ui/auth/LmsAuth.hpp new file mode 100644 index 00000000..1518f628 --- /dev/null +++ b/ui/auth/LmsAuth.hpp @@ -0,0 +1,32 @@ +#ifndef UI_AUTH_HPP +#define UI_AUTH_HPP + +#include +#include +#include +#include +#include + +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { + +class LmsAuth : public Wt::Auth::AuthWidget +{ + public: + + LmsAuth(Database::Handler& db); + + // LoggedInView is delegated to LmsHome + void createLoggedInView () ; + void createLoginView (); + + private: + + +}; + +} // namespace UserInterface + +#endif + diff --git a/ui/common/DirectoryValidator.cpp b/ui/common/DirectoryValidator.cpp new file mode 100644 index 00000000..2709d2df --- /dev/null +++ b/ui/common/DirectoryValidator.cpp @@ -0,0 +1,29 @@ +#include + +#include "DirectoryValidator.hpp" + +namespace UserInterface { + +DirectoryValidator::DirectoryValidator(bool mandatory, Wt::WObject *parent) +: Wt::WValidator(mandatory, parent) +{ +} + + +Wt::WValidator::Result +DirectoryValidator::validate(const Wt::WString& input) const +{ + boost::filesystem::path p(input.toUTF8()); + boost::system::error_code ec; + + bool res = boost::filesystem::is_directory(p, ec); + if (ec) + return Wt::WValidator::Result(Wt::WValidator::Invalid, ec.message()); + else if (res) + return Wt::WValidator::Result(Wt::WValidator::Valid, "Valid"); + else + return Wt::WValidator::Result(Wt::WValidator::Invalid, "Not a directory"); +} + + +} // namespace UserInterface diff --git a/ui/common/DirectoryValidator.hpp b/ui/common/DirectoryValidator.hpp new file mode 100644 index 00000000..c24cb816 --- /dev/null +++ b/ui/common/DirectoryValidator.hpp @@ -0,0 +1,14 @@ +#include + +namespace UserInterface { + +class DirectoryValidator : public Wt::WValidator +{ + public: + DirectoryValidator(bool mandatory, Wt::WObject *parent = 0); + + Wt::WValidator::Result validate(const Wt::WString& input) const; + +}; + +} // namespace UserInterface diff --git a/ui/common/SessionData.hpp b/ui/common/SessionData.hpp index 81c04622..98f17c64 100644 --- a/ui/common/SessionData.hpp +++ b/ui/common/SessionData.hpp @@ -1,8 +1,6 @@ #ifndef UI_SESSION_DATA_HPP #define UI_SESSION_DATA_HPP -#include - #include #include "database/DatabaseHandler.hpp" @@ -15,17 +13,12 @@ class SessionData SessionData(boost::filesystem::path dbPath); - void setAuthenticatedUser(std::string user); - - Database::Handler& getDatabaseHandler() { return _db;} const Database::Handler& getDatabaseHandler() const { return _db;} private: - Database::Handler _db; - std::string _authenticatedUser; }; diff --git a/ui/settings/Settings.cpp b/ui/settings/Settings.cpp new file mode 100644 index 00000000..9b7eb71e --- /dev/null +++ b/ui/settings/Settings.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +#include "SettingsDatabase.hpp" + +#include "Settings.hpp" + +namespace UserInterface { +namespace Settings { + +Settings::Settings(SessionData& sessionData, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), +_sessionData(sessionData) +{ + // Create a stack where the contents will be located. + Wt::WStackedWidget *contents = new Wt::WStackedWidget(); + + Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical, this); + menu->setStyleClass("nav nav-pills nav-stacked"); + menu->setWidth(150); + + Wt::Dbo::Transaction transaction( sessionData.getDatabaseHandler().getSession()); + + ::Database::User::pointer user = sessionData.getDatabaseHandler().getCurrentUser(); + + // Must be logged in here + assert(user); + + if (user->isAdmin()) + { + // TODO Special admin settings + menu->addItem("Database", new Database(sessionData)); + menu->addItem("Users", new Wt::WTextArea("Users here!")); + } + + // User specifics settings + menu->addItem("Transcoding", new Wt::WTextArea("User's transcoding settings here!")); + menu->addItem("Personal", new Wt::WTextArea("User's password + mail here!")); + + addWidget(contents); +} + +} // namespace Settings +} // namespace UserInterface diff --git a/ui/settings/Settings.hpp b/ui/settings/Settings.hpp new file mode 100644 index 00000000..da794474 --- /dev/null +++ b/ui/settings/Settings.hpp @@ -0,0 +1,20 @@ +#include + +#include "common/SessionData.hpp" + +namespace UserInterface { +namespace Settings { + +class Settings : public Wt::WContainerWidget +{ + public: + Settings(SessionData& sessionData, Wt::WContainerWidget* parent = 0); + + private: + + SessionData& _sessionData; + +}; + +} // namespace Settings +} // namespace UserInterface diff --git a/ui/settings/SettingsDatabase.cpp b/ui/settings/SettingsDatabase.cpp new file mode 100644 index 00000000..66c74ca5 --- /dev/null +++ b/ui/settings/SettingsDatabase.cpp @@ -0,0 +1,328 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "database/MediaDirectory.hpp" +#include "service/ServiceManager.hpp" +#include "service/DatabaseUpdateService.hpp" + +#include "common/DirectoryValidator.hpp" + +#include "SettingsDatabase.hpp" + +namespace UserInterface { +namespace Settings { + + +Database::Database(SessionData& sessionData, Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent), +_sessionData(sessionData) +{ + + // Media directories + { + Wt::WGroupBox *container = new Wt::WGroupBox("Media directories", this); + + _table = new Wt::WTable(container); + + _table->addStyleClass("table form-inline"); + _table->toggleStyleClass("table-hover", true); + _table->toggleStyleClass("table-striped", true); + + _table->setHeaderCount(1); + _table->elementAt(0, 0)->addWidget(new Wt::WText("Path")); + _table->elementAt(0, 1)->addWidget(new Wt::WText("Status")); + _table->elementAt(0, 2)->addWidget(new Wt::WText("Delete")); + + { + Wt::WPushButton* btn = new Wt::WPushButton("Add"); + container->addWidget(btn); + btn->clicked().connect(this, &Database::handleAddDirectory); + } + { + Wt::WPushButton* btn = new Wt::WPushButton("Delete"); + container->addWidget(btn); + btn->clicked().connect(this, &Database::handleDelDirectory); + } + + } + addWidget(new Wt::WBreak()); + + // Refresh settings + { + Wt::WGroupBox *container = new Wt::WGroupBox("Refresh settings", this); + + Wt::WTable *table = new Wt::WTable(container); + table->addStyleClass("table form-inline"); + + table->setHeaderCount(1, Wt::Vertical); + std::size_t line = 0; + table->elementAt(line, 0)->addWidget(new Wt::WText("Update period")); + table->elementAt(line, 1)->addWidget(_updatePeriod = new Wt::WComboBox() ); + // Populate combo + _updatePeriodModel = new Wt::WStringListModel(_updatePeriod); + + _updatePeriodModel->addString("Never"); + _updatePeriodModel->setData(0, 0, boost::posix_time::time_duration( boost::posix_time::hours(0) ), Wt::UserRole); + + _updatePeriodModel->addString("Daily"); + _updatePeriodModel->setData(1, 0, boost::posix_time::time_duration( boost::posix_time::hours(24) ), Wt::UserRole); + + _updatePeriodModel->addString("Weekly"); + _updatePeriodModel->setData(2, 0, boost::posix_time::time_duration( boost::posix_time::hours(24*7) ), Wt::UserRole); + + _updatePeriodModel->addString("Monthly"); + _updatePeriodModel->setData(3, 0, boost::posix_time::time_duration(boost::posix_time::hours(24*30) ), Wt::UserRole); + + _updatePeriod->setModel(_updatePeriodModel); + + line++; + + table->elementAt(line, 0)->addWidget(new Wt::WText("Update start time")); + table->elementAt(line, 1)->addWidget(_updateStartTime = new Wt::WComboBox( )); + // populate combo + _updateStartTimeModel = new Wt::WStringListModel(_updateStartTime); + + for (std::size_t i = 0; i < 24; ++i) + { + boost::posix_time::time_duration dur = boost::posix_time::hours(i); + + boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%H:%M"); + facet->time_duration_format("%H:%M"); + + std::ostringstream oss; + oss.imbue(std::locale(oss.getloc(), facet)); + + oss << dur; + + _updateStartTimeModel->addString( oss.str() ); + _updateStartTimeModel->setData(i, 0, dur, Wt::UserRole); + } + + _updateStartTime->setModel(_updateStartTimeModel); + + line++; + + Wt::WPushButton* btn = new Wt::WPushButton("Scan now", container); + btn->clicked().connect(this, &Database::handleScanNow); + } + + addWidget(new Wt::WBreak()); + addWidget(new Wt::WBreak()); + { + Wt::WPushButton* btn = new Wt::WPushButton("Apply", this); + btn->clicked().connect(this, &Database::handleApply); + } + { + Wt::WPushButton* btn = new Wt::WPushButton("Discard", this); + btn->clicked().connect(this, &Database::loadSettings); + } + + loadSettings(); +} + +void +Database::addDirectory(const std::string& path) +{ + int line = _table->rowCount(); + + Wt::WLineEdit* lineEdit = new Wt::WLineEdit( path ); + _table->elementAt(line, 0)->addWidget( lineEdit ); + + DirectoryValidator* validator = new DirectoryValidator(true); + lineEdit->setValidator(validator); + + std::string status; + + if (!path.empty()) + status = validator->validate( path ).message().toUTF8(); + + Wt::WText* statusText = new Wt::WText( status ); + _table->elementAt(line, 1)->addWidget( statusText ); + _table->elementAt(line, 2)->addWidget( new Wt::WCheckBox() ); + + lineEdit->blurred().connect(boost::bind(&Database::handleCheckDirectory, this, lineEdit, statusText)); +} + +void +Database::loadSettings(void) +{ + Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + + // Get directories + std::vector<::Database::MediaDirectory::pointer> directories = ::Database::MediaDirectory::getAll(_sessionData.getDatabaseHandler().getSession()); + + // delete rows + assert(_table->rowCount() > 0 ); + for (int i = _table->rowCount() - 1; i > 0; --i) + _table->deleteRow(i); + + BOOST_FOREACH(::Database::MediaDirectory::pointer directory, directories) + addDirectory( directory->getPath().string() ); + + // Get refresh settings + ::Database::MediaDirectorySettings::pointer settings = ::Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession()); + + { + int i; + for (i = 0; i < _updatePeriodModel->rowCount(); ++i) + { + if (boost::any_cast + (_updatePeriodModel->data(_updatePeriodModel->index(i, 0), Wt::UserRole)) + >= settings->getUpdatePeriod()) + break; + } + _updatePeriod->setCurrentIndex(i); + } + { + int i; + for (i = 0; i < _updateStartTimeModel->rowCount(); ++i) + { + if (boost::any_cast + (_updateStartTimeModel->data(_updateStartTimeModel->index(i, 0), Wt::UserRole)) + >= settings->getUpdateStartTime()) + break; + } + _updateStartTime->setCurrentIndex(i); + } + +} + +void +Database::handleAddDirectory(void) +{ + // TODO check if the previous entry is empty too + addDirectory(); +} + +void +Database::handleDelDirectory() +{ + assert(_table->rowCount() > 0 ); + for (int i = _table->rowCount() - 1; i > 0; --i) + { + assert(_table->elementAt(i, 2)->count() == 1); + Wt::WCheckBox *checkBox = dynamic_cast(_table->elementAt(i, 2)->widget(0)); + + if (checkBox->checkState() == Wt::CheckState::Checked) + _table->deleteRow(i); + } +} + +void +Database::handleCheckDirectory(Wt::WLineEdit* edit, Wt::WText* status) +{ + Wt::WValidator* validator = edit->validator(); + if (validator) + status->setText( validator->validate( edit->text().toUTF8() ).message().toUTF8()); +} + +bool +Database::saveSettings(void) +{ + Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + + assert(_table->rowCount() > 0 ); + // Validate each directory in the table + for (int i = 1; i < _table->rowCount(); ++i) + { + assert(_table->elementAt(i, 0)->count() == 1); + + Wt::WLineEdit* lineEdit = dynamic_cast(_table->elementAt(i, 0)->widget(0)); + if (!lineEdit->validate()) + return false;; + + } + + // erase all directories and add from the table + ::Database::MediaDirectory::eraseAll( _sessionData.getDatabaseHandler().getSession() ); + + for (int i = 1; i < _table->rowCount(); ++i) + { + Wt::WLineEdit* lineEdit = dynamic_cast(_table->elementAt(i, 0)->widget(0)); + + ::Database::MediaDirectory::create( _sessionData.getDatabaseHandler().getSession(), lineEdit->text().toUTF8(), ::Database::MediaDirectory::Audio); + } + + ::Database::MediaDirectorySettings::pointer settings = ::Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession() ); + { + int row = _updateStartTime->currentIndex(); + settings.modify()->setUpdateStartTime( boost::any_cast( _updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole))); + } + { + int row = _updatePeriod->currentIndex(); + settings.modify()->setUpdatePeriod( boost::any_cast( _updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole))); + } + + + return true; +} + +void +Database::handleApply(void) +{ + if (saveSettings()) + { + boost::lock_guard serviceLock (ServiceManager::instance().mutex()); + + DatabaseUpdateService::pointer service = ServiceManager::instance().getService(); + if (service) + service->restart(); + + // Show a popup to say it's fine! + Wt::WMessageBox *messageBox = new Wt::WMessageBox + ("Status", + "New settings applied!", + Wt::Information, Wt::Ok); + + messageBox->setModal(true); + + messageBox->buttonClicked().connect(std::bind([=] () + { + delete messageBox; + } + )); + + messageBox->show(); + } + else + { + Wt::WMessageBox *messageBox = new Wt::WMessageBox + ("Error", + "Cannot apply settings", + Wt::Critical, Wt::Ok); + + messageBox->setModal(true); + + messageBox->buttonClicked().connect(std::bind([=] () + { + delete messageBox; + } + )); + + messageBox->show(); + } + +} + +void +Database::handleScanNow(void) +{ + +} + + + + +} // namespace Settings +} // namespace UserInterface + + diff --git a/ui/settings/SettingsDatabase.hpp b/ui/settings/SettingsDatabase.hpp new file mode 100644 index 00000000..5b95de99 --- /dev/null +++ b/ui/settings/SettingsDatabase.hpp @@ -0,0 +1,50 @@ +#ifndef UI_SETTINGS_DB_HPP +#define UI_SETTINGS_DB_HPP + +#include +#include +#include +#include +#include + +#include "common/SessionData.hpp" + +namespace UserInterface { +namespace Settings { + +class Database : public Wt::WContainerWidget +{ + public: + Database(SessionData& sessionData, Wt::WContainerWidget *parent = 0); + + private: + void handleApply(); + + void handleCheckDirectory(Wt::WLineEdit* edit, Wt::WText* status); + void handleAddDirectory(); + void handleDelDirectory(); + + void handleScanNow(void); + + void loadSettings(void); // load from db + bool saveSettings(void); // save into db + + void addDirectory(const std::string& path = ""); + + SessionData& _sessionData; + + Wt::WTable* _table; + + Wt::WComboBox* _updatePeriod; + Wt::WStringListModel* _updatePeriodModel; + + Wt::WComboBox* _updateStartTime; + Wt::WStringListModel* _updateStartTimeModel; +}; + + +} // namespace Settings +} // namespace UserInterface + +#endif +