/***************************************************************************** * Media Library ***************************************************************************** * Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs * * Authors: Hugo Beauzée-Luyssen * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include "Album.h" #include "AlbumTrack.h" #include "Artist.h" #include "AudioTrack.h" #include "discoverer/DiscovererWorker.h" #include "discoverer/probe/CrawlerProbe.h" #include "utils/ModificationsNotifier.h" #include "Device.h" #include "File.h" #include "Folder.h" #include "Genre.h" #include "History.h" #include "Media.h" #include "MediaLibrary.h" #include "Label.h" #include "logging/Logger.h" #include "Movie.h" #include "parser/Parser.h" #include "Playlist.h" #include "Show.h" #include "ShowEpisode.h" #include "database/SqliteTools.h" #include "database/SqliteConnection.h" #include "utils/Filename.h" #include "VideoTrack.h" // Discoverers: #include "discoverer/FsDiscoverer.h" // Metadata services: #include "metadata_services/vlc/VLCMetadataService.h" #include "metadata_services/vlc/VLCThumbnailer.h" #include "metadata_services/MetadataParser.h" // FileSystem #include "factory/DeviceListerFactory.h" #include "factory/FileSystemFactory.h" #include "factory/NetworkFileSystemFactory.h" #include "filesystem/IDevice.h" namespace medialibrary { const char* const MediaLibrary::supportedExtensions[] = { "3gp", "a52", "aac", "ac3", "aif", "aifc", "aiff", "alac", "amr", "amv", "aob", "ape", "asf", "asx", "avi", "b4s", "conf", /*"cue",*/ "divx", "dts", "dv", "flac", "flv", "gxf", "ifo", "iso", "it", "itml", "m1v", "m2t", "m2ts", "m2v", "m3u", "m3u8", "m4a", "m4b", "m4p", "m4v", "mid", "mka", "mkv", "mlp", "mod", "mov", "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpeg1", "mpeg2", "mpeg4", "mpg", "mts", "mxf", "nsv", "nuv", "oga", "ogg", "ogm", "ogv", "ogx", "oma", "opus", "pls", "ps", "qtl", "ram", "rec", "rm", "rmi", "rmvb", "s3m", "sdp", "spx", "tod", "trp", "ts", "tta", "vlc", "vob", "voc", "vqf", "vro", "w64", "wav", "wax", "webm", "wma", "wmv", "wmx", "wpl", "wv", "wvx", "xa", "xm", "xspf" }; const size_t MediaLibrary::NbSupportedExtensions = sizeof(supportedExtensions) / sizeof(supportedExtensions[0]); MediaLibrary::MediaLibrary() : m_callback( nullptr ) , m_verbosity( LogLevel::Error ) , m_settings( this ) , m_initialized( false ) , m_discovererIdle( true ) , m_parserIdle( true ) { Log::setLogLevel( m_verbosity ); } MediaLibrary::~MediaLibrary() { // Explicitely stop the discoverer, to avoid it writting while tearing down. if ( m_discovererWorker != nullptr ) m_discovererWorker->stop(); if ( m_parser != nullptr ) m_parser->stop(); clearCache(); } void MediaLibrary::clearCache() { Media::clear(); Folder::clear(); Label::clear(); Album::clear(); AlbumTrack::clear(); Show::clear(); ShowEpisode::clear(); Movie::clear(); VideoTrack::clear(); AudioTrack::clear(); Artist::clear(); Device::clear(); File::clear(); Playlist::clear(); History::clear(); Genre::clear(); } void MediaLibrary::createAllTables() { // We need to create the tables in order of triggers creation // Device is the "root of all evil". When a device is modified, // we will trigger an update on folder, which will trigger // an update on files, and so on. Device::createTable( m_dbConnection.get() ); Folder::createTable( m_dbConnection.get() ); Media::createTable( m_dbConnection.get() ); File::createTable( m_dbConnection.get() ); Label::createTable( m_dbConnection.get() ); Playlist::createTable( m_dbConnection.get() ); Genre::createTable( m_dbConnection.get() ); Album::createTable( m_dbConnection.get() ); AlbumTrack::createTable( m_dbConnection.get() ); Show::createTable( m_dbConnection.get() ); ShowEpisode::createTable( m_dbConnection.get() ); Movie::createTable( m_dbConnection.get() ); VideoTrack::createTable( m_dbConnection.get() ); AudioTrack::createTable( m_dbConnection.get() ); Artist::createTable( m_dbConnection.get() ); Artist::createDefaultArtists( m_dbConnection.get() ); History::createTable( m_dbConnection.get() ); Settings::createTable( m_dbConnection.get() ); parser::Task::createTable( m_dbConnection.get() ); } void MediaLibrary::createAllTriggers() { auto dbModelVersion = m_settings.dbModelVersion(); Folder::createTriggers( m_dbConnection.get() ); Album::createTriggers( m_dbConnection.get() ); AlbumTrack::createTriggers( m_dbConnection.get() ); Artist::createTriggers( m_dbConnection.get(), dbModelVersion ); Media::createTriggers( m_dbConnection.get() ); File::createTriggers( m_dbConnection.get() ); Genre::createTriggers( m_dbConnection.get() ); Playlist::createTriggers( m_dbConnection.get() ); History::createTriggers( m_dbConnection.get() ); Label::createTriggers( m_dbConnection.get() ); } template static void propagateDeletionToCache( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; T::removeFromCache( rowId ); } void MediaLibrary::registerEntityHooks() { if ( m_modificationNotifier == nullptr ) return; m_dbConnection->registerUpdateHook( policy::MediaTable::Name, [this]( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; Media::removeFromCache( rowId ); m_modificationNotifier->notifyMediaRemoval( rowId ); }); m_dbConnection->registerUpdateHook( policy::ArtistTable::Name, [this]( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; Artist::removeFromCache( rowId ); m_modificationNotifier->notifyArtistRemoval( rowId ); }); m_dbConnection->registerUpdateHook( policy::AlbumTable::Name, [this]( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; Album::removeFromCache( rowId ); m_modificationNotifier->notifyAlbumRemoval( rowId ); }); m_dbConnection->registerUpdateHook( policy::AlbumTrackTable::Name, [this]( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; AlbumTrack::removeFromCache( rowId ); m_modificationNotifier->notifyAlbumTrackRemoval( rowId ); }); m_dbConnection->registerUpdateHook( policy::PlaylistTable::Name, [this]( sqlite::Connection::HookReason reason, int64_t rowId ) { if ( reason != sqlite::Connection::HookReason::Delete ) return; Playlist::removeFromCache( rowId ); m_modificationNotifier->notifyPlaylistRemoval( rowId ); }); m_dbConnection->registerUpdateHook( policy::DeviceTable::Name, &propagateDeletionToCache ); m_dbConnection->registerUpdateHook( policy::FileTable::Name, &propagateDeletionToCache ); m_dbConnection->registerUpdateHook( policy::FolderTable::Name, &propagateDeletionToCache ); m_dbConnection->registerUpdateHook( policy::GenreTable::Name, &propagateDeletionToCache ); m_dbConnection->registerUpdateHook( policy::LabelTable::Name, &propagateDeletionToCache