...
 
Commits (26)
......@@ -6,8 +6,8 @@ stages:
variables:
GIT_SUBMODULE_STRATEGY: normal
build-linux:
image: registry.videolan.org:5000/medialibrary:20181112164001
build:linux:
image: registry.videolan.org:5000/medialibrary:20190121111050
tags:
- debian
- amd64
......@@ -23,26 +23,30 @@ build-linux:
- samples
- .libs/
unit-tests-linux:
image: registry.videolan.org:5000/medialibrary:20181112164001
unit-tests:linux:
image: registry.videolan.org:5000/medialibrary:20190121111050
tags:
- debian
- amd64
stage: unit-tests
dependencies:
- build:linux
script:
- ./unittest
functional-tests-linux:
image: registry.videolan.org:5000/medialibrary:20181112164001
functional-tests:linux:
image: registry.videolan.org:5000/medialibrary:20190121111050
tags:
- debian
- amd64
stage: functional-tests
dependencies:
- build:linux
script:
- ./samples -v
build-win32:
image: registry.videolan.org:5000/medialibrary-win32:20181113011518
build:win32:
image: registry.videolan.org:5000/medialibrary-win32:20190121122230
tags:
- win32
stage: build
......@@ -58,8 +62,36 @@ build-win32:
- samples.exe
- .libs/
build-win64:
image: registry.videolan.org:5000/medialibrary-win64:20181113014943
unit-tests:win32:
image: registry.videolan.org:5000/medialibrary-win32:20190121122230
tags:
- win32
stage: unit-tests
dependencies:
- build:win32
script:
- cp /prefix/dll/libvlc.dll .
- cp /prefix/dll/libvlccore.dll .
- ln -s /prefix/lib/vlc/plugins/ .
- file libvlc.dll
- file unittest.exe
- wine unittest.exe
functional-tests:win32:
image: registry.videolan.org:5000/medialibrary-win32:20190121122230
tags:
- win32
stage: functional-tests
dependencies:
- build:win32
script:
- cp /prefix/dll/libvlc.dll .
- cp /prefix/dll/libvlccore.dll .
- ln -s /prefix/lib/vlc/plugins/ .
- wine samples.exe -v
build:win64:
image: registry.videolan.org:5000/medialibrary-win64:20190121124804
tags:
- win64
stage: build
......@@ -75,3 +107,28 @@ build-win64:
- samples.exe
- .libs/
unit-tests:win64:
image: registry.videolan.org:5000/medialibrary-win64:20190121124804
tags:
- win64
stage: unit-tests
dependencies:
- build:win64
script:
- cp /prefix/dll/libvlc.dll .
- cp /prefix/dll/libvlccore.dll .
- ln -s /prefix/lib/vlc/plugins/ .
- wine unittest.exe
functional-tests:win64:
image: registry.videolan.org:5000/medialibrary-win64:20190121124804
tags:
- win64
stage: functional-tests
dependencies:
- build:win64
script:
- cp /prefix/dll/libvlc.dll .
- cp /prefix/dll/libvlccore.dll .
- ln -s /prefix/lib/vlc/plugins/ .
- wine samples.exe -v
......@@ -75,7 +75,7 @@ Folder::Folder(MediaLibraryPtr ml, const std::string& path,
void Folder::createTable( sqlite::Connection* connection)
{
const std::string reqs[] = {
#include "database/tables/Folder_v14.sql"
#include "database/tables/Folder_v15.sql"
};
for ( const auto& req : reqs )
sqlite::Tools::executeRequest( connection, req );
......@@ -84,7 +84,7 @@ void Folder::createTable( sqlite::Connection* connection)
void Folder::createTriggers( sqlite::Connection* connection, uint32_t modelVersion )
{
const std::string reqs[] = {
#include "database/tables/Folder_triggers_v14.sql"
#include "database/tables/Folder_triggers_v15.sql"
};
for ( const auto& req : reqs )
sqlite::Tools::executeRequest( connection, req );
......@@ -351,33 +351,37 @@ std::string Folder::filterByMediaType( IMedia::Type type )
switch ( type )
{
case IMedia::Type::Audio:
return " nb_audio > 0";
return " f.nb_audio > 0";
case IMedia::Type::Video:
return " nb_video > 0";
return " f.nb_video > 0";
default:
assert( !"Only Audio/Video/Unknown types are supported when listing folders" );
/* Fall-through */
case IMedia::Type::Unknown:
return " (nb_audio > 0 OR nb_video > 0)";
return " (f.nb_audio > 0 OR f.nb_video > 0)";
}
}
Query<IFolder> Folder::withMedia( MediaLibraryPtr ml, IMedia::Type type,
const QueryParameters* params )
{
std::string req = "FROM " + Table::Name +
" WHERE " + filterByMediaType( type );
std::string req = "FROM " + Table::Name + " f "
" LEFT JOIN " + Device::Table::Name +
" d ON d.id_device = f.device_id "
" WHERE " + filterByMediaType( type ) +
" AND d.is_present != 0";
return make_query<Folder, IFolder>( ml, "*", req, sortRequest( params ) );
}
Query<IFolder> Folder::searchWithMedia( MediaLibraryPtr ml, const std::string& pattern,
IMedia::Type type, const QueryParameters* params )
{
std::string req = "FROM " + Table::Name + " f WHERE f.id_folder IN "
"(SELECT rowid FROM " + Table::Name + "Fts WHERE " +
Table::Name + "Fts MATCH '*' || ? || '*') "
"AND";
req += filterByMediaType( type );
std::string req = "FROM " + Table::Name + " f "
" LEFT JOIN " + Device::Table::Name +
" d ON d.id_device = f.device_id "
"WHERE f.id_folder IN (SELECT rowid FROM " + Table::Name + "Fts WHERE " +
Table::Name + "Fts MATCH '*' || ? || '*') "
"AND d.is_present != 0 AND " + filterByMediaType( type );
return make_query<Folder, IFolder>( ml, "*", req, sortRequest( params ), pattern );
}
......
......@@ -939,17 +939,17 @@ Query<IMedia> Media::fromFolderId( MediaLibraryPtr ml, IMedia::Type type,
{
// This assumes the folder is present, as folders are not expected to be
// manipulated when the device is not present
std::string req = "FROM " + Table::Name + " m WHERE folder_id = ?";
std::string req = "FROM " + Table::Name + " m ";
req += addRequestJoin( params, false, false );
req += " WHERE m.folder_id = ?";
if ( type != Type::Unknown )
{
req += " AND type = ?";
req += addRequestJoin( params, false, false );
req += " AND m.type = ?";
return make_query<Media, IMedia>( ml, "*", req, sortRequest( params ),
folderId, type );
}
// Don't explicitely filter by type since only video/audio media have a
// non NULL folder_id
req += addRequestJoin( params, false, false );
return make_query<Media, IMedia>( ml, "*", req, sortRequest( params ),
folderId );
}
......
......@@ -906,6 +906,11 @@ InitializeResult MediaLibrary::updateDatabaseModel( unsigned int previousVersion
migrateModel13to14( originalPreviousVersion );
previousVersion = 14;
}
if ( previousVersion == 14 )
{
migrateModel14to15();
previousVersion = 15;
}
// To be continued in the future!
if ( needRescan == true )
......@@ -1235,6 +1240,26 @@ void MediaLibrary::migrateModel13to14( uint32_t originalPreviousVersion )
t->commit();
}
/**
* Model 14 to 15 migration:
* - Folder.name is now case insensitive
* - New chapters table
*/
void MediaLibrary::migrateModel14to15()
{
auto dbConn = getConn();
sqlite::Connection::WeakDbContext weakConnCtx{ dbConn };
auto t = dbConn->newTransaction();
std::string reqs[] = {
# include "database/migrations/migration14-15.sql"
};
for ( const auto& req : reqs )
sqlite::Tools::executeRequest( dbConn, req );
Folder::createTriggers( dbConn, 15 );
t->commit();
}
void MediaLibrary::reload()
{
if ( m_discovererWorker != nullptr )
......@@ -1599,12 +1624,12 @@ void MediaLibrary::DeviceListerCb::onDeviceUnmounted( const std::string& uuid,
const std::string& unmountedMountpoint )
{
auto device = Device::fromUuid( m_ml, uuid );
assert( device->isRemovable() == true );
if ( device == nullptr )
{
LOG_WARN( "Unknown device ", uuid, " was unplugged. Ignoring." );
return;
}
assert( device->isRemovable() == true );
auto mountpoint = utils::file::toFolderPath( unmountedMountpoint );
LOG_INFO( "Device ", uuid, " was unplugged. Mountpoint was ", mountpoint );
for ( const auto& fsFactory : m_ml->m_fsFactories )
......
......@@ -216,6 +216,7 @@ private:
void migrateModel10to11();
void migrateModel12to13();
void migrateModel13to14( uint32_t originalPreviousVersion );
void migrateModel14to15();
void createAllTables();
void createAllTriggers();
void registerEntityHooks();
......
......@@ -350,6 +350,12 @@ void Playlist::clearExternalPlaylistContent(MediaLibraryPtr ml)
sqlite::Tools::executeDelete( ml->getConn(), req );
}
void Playlist::clearContent()
{
const std::string req = "DELETE FROM PlaylistMediaRelation WHERE playlist_id = ?";
sqlite::Tools::executeDelete( m_ml->getConn(), req, m_id );
}
void Playlist::insertMrlFromMediaId( MediaLibraryPtr ml )
{
sqlite::Statement stmt{ ml->getConn()->handle(),
......
......@@ -79,6 +79,11 @@ public:
*/
static void clearExternalPlaylistContent( MediaLibraryPtr ml );
/**
* @brief clearContent Removes all media from this playlist.
*/
void clearContent();
/**
* Fetches all PlaylistMediaRelation items without an MRL and assign it
*/
......
......@@ -32,7 +32,7 @@
namespace medialibrary
{
const uint32_t Settings::DbModelVersion = 14u;
const uint32_t Settings::DbModelVersion = 15u;
Settings::Settings( MediaLibrary* ml )
: m_ml( ml )
......
/******************* Migrate Folder table *************************************/
"CREATE TEMPORARY TABLE " + Folder::Table::Name + "_backup"
"("
"id_folder INTEGER PRIMARY KEY AUTOINCREMENT,"
"path TEXT,"
"name TEXT,"
"parent_id UNSIGNED INTEGER,"
"is_banned BOOLEAN NOT NULL DEFAULT 0,"
"device_id UNSIGNED INTEGER,"
"is_removable BOOLEAN NOT NULL,"
"nb_audio UNSIGNED INTEGER NOT NULL DEFAULT 0,"
"nb_video UNSIGNED INTEGER NOT NULL DEFAULT 0"
")",
"INSERT INTO " + Folder::Table::Name + "_backup SELECT * FROM " + Folder::Table::Name,
"DROP TABLE " + Folder::Table::Name,
#include "database/tables/Folder_v15.sql"
"INSERT INTO " + Folder::Table::Name + "("
"id_folder, path, name, parent_id, is_banned, device_id, is_removable,"
"nb_audio, nb_video"
") "
"SELECT id_folder, path, name, parent_id, is_banned, device_id, is_removable,"
"nb_audio, nb_video "
"FROM " + Folder::Table::Name + "_backup",
"DROP TABLE " + Folder::Table::Name + "_backup",
#include "Folder_triggers_v14.sql"
"CREATE TABLE IF NOT EXISTS " + Folder::Table::Name +
"("
"id_folder INTEGER PRIMARY KEY AUTOINCREMENT,"
"path TEXT,"
"name TEXT COLLATE NOCASE,"
"parent_id UNSIGNED INTEGER,"
"is_banned BOOLEAN NOT NULL DEFAULT 0,"
"device_id UNSIGNED INTEGER,"
"is_removable BOOLEAN NOT NULL,"
"nb_audio UNSIGNED INTEGER NOT NULL DEFAULT 0,"
"nb_video UNSIGNED INTEGER NOT NULL DEFAULT 0,"
"FOREIGN KEY (parent_id) REFERENCES " + Folder::Table::Name +
"(id_folder) ON DELETE CASCADE,"
"FOREIGN KEY (device_id) REFERENCES " + Device::Table::Name +
"(id_device) ON DELETE CASCADE,"
"UNIQUE(path, device_id) ON CONFLICT FAIL"
")",
"CREATE INDEX IF NOT EXISTS folder_device_id ON " + Folder::Table::Name +
"(device_id)",
"CREATE INDEX IF NOT EXISTS folder_parent_id ON " + Folder::Table::Name +
"(parent_id)",
"CREATE TABLE IF NOT EXISTS ExcludedEntryFolder"
"("
"folder_id UNSIGNED INTEGER NOT NULL,"
"FOREIGN KEY (folder_id) REFERENCES " + Folder::Table::Name +
"(id_folder) ON DELETE CASCADE,"
"UNIQUE(folder_id) ON CONFLICT FAIL"
")",
"CREATE VIRTUAL TABLE IF NOT EXISTS " + Folder::Table::Name + "Fts USING FTS3"
"("
"name"
")",
......@@ -78,9 +78,8 @@ std::shared_ptr<fs::IDevice> FileSystemFactory::createDeviceFromMrl( const std::
std::string canonicalMrl;
try
{
canonicalMrl = utils::fs::toAbsolute(
utils::url::decode( utils::file::stripScheme( mrl ) ) );
canonicalMrl = scheme() + canonicalMrl;
auto canonicalPath = utils::fs::toAbsolute( utils::file::toLocalPath( mrl ) );
canonicalMrl = utils::file::toMrl( canonicalPath );
}
catch ( const std::system_error& ex )
{
......
......@@ -20,6 +20,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#pragma once
#include "medialibrary/filesystem/IFileSystemFactory.h"
#include "filesystem/network/Device.h"
#include "compat/ConditionVariable.h"
......
......@@ -82,9 +82,9 @@ CommonDevice::matchesMountpoint( const std::string& mrl ) const
for ( const auto& m : m_mountpoints )
{
if ( mrl.find( m ) == 0 )
return { true, m };
return std::make_tuple( true, m );
}
return { false, "" };
return std::make_tuple( false, "" );
}
std::string CommonDevice::relativeMrl( const std::string& absoluteMrl ) const
......
......@@ -20,6 +20,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#pragma once
#include "filesystem/common/CommonDirectory.h"
namespace medialibrary
......
......@@ -77,11 +77,19 @@ void Directory::read() const
if ( file[0] == '.' && strcasecmp( file.get(), ".nomedia" ) )
continue;
auto fullpath = m_path + file.get();
if ( ( f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
m_dirs.emplace_back( m_fsFactory.createDirectory(
m_mrl + utils::url::encode( file.get() ) ) );
else
m_files.emplace_back( std::make_shared<File>( fullpath ) );
try
{
if ( ( f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
m_dirs.emplace_back( m_fsFactory.createDirectory(
m_mrl + utils::url::encode( file.get() ) ) );
else
m_files.emplace_back( std::make_shared<File>( fullpath ) );
}
catch ( const std::system_error& ex )
{
LOG_WARN( "Failed to access a listed file/dir: ", ex.what() ,
". Ignoring this entry." );
}
} while ( FindNextFile( h, &f ) != 0 );
FindClose( h );
#else
......@@ -134,10 +142,18 @@ void Directory::read() const
auto file = charset::FromWide( dirInfo->FileName );
if ( file[0] == '.' && strcasecmp( file.get(), ".nomedia" ) )
continue;
if ( ( dirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
m_dirs.emplace_back( m_fsFactory.createDirectory( m_mrl + utils::url::encode( file.get() ) ) );
else
m_files.emplace_back( std::make_shared<File>( m_path + file.get()) );
try
{
if ( ( dirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
m_dirs.emplace_back( m_fsFactory.createDirectory( m_path + utils::url::encode( file.get() ) ) );
else
m_files.emplace_back( std::make_shared<File>( m_path + file.get()) );
}
catch ( const std::system_error& ex )
{
LOG_WARN( "Failed to access a listed file/dir: ", ex.what() ,
". Ignoring this entry." );
}
}
#endif
}
......
......@@ -102,6 +102,11 @@ Status MetadataAnalyzer::run( IItem& item )
std::tie( success, needRescan ) = refreshFile( item );
if ( success == false )
return Status::Fatal;
auto file = std::static_pointer_cast<File>( item.file() );
// Now that the refresh request was processed, we can update the last
// modification date in database
file->updateFsInfo( item.fileFs()->lastModificationDate(),
item.fileFs()->size() );
if ( needRescan == false )
return Status::Success;
}
......@@ -502,9 +507,7 @@ std::tuple<bool, bool> MetadataAnalyzer::refreshFile( IItem& item ) const
assert( item.media() == nullptr );
assert( item.file() != nullptr );
auto file = std::static_pointer_cast<File>( item.file() );
file->updateFsInfo( item.fileFs()->lastModificationDate(), item.fileFs()->size() );
auto file = item.file();
switch ( file->type() )
{
......@@ -517,11 +520,11 @@ std::tuple<bool, bool> MetadataAnalyzer::refreshFile( IItem& item ) const
{
LOG_WARN( "Failed to find playlist associated to modified playlist file ",
item.mrl() );
return { false, false };
return std::make_tuple( false, false );
}
LOG_INFO( "Reloading playlist ", playlist->name(), " on ", item.mrl() );
Playlist::destroy( m_ml, playlist->id() );
return { true, true };
playlist->clearContent();
return std::make_tuple( true, true );
}
case IFile::Type::Part:
case IFile::Type::Soundtrack:
......@@ -533,7 +536,7 @@ std::tuple<bool, bool> MetadataAnalyzer::refreshFile( IItem& item ) const
LOG_WARN( "Refreshing of file type ",
static_cast<std::underlying_type<IFile::Type>::type>( file->type() ),
" is unsupported" );
return { false, false };
return std::make_tuple( false, false );
}
std::tuple<bool, bool> MetadataAnalyzer::refreshMedia( IItem& item ) const
......
......@@ -159,6 +159,9 @@ void Parser::done( std::shared_ptr<Task> t, Status status )
m_opToDo -= m_services.size() - serviceIdx;
}
updateStats();
// We create a separate task for refresh, which doesn't count toward
// (mrl,parent_playlist) uniqueness. In order to allow for a subsequent
// refresh of the same file, we remove it once the refresh is complete.
if ( t->item().isRefresh() == true )
Task::destroy( m_ml, t->id() );
return;
......
......@@ -534,9 +534,9 @@ Task::createRefreshTask( MediaLibraryPtr ml, std::shared_ptr<File> file,
{
auto self = std::make_shared<Task>( ml, std::move( file ), std::move( fileFs ) );
const std::string req = "INSERT INTO " + Task::Table::Name +
"(mrl, file_id, is_refresh) VALUES(?, ?, ?)";
if ( insert( ml, self, req, self->m_item.mrl(), self->m_item.file()->id(),
true ) == false )
"(mrl, file_type, file_id, is_refresh) VALUES(?, ?, ?, ?)";
if ( insert( ml, self, req, self->m_item.mrl(), self->m_item.file()->type(),
self->m_item.file()->id(), true ) == false )
return nullptr;
return self;
}
......
......@@ -28,6 +28,7 @@
#include "utils/Url.h"
#include <stdexcept>
#include <algorithm>
#ifdef _WIN32
# define DIR_SEPARATOR "\\/"
......@@ -196,16 +197,19 @@ std::string toLocalPath( const std::string& mrl )
// Note that the initial '/' (after the 2 forward slashes from the scheme)
// is not a backslash, as it is not a path separator, so do not use
// DIR_SEPARATOR_CHAR here.
if ( path[0] == '/' )
if ( path[0] == '/' && isalpha( path[1] ) )
path.erase( 0, 1 );
std::replace( begin( path ), end( path ), '/', '\\' );
return utils::url::decode( path );
}
std::string toMrl( const std::string& path )
{
auto normalized = path;
std::replace( begin( normalized ), end( normalized ), '\\', '/' );
return std::string{ "file://" } +
( isalpha( path[0] ) ? "/" : "" ) +
utils::url::encode( path );
( isalpha( normalized[0] ) ? "/" : "" ) +
utils::url::encode( normalized );
}
#endif
......
......@@ -20,6 +20,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#pragma once
#include <string>
namespace medialibrary
......
......@@ -25,6 +25,8 @@
#endif
#include "Tester.h"
#include "utils/Filename.h"
#include "utils/Directory.h"
static std::string TestDirectory = SRC_DIR "/test/samples/";
static std::string ForcedTestDirectory;
......@@ -73,11 +75,10 @@ TEST_P( Tests, Parse )
{
// Quick and dirty check to ensure we're discovering something that exists
auto samplesDir = testDir + "samples/" + input[i].GetString();
struct stat s;
auto res = stat( samplesDir.c_str(), &s );
ASSERT_EQ( 0, res );
ASSERT_TRUE( utils::fs::isDirectory( samplesDir ) );
samplesDir = utils::fs::toAbsolute( samplesDir );
m_ml->discover( "file://" + samplesDir );
m_ml->discover( utils::file::toMrl( samplesDir ) );
}
ASSERT_TRUE( m_cb->waitForParsingComplete() );
......@@ -103,11 +104,10 @@ TEST_P( ResumeTests, Parse )
{
// Quick and dirty check to ensure we're discovering something that exists
auto samplesDir = testDir + "samples/" + input[i].GetString();
struct stat s;
auto res = stat( samplesDir.c_str(), &s );
ASSERT_EQ( 0, res );
ASSERT_TRUE( utils::fs::isDirectory( samplesDir ) );
samplesDir = utils::fs::toAbsolute( samplesDir );
m_ml->discover( "file://" + samplesDir );
m_ml->discover( utils::file::toMrl( samplesDir ) );
}
ASSERT_TRUE( m_cb->waitForDiscoveryComplete() );
auto testMl = static_cast<MediaLibraryResumeTest*>( m_ml.get() );
......@@ -136,11 +136,10 @@ TEST_P( ResumeTests, Rescan )
{
// Quick and dirty check to ensure we're discovering something that exists
auto samplesDir = testDir + "samples/" + input[i].GetString();
struct stat s;
auto res = stat( samplesDir.c_str(), &s );
ASSERT_EQ( 0, res );
ASSERT_TRUE( utils::fs::isDirectory( samplesDir ) );
samplesDir = utils::fs::toAbsolute( samplesDir );
m_ml->discover( "file://" + samplesDir );
m_ml->discover( utils::file::toMrl( samplesDir ) );
}
ASSERT_TRUE( m_cb->waitForDiscoveryComplete() );
auto testMl = static_cast<MediaLibraryResumeTest*>( m_ml.get() );
......
......@@ -364,27 +364,37 @@ TEST_F( Artists, SortMediaByAlbum )
for ( auto iAlbum = 0; iAlbum < 2; ++iAlbum )
{
auto f = std::static_pointer_cast<Media>( ml->addMedia( "alb" +
std::to_string( iAlbum ) + "_song" + std::to_string(iTrack) + ".mp3" ) );
std::to_string( 9 - iAlbum ) + "_song" +
std::to_string( 10 - iTrack) + ".mp3" ) );
artist->addMedia( *f );
albums[iAlbum]->addTrack( f, iTrack, 0, artist->id(), nullptr );
f->save();
}
}
QueryParameters params { SortingCriteria::Album, false };
auto tracks = artist->tracks( &params )->all();
ASSERT_EQ( 4u, tracks.size() );
ASSERT_EQ( "alb0_song1.mp3", tracks[0]->title() );
ASSERT_EQ( "alb0_song2.mp3", tracks[1]->title() );
ASSERT_EQ( "alb1_song1.mp3", tracks[2]->title() );
ASSERT_EQ( "alb1_song2.mp3", tracks[3]->title() );
ASSERT_EQ( "alb9_song9.mp3", tracks[0]->title() );
ASSERT_EQ( 1u, tracks[0]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb9_song8.mp3", tracks[1]->title() );
ASSERT_EQ( 2u, tracks[1]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb8_song9.mp3", tracks[2]->title() );
ASSERT_EQ( 1u, tracks[2]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb8_song8.mp3", tracks[3]->title() );
ASSERT_EQ( 2u, tracks[3]->albumTrack()->trackNumber() );
params.desc = true;
tracks = artist->tracks( &params )->all();
ASSERT_EQ( 4u, tracks.size() );
ASSERT_EQ( "alb1_song1.mp3", tracks[0]->title() );
ASSERT_EQ( "alb1_song2.mp3", tracks[1]->title() );
ASSERT_EQ( "alb0_song1.mp3", tracks[2]->title() );
ASSERT_EQ( "alb0_song2.mp3", tracks[3]->title() );
ASSERT_EQ( "alb8_song9.mp3", tracks[0]->title() );
ASSERT_EQ( 1u, tracks[0]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb8_song8.mp3", tracks[1]->title() );
ASSERT_EQ( 2u, tracks[1]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb9_song9.mp3", tracks[2]->title() );
ASSERT_EQ( 1u, tracks[2]->albumTrack()->trackNumber() );
ASSERT_EQ( "alb9_song8.mp3", tracks[3]->title() );
ASSERT_EQ( 2u, tracks[3]->albumTrack()->trackNumber() );
}
TEST_F( Artists, SortAlbum )
......
......@@ -302,9 +302,11 @@ TEST_F( FoldersNoDiscover, BanTwice )
TEST_F( FoldersNoDiscover, BanNonExistant )
{
ml->banFolder( "foo/bar/otters" );
// Unhandled scheme
ml->banFolder( "foo://bar/otters" );
cbMock->waitBanFolder();
ml->banFolder( "/foo/bar/otters" );
// valid scheme, unknown root folder
ml->banFolder( "file:///foo/bar/otters" );
cbMock->waitBanFolder();
// Ban with an existing base
ml->banFolder( mock::FileSystemFactory::Root + "grouik/" );
......@@ -622,11 +624,38 @@ TEST_F( FoldersNoDiscover, ListWithMedia )
ASSERT_EQ( 1u, folders[0]->media( IMedia::Type::Unknown, nullptr )->count() );
// List folders with audio media only
folders = ml->folders( IMedia::Type::Audio, &params )->all();
auto query = ml->folders( IMedia::Type::Audio, &params );
folders = query->all();
ASSERT_EQ( 1u, query->count() );
ASSERT_EQ( 1u, folders.size() );
ASSERT_EQ( folders[0]->mrl(), mock::FileSystemFactory::Root );
// Check the fetching of those media
// For each query, test with count() and all()
auto mediaQuery = folders[0]->media( IMedia::Type::Audio, &params );
ASSERT_EQ( 1u, mediaQuery->count() );
ASSERT_EQ( 1u, mediaQuery->all().size() );
// Try again with a different sort, which triggers a more complex request
params.sort = SortingCriteria::Artist;
mediaQuery = folders[0]->media( IMedia::Type::Audio, &params );
ASSERT_EQ( 1u, mediaQuery->count() );
ASSERT_EQ( 1u, mediaQuery->all().size() );
// But check that we still have all the media when we filter with 'Unknown'
ASSERT_EQ( 2u, folders[0]->media( IMedia::Type::Unknown, nullptr )->count() );
mediaQuery = folders[0]->media( IMedia::Type::Unknown, nullptr );
ASSERT_EQ( 2u, mediaQuery->count() );
ASSERT_EQ( 2u, mediaQuery->all().size() );
// Now try sorting by last modified date, which was causing a crash
params.sort = SortingCriteria::LastModificationDate;
mediaQuery = folders[0]->media( IMedia::Type::Unknown, &params );
ASSERT_EQ( 2u, mediaQuery->count() );
ASSERT_EQ( 2u, mediaQuery->all().size() );
mediaQuery = folders[0]->media( IMedia::Type::Audio, &params );
ASSERT_EQ( 1u, mediaQuery->count() );
ASSERT_EQ( 1u, mediaQuery->all().size() );
}
TEST_F( FoldersNoDiscover, ListSubFolders )
......
......@@ -122,10 +122,21 @@ TEST( FsUtils, toLocalPath )
ASSERT_EQ( "/tést/ßóíú/file", utils::file::toLocalPath( "file:///t%C3%A9st/%C3%9F%C3%B3%C3%AD%C3%BA/file" ) );
ASSERT_EQ( "/&/#/~", utils::file::toLocalPath( "file:///%26/%23/%7E" ) );
#else
ASSERT_EQ( "a/b/c/movie.avi", utils::file::toLocalPath( "file:///a/b/c/movie.avi" ) );
ASSERT_EQ( "x/yea /sp ace", utils::file::toLocalPath( "file:///x/yea%20/sp%20ace" ) );
ASSERT_EQ( "d/tést/ßóíú/file", utils::file::toLocalPath( "file:///d/t%C3%A9st/%C3%9F%C3%B3%C3%AD%C3%BA/file" ) );
ASSERT_EQ( "c/&/#/~", utils::file::toLocalPath( "file:///c/%26/%23/%7E" ) );
ASSERT_EQ( "a\\b\\c\\movie.avi", utils::file::toLocalPath( "file:///a/b/c/movie.avi" ) );
ASSERT_EQ( "x\\yea \\sp ace", utils::file::toLocalPath( "file:///x/yea%20/sp%20ace" ) );
ASSERT_EQ( "d\\tést\\ßóíú\\file", utils::file::toLocalPath( "file:///d/t%C3%A9st/%C3%9F%C3%B3%C3%AD%C3%BA/file" ) );
ASSERT_EQ( "c\\&\\#\\~", utils::file::toLocalPath( "file:///c/%26/%23/%7E" ) );
#endif
}
TEST( FsUtils, toMrl )
{
#ifndef _WIN32
ASSERT_EQ( "/yea /sp ace", utils::file::toLocalPath( "file:///yea%20/sp%20ace" ) );
ASSERT_EQ( "/c/foo/bar.mkv", utils::file::toLocalPath( "file:///c/foo/bar.mkv" ) );
#else
ASSERT_EQ( "c\\foo\\bar.mkv", utils::file::toLocalPath( "file:///c/foo/bar.mkv" ) );
ASSERT_EQ( "x\\yea \\sp ace", utils::file::toLocalPath( "file:///x/yea%20/sp%20ace" ) );
#endif
}
......
......@@ -233,3 +233,11 @@ TEST_F( DbModel, Upgrade13to14 )
CheckNbTriggers( 35 );
}
TEST_F( DbModel, Upgrade14to15 )
{
LoadFakeDB( SRC_DIR "/test/unittest/db_v14.sql" );
auto res = ml->initialize( "test.db", "/tmp", cbMock.get() );
ASSERT_EQ( InitializeResult::Success, res );
CheckNbTriggers( 35 );
}
......@@ -425,3 +425,21 @@ TEST_F( Playlists, RemoveMedia )
ASSERT_EQ( 1u, media.size() );
ASSERT_EQ( m3->id(), media[0]->id() );
}
TEST_F( Playlists, ClearContent )
{
auto m1 = ml->addMedia( "seaotter.mkv" );
auto m2 = ml->addMedia( "fluffyfurball.mp4" );
auto pl2 = ml->createPlaylist( "playlist 2" );
pl->append( *m1 );
pl2->append( *m2 );
ASSERT_EQ( 1u, pl->media()->count() );
ASSERT_EQ( 1u, pl2->media()->count() );
pl->clearContent();
ASSERT_EQ( 0u, pl->media()->count() );
ASSERT_EQ( 1u, pl2->media()->count() );
}
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS `VideoTrack` (`id_track` INTEGER PRIMARY KEY AUTOINCREMENT,`codec` TEXT, `width` UNSIGNED INTEGER, `height` UNSIGNED INTEGER, `fps_num` UNSIGNED INTEGER, `fps_den` UNSIGNED INTEGER, `bitrate` UNSIGNED INTEGER, `sar_num` UNSIGNED INTEGER, `sar_den` UNSIGNED INTEGER, `media_id` UNSIGNED INT, `language` TEXT, `description` TEXT, FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Thumbnail` (`id_thumbnail` INTEGER PRIMARY KEY AUTOINCREMENT, `mrl` TEXT, `origin` INTEGER NOT NULL, `is_generated` BOOLEAN NOT NULL);
CREATE TABLE IF NOT EXISTS `Task` (`id_task` INTEGER PRIMARY KEY AUTOINCREMENT, `step` INTEGER NOT NULL DEFAULT 0, `retry_count` INTEGER NOT NULL DEFAULT 0, `mrl` TEXT, `file_type` INTEGER NOT NULL, `file_id` UNSIGNED INTEGER, `parent_folder_id` UNSIGNED INTEGER, `parent_playlist_id` INTEGER, `parent_playlist_index` UNSIGNED INTEGER, `is_refresh` BOOLEAN NOT NULL DEFAULT 0, UNIQUE(`mrl`,`parent_playlist_id`,`is_refresh`), FOREIGN KEY(`parent_playlist_id`) REFERENCES `Playlist`(`id_playlist`) ON DELETE CASCADE, FOREIGN KEY(`file_id`) REFERENCES `File`(`id_file`) ON DELETE CASCADE, FOREIGN KEY(`parent_folder_id`) REFERENCES `Folder`(`id_folder`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `SubtitleTrack` (`id_track` INTEGER PRIMARY KEY AUTOINCREMENT, `codec` TEXT, `language` TEXT, `description` TEXT, `encoding` TEXT, `media_id` UNSIGNED INT, FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE VIRTUAL TABLE ShowFts USING FTS3(title);
CREATE TABLE IF NOT EXISTS `ShowEpisode` (`id_episode` INTEGER PRIMARY KEY AUTOINCREMENT, `media_id` UNSIGNED INTEGER NOT NULL, `episode_number` UNSIGNED INT, `season_number` UNSIGNED INT, `episode_summary` TEXT, `tvdb_id` TEXT, `show_id` UNSIGNED INT, FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE, FOREIGN KEY(`show_id`) REFERENCES `Show`(`id_show`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Show` (`id_show` INTEGER PRIMARY KEY AUTOINCREMENT,`title` TEXT,`release_date` UNSIGNED INTEGER,`short_summary` TEXT,`artwork_mrl` TEXT,`tvdb_id` TEXT);
CREATE TABLE IF NOT EXISTS `Settings` (`db_model_version` UNSIGNED INTEGER NOT NULL);
INSERT INTO `Settings` (db_model_version) VALUES (14);
CREATE TABLE IF NOT EXISTS `PlaylistMediaRelation` (`media_id` INTEGER,`mrl` STRING,`playlist_id` INTEGER,`position` INTEGER,FOREIGN KEY(`playlist_id`) REFERENCES `Playlist`(`id_playlist`) ON DELETE CASCADE,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE SET NULL);
CREATE VIRTUAL TABLE PlaylistFts USING FTS3(name);
CREATE TABLE IF NOT EXISTS `Playlist` (`id_playlist` INTEGER PRIMARY KEY AUTOINCREMENT,`name` TEXT COLLATE NOCASE,`file_id` UNSIGNED INT DEFAULT NULL,`creation_date` UNSIGNED INT NOT NULL,`artwork_mrl` TEXT,FOREIGN KEY(`file_id`) REFERENCES `File`(`id_file`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Movie` (`id_movie` INTEGER PRIMARY KEY AUTOINCREMENT,`media_id` UNSIGNED INTEGER NOT NULL,`summary` TEXT,`imdb_id` TEXT,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Metadata` (`id_media` INTEGER,`entity_type` INTEGER,`type` INTEGER,`value` TEXT,PRIMARY KEY(`id_media`,`entity_type`,`type`));
CREATE VIRTUAL TABLE MediaFts USING FTS3(title,labels);
CREATE TABLE IF NOT EXISTS `MediaArtistRelation` (`media_id` INTEGER NOT NULL,`artist_id` INTEGER,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE,PRIMARY KEY(`media_id`,`artist_id`),FOREIGN KEY(`artist_id`) REFERENCES `Artist`(`id_artist`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Media` (`id_media` INTEGER PRIMARY KEY AUTOINCREMENT,`type` INTEGER,`subtype` INTEGER NOT NULL DEFAULT 0,`duration` INTEGER DEFAULT -1,`play_count` UNSIGNED INTEGER,`last_played_date` UNSIGNED INTEGER, `real_last_played_date` UNSIGNED INTEGER,`insertion_date` UNSIGNED INTEGER,`release_date` UNSIGNED INTEGER,`thumbnail_id` INTEGER,`title` TEXT COLLATE NOCASE,`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,`folder_id` UNSIGNED INTEGER,FOREIGN KEY(`thumbnail_id`) REFERENCES `Thumbnail`(`id_thumbnail`),FOREIGN KEY(`folder_id`) REFERENCES `Folder`(`id_folder`));
CREATE TABLE IF NOT EXISTS `LabelFileRelation` (`label_id` INTEGER,`media_id` INTEGER,FOREIGN KEY(`label_id`) REFERENCES `Label`(`id_label`) ON DELETE CASCADE,PRIMARY KEY(`label_id`,`media_id`),FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Label` (`id_label` INTEGER PRIMARY KEY AUTOINCREMENT,`name` TEXT UNIQUE);
CREATE VIRTUAL TABLE GenreFts USING FTS3(name);
CREATE TABLE IF NOT EXISTS `Genre` (`id_genre` INTEGER PRIMARY KEY AUTOINCREMENT,`name` TEXT UNIQUE COLLATE NOCASE,`nb_tracks` INTEGER NOT NULL DEFAULT 0);
CREATE VIRTUAL TABLE FolderFts USING FTS3(name);
CREATE TABLE IF NOT EXISTS `Folder` (`id_folder` INTEGER PRIMARY KEY AUTOINCREMENT,`path` TEXT,`name` TEXT,`parent_id` UNSIGNED INTEGER,`is_banned` BOOLEAN NOT NULL DEFAULT 0, `device_id` UNSIGNED INTEGER,`is_removable` BOOLEAN NOT NULL,`nb_audio` UNSIGNED INTEGER NOT NULL DEFAULT 0,`nb_video` UNSIGNED INTEGER NOT NULL DEFAULT 0,FOREIGN KEY(`device_id`) REFERENCES `Device`(`id_device`) ON DELETE CASCADE,FOREIGN KEY(`parent_id`) REFERENCES `Folder`(`id_folder`) ON DELETE CASCADE,UNIQUE(`path`,`device_id`));
CREATE TABLE IF NOT EXISTS `File` (`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_removable` BOOLEAN NOT NULL,`is_external` BOOLEAN NOT NULL,`is_network` BOOLEAN NOT NULL,FOREIGN KEY(`folder_id`) REFERENCES `Folder`(`id_folder`) ON DELETE CASCADE,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE,UNIQUE(`mrl`,`folder_id`),FOREIGN KEY(`playlist_id`) REFERENCES `Playlist`(`id_playlist`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `ExcludedEntryFolder` (`folder_id` UNSIGNED INTEGER NOT NULL UNIQUE,FOREIGN KEY(`folder_id`) REFERENCES `Folder`(`id_folder`) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS `Device` (`id_device` INTEGER PRIMARY KEY AUTOINCREMENT,`uuid` TEXT UNIQUE COLLATE NOCASE, `scheme` TEXT,`is_removable` BOOLEAN,`is_present` BOOLEAN,`last_seen` UNSIGNED INTEGER);
CREATE TABLE IF NOT EXISTS `AudioTrack` (`id_track` INTEGER PRIMARY KEY AUTOINCREMENT,`codec` TEXT,`bitrate` UNSIGNED INTEGER,`samplerate` UNSIGNED INTEGER,`nb_channels` UNSIGNED INTEGER,`language` TEXT,`description` TEXT,`media_id` UNSIGNED INT,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE VIRTUAL TABLE ArtistFts USING FTS3(name);
CREATE TABLE IF NOT EXISTS `Artist` (`id_artist` INTEGER PRIMARY KEY AUTOINCREMENT,`name` TEXT UNIQUE COLLATE NOCASE,`shortbio` TEXT,`thumbnail_id` TEXT,`nb_albums` UNSIGNED INT DEFAULT 0,`nb_tracks` UNSIGNED INT DEFAULT 0,`mb_id` TEXT,`is_present` UNSIGNED INTEGER NOT NULL DEFAULT 0,FOREIGN KEY(`thumbnail_id`) REFERENCES `Thumbnail`(`id_thumbnail`));
CREATE TABLE IF NOT EXISTS `AlbumTrack` (`id_track` INTEGER PRIMARY KEY AUTOINCREMENT,`media_id` INTEGER UNIQUE,`duration` INTEGER NOT NULL,`artist_id` UNSIGNED INTEGER,`genre_id` INTEGER,`track_number` UNSIGNED INTEGER,`album_id` UNSIGNED INTEGER NOT NULL,`disc_number` UNSIGNED INTEGER,FOREIGN KEY(`genre_id`) REFERENCES `Genre`(`id_genre`),FOREIGN KEY(`artist_id`) REFERENCES `Artist`(`id_artist`) ON DELETE CASCADE, FOREIGN KEY(`album_id`) REFERENCES `Album`(`id_album`) ON DELETE CASCADE,FOREIGN KEY(`media_id`) REFERENCES `Media`(`id_media`) ON DELETE CASCADE);
CREATE VIRTUAL TABLE AlbumFts USING FTS3(title,artist);
CREATE TABLE IF NOT EXISTS `Album` (`id_album` INTEGER PRIMARY KEY AUTOINCREMENT,`title` TEXT COLLATE NOCASE,`artist_id` UNSIGNED INTEGER,`release_year` UNSIGNED INTEGER,`short_summary` TEXT,`thumbnail_id` UNSIGNED INT,`nb_tracks` UNSIGNED INTEGER DEFAULT 0,`duration` UNSIGNED INTEGER NOT NULL DEFAULT 0,`nb_discs` UNSIGNED INTEGER NOT NULL DEFAULT 1,`is_present` UNSIGNED INTEGER NOT NULL DEFAULT 0,FOREIGN KEY(`thumbnail_id`) REFERENCES `Thumbnail`(`id_thumbnail`), FOREIGN KEY(`artist_id`) REFERENCES `Artist`(`id_artist`) ON DELETE CASCADE);
CREATE INDEX IF NOT EXISTS `video_track_media_idx` ON `VideoTrack` (`media_id`);
CREATE INDEX IF NOT EXISTS `subtitle_track_media_idx` ON `SubtitleTrack` (`media_id`);
CREATE INDEX IF NOT EXISTS `show_episode_media_show_idx` ON `ShowEpisode` (`media_id`,`show_id`);
CREATE INDEX IF NOT EXISTS `playlist_media_pl_id_index` ON `PlaylistMediaRelation` (`media_id`,`playlist_id`);
CREATE INDEX IF NOT EXISTS `parent_folder_id_idx` ON `Folder` (`parent_id`);
CREATE INDEX IF NOT EXISTS `movie_media_idx` ON `Movie` (`media_id`);
CREATE INDEX IF NOT EXISTS `media_types_idx` ON `Media` (`type`,`subtype`);
CREATE INDEX IF NOT EXISTS `index_media_presence` ON `Media` (`is_present`);
CREATE INDEX IF NOT EXISTS `index_last_played_date` ON `Media` (`last_played_date` DESC);
CREATE INDEX IF NOT EXISTS `folder_parent_id` ON `Folder` (`parent_id`);
CREATE INDEX IF NOT EXISTS `folder_device_id_idx` ON `Folder` (`device_id`);
CREATE INDEX IF NOT EXISTS `folder_device_id` ON `Folder` (`device_id`);
CREATE INDEX IF NOT EXISTS `file_media_id_index` ON `File` (`media_id`);
CREATE INDEX IF NOT EXISTS `file_folder_id_index` ON `File` (`folder_id`);
CREATE INDEX IF NOT EXISTS `audio_track_media_idx` ON `AudioTrack` (`media_id`);
CREATE INDEX IF NOT EXISTS `album_track_album_genre_artist_ids` ON `AlbumTrack` (`album_id`,`genre_id`,`artist_id`);
CREATE INDEX IF NOT EXISTS `album_media_artist_genre_album_idx` ON `AlbumTrack` (`media_id`,`artist_id`,`genre_id`,`album_id`);
CREATE INDEX IF NOT EXISTS `album_artist_id_idx` ON `Album` (`artist_id`);
CREATE TRIGGER update_playlist_order_on_insert AFTER INSERT ON PlaylistMediaRelation WHEN new.position IS NOT NULL BEGIN UPDATE PlaylistMediaRelation SET position = position + 1 WHERE playlist_id = new.playlist_id AND position = new.position AND media_id != new.media_id; END;
CREATE TRIGGER update_playlist_order AFTER UPDATE OF position ON PlaylistMediaRelation BEGIN UPDATE PlaylistMediaRelation SET position = position + 1 WHERE playlist_id = new.playlist_id AND position = new.position AND media_id != new.media_id; END;
CREATE TRIGGER update_playlist_fts AFTER UPDATE OF name ON Playlist BEGIN UPDATE PlaylistFts SET name = new.name WHERE rowid = new.id_playlist; END;
CREATE TRIGGER update_media_title_fts AFTER UPDATE OF title ON Media BEGIN UPDATE MediaFts SET title = new.title WHERE rowid = new.id_media; END;
CREATE TRIGGER update_genre_on_track_deleted AFTER DELETE ON AlbumTrack WHEN old.genre_id IS NOT NULL BEGIN UPDATE Genre SET nb_tracks = nb_tracks - 1 WHERE id_genre = old.genre_id; DELETE FROM Genre WHERE nb_tracks = 0; END;
CREATE TRIGGER update_genre_on_new_track AFTER INSERT ON AlbumTrack WHEN new.genre_id IS NOT NULL BEGIN UPDATE Genre SET nb_tracks = nb_tracks + 1 WHERE id_genre = new.genre_id; END;
CREATE TRIGGER update_folder_nb_media_on_update AFTER UPDATE ON Media WHEN new.folder_id IS NOT NULL AND old.type != new.type BEGIN UPDATE Folder SET nb_audio = nb_audio + (CASE old.type WHEN 2 THEN -1 ELSE 0 END)+(CASE new.type WHEN 2 THEN 1 ELSE 0 END),nb_video = nb_video + (CASE old.type WHEN 1 THEN -1 ELSE 0 END)+(CASE new.type WHEN 1 THEN 1 ELSE 0 END)WHERE id_folder = new.folder_id;END;
CREATE TRIGGER update_folder_nb_media_on_insert AFTER INSERT ON Media WHEN new.folder_id IS NOT NULL BEGIN UPDATE Folder SET nb_audio = nb_audio + (CASE new.type WHEN 2 THEN 1 ELSE 0 END),nb_video = nb_video + (CASE new.type WHEN 1 THEN 1 ELSE 0 END) WHERE id_folder = new.folder_id;END;
CREATE TRIGGER update_folder_nb_media_on_delete AFTER DELETE ON Media WHEN old.folder_id IS NOT NULL BEGIN UPDATE Folder SET nb_audio = nb_audio + (CASE old.type WHEN 2 THEN -1 ELSE 0 END),nb_video = nb_video + (CASE old.type WHEN 1 THEN -1 ELSE 0 END) WHERE id_folder = old.folder_id;END;
CREATE TRIGGER is_media_device_present AFTER UPDATE OF is_present ON Device BEGIN UPDATE Media SET is_present=new.is_present WHERE device_id=new.id_device;END;
CREATE TRIGGER is_album_present AFTER UPDATE OF is_present ON Media WHEN new.subtype = 3 BEGIN UPDATE Album SET is_present=is_present + (CASE new.is_present WHEN 0 THEN -1 ELSE 1 END)WHERE id_album = (SELECT album_id FROM AlbumTrack WHERE media_id = new.id_media); END;
CREATE TRIGGER insert_show_fts AFTER INSERT ON Show BEGIN INSERT INTO ShowFts(rowid,title) VALUES(new.id_show, new.title); END;
CREATE TRIGGER insert_playlist_fts AFTER INSERT ON Playlist BEGIN INSERT INTO PlaylistFts(rowid, name) VALUES(new.id_playlist, new.name); END;
CREATE TRIGGER insert_media_fts AFTER INSERT ON Media BEGIN INSERT INTO MediaFts(rowid,title,labels) VALUES(new.id_media, new.title, ''); END;
CREATE TRIGGER insert_genre_fts AFTER INSERT ON Genre BEGIN INSERT INTO GenreFts(rowid,name) VALUES(new.id_genre, new.name); END;
CREATE TRIGGER insert_folder_fts AFTER INSERT ON Folder BEGIN INSERT INTO FolderFts(rowid,name) VALUES(new.id_folder,new.name);END;
CREATE TRIGGER insert_artist_fts AFTER INSERT ON Artist WHEN new.name IS NOT NULL BEGIN INSERT INTO ArtistFts(rowid,name) VALUES(new.id_artist, new.name); END;
CREATE TRIGGER insert_album_fts AFTER INSERT ON Album WHEN new.title IS NOT NULL BEGIN INSERT INTO AlbumFts(rowid, title) VALUES(new.id_album, new.title); END;
CREATE TRIGGER increment_media_nb_playlist AFTER INSERT ON PlaylistMediaRelation BEGIN UPDATE Media SET nb_playlists = nb_playlists + 1 WHERE id_media = new.media_id; END;
CREATE TRIGGER has_tracks_present AFTER UPDATE OF is_present ON Media WHEN new.subtype = 3 BEGIN UPDATE Artist SET is_present=is_present + (CASE new.is_present WHEN 0 THEN -1 ELSE 1 END)WHERE id_artist = (SELECT artist_id FROM AlbumTrack WHERE media_id = new.id_media ); END;
CREATE TRIGGER has_track_remaining AFTER DELETE ON AlbumTrack WHEN old.artist_id != 1 AND old.artist_id != 2 BEGIN UPDATE Artist SET nb_tracks = nb_tracks - 1, is_present = is_present - 1 WHERE id_artist = old.artist_id; DELETE FROM Artist WHERE id_artist = old.artist_id AND nb_albums = 0 AND nb_tracks = 0; END;
CREATE TRIGGER has_album_remaining AFTER DELETE ON Album WHEN old.artist_id != 1 AND old.artist_id != 2 BEGIN UPDATE Artist SET nb_albums = nb_albums - 1 WHERE id_artist = old.artist_id; DELETE FROM Artist WHERE id_artist = old.artist_id AND nb_albums = 0 AND nb_tracks = 0; END;
CREATE TRIGGER delete_show_fts BEFORE DELETE ON Show BEGIN DELETE FROM ShowFts WHERE rowid = old.id_show; END;
CREATE TRIGGER delete_playlist_fts BEFORE DELETE ON Playlist BEGIN DELETE FROM PlaylistFts WHERE rowid = old.id_playlist; END;
CREATE TRIGGER delete_media_fts BEFORE DELETE ON Media BEGIN DELETE FROM MediaFts WHERE rowid = old.id_media; END;
CREATE TRIGGER delete_label_fts BEFORE DELETE ON Label BEGIN UPDATE MediaFts SET labels = TRIM(REPLACE(labels, old.name, '')) WHERE labels MATCH old.name; END;
CREATE TRIGGER delete_genre_fts BEFORE DELETE ON Genre BEGIN DELETE FROM GenreFts WHERE rowid = old.id_genre; END;
CREATE TRIGGER delete_folder_fts BEFORE DELETE ON Folder BEGIN DELETE FROM FolderFts WHERE rowid = old.id_folder;END;
CREATE TRIGGER delete_artist_fts BEFORE DELETE ON Artist WHEN old.name IS NOT NULL BEGIN DELETE FROM ArtistFts WHERE rowid=old.id_artist; END;
CREATE TRIGGER delete_album_track AFTER DELETE ON AlbumTrack BEGIN UPDATE Album SET nb_tracks = nb_tracks - 1, is_present = is_present - 1, duration = duration - old.duration WHERE id_album = old.album_id; DELETE FROM Album WHERE id_album=old.album_id AND nb_tracks = 0; END;
CREATE TRIGGER delete_album_fts BEFORE DELETE ON Album WHEN old.title IS NOT NULL BEGIN DELETE FROM AlbumFts WHERE rowid = old.id_album; END;
CREATE TRIGGER decrement_media_nb_playlist AFTER DELETE ON PlaylistMediaRelation BEGIN UPDATE Media SET nb_playlists = nb_playlists - 1 WHERE id_media = old.media_id; END;
CREATE TRIGGER cascade_file_deletion AFTER DELETE ON File BEGIN DELETE FROM Media WHERE (SELECT COUNT(id_file) FROM File WHERE media_id=old.media_id) = 0 AND id_media=old.media_id; END;
CREATE TRIGGER append_new_playlist_record AFTER INSERT ON PlaylistMediaRelation WHEN new.position IS NULL BEGIN UPDATE PlaylistMediaRelation SET position = (SELECT COUNT(media_id) FROM PlaylistMediaRelation WHERE playlist_id = new.playlist_id) WHERE playlist_id=new.playlist_id AND media_id = new.media_id; END;
CREATE TRIGGER add_album_track AFTER INSERT ON AlbumTrack BEGIN UPDATE Album SET duration = duration + new.duration, nb_tracks = nb_tracks + 1, is_present = is_present + 1 WHERE id_album = new.album_id; END;
INSERT INTO `Device` (id_device,uuid,scheme,is_removable,is_present,last_seen) VALUES (1,NULL,NULL,NULL,NULL,NULL);
INSERT INTO `Folder` (id_folder,path,name,parent_id,is_banned,device_id,is_removable,nb_audio,nb_video) VALUES (1,'foo/','TestFolder',NULL,0,1,0,0,0);
COMMIT;