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

Do not retry a request from within a transaction.

Retry the entire transaction instead
parent ee347c9f
......@@ -126,41 +126,43 @@ std::shared_ptr<Folder> Folder::create( MediaLibraryPtr ml, const std::string& m
bool Folder::blacklist( MediaLibraryPtr ml, const std::string& mrl )
{
// Ensure we delete the existing folder if any & blacklist the folder in an "atomic" way
auto t = ml->getConn()->newTransaction();
return sqlite::Tools::withRetries( 3, [ml, &mrl]() {
auto t = ml->getConn()->newTransaction();
auto f = fromMrl( ml, mrl, BannedType::Any );
if ( f != nullptr )
{
// No need to blacklist a folder twice
if ( f->m_isBlacklisted == true )
return true;
// Let the foreign key destroy everything beneath this folder
destroy( ml, f->id() );
}
auto fsFactory = ml->fsFactoryForMrl( mrl );
if ( fsFactory == nullptr )
return false;
auto folderFs = fsFactory->createDirectory( mrl );
assert( folderFs != nullptr );
auto deviceFs = folderFs->device();
if ( deviceFs == nullptr )
{
LOG_ERROR( "Can't find device associated with mrl ", mrl );
return false;
}
auto device = Device::fromUuid( ml, deviceFs->uuid() );
if ( device == nullptr )
device = Device::create( ml, deviceFs->uuid(), utils::file::scheme( mrl ), deviceFs->isRemovable() );
std::string path;
if ( deviceFs->isRemovable() == true )
path = utils::file::removePath( mrl, deviceFs->mountpoint() );
else
path = mrl;
static const std::string req = "INSERT INTO " + policy::FolderTable::Name +
"(path, parent_id, is_blacklisted, device_id, is_removable) VALUES(?, ?, ?, ?, ?)";
auto res = sqlite::Tools::executeInsert( ml->getConn(), req, path, nullptr, true, device->id(), deviceFs->isRemovable() ) != 0;
t->commit();
return res;
auto f = fromMrl( ml, mrl, BannedType::Any );
if ( f != nullptr )
{
// No need to blacklist a folder twice
if ( f->m_isBlacklisted == true )
return true;
// Let the foreign key destroy everything beneath this folder
destroy( ml, f->id() );
}
auto fsFactory = ml->fsFactoryForMrl( mrl );
if ( fsFactory == nullptr )
return false;
auto folderFs = fsFactory->createDirectory( mrl );
assert( folderFs != nullptr );
auto deviceFs = folderFs->device();
if ( deviceFs == nullptr )
{
LOG_ERROR( "Can't find device associated with mrl ", mrl );
return false;
}
auto device = Device::fromUuid( ml, deviceFs->uuid() );
if ( device == nullptr )
device = Device::create( ml, deviceFs->uuid(), utils::file::scheme( mrl ), deviceFs->isRemovable() );
std::string path;
if ( deviceFs->isRemovable() == true )
path = utils::file::removePath( mrl, deviceFs->mountpoint() );
else
path = mrl;
static const std::string req = "INSERT INTO " + policy::FolderTable::Name +
"(path, parent_id, is_blacklisted, device_id, is_removable) VALUES(?, ?, ?, ?, ?)";
auto res = sqlite::Tools::executeInsert( ml->getConn(), req, path, nullptr, true, device->id(), deviceFs->isRemovable() ) != 0;
t->commit();
return res;
});
}
std::shared_ptr<Folder> Folder::fromMrl( MediaLibraryPtr ml, const std::string& mrl )
......
......@@ -353,14 +353,16 @@ MediaPtr MediaLibrary::media( const std::string& mrl ) const
MediaPtr MediaLibrary::addMedia( const std::string& mrl )
{
auto t = m_dbConnection->newTransaction();
auto media = Media::create( this, IMedia::Type::Unknown, utils::file::fileName( mrl ) );
if ( media == nullptr )
return nullptr;
if ( media->addExternalMrl( mrl, IFile::Type::Main ) == nullptr )
return nullptr;
t->commit();
return media;
return sqlite::Tools::withRetries( 3, [this, &mrl]() -> MediaPtr {
auto t = m_dbConnection->newTransaction();
auto media = Media::create( this, IMedia::Type::Unknown, utils::file::fileName( mrl ) );
if ( media == nullptr )
return nullptr;
if ( media->addExternalMrl( mrl, IFile::Type::Main ) == nullptr )
return nullptr;
t->commit();
return media;
});
}
std::vector<MediaPtr> MediaLibrary::audioFiles( SortingCriteria sort, bool desc ) const
......@@ -543,12 +545,14 @@ std::vector<MediaPtr> MediaLibrary::lastMediaPlayed() const
bool MediaLibrary::clearHistory()
{
auto t = getConn()->newTransaction();
Media::clearHistory( this );
if ( History::clearStreams( this ) == false )
return false;
t->commit();
return true;
return sqlite::Tools::withRetries( 3, [this]() {
auto t = getConn()->newTransaction();
Media::clearHistory( this );
if ( History::clearStreams( this ) == false )
return false;
t->commit();
return true;
});
}
MediaSearchAggregate MediaLibrary::searchMedia( const std::string& title ) const
......
......@@ -311,6 +311,32 @@ class Tools
return sqlite3_last_insert_rowid( dbConnection->getConn() );
}
/**
* \brief Automatically retry a code block when innocuous sqlite errors occur.
*
* We can't retry individual requests as sqlite might implicitely rollback the current transaction
* causing previously sucessfuly inserted entities to be removed from the database.
*/
template <typename T, typename... Args>
static auto withRetries( uint8_t nbRetries, T&& f, Args&&... args ) -> decltype( f( args... ) )
{
uint8_t i = 0;
while ( true )
{
try
{
return f( std::forward<Args>( args )... );
}
catch ( const sqlite::errors::GenericExecution& ex )
{
if ( i > nbRetries || sqlite::errors::isInnocuous( ex ) == false )
throw;
++i;
LOG_WARN( ex.what(), ". Retrying (", i, '/', nbRetries, ')' );
}
}
}
private:
template <typename... Args>
static bool executeRequestLocked( DBConnection dbConnection, const std::string& req, Args&&... args )
......
......@@ -281,38 +281,44 @@ void FsDiscoverer::checkFiles( fs::IDirectory& parentFolderFs, Folder& parentFol
filesToAdd.push_back( std::move( fileFs ) );
files.erase( it );
}
auto t = m_ml->getConn()->newTransaction();
for ( auto file : files )
{
LOG_INFO( "File ", file->mrl(), " not found on filesystem, deleting it" );
auto media = file->media();
if ( media != nullptr && media->isDeleted() == false )
media->removeFile( *file );
else if ( file->isDeleted() == false )
using FilesT = decltype( files );
using FilesToRemoveT = decltype( filesToRemove );
using FilesToAddT = decltype( filesToAdd );
sqlite::Tools::withRetries( 3, [this, &parentFolder, &parentFolderFs]
( FilesT files, FilesToAddT filesToAdd, FilesToRemoveT filesToRemove ) {
auto t = m_ml->getConn()->newTransaction();
for ( auto file : files )
{
// This is unexpected, as the file should have been deleted when the media was
// removed.
LOG_WARN( "Deleting a file without an associated media." );
file->destroy();
LOG_INFO( "File ", file->mrl(), " not found on filesystem, deleting it" );
auto media = file->media();
if ( media != nullptr && media->isDeleted() == false )
media->removeFile( *file );
else if ( file->isDeleted() == false )
{
// This is unexpected, as the file should have been deleted when the media was
// removed.
LOG_WARN( "Deleting a file without an associated media." );
file->destroy();
}
}
}
for ( auto& f : filesToRemove )
{
auto media = f->media();
if ( media != nullptr )
media->removeFile( *f );
else
for ( auto& f : filesToRemove )
{
// If there is no media associated with this file, the file had to be removed through
// a trigger
assert( f->isDeleted() );
auto media = f->media();
if ( media != nullptr )
media->removeFile( *f );
else
{
// If there is no media associated with this file, the file had to be removed through
// a trigger
assert( f->isDeleted() );
}
}
}
// Insert all files at once to avoid SQL write contention
for ( auto& p : filesToAdd )
m_ml->addFile( *p, parentFolder, parentFolderFs );
t->commit();
LOG_INFO( "Done checking files in ", parentFolderFs.mrl() );
// Insert all files at once to avoid SQL write contention
for ( auto& p : filesToAdd )
m_ml->addFile( *p, parentFolder, parentFolderFs );
t->commit();
LOG_INFO( "Done checking files in ", parentFolderFs.mrl() );
}, std::move( files ), std::move( filesToAdd ), std::move( filesToRemove ) );
}
bool FsDiscoverer::hasDotNoMediaFile( const fs::IDirectory& directory )
......
......@@ -71,7 +71,6 @@ int MetadataParser::toInt( VLC::Media& vlcMedia, libvlc_meta_t meta, const char*
parser::Task::Status MetadataParser::run( parser::Task& task )
{
auto& media = task.media;
const auto& tracks = task.vlcMedia.tracks();
// If we failed to extract any tracks, don't make any assumption and forward to the
......@@ -96,26 +95,29 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
bool isAudio = true;
{
auto t = m_ml->getConn()->newTransaction();
for ( const auto& t : tracks )
{
auto codec = t.codec();
std::string fcc( reinterpret_cast<const char*>( &codec ), 4 );
if ( t.type() == VLC::MediaTrack::Type::Video )
{
media->addVideoTrack( fcc, t.width(), t.height(),
static_cast<float>( t.fpsNum() ) / static_cast<float>( t.fpsDen() ),
t.language(), t.description() );
isAudio = false;
}
else if ( t.type() == VLC::MediaTrack::Type::Audio )
using TracksT = decltype( tracks );
sqlite::Tools::withRetries( 3, [this, &isAudio, &task]( TracksT tracks ) {
auto t = m_ml->getConn()->newTransaction();
for ( const auto& t : tracks )
{
media->addAudioTrack( fcc, t.bitrate(), t.rate(), t.channels(),
t.language(), t.description() );
auto codec = t.codec();
std::string fcc( reinterpret_cast<const char*>( &codec ), 4 );
if ( t.type() == VLC::MediaTrack::Type::Video )
{
task.media->addVideoTrack( fcc, t.width(), t.height(),
static_cast<float>( t.fpsNum() ) / static_cast<float>( t.fpsDen() ),
t.language(), t.description() );
isAudio = false;
}
else if ( t.type() == VLC::MediaTrack::Type::Audio )
{
task.media->addAudioTrack( fcc, t.bitrate(), t.rate(), t.channels(),
t.language(), t.description() );
}
}
}
media->setDuration( task.vlcMedia.duration() );
t->commit();
task.media->setDuration( task.vlcMedia.duration() );
t->commit();
}, std::move( tracks ) );
}
if ( isAudio == true )
{
......@@ -138,7 +140,7 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
task.file->markStepCompleted( File::ParserStep::Thumbnailer );
if ( task.file->saveParserStep() == false )
return parser::Task::Status::Fatal;
m_notifier->notifyMediaCreation( media );
m_notifier->notifyMediaCreation( task.media );
return parser::Task::Status::Success;
}
......@@ -155,23 +157,26 @@ bool MetadataParser::parseVideoFile( parser::Task& task ) const
const auto& showName = task.vlcMedia.meta( libvlc_meta_ShowName );
if ( showName.length() == 0 )
{
auto t = m_ml->getConn()->newTransaction();
return sqlite::Tools::withRetries( 3, [this, &showName, &title, &task]() {
auto t = m_ml->getConn()->newTransaction();
auto show = m_ml->show( showName );
if ( show == nullptr )
{
show = m_ml->createShow( showName );
auto show = m_ml->show( showName );
if ( show == nullptr )
return false;
}
auto episode = toInt( task.vlcMedia, libvlc_meta_Episode, "episode number" );
if ( episode != 0 )
{
std::shared_ptr<Show> s = std::static_pointer_cast<Show>( show );
s->addEpisode( *media, title, episode );
}
task.media->save();
t->commit();
{
show = m_ml->createShow( showName );
if ( show == nullptr )
return false;
}
auto episode = toInt( task.vlcMedia, libvlc_meta_Episode, "episode number" );
if ( episode != 0 )
{
std::shared_ptr<Show> s = std::static_pointer_cast<Show>( show );
s->addEpisode( *task.media, title, episode );
}
task.media->save();
t->commit();
return true;
});
}
else
{
......@@ -186,7 +191,7 @@ bool MetadataParser::parseAudioFile( parser::Task& task )
{
task.media->setType( IMedia::Type::Audio );
const auto artworkMrl = task.vlcMedia.meta( libvlc_meta_ArtworkURL );
auto artworkMrl = task.vlcMedia.meta( libvlc_meta_ArtworkURL );
if ( artworkMrl.empty() == false )
task.media->setThumbnail( artworkMrl );
......@@ -195,23 +200,26 @@ bool MetadataParser::parseAudioFile( parser::Task& task )
if ( artists.first == nullptr && artists.second == nullptr )
return false;
auto album = findAlbum( task, artists.first, artists.second );
auto t = m_ml->getConn()->newTransaction();
if ( album == nullptr )
{
const auto& albumName = task.vlcMedia.meta( libvlc_meta_Album );
album = m_ml->createAlbum( albumName, artworkMrl );
return sqlite::Tools::withRetries( 3, [this, &task, &artists]( std::string artworkMrl,
std::shared_ptr<Album> album, std::shared_ptr<Genre> genre ) {
auto t = m_ml->getConn()->newTransaction();
if ( album == nullptr )
return false;
m_notifier->notifyAlbumCreation( album );
}
// If we know a track artist, specify it, otherwise, fallback to the album/unknown artist
auto track = handleTrack( album, task, artists.second ? artists.second : artists.first,
genre.get() );
auto res = link( *task.media, album, artists.first, artists.second );
task.media->save();
t->commit();
return res;
{
const auto& albumName = task.vlcMedia.meta( libvlc_meta_Album );
album = m_ml->createAlbum( albumName, artworkMrl );
if ( album == nullptr )
return false;
m_notifier->notifyAlbumCreation( album );
}
// If we know a track artist, specify it, otherwise, fallback to the album/unknown artist
auto track = handleTrack( album, task, artists.second ? artists.second : artists.first,
genre.get() );
auto res = link( *task.media, album, artists.first, artists.second );
task.media->save();
t->commit();
return res;
}, std::move( artworkMrl ), std::move( album ), std::move( genre ) );
}
std::shared_ptr<Genre> MetadataParser::handleGenre( parser::Task& task ) const
......
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