From 2fbb1a7260746abb3e93e314c05df982972a9ec4 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 7 Aug 2014 15:29:54 +0200 Subject: [PATCH] WIP. Improved database cleanup + various db fixes --- TODO | 16 +- database-updater/DatabaseUpdater.cpp | 238 ++++++++++++------ database-updater/DatabaseUpdater.hpp | 28 ++- database/Artist.cpp | 6 + database/AudioTypes.hpp | 9 +- database/MediaDirectory.cpp | 15 +- database/MediaDirectory.hpp | 3 +- database/Release.cpp | 7 + ui/settings/Settings.cpp | 30 ++- ui/settings/Settings.hpp | 2 + ui/settings/SettingsDatabaseFormView.cpp | 12 +- ui/settings/SettingsDatabaseFormView.hpp | 5 + ui/settings/SettingsMediaDirectories.cpp | 16 +- ui/settings/SettingsMediaDirectories.hpp | 9 +- .../SettingsMediaDirectoryFormView.cpp | 6 + 15 files changed, 274 insertions(+), 128 deletions(-) diff --git a/TODO b/TODO index b0801c06..8eea6970 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,7 @@ [ServiceManager] - Rework the whole start/stop/try/cach/thread/interrupts things -- Rework the io_service thread pool thing - - Use our own WIOService +- Use our own WIOService [Services] - [UI] generate argc/argv from a config file (crypto, port info, db path) @@ -17,28 +16,27 @@ - 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 +- Optim, use SQL query to get genre orphans - Use Inotify like system to watch modified/added files? - Implement a video database cleanup - Group video in "video groups". Each video may has sub groups (current "Path" class) -> Simplify database and remove the Path class - Use size limits for strings (artist, release, genre)? -- Process only files whose extensions are well known in audio/video world (avoid useless parsing/errors) +- Process only files whose extensions are well known in audio/video world (avoid useless parsing/errors)? + +[Metadata] +- Skip trailing non printable characters (spaces) in track name, artist, etc. [Transcode] - some zombies seem to be remaining after a week of use -- some early playback end spotted on flac files +- some early playback end spotted on flac files (windows chrome only?) [UI] [Settings] - logout users that are being changed (loss of admin admin rights), or make sure they are still admin when they make changes - "signal not exposed" problem if a user logout and login again. bad resource destruction? [admin/DB] - - Add a dedicated Menu to add/delete and view the Media pathes (see Users for the idea) - Do not make the update start time field active when update perdiod is "Never" - - Do not restart the db update service if user applied no changes - - Uncheck the "Request immediate scan" once setins are applied [user/transcoding] - Prefered codecs for audio/video? diff --git a/database-updater/DatabaseUpdater.cpp b/database-updater/DatabaseUpdater.cpp index ea196df4..756bb7c5 100644 --- a/database-updater/DatabaseUpdater.cpp +++ b/database-updater/DatabaseUpdater.cpp @@ -14,6 +14,23 @@ namespace DatabaseUpdater { using namespace Database; +namespace { + + std::vector + getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Type type) + { + std::vector res; + std::vector rootDirs = Database::MediaDirectory::getByType(session, type); + + BOOST_FOREACH(Database::MediaDirectory::pointer rootDir, rootDirs) + res.push_back(rootDir->getPath()); + + return res; + } + +} + + Updater::Updater(boost::filesystem::path dbPath, MetaData::Parser& parser) : _running(false), _scheduleTimer(_ioService), @@ -53,11 +70,7 @@ Updater::processNextJob(void) MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession()); if (settings->getManualScanRequested()) - { - settings.modify()->setManualScanRequested(false); - // Schedule immediate scan scheduleScan( boost::posix_time::seconds(0) ); - } else { // boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -86,47 +99,49 @@ Updater::process(boost::system::error_code err) { if (!err) { - removeMissingAudioFiles(_result.audioStats); + Stats stats; + + checkAudioFiles(stats); // TODO video files - // TODO remove files that do not belong to a root directory - - std::vector pathes; + typedef std::pair RootDirectory; + std::vector rootDirectories; { Wt::Dbo::Transaction transaction(_db.getSession()); std::vector mediaDirectories = MediaDirectory::getAll(_db.getSession()); BOOST_FOREACH(MediaDirectory::pointer directory, mediaDirectories) - { - if (directory->getType() == Database::MediaDirectory::Audio) - pathes.push_back(directory->getPath()); - } + rootDirectories.push_back( std::make_pair( directory->getPath(), directory->getType() )); } - BOOST_FOREACH( boost::filesystem::path p, pathes) - refreshAudioDirectory(p, _result.audioStats); + BOOST_FOREACH( RootDirectory rootDirectory, rootDirectories) + processDirectory(rootDirectory.first, rootDirectory.first, rootDirectory.second, stats); - std::cout << "Audio changes = " << _result.audioStats.nbChanges() << std::endl; - std::cout << "Video changes = " << _result.videoStats.nbChanges() << std::endl; + std::cout << "Changes = " << stats.nbChanges() << std::endl; - // Update database stats only if it has not been interrupted - if (_running) + // Update database stats + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db.getSession()); + Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db.getSession()); - if (_result.audioStats.nbChanges() + _result.videoStats.nbChanges() > 0) - settings.modify()->setLastUpdate(now); + if (stats.nbChanges() > 0) + settings.modify()->setLastUpdate(now); + // Save the last scan only if it has been completed + if (_running) settings.modify()->setLastScan(now); - } - processNextJob(); + // If the manual scan was required we can now set it to done + // Update only if the scan is complete! + if (settings->getManualScanRequested() & _running) + settings.modify()->setManualScanRequested(false); + } + if (_running) + processNextJob(); } } @@ -145,13 +160,6 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (track && track->getLastWriteTime() == lastWriteTime) return; - std::vector checksum; - computeCrc( file, checksum ); - - // Skip file if its checksum is still the same - if (track && track->getChecksum() == checksum) - return; - MetaData::Items items; _metadataParser.parse(file, items); @@ -262,7 +270,6 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) assert(track); - track.modify()->setChecksum(checksum); track.modify()->setLastWriteTime(lastWriteTime); track.modify()->setName(title); @@ -304,70 +311,139 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) void -Updater::refreshAudioDirectory( const boost::filesystem::path& p, Stats& stats) +Updater::processDirectory(const boost::filesystem::path& rootDirectory, + const boost::filesystem::path& p, + Database::MediaDirectory::Type type, + Stats& stats) { if (!_running) - { - std::cerr << "Not running! Stopping scan" << std::endl; return; - } - if (boost::filesystem::exists(p) && boost::filesystem::is_directory(p)) { + if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p)) + return; - typedef std::vector Paths; // store paths, + boost::filesystem::recursive_directory_iterator itPath(rootDirectory); + boost::filesystem::recursive_directory_iterator itEnd; + while (itPath != itEnd) + { + if (!_running) + return; - // TODO use a recursive directory iterator instead - Paths files; - std::copy(boost::filesystem::directory_iterator(p), boost::filesystem::directory_iterator(), std::back_inserter(files)); + if (boost::filesystem::is_regular(*itPath)) { + switch( type ) + { + case Database::MediaDirectory::Audio: + processAudioFile( *itPath, stats ); - BOOST_FOREACH(const boost::filesystem::path& file, files) { + break; - boost::this_thread::interruption_point(); - - try { - if (boost::filesystem::is_directory(file)) { - refreshAudioDirectory( file, stats ); - } - - else if (boost::filesystem::is_regular(file)) { - processAudioFile( file, stats ); - } - else { - std::cout << "Skipped '" << file << "' (not regular)" << std::endl; - } - } - catch(std::exception& e) { - std::cerr << "Exception while accessing '" << file << ": " << e.what() << std::endl; + case Database::MediaDirectory::Video: +// processVideoFile( rootDirectory, *itPath, stats); + break; } } + + ++itPath; } } -void -Updater::removeMissingAudioFiles( Stats& stats ) +bool +Updater::checkFile(const boost::filesystem::path& p, const std::vector& rootDirs) { - std::cerr << "Removing missing files..." << std::endl; - Wt::Dbo::Transaction transaction(_db.getSession()); + bool status = true; - typedef Wt::Dbo::collection< Wt::Dbo::ptr > Tracks; - - Tracks tracks = Track::getAll(_db.getSession()); - - for (Tracks::iterator i = tracks.begin(); i != tracks.end(); ++i) - { - const boost::filesystem::path p ((*i)->getPath() ); - if (!boost::filesystem::exists( p ) + // For each track, make sure the the file still exists + // and still belongs to a root directory + if (!boost::filesystem::exists( p ) || !boost::filesystem::is_regular( p ) ) + { + std::cerr << "Missing file '" << p << "'" << std::endl; + status = false; + } + else + { + bool foundRoot = false; + BOOST_FOREACH(const boost::filesystem::path& rootDir, rootDirs) { - (*i).remove(); - stats.nbRemoved++; - std::cerr << "Removing file '" << p << "'" << std::endl; + if (p.string().find( rootDir.string() ) != std::string::npos) + { + foundRoot = true; + break; + } + } + + if (!foundRoot) + { + std::cerr << "Out of root file '" << p << "'" << std::endl; + status = false; } } - transaction.commit(); + return status; +} - std::cerr << "Refreshing missing files done!" << std::endl; + +void +Updater::checkAudioFiles( Stats& stats ) +{ + + std::cerr << "Checking audio files..." << std::endl; + Wt::Dbo::Transaction transaction(_db.getSession()); + + std::vector rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio); + + std::cerr << "Checking tracks..." << std::endl; + typedef Wt::Dbo::collection< Wt::Dbo::ptr > Tracks; + Tracks tracks = Track::getAll(_db.getSession()); + + for (Tracks::iterator it = tracks.begin(); it != tracks.end(); ++it) + { + Track::pointer track = (*it); + + if (!checkFile(track->getPath(), rootDirs)) + { + track.remove(); + stats.nbRemoved++; + } + } + + std::cerr << "Checking Artists..." << std::endl; + // Now process orphan Artists (no track) + typedef Wt::Dbo::collection< Wt::Dbo::ptr > Artists; + Artists artists = Artist::getAllOrphans(_db.getSession()); + + for (Artists::iterator it = artists.begin(); it != artists.end(); ++it) + { + std::cout << "Removing orphan artist " << (*it)->getName() << std::endl; + (*it).remove(); + } + + std::cerr << "Checking Releases..." << std::endl; + // Now process orphan Release (no track) + typedef Wt::Dbo::collection< Wt::Dbo::ptr > Releases; + Releases releases = Release::getAllOrphans(_db.getSession()); + + for (Releases::iterator it = releases.begin(); it != releases.end(); ++it) + { + std::cout << "Removing orphan release " << (*it)->getName() << std::endl; + (*it).remove(); + } + + std::cerr << "Checking Genres..." << std::endl; + typedef Wt::Dbo::collection< Wt::Dbo::ptr > Genres; + Genres genres = Genre::getAll(_db.getSession()); + + for (Genres::iterator it = genres.begin(); it != genres.end(); ++it) + { + Genre::pointer genre = (*it); + + if (genre->getTracks().size() == 0) + genre.remove(); + } + + // Now process orphan Genre (no track) + + std::cerr << "Check audio files done!" << std::endl; } Path::pointer @@ -393,7 +469,7 @@ Updater::getAddPath(const boost::filesystem::path& path) } -void +/*void Updater::refreshVideoDirectory( const boost::filesystem::path& path) { std::cout << "Refreshing video directory " << path << std::endl; @@ -430,8 +506,8 @@ Updater::refreshVideoDirectory( const boost::filesystem::path& path) } } std::cout << "Refreshing video directory " << path << ": DONE" << std::endl; -} - +}*/ +/* void Updater::processVideoFile( const boost::filesystem::path& file) { @@ -510,5 +586,5 @@ Updater::processVideoFile( const boost::filesystem::path& file) std::cerr << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!" << std::endl; } } - +*/ } // namespace DatabaseUpdater diff --git a/database-updater/DatabaseUpdater.hpp b/database-updater/DatabaseUpdater.hpp index c27b2896..4940e425 100644 --- a/database-updater/DatabaseUpdater.hpp +++ b/database-updater/DatabaseUpdater.hpp @@ -5,9 +5,10 @@ #include #include "metadata/MetaData.hpp" -#include "database/DatabaseHandler.hpp" -#include "database/FileTypes.hpp" +#include "database/DatabaseHandler.hpp" +#include "database/MediaDirectory.hpp" +#include "database/FileTypes.hpp" // to remove #include "database/DatabaseHandler.hpp" namespace DatabaseUpdater { @@ -30,30 +31,32 @@ class Updater std::size_t nbModified; Stats() : nbAdded(0), nbRemoved(0), nbModified(0) {} + void clear(void) { nbAdded = 0; nbRemoved = 0; nbModified = 0; } std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;} }; - struct Result - { - Stats audioStats; - Stats videoStats; - }; - // Job handling void processNextJob(); void scheduleScan(boost::posix_time::time_duration duration); void scheduleScan(boost::posix_time::ptime time); - // Update database + // Update database (scheduled callback) void process(boost::system::error_code ec); + // Check if a file exists and is still in a root directory + static bool checkFile(const boost::filesystem::path& p, const std::vector& rootDirectories); + // Video - void refreshVideoDirectory( const boost::filesystem::path& directory ); + + void processDirectory( const boost::filesystem::path& rootDirectory, + const boost::filesystem::path& directory, + Database::MediaDirectory::Type type, + Stats& stats); + void processVideoFile( const boost::filesystem::path& file); // Audio - void removeMissingAudioFiles( Stats& stats ); - void refreshAudioDirectory( const boost::filesystem::path& directory, Stats& stats); + void checkAudioFiles( Stats& stats ); void processAudioFile( const boost::filesystem::path& file, Stats& stats); Database::Path::pointer getAddPath(const boost::filesystem::path& path); @@ -67,7 +70,6 @@ class Updater MetaData::Parser& _metadataParser; - Result _result; // update results }; // class Updater } // DatabaseUpdater diff --git a/database/Artist.cpp b/database/Artist.cpp index eb6636e3..00312d9e 100644 --- a/database/Artist.cpp +++ b/database/Artist.cpp @@ -38,5 +38,11 @@ Artist::getAll(Wt::Dbo::Session& session, int offset, int size) return session.find().offset(offset).limit(size); } +Wt::Dbo::collection +Artist::getAllOrphans(Wt::Dbo::Session& session) +{ + return session.query< Wt::Dbo::ptr >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); +} + } // namespace Database diff --git a/database/AudioTypes.hpp b/database/AudioTypes.hpp index e0c53b07..52d2caa6 100644 --- a/database/AudioTypes.hpp +++ b/database/AudioTypes.hpp @@ -31,8 +31,10 @@ class Artist static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getNone(Wt::Dbo::Session& session); static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); + static Wt::Dbo::collection getAllOrphans(Wt::Dbo::Session& session); const std::string& getName(void) const { return _name; } + const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks(void) const { return _tracks;} // Create static pointer create(Wt::Dbo::Session& session, const std::string& name); @@ -70,7 +72,7 @@ class Release static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getNone(Wt::Dbo::Session& session); - + static Wt::Dbo::collection getAllOrphans(Wt::Dbo::Session& session); static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, std::vector artistIds, int offset = -1, int size = -1); // Create @@ -78,7 +80,7 @@ class Release std::string getName() const { return _name; } bool isNone(void) const; - Wt::Dbo::collection > getTracks(void) const { return _tracks;} + const Wt::Dbo::collection >& getTracks(void) const { return _tracks;} boost::posix_time::time_duration getDuration(void) const; @@ -110,7 +112,7 @@ class Genre // Find utility static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getNone(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, std::size_t offset, std::size_t size); + static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, std::size_t offset = -1, std::size_t size = -1); // Create utility static pointer create(Wt::Dbo::Session& session, const std::string& name); @@ -118,6 +120,7 @@ class Genre // Accessors const std::string& getName(void) const { return _name; } bool isNone(void) const; + const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks() const { return _tracks;} template void persist(Action& a) diff --git a/database/MediaDirectory.cpp b/database/MediaDirectory.cpp index 63c42b03..9cd7b405 100644 --- a/database/MediaDirectory.cpp +++ b/database/MediaDirectory.cpp @@ -44,10 +44,19 @@ MediaDirectory::getAll(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } -MediaDirectory::pointer -MediaDirectory::getByPath(Wt::Dbo::Session& session, boost::filesystem::path p) + +std::vector +MediaDirectory::getByType(Wt::Dbo::Session& session, Type type) { - return session.find().where("path = ?").bind( p.string() ); + Wt::Dbo::collection< MediaDirectory::pointer > res = session.find().where("type = ?").bind (type); + + return std::vector(res.begin(), res.end()); +} + +MediaDirectory::pointer +MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type type) +{ + return session.find().where("path = ?").where("type = ?").bind( p.string()).bind(type); } } // namespace Database diff --git a/database/MediaDirectory.hpp b/database/MediaDirectory.hpp index 0fafb925..d466f704 100644 --- a/database/MediaDirectory.hpp +++ b/database/MediaDirectory.hpp @@ -76,7 +76,8 @@ class MediaDirectory // Accessors static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type); static std::vector getAll(Wt::Dbo::Session& session); - static pointer getByPath(Wt::Dbo::Session& session, boost::filesystem::path p); + static std::vector getByType(Wt::Dbo::Session& session, Type type); + static pointer get(Wt::Dbo::Session& session, boost::filesystem::path p, Type type); static void eraseAll(Wt::Dbo::Session& session); diff --git a/database/Release.cpp b/database/Release.cpp index 19670556..ef7ec109 100644 --- a/database/Release.cpp +++ b/database/Release.cpp @@ -73,5 +73,12 @@ Release::getDuration(void) const return res; } +Wt::Dbo::collection +Release::getAllOrphans(Wt::Dbo::Session& session) +{ + return session.query< Wt::Dbo::ptr >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); +} + + } // namespace Database diff --git a/ui/settings/Settings.cpp b/ui/settings/Settings.cpp index 7c592a54..bf055666 100644 --- a/ui/settings/Settings.cpp +++ b/ui/settings/Settings.cpp @@ -9,6 +9,9 @@ #include "SettingsMediaDirectories.hpp" #include "SettingsUsers.hpp" +#include "service/ServiceManager.hpp" +#include "service/DatabaseUpdateService.hpp" + #include "Settings.hpp" namespace UserInterface { @@ -35,8 +38,14 @@ _sessionData(sessionData) menu->addItem("Audio", new AudioFormView(sessionData, Database::User::getId(user))); if (user->isAdmin()) { - menu->addItem("Media Folders", new MediaDirectories(sessionData)); - menu->addItem("Database Update", new DatabaseFormView(sessionData)); + MediaDirectories* mediaDirectory = new MediaDirectories(sessionData); + mediaDirectory->changed().connect(this, &Settings::handleDatabaseSettingsChanged); + menu->addItem("Media Folders", mediaDirectory); + + DatabaseFormView* databaseFormView = new DatabaseFormView(sessionData); + databaseFormView->changed().connect(this, &Settings::handleDatabaseSettingsChanged); + menu->addItem("Database Update", databaseFormView); + menu->addItem("Users", new Users(sessionData)); } else @@ -47,5 +56,22 @@ _sessionData(sessionData) addWidget(contents); } +void +Settings::handleDatabaseSettingsChanged() +{ + // On settings change, request an immediate scan + { + Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession()).modify()->setManualScanRequested(true); + } + + // Restarting the update service + boost::lock_guard serviceLock (ServiceManager::instance().mutex()); + + DatabaseUpdateService::pointer service = ServiceManager::instance().getService(); + if (service) + service->restart(); +} + } // namespace Settings } // namespace UserInterface diff --git a/ui/settings/Settings.hpp b/ui/settings/Settings.hpp index da794474..eb9c53ca 100644 --- a/ui/settings/Settings.hpp +++ b/ui/settings/Settings.hpp @@ -12,6 +12,8 @@ class Settings : public Wt::WContainerWidget private: + void handleDatabaseSettingsChanged(); + SessionData& _sessionData; }; diff --git a/ui/settings/SettingsDatabaseFormView.cpp b/ui/settings/SettingsDatabaseFormView.cpp index 659fed0b..59a37029 100644 --- a/ui/settings/SettingsDatabaseFormView.cpp +++ b/ui/settings/SettingsDatabaseFormView.cpp @@ -10,9 +10,6 @@ #include "database/MediaDirectory.hpp" -#include "service/ServiceManager.hpp" -#include "service/DatabaseUpdateService.hpp" - #include "common/DirectoryValidator.hpp" #include "SettingsDatabaseFormView.hpp" @@ -291,14 +288,7 @@ DatabaseFormView::processSave() // Make the model to commit data into DB model->saveData(); - // Restarting the update service - { - boost::lock_guard serviceLock (ServiceManager::instance().mutex()); - - DatabaseUpdateService::pointer service = ServiceManager::instance().getService(); - if (service) - service->restart(); - } + _sigChanged.emit(); // uncheck the special button model->setValue(DatabaseFormModel::UpdateRequestImmediateField, false); diff --git a/ui/settings/SettingsDatabaseFormView.hpp b/ui/settings/SettingsDatabaseFormView.hpp index 75255c17..3681ca91 100644 --- a/ui/settings/SettingsDatabaseFormView.hpp +++ b/ui/settings/SettingsDatabaseFormView.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/SessionData.hpp" @@ -17,8 +18,12 @@ class DatabaseFormView : public Wt::WTemplateFormView public: DatabaseFormView(SessionData& sessionData, Wt::WContainerWidget *parent = 0); + Wt::Signal& changed() { return _sigChanged; } + private: + Wt::Signal _sigChanged; + void processSave(); void processDiscard(); diff --git a/ui/settings/SettingsMediaDirectories.cpp b/ui/settings/SettingsMediaDirectories.cpp index 1d7c9135..c8cb89d9 100644 --- a/ui/settings/SettingsMediaDirectories.cpp +++ b/ui/settings/SettingsMediaDirectories.cpp @@ -75,14 +75,14 @@ MediaDirectories::refresh(void) Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); delBtn->setStyleClass("btn-danger"); _table->elementAt(id, 3)->addWidget(delBtn); - delBtn->clicked().connect(boost::bind( &MediaDirectories::handleDelMediaDirectory, this, mediaDirectory->getPath() ) ); + delBtn->clicked().connect(boost::bind( &MediaDirectories::handleDelMediaDirectory, this, mediaDirectory->getPath(), mediaDirectory->getType() ) ); ++id; } } void -MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p) +MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type) { Wt::WMessageBox *messageBox = new Wt::WMessageBox ("Delete Folder", @@ -98,12 +98,15 @@ MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p) Wt::Dbo::Transaction transaction(_db.getSession()); // Delete the media diretory - Database::MediaDirectory::pointer mediaDirectory = Database::MediaDirectory::getByPath(_db.getSession(), p); + Database::MediaDirectory::pointer mediaDirectory = Database::MediaDirectory::get(_db.getSession(), p, type); if (mediaDirectory) mediaDirectory.remove(); } refresh(); + + // Emit something changed in the settings + _sigChanged.emit(); } delete messageBox; @@ -129,10 +132,15 @@ MediaDirectories::handleMediaDirectoryFormCompleted(bool changed) { _stack->setCurrentIndex(0); - // Refresh the user table if a change has been made if (changed) + { + // Refresh the user table if a change has been made refresh(); + // Emit something changed in the settings + _sigChanged.emit(); + } + // Delete the form view delete _stack->widget(1); diff --git a/ui/settings/SettingsMediaDirectories.hpp b/ui/settings/SettingsMediaDirectories.hpp index c06fda63..472b3831 100644 --- a/ui/settings/SettingsMediaDirectories.hpp +++ b/ui/settings/SettingsMediaDirectories.hpp @@ -4,6 +4,9 @@ #include #include #include +#include + +#include "database/MediaDirectory.hpp" #include "common/SessionData.hpp" @@ -17,11 +20,15 @@ class MediaDirectories : public Wt::WContainerWidget void refresh(); + Wt::Signal& changed() { return _sigChanged; } + private: + Wt::Signal _sigChanged; + void handleMediaDirectoryFormCompleted(bool changed); - void handleDelMediaDirectory(boost::filesystem::path p); + void handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type); void handleCreateMediaDirectory(void); Database::Handler& _db; diff --git a/ui/settings/SettingsMediaDirectoryFormView.cpp b/ui/settings/SettingsMediaDirectoryFormView.cpp index 70c01a67..f4f5daad 100644 --- a/ui/settings/SettingsMediaDirectoryFormView.cpp +++ b/ui/settings/SettingsMediaDirectoryFormView.cpp @@ -51,6 +51,12 @@ class MediaDirectoryFormModel : public Wt::WFormModel Database::MediaDirectory::Type type = (valueText(TypeField) == "Audio") ? Database::MediaDirectory::Audio : Database::MediaDirectory::Video; + if (Database::MediaDirectory::get(_db.getSession(), valueText(PathField).toUTF8(), type)) + { + error = "This Path/Type already exists!"; + return false; + } + Database::MediaDirectory::create(_db.getSession(), valueText(PathField).toUTF8(), type); }