diff --git a/src/MediaLibrary.cpp b/src/MediaLibrary.cpp index c3b0a950c6c9da7f917776983ded3e91ffd88a30..e54a5f3338f4eaefe31c47d1dd4858925a10a37e 100644 --- a/src/MediaLibrary.cpp +++ b/src/MediaLibrary.cpp @@ -396,11 +396,121 @@ bool MediaLibrary::createAllTables() return true; } +void MediaLibrary::deleteAllTriggers(sqlite::Connection* dbConn) +{ + const std::string triggers[] = { + "album_delete_empty", + "album_delete_track", + "album_is_present", + "artist_decrement_nb_albums", + "artist_decrement_nb_tracks", + "artist_has_tracks_present", + "artist_increment_nb_albums_unknown_album", + "artist_increment_nb_tracks", + "artist_update_nb_albums", + "auto_delete_album_thumbnail", + "auto_delete_artist_thumbnail", "auto_delete_media_thumbnail", + "decr_thumbnail_refcount", + "decrement_media_nb_playlist", + "delete_album_fts", "delete_artist_fts", + "delete_artist_without_tracks", + "delete_folder_fts", "delete_genre_fts", "delete_label_fts", + "delete_media_fts", "delete_playlist_fts", "delete_playlist_linking_tasks", + "delete_show_fts", "delete_unused_thumbnail", + "folder_update_nb_media_on_media_update", + "genre_delete_empty", + "genre_update_is_present", + "genre_update_on_track_deleted", + "incr_thumbnail_refcount", + "increment_media_nb_playlist", "insert_album_fts", "insert_artist_fts", + "insert_folder_fts", "insert_genre_fts", "insert_media_fts", + "insert_playlist_fts", "insert_show_fts", + "media_cascade_file_deletion", + "media_cascade_file_update", + "media_group_decrement_nb_media_on_deletion", + "media_group_delete_empty_group", + "media_group_delete_fts", + "media_group_insert_fts", + "media_group_rename_forced_singleton", + "media_group_update_duration_on_media_change", + "media_group_update_duration_on_media_deletion", + "media_group_update_media_count_on_import_type_change", + "media_group_update_nb_media_types", + "media_group_update_nb_media_types_presence", + "media_update_device_presence", + "playlist_cascade_file_deletion", + "playlist_update_duration_on_media_change", + "playlist_update_nb_media_on_media_change", + "playlist_update_nb_media_on_media_deletion", + "show_decrement_nb_episode", + "show_increment_nb_episode", + "show_update_is_present", + "thumbnail_insert_cleanup", + "update_folder_nb_media_on_delete", + "update_folder_nb_media_on_insert", + "update_media_title_fts", "update_playlist_fts", + "update_playlist_order_on_delete", "update_playlist_order_on_insert", + "update_thumbnail_refcount", + }; + for ( const auto& t : triggers ) + sqlite::Tools::executeRequest( dbConn, "DROP TRIGGER IF EXISTS " + t ); +} + void MediaLibrary::deleteAllTables( sqlite::Connection* dbConn ) { - auto tables = sqlite::Tools::listTables( dbConn ); + /* + * For the general case, don't probe the database to delete the active tables. + * If the database is badly corrupted, we might not get any table which would + * cause a failure down the line. + */ + const std::string tables[] = { + Device::Table::Name, + Device::MountpointTable::Name, + Folder::Table::Name, + Folder::FtsTable::Name, + Thumbnail::Table::Name, + Thumbnail::LinkingTable::Name, + Thumbnail::CleanupTable::Name, + Media::Table::Name, + Media::FtsTable::Name, + File::Table::Name, + Label::Table::Name, + Label::FileRelationTable::Name, + Playlist::Table::Name, + Playlist::FtsTable::Name, + Playlist::MediaRelationTable::Name, + Genre::Table::Name, + Genre::FtsTable::Name, + Album::Table::Name, + Album::FtsTable::Name, + Show::Table::Name, + Show::FtsTable::Name, + ShowEpisode::Table::Name, + Movie::Table::Name, + VideoTrack::Table::Name, + AudioTrack::Table::Name, + Artist::Table::Name, + Artist::FtsTable::Name, + Artist::MediaRelationTable::Name, + parser::Task::Table::Name, + Metadata::Table::Name, + SubtitleTrack::Table::Name, + Chapter::Table::Name, + Bookmark::Table::Name, + MediaGroup::Table::Name, + MediaGroup::FtsTable::Name, + "Settings", + }; for ( const auto& table : tables ) + sqlite::Tools::executeRequest( dbConn, "DROP TABLE IF EXISTS " + table ); + /* + * Keep probing the database in case it is quite old and still contains a + * deprecated table not listed above + */ + const auto leftOverTables = sqlite::Tools::listTables( dbConn ); + for ( const auto& table : leftOverTables ) sqlite::Tools::executeRequest( dbConn, "DROP TABLE " + table ); + } void MediaLibrary::createAllTriggers() @@ -1353,6 +1463,7 @@ bool MediaLibrary::recreateDatabase() { sqlite::Connection::DisableForeignKeyContext ctx{ m_dbConnection.get() }; auto t = m_dbConnection->newTransaction(); + deleteAllTriggers( m_dbConnection.get() ); deleteAllTables( m_dbConnection.get() ); sqlite::Statement::FlushStatementCache(); Settings::createTable( m_dbConnection.get() ); @@ -1364,6 +1475,12 @@ bool MediaLibrary::recreateDatabase() if ( m_settings.load() == false ) return false; t->commit(); + /* + * Now that we removed all the tables, flush all the connections to avoid + * Database is locked errors. + * See https://www.sqlite.org/c3ref/close.html + */ + m_dbConnection->flushAll(); /* * We just delete all tables but this won't invoke the thumbnails deletion diff --git a/src/MediaLibrary.h b/src/MediaLibrary.h index e5a843a804f318ddaa15c2dfd9afe68e8ea80c14..91886eac5bf42bb5c9dab2522a5d6698c8e02239 100644 --- a/src/MediaLibrary.h +++ b/src/MediaLibrary.h @@ -303,6 +303,7 @@ private: protected: virtual void addLocalFsFactory(); + void deleteAllTriggers( sqlite::Connection* dbConn ); void deleteAllTables( medialibrary::sqlite::Connection *dbConn ); protected: diff --git a/src/database/SqliteConnection.cpp b/src/database/SqliteConnection.cpp index d45345704e53afd024b66e6de35fada392f0ffef..b1a82ed991a909a217922d946ea246716c9c7814 100644 --- a/src/database/SqliteConnection.cpp +++ b/src/database/SqliteConnection.cpp @@ -259,6 +259,13 @@ const std::string&Connection::dbPath() const return m_dbPath; } +void Connection::flushAll() +{ + Statement::FlushStatementCache(); + std::unique_lock lock( m_connMutex ); + m_conns.clear(); +} + std::shared_ptr Connection::connect( const std::string& dbPath ) { // Use a wrapper to allow make_shared to use the private Connection ctor diff --git a/src/database/SqliteConnection.h b/src/database/SqliteConnection.h index 6ee28ecaff44bbbaf1a5e90213f77a6e9f1f52f5..67713a4c26f6b30c04997581ed09ccca5815c452 100644 --- a/src/database/SqliteConnection.h +++ b/src/database/SqliteConnection.h @@ -90,6 +90,7 @@ public: bool checkSchemaIntegrity(); bool checkForeignKeysIntegrity(); const std::string& dbPath() const; + void flushAll(); static std::shared_ptr connect( const std::string& dbPath ); diff --git a/test/samples/Tester.cpp b/test/samples/Tester.cpp index bacfc88d7c7d929617014c07726bde6ad1d7b604..b2f5900f27f8bf16739d12365e12eb23b01e8879 100644 --- a/test/samples/Tester.cpp +++ b/test/samples/Tester.cpp @@ -251,8 +251,10 @@ class MediaLibraryTester : public MediaLibrary { sqlite::Connection::DisableForeignKeyContext ctx{ m_dbConnection.get() }; auto t = m_dbConnection->newTransaction(); + deleteAllTriggers( m_dbConnection.get() ); deleteAllTables( dbConn ); t->commit(); + m_dbConnection->flushAll(); } }; } @@ -867,8 +869,10 @@ void MediaLibraryResumeTest::onDbConnectionReady( sqlite::Connection *dbConn ) { sqlite::Connection::DisableForeignKeyContext ctx{ m_dbConnection.get() }; auto t = m_dbConnection->newTransaction(); + deleteAllTriggers( m_dbConnection.get() ); deleteAllTables( dbConn ); t->commit(); + m_dbConnection->flushAll(); } parser::Parser* MediaLibraryResumeTest::getParser() const diff --git a/test/unittest/MediaLibraryTester.cpp b/test/unittest/MediaLibraryTester.cpp index 0a44cae6d8cad7448e20468c29ea65010635ee6d..2792c946a544f181de33b84c6e665f8e33cfb815 100644 --- a/test/unittest/MediaLibraryTester.cpp +++ b/test/unittest/MediaLibraryTester.cpp @@ -51,8 +51,10 @@ void MediaLibraryTester::onDbConnectionReady( sqlite::Connection* dbConn ) { sqlite::Connection::WeakDbContext ctx{ m_dbConnection.get() }; auto t = m_dbConnection->newTransaction(); + deleteAllTriggers( m_dbConnection.get() ); deleteAllTables( dbConn ); t->commit(); + m_dbConnection->flushAll(); } std::shared_ptr MediaLibraryTester::media( int64_t id )