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
/// \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;
};
}
......@@ -42,7 +42,8 @@ Folder::Folder(DBConnection dbConnection, sqlite::Row& row )
>> m_parent
>> m_lastModificationDate
>> m_isBlacklisted
>> m_deviceId;
>> m_deviceId
>> m_isPresent;
}
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
, m_lastModificationDate( lastModificationDate )
, m_isBlacklisted( false )
, m_deviceId( deviceId )
, m_isPresent( true )
{
}
......@@ -65,10 +67,19 @@ bool Folder::createTable(DBConnection connection)
"last_modification_date UNSIGNED INTEGER,"
"is_blacklisted INTEGER,"
"device_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"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 )
......
......@@ -76,6 +76,7 @@ private:
unsigned int m_lastModificationDate;
bool m_isBlacklisted;
unsigned int m_deviceId;
bool m_isPresent;
friend struct policy::FolderTable;
};
......@@ -60,7 +60,8 @@ Media::Media( DBConnection dbConnection, sqlite::Row& row )
>> m_insertionDate
>> m_snapshot
>> m_isParsed
>> m_title;
>> m_title
>> m_isPresent;
}
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
, m_insertionDate( time( nullptr ) )
, m_isParsed( false )
, m_title( title )
, m_isPresent( true )
, m_changed( false )
{
}
......@@ -330,6 +332,7 @@ bool Media::createTable( DBConnection connection )
"snapshot TEXT,"
"parsed BOOLEAN NOT NULL DEFAULT 0,"
"title TEXT,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name
+ "(id_episode) ON DELETE CASCADE,"
"FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name
......@@ -337,7 +340,13 @@ bool Media::createTable( DBConnection connection )
"FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
+ "(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 )
......
......@@ -118,6 +118,7 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
std::string m_snapshot;
bool m_isParsed;
std::string m_title;
bool m_isPresent;
// Auto fetched related properties
AlbumTrackPtr m_albumTrack;
......
......@@ -143,8 +143,13 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
}
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() ) &&
Media::createTable( m_dbConnection.get() ) &&
Label::createTable( m_dbConnection.get() ) &&
Album::createTable( m_dbConnection.get() ) &&
AlbumTrack::createTable( m_dbConnection.get() ) &&
......@@ -155,8 +160,7 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
AudioTrack::createTable( m_dbConnection.get() ) &&
Artist::createTable( m_dbConnection.get() ) &&
Artist::createDefaultArtists( m_dbConnection.get() ) &&
Settings::createTable( m_dbConnection.get() ) &&
Device::createTable( m_dbConnection.get() ) ) == false )
Settings::createTable( m_dbConnection.get() ) ) == false )
{
LOG_ERROR( "Failed to create database structure" );
return false;
......@@ -182,25 +186,26 @@ void MediaLibrary::setVerbosity(LogLevel v)
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()
{
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 );
}
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 );
}
MediaPtr MediaLibrary::file( const std::string& path )
{
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 );
}
......
......@@ -143,7 +143,7 @@ void Parser::restore()
return;
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 );
std::lock_guard<std::mutex> lock( m_lock );
......
......@@ -91,8 +91,6 @@ void FsDiscoverer::reload()
device->setPresent( false );
continue;
}
if ( folder->lastModificationDate() == f->lastModificationDate() )
continue;
checkSubfolders( folder.get(), f.get(), blist );
checkFiles( folder.get(), f.get() );
f->setLastModificationDate( folder->lastModificationDate() );
......@@ -135,15 +133,25 @@ bool FsDiscoverer::checkSubfolders( fs::IDirectory* folder, Folder* parentFolder
continue;
}
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 )
{
auto device = Device::fetch( m_dbConn, folderInDb->deviceId() );
if ( device == nullptr )
{
// Remove all folders that still exist in FS. That way, the list of folders that
// will still be in subFoldersInDB when we're done is the list of folders that have
// 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 );
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 );
checkFiles( subFolder.get(), folderInDb.get() );
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