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

Support deletion of a media without removing it from a playlist

parent d287037d
......@@ -30,6 +30,8 @@
#include "database/SqliteQuery.h"
#include <algorithm>
namespace medialibrary
{
......@@ -114,15 +116,58 @@ Query<IMedia> Playlist::media() const
"LEFT JOIN PlaylistMediaRelation pmr ON pmr.media_id = m.id_media "
"WHERE pmr.playlist_id = ? AND m.is_present != 0 "
"ORDER BY pmr.position";
curateNullMediaID();
return make_query<Media, IMedia>( m_ml, "m.*", req, m_id );
}
Query<IMedia> Playlist::searchMedia( const std::string& pattern,
const QueryParameters* params ) const
{
curateNullMediaID();
return Media::searchInPlaylist( m_ml, pattern, m_id, params );
}
void Playlist::curateNullMediaID() const
{
auto dbConn = m_ml->getConn();
auto t = dbConn->newTransaction();
std::string req = "SELECT rowid, mrl FROM PlaylistMediaRelation "
"WHERE media_id IS NULL "
"AND playlist_id = ?";
sqlite::Statement stmt{ dbConn->handle(), req };
stmt.execute( m_id );
std::string updateReq = "UPDATE PlaylistMediaRelation SET media_id = ? WHERE rowid = ?";
auto mediaNotFound = false;
for ( sqlite::Row row = stmt.row(); row != nullptr; row = stmt.row() )
{
int64_t rowId;
std::string mrl;
row >> rowId >> mrl;
auto media = m_ml->media( mrl );
if ( media != nullptr )
{
LOG_INFO( "Updating playlist item mediaId (playlist: ", m_id,
"; mrl: ", mrl, ')' );
sqlite::Tools::executeUpdate( dbConn, updateReq, media->id(), rowId );
}
else
{
LOG_INFO( "Can't restore media association for media ", mrl,
" in playlist ", m_id, ". Media will be removed from the playlist" );
mediaNotFound = true;
}
}
if ( mediaNotFound )
{
// Batch all deletion at once instead of doing it during the loop
std::string deleteReq = "DELETE FROM PlaylistMediaRelation "
"WHERE playlist_id = ? AND media_id IS NULL";
sqlite::Tools::executeDelete( dbConn, deleteReq, m_id );
}
t->commit();
}
bool Playlist::append( const IMedia& media )
{
return add( media, 0 );
......@@ -130,12 +175,26 @@ bool Playlist::append( const IMedia& media )
bool Playlist::add( const IMedia& media, unsigned int position )
{
static const std::string req = "INSERT INTO PlaylistMediaRelation(media_id, playlist_id, position) VALUES(?, ?, ?)";
// position isn't a foreign key, but we want it to be passed as NULL if it equals to 0
// When the position is NULL, the insertion triggers takes care of counting the number of records to auto append.
static const std::string req = "INSERT INTO PlaylistMediaRelation"
"(media_id, mrl, playlist_id, position) VALUES(?, ?, ?, ?)";
try
{
return sqlite::Tools::executeInsert( m_ml->getConn(), req, media.id(), m_id, sqlite::ForeignKey{ position } );
// position isn't a foreign key, but we want it to be passed as NULL if it equals to 0
// When the position is NULL, the insertion triggers takes care of
// counting the number of records to auto append.
auto files = media.files();
assert( files.size() > 0 );
auto mainFile = std::find_if( begin( files ), end( files ), []( const FilePtr& f) {
return f->type() == IFile::Type::Main;
});
if ( mainFile == end( files ) )
{
LOG_ERROR( "Can't add a media without any files to a playlist" );
return false;
}
return sqlite::Tools::executeInsert( m_ml->getConn(), req, media.id(),
(*mainFile)->mrl(), m_id,
sqlite::ForeignKey{ position } );
}
catch (const sqlite::errors::ConstraintViolation& ex)
{
......@@ -189,10 +248,11 @@ void Playlist::createTable( sqlite::Connection* dbConn )
")";
const std::string relTableReq = "CREATE TABLE IF NOT EXISTS PlaylistMediaRelation("
"media_id INTEGER,"
"mrl STRING NOT NULL,"
"playlist_id INTEGER,"
"position INTEGER,"
"FOREIGN KEY(media_id) REFERENCES " + policy::MediaTable::Name + "("
+ policy::MediaTable::PrimaryKeyColumn + ") ON DELETE CASCADE,"
+ policy::MediaTable::PrimaryKeyColumn + ") ON DELETE SET NULL,"
"FOREIGN KEY(playlist_id) REFERENCES " + policy::PlaylistTable::Name + "("
+ policy::PlaylistTable::PrimaryKeyColumn + ") ON DELETE CASCADE"
")";
......
......@@ -82,6 +82,7 @@ public:
private:
static std::string sortRequest( const QueryParameters* params );
void curateNullMediaID() const;
private:
MediaLibraryPtr m_ml;
......
......@@ -209,3 +209,8 @@ MediaPtr MediaLibraryTester::addMedia( const std::string& mrl )
{
return addExternalMedia( mrl );
}
void MediaLibraryTester::deleteMedia( int64_t mediaId )
{
Media::destroy( this, mediaId );
}
......@@ -76,6 +76,7 @@ public:
virtual void startThumbnailer();
virtual void populateFsFactories();
MediaPtr addMedia( const std::string& mrl );
void deleteMedia( int64_t mediaId );
private:
std::shared_ptr<fs::IDirectory> dummyDirectory;
......
......@@ -360,3 +360,67 @@ TEST_F( Playlists, SearchMedia )
ASSERT_EQ( 1u, media.size() );
ASSERT_EQ( m1->id(), media[0]->id() );
}
TEST_F( Playlists, ReinsertMedia )
{
auto m1 = ml->addMedia( "http://sea.otters/fluffy.mkv" );
auto m2 = ml->addMedia( "file:///cute_otters_holding_hands.mp4" );
auto m3 = ml->addMedia( "media.mp3" );
pl->append( *m1 );
pl->append( *m2 );
pl->append( *m3 );
auto media = pl->media()->all();
ASSERT_EQ( 3u, media.size() );
ASSERT_EQ( m1->id(), media[0]->id() );
ASSERT_EQ( m2->id(), media[1]->id() );
ASSERT_EQ( m3->id(), media[2]->id() );
// Ensure the media we fetch are recreated by checking their ids before/after
auto m1Id = m1->id();
auto m2Id = m2->id();
ml->deleteMedia( m1->id() );
ml->deleteMedia( m2->id() );
Reload();
pl = ml->playlist( pl->id() );
m1 = ml->addMedia( "http://sea.otters/fluffy.mkv" );
m2 = ml->addMedia( "file:///cute_otters_holding_hands.mp4" );
media = pl->media()->all();
ASSERT_EQ( 3u, media.size() );
ASSERT_EQ( m1->id(), media[0]->id() );
ASSERT_EQ( m2->id(), media[1]->id() );
ASSERT_EQ( m3->id(), media[2]->id() );
ASSERT_NE( m1->id(), m1Id );
ASSERT_NE( m2->id(), m2Id );
}
TEST_F( Playlists, RemoveMedia )
{
auto m1 = ml->addMedia( "http://sea.otters/fluffy.mkv" );
auto m2 = ml->addMedia( "file:///cute_otters_holding_hands.mp4" );
auto m3 = ml->addMedia( "media.mp3" );
pl->append( *m1 );
pl->append( *m2 );
pl->append( *m3 );
auto media = pl->media()->all();
ASSERT_EQ( 3u, media.size() );
ASSERT_EQ( m1->id(), media[0]->id() );
ASSERT_EQ( m2->id(), media[1]->id() );
ASSERT_EQ( m3->id(), media[2]->id() );
ml->deleteMedia( m1->id() );
ml->deleteMedia( m2->id() );
Reload();
pl = ml->playlist( pl->id() );
media = pl->media()->all();
ASSERT_EQ( 1u, media.size() );
ASSERT_EQ( m3->id(), media[0]->id() );
}
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