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

Remove is_present from File & Folder tables

Bypass those tables and directly propagate the presence state in the
media
parent be8cbd48
......@@ -47,7 +47,6 @@ File::File( MediaLibraryPtr ml, sqlite::Row& row )
, m_lastModificationDate( row.extract<decltype(m_lastModificationDate)>() )
, m_size( row.extract<decltype(m_size)>() )
, m_folderId( row.extract<decltype(m_folderId)>() )
, m_isPresent( row.extract<decltype(m_isPresent)>() )
, m_isRemovable( row.extract<decltype(m_isRemovable)>() )
, m_isExternal( row.extract<decltype(m_isExternal)>() )
{
......@@ -64,7 +63,6 @@ File::File( MediaLibraryPtr ml, int64_t mediaId, int64_t playlistId, Type type,
, m_lastModificationDate( file.lastModificationDate() )
, m_size( file.size() )
, m_folderId( folderId )
, m_isPresent( true )
, m_isRemovable( isRemovable )
, m_isExternal( false )
{
......@@ -82,7 +80,6 @@ File::File( MediaLibraryPtr ml, int64_t mediaId, int64_t playlistId, IFile::Type
, m_lastModificationDate( 0 )
, m_size( 0 )
, m_folderId( 0 )
, m_isPresent( true )
, m_isRemovable( false )
, m_isExternal( true )
, m_fullPath( mrl )
......@@ -267,9 +264,8 @@ std::shared_ptr<File> File::fromMrl( MediaLibraryPtr ml, const std::string& mrl
auto file = fetch( ml, req, mrl );
if ( file == nullptr )
return nullptr;
// safety checks: since this only works for files on non removable devices, isPresent must be true
// and isRemovable must be false
assert( file->m_isPresent == true );
// safety checks: since this only works for files on non removable devices
// isRemovable must be false
assert( file->m_isRemovable == false );
return file;
}
......
......@@ -114,7 +114,6 @@ private:
std::time_t m_lastModificationDate;
unsigned int m_size;
const int64_t m_folderId;
const bool m_isPresent;
const bool m_isRemovable;
const bool m_isExternal;
......
......@@ -51,8 +51,7 @@ Folder::Folder( MediaLibraryPtr ml, sqlite::Row& row )
, m_parent( row.load<decltype(m_parent)>( 2 ) )
, m_isBlacklisted( row.load<decltype(m_isBlacklisted)>( 3 ) )
, m_deviceId( row.load<decltype(m_deviceId)>( 4 ) )
// Skip is_present
, m_isRemovable( row.load<decltype(m_isRemovable)>( 6 ) )
, m_isRemovable( row.load<decltype(m_isRemovable)>( 5 ) )
{
}
......@@ -297,8 +296,9 @@ std::vector<std::shared_ptr<File>> Folder::files()
std::vector<std::shared_ptr<Folder>> Folder::folders()
{
static const std::string req = "SELECT * FROM " + Folder::Table::Name
+ " WHERE parent_id = ? AND is_blacklisted = 0 AND is_present != 0";
static const std::string req = "SELECT * FROM " + Folder::Table::Name + " f "
" LEFT JOIN " + Device::Table::Name + " d ON d.id_device = f.device_id"
" WHERE parent_id = ? AND is_blacklisted = 0 AND d.is_present != 0";
return DatabaseHelpers::fetchAll<Folder>( m_ml, req, m_id );
}
......@@ -338,11 +338,12 @@ bool Folder::isRootFolder() const
std::vector<std::shared_ptr<Folder>> Folder::fetchRootFolders( MediaLibraryPtr ml )
{
static const std::string req = "SELECT * FROM " + Folder::Table::Name +
static const std::string req = "SELECT * FROM " + Folder::Table::Name + " f "
" LEFT JOIN ExcludedEntryFolder"
" ON " + Folder::Table::Name + ".id_folder = ExcludedEntryFolder.folder_id"
" ON f.id_folder = ExcludedEntryFolder.folder_id"
" LEFT JOIN " + Device::Table::Name + " d ON d.id_device = f.device_id"
" WHERE ExcludedEntryFolder.folder_id IS NULL AND"
" parent_id IS NULL AND is_blacklisted = 0 AND is_present != 0";
" parent_id IS NULL AND is_blacklisted = 0 AND d.is_present != 0";
return DatabaseHelpers::fetchAll<Folder>( ml, req );
}
......
......@@ -34,6 +34,7 @@
#include "AlbumTrack.h"
#include "Artist.h"
#include "AudioTrack.h"
#include "Device.h"
#include "Media.h"
#include "File.h"
#include "Folder.h"
......@@ -74,8 +75,9 @@ Media::Media( MediaLibraryPtr ml, sqlite::Row& row )
, m_title( row.load<decltype(m_title)>( 11 ) )
, m_filename( row.load<decltype(m_filename)>( 12 ) )
, m_isFavorite( row.load<decltype(m_isFavorite)>( 13 ) )
, m_isPresent( row.load<decltype(m_isPresent)>( 14 ) )
, m_nbPlaylists( row.load<unsigned int>( 15 ) )
// Skip is_present
// Skip device_id
, m_nbPlaylists( row.load<unsigned int>( 16 ) )
// End of DB fields extraction
, m_metadata( m_ml, IMetadata::EntityType::Media )
, m_changed( false )
......@@ -98,20 +100,22 @@ Media::Media( MediaLibraryPtr ml, const std::string& title, Type type )
// When creating a Media, meta aren't parsed, and therefor, the title is the filename
, m_filename( title )
, m_isFavorite( false )
, m_isPresent( true )
, m_nbPlaylists( 0 )
, m_metadata( m_ml, IMetadata::EntityType::Media )
, m_changed( false )
{
}
std::shared_ptr<Media> Media::create( MediaLibraryPtr ml, Type type, const std::string& fileName )
std::shared_ptr<Media> Media::create( MediaLibraryPtr ml, Type type,
int64_t deviceId, const std::string& fileName )
{
auto self = std::make_shared<Media>( ml, fileName, type );
static const std::string req = "INSERT INTO " + Media::Table::Name +
"(type, insertion_date, title, filename) VALUES(?, ?, ?, ?)";
"(type, insertion_date, title, filename, device_id) "
"VALUES(?, ?, ?, ?, ?)";
if ( insert( ml, self, req, type, self->m_insertionDate, self->m_title, self->m_filename ) == false )
if ( insert( ml, self, req, type, self->m_insertionDate, self->m_title,
self->m_filename, sqlite::ForeignKey{ deviceId } ) == false )
return nullptr;
return self;
}
......@@ -533,7 +537,7 @@ Query<IMedia> Media::listAll( MediaLibraryPtr ml, IMedia::Type type,
req += " WHERE m.type = ?"
" AND f.type = ?"
" AND f.is_present != 0";
" AND m.is_present != 0";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
sortRequest( params, type == IMedia::Type::Audio ),
......@@ -729,7 +733,7 @@ Query<IMedia> Media::search( MediaLibraryPtr ml, const std::string& title,
" WHERE"
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.type != ? AND m.type != ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......@@ -746,7 +750,7 @@ Query<IMedia> Media::search( MediaLibraryPtr ml, const std::string& title,
" WHERE"
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.type = ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......@@ -763,7 +767,7 @@ Query<IMedia> Media::searchAlbumTracks(MediaLibraryPtr ml, const std::string& pa
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND tra.album_id = ?"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.subtype = ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......@@ -780,7 +784,7 @@ Query<IMedia> Media::searchArtistTracks(MediaLibraryPtr ml, const std::string& p
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND tra.artist_id = ?"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.subtype = ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......@@ -797,7 +801,7 @@ Query<IMedia> Media::searchGenreTracks(MediaLibraryPtr ml, const std::string& pa
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND tra.genre_id = ?"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.subtype = ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......@@ -815,7 +819,7 @@ Query<IMedia> Media::searchShowEpisodes(MediaLibraryPtr ml, const std::string& p
" m.id_media IN (SELECT rowid FROM " + Media::Table::Name + "Fts"
" WHERE " + Media::Table::Name + "Fts MATCH '*' || ? || '*')"
" AND ep.show_id = ?"
" AND f.is_present = 1"
" AND m.is_present = 1"
" AND f.type = ?"
" AND m.subtype = ?";
return make_query<Media, IMedia>( ml, "m.*", std::move( req ),
......
......@@ -61,7 +61,9 @@ class Media : public IMedia, public DatabaseHelpers<Media>
Media( MediaLibraryPtr ml , sqlite::Row& row );
Media( MediaLibraryPtr ml, const std::string& title, Type type);
static std::shared_ptr<Media> create( MediaLibraryPtr ml, Type type, const std::string& fileName );
static std::shared_ptr<Media> create( MediaLibraryPtr ml, Type type,
int64_t deviceId,
const std::string& fileName );
static void createTable( sqlite::Connection* connection, uint32_t modelVersion );
static void createTriggers( sqlite::Connection* connection, uint32_t modelVersion );
......@@ -175,7 +177,6 @@ private:
// might be used as a fallback
std::string m_filename;
bool m_isFavorite;
bool m_isPresent;
mutable std::atomic_uint m_nbPlaylists;
// Auto fetched related properties
......
......@@ -444,7 +444,7 @@ MediaPtr MediaLibrary::addExternalMedia( const std::string& mrl, IMedia::Type ty
return sqlite::Tools::withRetries( 3, [this, &mrl, type]() -> MediaPtr {
auto t = m_dbConnection->newTransaction();
auto fileName = utils::file::fileName( mrl );
auto media = Media::create( this, type, utils::url::decode( fileName ) );
auto media = Media::create( this, type, 0, utils::url::decode( fileName ) );
if ( media == nullptr )
return nullptr;
if ( media->addExternalMrl( mrl, IFile::Type::Main ) == nullptr )
......
......@@ -48,6 +48,18 @@
"(SELECT COUNT(media_id) FROM PlaylistMediaRelation WHERE media_id = id_media )"
"WHERE id_media IN (SELECT media_id FROM PlaylistMediaRelation)",
/********************* Populate new media.device_id ***************************/
"UPDATE " + Media::Table::Name + " SET device_id = "
"(SELECT d.id_device FROM " + Device::Table::Name + " d "
"INNER JOIN " + Folder::Table::Name + " f ON d.id_device = f.device_id "
"INNER JOIN " + File::Table::Name + " fi ON fi.folder_id = f.id_folder "
"WHERE fi.type = " +
std::to_string( static_cast<typename std::underlying_type<IFile::Type>::type>(
IFile::Type::Main) ) + " "
"AND fi.media_id = " + Media::Table::Name + ".id_media"
")",
/************ Playlist external media were stored as Unknown ******************/
"UPDATE " + Media::Table::Name + " SET type = " +
......@@ -136,9 +148,75 @@ IMedia::Type::Unknown ) ),
"DROP TABLE " + Device::Table::Name + "_backup",
/******************* Migrate Folder table *************************************/
"CREATE TEMPORARY TABLE " + Folder::Table::Name + "_backup"
"("
"id_folder INTEGER PRIMARY KEY AUTOINCREMENT,"
"path TEXT,"
"parent_id UNSIGNED INTEGER,"
"is_blacklisted BOOLEAN NOT NULL DEFAULT 0,"
"device_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL"
")",
"INSERT INTO " + Folder::Table::Name + "_backup SELECT * FROM " + Folder::Table::Name,
"DROP TABLE " + Folder::Table::Name,
#include "database/tables/Folder_v14.sql"
"INSERT INTO " + Folder::Table::Name + "("
"id_folder, path, parent_id, is_blacklisted, device_id, is_removable"
") "
"SELECT id_folder, path, parent_id, is_blacklisted, device_id, is_removable "
"FROM " + Folder::Table::Name + "_backup",
"DROP TABLE " + Folder::Table::Name + "_backup",
/******************* Migrate File table *************************************/
"CREATE TEMPORARY TABLE " + File::Table::Name + "_backup"
"("
"id_file INTEGER PRIMARY KEY AUTOINCREMENT,"
"media_id UNSIGNED INT DEFAULT NULL,"
"playlist_id UNSIGNED INT DEFAULT NULL,"
"mrl TEXT,"
"type UNSIGNED INTEGER,"
"last_modification_date UNSIGNED INT,"
"size UNSIGNED INT,"
"folder_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"is_external BOOLEAN NOT NULL"
")",
"INSERT INTO " + File::Table::Name + "_backup SELECT * FROM " + File::Table::Name,
"DROP TABLE " + File::Table::Name,
#include "database/tables/File_v14.sql"
"INSERT INTO " + File::Table::Name + "("
"id_file, media_id, playlist_id, mrl, type, last_modification_date, size,"
"folder_id, is_removable, is_external) "
"SELECT id_file, media_id, playlist_id, mrl, type, last_modification_date, size,"
"folder_id, is_removable, is_external FROM " + File::Table::Name + "_backup",
"DROP TABLE " + File::Table::Name + "_backup",
/******************* Delete removed triggers **********************************/
"DROP TRIGGER on_track_genre_changed",
// Old Folder -> File is_present trigger
// is_folder_present has been implicitely removed by dropping the File table
// Old File -> Media is_present trigger
// has_files_present has been implicitely removed by dropping the Folder table
// Old Device -> Folder is_present trigger
// is_device_present has been implicitely removed by dropping the Device table
/******************* Delete other tables **************************************/
......
"CREATE TRIGGER IF NOT EXISTS is_folder_present AFTER UPDATE OF is_present ON "
+ Folder::Table::Name +
" BEGIN"
" UPDATE " + File::Table::Name + " SET is_present = new.is_present WHERE folder_id = new.id_folder;"
" END",
"CREATE INDEX IF NOT EXISTS file_media_id_index ON " +
File::Table::Name + "(media_id)",
......
......@@ -8,7 +8,6 @@
"last_modification_date UNSIGNED INT,"
"size UNSIGNED INT,"
"folder_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"is_external BOOLEAN NOT NULL,"
......@@ -22,4 +21,4 @@
+ "(id_folder) ON DELETE CASCADE,"
"UNIQUE( mrl, folder_id ) ON CONFLICT FAIL"
")"
")",
"CREATE TRIGGER IF NOT EXISTS is_device_present AFTER UPDATE OF is_present ON "
+ Device::Table::Name +
" WHEN old.is_present != new.is_present"
" BEGIN"
" UPDATE " + Folder::Table::Name + " SET is_present = new.is_present WHERE device_id = new.id_device;"
" END",
"CREATE INDEX IF NOT EXISTS folder_device_id_idx ON " +
Folder::Table::Name + " (device_id)",
......
......@@ -5,7 +5,6 @@
"parent_id UNSIGNED INTEGER,"
"is_blacklisted BOOLEAN NOT NULL DEFAULT 0,"
"device_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"FOREIGN KEY (parent_id) REFERENCES " + Folder::Table::Name +
......@@ -28,4 +27,4 @@
"(id_folder) ON DELETE CASCADE,"
"UNIQUE(folder_id) ON CONFLICT FAIL"
")"
")",
"CREATE INDEX IF NOT EXISTS index_last_played_date ON "
+ Media::Table::Name + "(last_played_date DESC)",
"CREATE TRIGGER IF NOT EXISTS has_files_present AFTER UPDATE OF "
"is_present ON " + File::Table::Name + " "
"CREATE TRIGGER IF NOT EXISTS is_media_device_present AFTER UPDATE OF "
"is_present ON " + Device::Table::Name + " "
"BEGIN "
"UPDATE " + Media::Table::Name + " SET is_present="
"(SELECT EXISTS("
"SELECT id_file FROM " + File::Table::Name +
" WHERE media_id=new.media_id AND is_present != 0 LIMIT 1"
") )"
"WHERE id_media=new.media_id;"
"UPDATE " + Media::Table::Name + " "
"SET is_present=new.is_present "
"WHERE device_id=new.id_device;"
"END;",
"CREATE TRIGGER IF NOT EXISTS cascade_file_deletion AFTER DELETE ON "
......
......@@ -16,6 +16,7 @@
"filename TEXT COLLATE NOCASE,"
"is_favorite BOOLEAN NOT NULL DEFAULT 0,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"device_id INTEGER,"
"nb_playlists UNSIGNED INTEGER NOT NULL DEFAULT 0,"
"FOREIGN KEY(thumbnail_id) REFERENCES " + Thumbnail::Table::Name
......
......@@ -259,7 +259,7 @@ void MetadataAnalyzer::addPlaylistElement( IItem& item,
{
auto t2 = m_ml->getConn()->newTransaction();
auto externalMedia = Media::create( m_ml, IMedia::Type::External,
subitem.meta( IItem::Metadata::Title ) );
0, subitem.meta( IItem::Metadata::Title ) );
if ( externalMedia == nullptr )
{
LOG_ERROR( "Failed to create external media for ", mrl, " in the playlist ", item.mrl() );
......@@ -398,8 +398,9 @@ std::tuple<Status, bool> MetadataAnalyzer::createFileAndMedia( IItem& item ) con
}) == end( tracks );
auto t = m_ml->getConn()->newTransaction();
LOG_INFO( "Adding ", mrl );
auto folder = static_cast<Folder*>( item.parentFolder().get() );
auto m = Media::create( m_ml, isAudio ? IMedia::Type::Audio : IMedia::Type::Video,
utils::url::decode( utils::file::fileName( mrl ) ) );
folder->deviceId(), utils::url::decode( utils::file::fileName( mrl ) ) );
if ( m == nullptr )
{
LOG_ERROR( "Failed to add media ", mrl, " to the media library" );
......
......@@ -29,6 +29,7 @@
#include "medialibrary/filesystem/IFile.h"
#include "medialibrary/filesystem/IDirectory.h"
#include "Device.h"
#include "File.h"
#include "Folder.h"
#include "Playlist.h"
......@@ -474,7 +475,9 @@ std::vector<std::shared_ptr<Task>> Task::fetchUncompleted( MediaLibraryPtr ml )
{
static const std::string req = "SELECT * FROM " + Task::Table::Name + " t"
" LEFT JOIN " + File::Table::Name + " f ON f.id_file = t.file_id"
" WHERE step & ? != ? AND retry_count < 3 AND (f.is_present != 0 OR "
" LEFT JOIN " + Folder::Table::Name + " fol ON f.folder_id = fol.id_folder"
" LEFT JOIN " + Device::Table::Name + " d ON d.id_device = fol.device_id"
" WHERE step & ? != ? AND retry_count < 3 AND (d.is_present != 0 OR "
" t.file_id IS NULL)";
return Task::fetchAll<Task>( ml, req, Step::Completed,
Step::Completed );
......
......@@ -71,7 +71,7 @@ std::shared_ptr<Media> MediaLibraryTester::addFile( std::shared_ptr<fs::IFile> f
IMedia::Type type )
{
LOG_INFO( "Adding ", fileFs->mrl() );
auto mptr = Media::create( this, type,
auto mptr = Media::create( this, type, parentFolder->deviceId(),
utils::file::stripExtension( fileFs->name() ) );
if ( mptr == nullptr )
{
......
......@@ -282,7 +282,9 @@ TEST_F( Albums, AlbumArtist )
TEST_F( Albums, SearchByTitle )
{
ml->createAlbum( "sea otters" );
ml->createAlbum( "pangolins of fire" );
auto a = ml->createAlbum( "pangolins of fire" );
auto m = std::static_pointer_cast<Media>( ml->addMedia( "media.mp3" ) );
a->addTrack( m, 1, 0, 0, nullptr );
auto albums = ml->searchAlbums( "otte", nullptr )->all();
ASSERT_EQ( 1u, albums.size() );
......
......@@ -174,7 +174,7 @@ TEST_F( DbModel, Upgrade12to13 )
// We can't check for the number of albums anymore since they are deleted
// as part of 13 -> 14 migration
CheckNbTriggers( 33 );
CheckNbTriggers( 31 );
}
TEST_F( DbModel, Upgrade13to14 )
......@@ -217,5 +217,5 @@ TEST_F( DbModel, Upgrade13to14 )
ASSERT_EQ( 0u, std::static_pointer_cast<Media>( externalMedia )->nbPlaylists() );
CheckNbTriggers( 33 );
CheckNbTriggers( 31 );
}
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