Commit 8bc2fddf authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen

Propagate device removal to files & folders

parent 69be8ef7
...@@ -54,6 +54,5 @@ namespace factory ...@@ -54,6 +54,5 @@ namespace factory
/// \return A representation of the device, or nullptr if the device is currently unavailable. /// \return A representation of the device, or nullptr if the device is currently unavailable.
/// ///
virtual std::shared_ptr<fs::IDevice> createDevice( const std::string& uuid ) = 0; virtual std::shared_ptr<fs::IDevice> createDevice( const std::string& uuid ) = 0;
}; };
} }
...@@ -42,7 +42,8 @@ Folder::Folder(DBConnection dbConnection, sqlite::Row& row ) ...@@ -42,7 +42,8 @@ Folder::Folder(DBConnection dbConnection, sqlite::Row& row )
>> m_parent >> m_parent
>> m_lastModificationDate >> m_lastModificationDate
>> m_isBlacklisted >> m_isBlacklisted
>> m_deviceId; >> m_deviceId
>> m_isPresent;
} }
Folder::Folder( const std::string& path, time_t lastModificationDate, unsigned int parent, unsigned int deviceId ) Folder::Folder( const std::string& path, time_t lastModificationDate, unsigned int parent, unsigned int deviceId )
...@@ -52,6 +53,7 @@ Folder::Folder( const std::string& path, time_t lastModificationDate, unsigned i ...@@ -52,6 +53,7 @@ Folder::Folder( const std::string& path, time_t lastModificationDate, unsigned i
, m_lastModificationDate( lastModificationDate ) , m_lastModificationDate( lastModificationDate )
, m_isBlacklisted( false ) , m_isBlacklisted( false )
, m_deviceId( deviceId ) , m_deviceId( deviceId )
, m_isPresent( true )
{ {
} }
...@@ -65,10 +67,19 @@ bool Folder::createTable(DBConnection connection) ...@@ -65,10 +67,19 @@ bool Folder::createTable(DBConnection connection)
"last_modification_date UNSIGNED INTEGER," "last_modification_date UNSIGNED INTEGER,"
"is_blacklisted INTEGER," "is_blacklisted INTEGER,"
"device_id UNSIGNED INTEGER," "device_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"FOREIGN KEY (id_parent) REFERENCES " + policy::FolderTable::Name + "FOREIGN KEY (id_parent) REFERENCES " + policy::FolderTable::Name +
"(id_folder) ON DELETE CASCADE" "(id_folder) ON DELETE CASCADE,"
"FOREIGN KEY (device_id) REFERENCES " + policy::DeviceTable::Name +
"(id_device) ON DELETE CASCADE"
")"; ")";
return sqlite::Tools::executeRequest( connection, req ); std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_device_present AFTER UPDATE OF is_present ON "
+ policy::DeviceTable::Name +
" BEGIN"
" UPDATE " + policy::FolderTable::Name + " SET is_present = new.is_present WHERE device_id = new.id_device;"
" END";
return sqlite::Tools::executeRequest( connection, req ) &&
sqlite::Tools::executeRequest( connection, triggerReq );
} }
std::shared_ptr<Folder> Folder::create(DBConnection connection, const std::string& path, time_t lastModificationDate, unsigned int parentId, Device& device ) std::shared_ptr<Folder> Folder::create(DBConnection connection, const std::string& path, time_t lastModificationDate, unsigned int parentId, Device& device )
......
...@@ -76,6 +76,7 @@ private: ...@@ -76,6 +76,7 @@ private:
unsigned int m_lastModificationDate; unsigned int m_lastModificationDate;
bool m_isBlacklisted; bool m_isBlacklisted;
unsigned int m_deviceId; unsigned int m_deviceId;
bool m_isPresent;
friend struct policy::FolderTable; friend struct policy::FolderTable;
}; };
...@@ -60,7 +60,8 @@ Media::Media( DBConnection dbConnection, sqlite::Row& row ) ...@@ -60,7 +60,8 @@ Media::Media( DBConnection dbConnection, sqlite::Row& row )
>> m_insertionDate >> m_insertionDate
>> m_snapshot >> m_snapshot
>> m_isParsed >> m_isParsed
>> m_title; >> m_title
>> m_isPresent;
} }
Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& title, Type type ) Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& title, Type type )
...@@ -76,6 +77,7 @@ Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& t ...@@ -76,6 +77,7 @@ Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& t
, m_insertionDate( time( nullptr ) ) , m_insertionDate( time( nullptr ) )
, m_isParsed( false ) , m_isParsed( false )
, m_title( title ) , m_title( title )
, m_isPresent( true )
, m_changed( false ) , m_changed( false )
{ {
} }
...@@ -330,6 +332,7 @@ bool Media::createTable( DBConnection connection ) ...@@ -330,6 +332,7 @@ bool Media::createTable( DBConnection connection )
"snapshot TEXT," "snapshot TEXT,"
"parsed BOOLEAN NOT NULL DEFAULT 0," "parsed BOOLEAN NOT NULL DEFAULT 0,"
"title TEXT," "title TEXT,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name "FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name
+ "(id_episode) ON DELETE CASCADE," + "(id_episode) ON DELETE CASCADE,"
"FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name "FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name
...@@ -337,7 +340,13 @@ bool Media::createTable( DBConnection connection ) ...@@ -337,7 +340,13 @@ bool Media::createTable( DBConnection connection )
"FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name "FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
+ "(id_folder) ON DELETE CASCADE" + "(id_folder) ON DELETE CASCADE"
")"; ")";
return sqlite::Tools::executeRequest( connection, req ); std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_folder_present AFTER UPDATE OF is_present ON "
+ policy::FolderTable::Name +
" BEGIN"
" UPDATE " + policy::MediaTable::Name + " SET is_present = new.is_present WHERE folder_id = new.id_folder;"
" END";
return sqlite::Tools::executeRequest( connection, req ) &&
sqlite::Tools::executeRequest( connection, triggerReq );
} }
bool Media::addLabel( LabelPtr label ) bool Media::addLabel( LabelPtr label )
......
...@@ -118,6 +118,7 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable> ...@@ -118,6 +118,7 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
std::string m_snapshot; std::string m_snapshot;
bool m_isParsed; bool m_isParsed;
std::string m_title; std::string m_title;
bool m_isPresent;
// Auto fetched related properties // Auto fetched related properties
AlbumTrackPtr m_albumTrack; AlbumTrackPtr m_albumTrack;
......
...@@ -143,8 +143,13 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna ...@@ -143,8 +143,13 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
} }
auto t = m_dbConnection->newTransaction(); auto t = m_dbConnection->newTransaction();
if ( ( Media::createTable( m_dbConnection.get() ) && // We need to create the tables in order of triggers creation
// Device is the "root of all evil". When a device is modified,
// we will trigger an update on folder, which will trigger
// an update on files, and so on.
if ( ( Device::createTable( m_dbConnection.get() ) &&
Folder::createTable( m_dbConnection.get() ) && Folder::createTable( m_dbConnection.get() ) &&
Media::createTable( m_dbConnection.get() ) &&
Label::createTable( m_dbConnection.get() ) && Label::createTable( m_dbConnection.get() ) &&
Album::createTable( m_dbConnection.get() ) && Album::createTable( m_dbConnection.get() ) &&
AlbumTrack::createTable( m_dbConnection.get() ) && AlbumTrack::createTable( m_dbConnection.get() ) &&
...@@ -155,8 +160,7 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna ...@@ -155,8 +160,7 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
AudioTrack::createTable( m_dbConnection.get() ) && AudioTrack::createTable( m_dbConnection.get() ) &&
Artist::createTable( m_dbConnection.get() ) && Artist::createTable( m_dbConnection.get() ) &&
Artist::createDefaultArtists( m_dbConnection.get() ) && Artist::createDefaultArtists( m_dbConnection.get() ) &&
Settings::createTable( m_dbConnection.get() ) && Settings::createTable( m_dbConnection.get() ) ) == false )
Device::createTable( m_dbConnection.get() ) ) == false )
{ {
LOG_ERROR( "Failed to create database structure" ); LOG_ERROR( "Failed to create database structure" );
return false; return false;
...@@ -182,25 +186,26 @@ void MediaLibrary::setVerbosity(LogLevel v) ...@@ -182,25 +186,26 @@ void MediaLibrary::setVerbosity(LogLevel v)
std::vector<MediaPtr> MediaLibrary::files() std::vector<MediaPtr> MediaLibrary::files()
{ {
return Media::fetchAll<IMedia>( m_dbConnection.get() ); static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE is_present = 1";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req );
} }
std::vector<MediaPtr> MediaLibrary::audioFiles() std::vector<MediaPtr> MediaLibrary::audioFiles()
{ {
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? ORDER BY title"; static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY title";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::AudioType ); return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::AudioType );
} }
std::vector<MediaPtr> MediaLibrary::videoFiles() std::vector<MediaPtr> MediaLibrary::videoFiles()
{ {
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? ORDER BY title"; static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY title";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::VideoType ); return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::VideoType );
} }
MediaPtr MediaLibrary::file( const std::string& path ) MediaPtr MediaLibrary::file( const std::string& path )
{ {
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + static const std::string req = "SELECT * FROM " + policy::MediaTable::Name +
" WHERE mrl = ?"; " WHERE mrl = ? AND is_present = 1";
return Media::fetch( m_dbConnection.get(), req, path ); return Media::fetch( m_dbConnection.get(), req, path );
} }
......
...@@ -143,7 +143,7 @@ void Parser::restore() ...@@ -143,7 +143,7 @@ void Parser::restore()
return; return;
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name static const std::string req = "SELECT * FROM " + policy::MediaTable::Name
+ " WHERE parsed = 0"; + " WHERE parsed = 0 AND is_present = 1";
auto media = Media::fetchAll<Media>( m_dbConnection, req ); auto media = Media::fetchAll<Media>( m_dbConnection, req );
std::lock_guard<std::mutex> lock( m_lock ); std::lock_guard<std::mutex> lock( m_lock );
......
...@@ -91,8 +91,6 @@ void FsDiscoverer::reload() ...@@ -91,8 +91,6 @@ void FsDiscoverer::reload()
device->setPresent( false ); device->setPresent( false );
continue; continue;
} }
if ( folder->lastModificationDate() == f->lastModificationDate() )
continue;
checkSubfolders( folder.get(), f.get(), blist ); checkSubfolders( folder.get(), f.get(), blist );
checkFiles( folder.get(), f.get() ); checkFiles( folder.get(), f.get() );
f->setLastModificationDate( folder->lastModificationDate() ); f->setLastModificationDate( folder->lastModificationDate() );
...@@ -135,15 +133,25 @@ bool FsDiscoverer::checkSubfolders( fs::IDirectory* folder, Folder* parentFolder ...@@ -135,15 +133,25 @@ bool FsDiscoverer::checkSubfolders( fs::IDirectory* folder, Folder* parentFolder
continue; continue;
} }
auto folderInDb = *it; auto folderInDb = *it;
if ( subFolder->lastModificationDate() == folderInDb->lastModificationDate() ) auto deviceFs = subFolder->device();
// If the device supposed to contain this folder is not present anymore, flag it as removed
if ( deviceFs == nullptr )
{ {
// Remove all folders that still exist in FS. That way, the list of folders that auto device = Device::fetch( m_dbConn, folderInDb->deviceId() );
// will still be in subFoldersInDB when we're done is the list of folders that have if ( device == nullptr )
// been deleted from the FS {
LOG_ERROR( "Failed to fetch device containing folder ", folderInDb->path() );
continue;
}
LOG_INFO( "Device containing ", folderInDb->path(), " is not present anymore" );
device->setPresent( false );
// Don't let this folder be deleted after the main loop.
subFoldersInDB.erase( it ); subFoldersInDB.erase( it );
continue; continue;
} }
// This folder was modified, let's recurse // In any case, check for modifications, as a change related to a mountpoint might
// not update the folder modification date.
// Also, relying on the modification date probably isn't portable
checkSubfolders( subFolder.get(), folderInDb.get(), blacklist ); checkSubfolders( subFolder.get(), folderInDb.get(), blacklist );
checkFiles( subFolder.get(), folderInDb.get() ); checkFiles( subFolder.get(), folderInDb.get() );
folderInDb->setLastModificationDate( subFolder->lastModificationDate() ); folderInDb->setLastModificationDate( subFolder->lastModificationDate() );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment