Commit c31d9702 authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen
Browse files

MediaLibrary: Split fs factories & device lister handling in a new class

This is part of a refactoring aiming at fixing #362
The fsHolder object is initialized during construction and doesn't
require locking from the MediaLibrary instance, and will manage its
internal locking as needed.
The end goal is to have the FsDiscoverer class using the FsHolder
directly without going through the media library
parent f4cc91e8
......@@ -302,19 +302,17 @@ MediaLibrary::MediaLibrary( const std::string& dbPath,
std::unique_ptr<LockFile> lockFile )
: m_settings( this )
, m_initialized( false )
, m_networkDiscoveryEnabled( false )
, m_discovererIdle( true )
, m_parserIdle( true )
, m_fsFactoryCb( this )
, m_dbPath( dbPath )
, m_mlFolderPath( utils::file::toFolderPath( mlFolderPath ) )
, m_thumbnailPath( m_mlFolderPath + "thumbnails/" )
, m_playlistPath( m_mlFolderPath + "playlists/" )
, m_lockFile( std::move( lockFile ) )
, m_callback( nullptr )
, m_fsHolder( this )
{
Log::setLogLevel( LogLevel::Error );
addDefaultDeviceListers();
}
MediaLibrary::~MediaLibrary()
......@@ -1146,7 +1144,7 @@ void MediaLibrary::startThumbnailer() const
void MediaLibrary::populateNetworkFsFactories()
{
#ifdef HAVE_LIBVLC
addFileSystemFactoryLocked( std::make_shared<fs::libvlc::FileSystemFactory>( this, "smb://" ) );
m_fsHolder.addFsFactory( std::make_shared<fs::libvlc::FileSystemFactory>( this, "smb://" ) );
#endif
}
......@@ -1157,23 +1155,7 @@ void MediaLibrary::onDbConnectionReady( sqlite::Connection* )
void MediaLibrary::addLocalFsFactory()
{
#ifdef HAVE_LIBVLC
addFileSystemFactoryLocked( std::make_shared<fs::libvlc::FileSystemFactory>( this, "file://" ) );
#endif
}
void MediaLibrary::addDefaultDeviceListers()
{
/* Called from the constructor, no locking required */
assert( m_deviceListers.empty() == true );
auto devLister = factory::createDeviceLister();
if ( devLister != nullptr )
m_deviceListers["file://"] = std::move( devLister );
#ifdef HAVE_LIBVLC
auto lanSds = VLCInstance::get().mediaDiscoverers( VLC::MediaDiscoverer::Category::Lan );
auto deviceLister = std::make_shared<fs::libvlc::DeviceLister>( "smb://" );
for ( const auto& sd : lanSds )
deviceLister->addSD( sd.name() );
m_deviceListers["smb://"] = std::move( deviceLister );
m_fsHolder.addFsFactory( std::make_shared<fs::libvlc::FileSystemFactory>( this, "file://" ) );
#endif
}
......@@ -2492,36 +2474,17 @@ void MediaLibrary::registerDeviceLister( DeviceListerPtr lister,
const std::string& scheme )
{
assert( m_initialized == false );
std::lock_guard<compat::Mutex> lock( m_mutex );
m_deviceListers[scheme] = std::move( lister );
m_fsHolder.registerDeviceLister( scheme, std::move( lister ) );
}
DeviceListerPtr MediaLibrary::deviceLister( const std::string& scheme ) const
{
std::lock_guard<compat::Mutex> lock( m_mutex );
return deviceListerLocked( scheme );
}
DeviceListerPtr MediaLibrary::deviceListerLocked(const std::string& scheme) const
{
auto it = m_deviceListers.find( scheme );
if ( it == cend( m_deviceListers ) )
return nullptr;
return it->second;
return m_fsHolder.deviceLister( scheme );
}
std::shared_ptr<fs::IFileSystemFactory> MediaLibrary::fsFactoryForMrl( const std::string& mrl ) const
{
for ( const auto& f : m_fsFactories )
{
if ( f->isMrlSupported( mrl ) )
{
if ( f->isNetworkFileSystem() && m_networkDiscoveryEnabled == false )
return nullptr;
return f;
}
}
return nullptr;
return m_fsHolder.fsFactoryForMrl( mrl );
}
void MediaLibrary::discover( const std::string& entryPoint )
......@@ -2532,74 +2495,17 @@ void MediaLibrary::discover( const std::string& entryPoint )
bool MediaLibrary::addFileSystemFactory( std::shared_ptr<fs::IFileSystemFactory> fsFactory )
{
std::lock_guard<compat::Mutex> lock( m_mutex );
return addFileSystemFactoryLocked( std::move( fsFactory ) );
}
bool MediaLibrary::addFileSystemFactoryLocked(std::shared_ptr<fs::IFileSystemFactory> fsFactory)
{
assert( m_initialized == false );
auto it = std::find_if( cbegin( m_fsFactories ),
cend( m_fsFactories ),
[&fsFactory]( const std::shared_ptr<fs::IFileSystemFactory>& fsf ) {
return fsFactory->scheme() == fsf->scheme();
});
if ( it != cend( m_fsFactories ) )
return false;
m_fsFactories.emplace_back( std::move( fsFactory ) );
return true;
return m_fsHolder.addFsFactory( std::move( fsFactory ) );
}
bool MediaLibrary::setDiscoverNetworkEnabled( bool enabled )
{
std::lock_guard<compat::Mutex> lock( m_mutex );
if ( enabled == m_networkDiscoveryEnabled )
return true;
if ( m_discovererWorker == nullptr )
{
m_networkDiscoveryEnabled = enabled;
return true;
}
auto affected = false;
std::unique_ptr<sqlite::Transaction> t;
if ( enabled == false )
t = m_dbConnection->newTransaction();
for ( auto fsFactory : m_fsFactories )
{
if ( fsFactory->isNetworkFileSystem() == false )
continue;
if ( enabled == true )
{
if ( fsFactory->start( &m_fsFactoryCb ) == true )
{
fsFactory->refreshDevices();
affected = true;
}
}
else
{
auto devices = Device::fetchByScheme( this, fsFactory->scheme() );
for ( const auto& d : devices )
d->setPresent( false );
fsFactory->stop();
affected = true;
}
}
if ( t != nullptr )
t->commit();
m_networkDiscoveryEnabled = enabled;
return affected;
return m_fsHolder.setNetworkEnabled( enabled );
}
bool MediaLibrary::isDiscoverNetworkEnabled() const
{
std::lock_guard<compat::Mutex> lock( m_mutex );
return m_networkDiscoveryEnabled;
return m_fsHolder.isNetworkEnabled();
}
Query<IFolder> MediaLibrary::entryPoints() const
......@@ -2701,45 +2607,30 @@ void MediaLibrary::refreshDevice( Device& device, fs::IFileSystemFactory* fsFact
device.updateLastSeen();
}
void MediaLibrary::refreshDevices( fs::IFileSystemFactory& fsFactory )
DiscovererWorker* MediaLibrary::getDiscovererWorker()
{
auto devices = Device::fetchByScheme( this, fsFactory.scheme() );
for ( auto& d : devices )
{
refreshDevice( *d, &fsFactory );
}
LOG_DEBUG( "Done refreshing devices in database." );
std::lock_guard<compat::Mutex> lock{ m_mutex };
return m_discovererWorker.get();
}
void MediaLibrary::startFsFactoriesAndRefresh()
{
std::lock_guard<compat::Mutex> lock( m_mutex );
m_fsHolder.startFsFactoriesAndRefresh();
}
for ( const auto& fsFactory : m_fsFactories )
{
/*
* We only want to start the fs factory if it is a local one, or if
* it's a network one and network discovery is enabled
*/
if ( m_networkDiscoveryEnabled == true ||
fsFactory->isNetworkFileSystem() == false )
{
fsFactory->start( &m_fsFactoryCb );
fsFactory->refreshDevices();
}
}
auto devices = Device::fetchAll( this );
for ( const auto& d : devices )
void MediaLibrary::refreshDevices( fs::IFileSystemFactory& fsFactory )
{
auto devices = Device::fetchByScheme( this, fsFactory.scheme() );
for ( auto& d : devices )
{
auto fsFactory = fsFactoryForMrl( d->scheme() );
refreshDevice( *d, fsFactory.get() );
refreshDevice( *d, &fsFactory );
}
LOG_DEBUG( "Done refreshing devices in database." );
}
void MediaLibrary::startFsFactory( fs::IFileSystemFactory &fsFactory ) const
{
fsFactory.start( &m_fsFactoryCb );
fsFactory.refreshDevices();
m_fsHolder.startFsFactory( fsFactory );
}
bool MediaLibrary::forceRescan()
......@@ -2830,84 +2721,6 @@ void MediaLibrary::addThumbnailer( std::shared_ptr<IThumbnailer> thumbnailer )
m_thumbnailer = std::move( thumbnailer );
}
MediaLibrary::FsFactoryCb::FsFactoryCb(MediaLibrary* ml)
: m_ml( ml )
{
}
void MediaLibrary::FsFactoryCb::onDeviceMounted( const fs::IDevice& deviceFs,
const std::string& newMountpoint )
{
auto device = Device::fromUuid( m_ml, deviceFs.uuid(), deviceFs.scheme() );
if ( device == nullptr )
return;
if ( device->isPresent() == deviceFs.isPresent() )
{
if ( deviceFs.isNetwork() == true )
device->addMountpoint( newMountpoint, time( nullptr ) );
return;
}
assert( device->isRemovable() == true );
LOG_INFO( "Device ", deviceFs.uuid(), " changed presence state: ",
device->isPresent() ? "1" : "0", " -> ",
deviceFs.isPresent() ? "1" : "0" );
auto previousPresence = device->isPresent();
auto t = m_ml->getConn()->newTransaction();
device->setPresent( deviceFs.isPresent() );
if ( deviceFs.isNetwork() == true )
device->addMountpoint( newMountpoint, time( nullptr ) );
t->commit();
if ( previousPresence == false )
{
// We need to reload the entrypoint in case a previous discovery was
// interrupted before its end (causing the tasks that were spawned
// to be deleted when the device go away, requiring a new discovery)
// Also, there might be new content on the device since it was last
// scanned.
// We also want to resume any parsing tasks that were previously
// started before the device went away
assert( deviceFs.isPresent() == true );
if ( m_ml->m_discovererWorker != nullptr )
m_ml->m_discovererWorker->reloadDevice( device->id() );
if ( m_ml->m_parser != nullptr )
m_ml->m_parser->refreshTaskList();
}
}
void MediaLibrary::FsFactoryCb::onDeviceUnmounted( const fs::IDevice& deviceFs,
const std::string& )
{
auto device = Device::fromUuid( m_ml, deviceFs.uuid(), deviceFs.scheme() );
if ( device == nullptr )
{
/*
* If we haven't added this device to the database, it means we never
* discovered anything on it, so we don't really care if it's mounted
* or not
*/
return;
}
assert( device->isRemovable() == true );
if ( device->isPresent() == deviceFs.isPresent() )
return;
LOG_INFO( "Device ", deviceFs.uuid(), " changed presence state: ",
device->isPresent() ? "1" : "0", " -> ",
deviceFs.isPresent() ? "1" : "0" );
device->setPresent( deviceFs.isPresent() );
if ( deviceFs.isPresent() == false && m_ml->m_parser != nullptr )
{
/*
* The device went away, let's ensure we're not still trying to
* analyze its content
*/
m_ml->m_parser->refreshTaskList();
}
}
const std::vector<const char*>& MediaLibrary::supportedMediaExtensions() const
{
return SupportedMediaExtensions;
......@@ -2964,13 +2777,8 @@ bool MediaLibrary::setExternalLibvlcInstance( libvlc_instance_t* inst )
* This assumes that all network device lister are using libvlc and therefor
* they will need to be recreated
*/
for ( auto& fsFactory : m_fsFactories )
{
if ( fsFactory->isNetworkFileSystem() == false ||
fsFactory->isStarted() == false )
continue;
fsFactory->stop();
}
m_fsHolder.stopNetworkFsFactories();
/*
* The VLCMetadataService will fetch the new instance during its next run
* The thumbnailer also fetches the instance before using it
......
......@@ -32,6 +32,7 @@
#include "compat/Mutex.h"
#include "LockFile.h"
#include "database/SqliteConnection.h"
#include "filesystem/FsHolder.h"
#include <atomic>
......@@ -225,7 +226,6 @@ public:
virtual void registerDeviceLister( DeviceListerPtr lister,
const std::string& scheme ) override;
virtual DeviceListerPtr deviceLister( const std::string& scheme ) const override;
DeviceListerPtr deviceListerLocked( const std::string& scheme ) const;
std::shared_ptr<fs::IFileSystemFactory> fsFactoryForMrl( const std::string& mrl ) const;
......@@ -237,17 +237,6 @@ public:
* was plugged/unplugged.
*/
void refreshDevices(fs::IFileSystemFactory& fsFactory);
/**
* @brief startFsFactoriesAndRefresh Starts fs factories & refreshes all known devices
*
* This will start all provided & required file system factories (ie. local
* ones, and network ones if network discovery is enabled), and refresh the
* presence & last seen date for all known devices we have in database.
* This operation must not be based on the available FsFactories, as we might
* not have a factory that was used to create a device before.
* We still need to mark all the associated devices as missing.
*/
void startFsFactoriesAndRefresh();
void startFsFactory( fs::IFileSystemFactory& fsFactory ) const;
......@@ -260,7 +249,6 @@ public:
virtual void addThumbnailer( std::shared_ptr<IThumbnailer> thumbnailer ) override;
virtual bool addFileSystemFactory( std::shared_ptr<fs::IFileSystemFactory> fsFactory ) override;
bool addFileSystemFactoryLocked( std::shared_ptr<fs::IFileSystemFactory> fsFactory );
static void removeOldEntities( MediaLibraryPtr ml );
......@@ -346,45 +334,30 @@ private:
bool checkDatabaseIntegrity();
void registerEntityHooks();
void removeThumbnails();
void refreshDevice( Device& device, fs::IFileSystemFactory* fsFactory );
void startThumbnailer() const;
parser::Parser* getParserLocked() const;
virtual bool forceRescanLocked();
void startDiscovererLocked();
void addDefaultDeviceListers();
/* Temporary public accessors during refactoring */
public:
void refreshDevice( Device& device, fs::IFileSystemFactory* fsFactory );
DiscovererWorker* getDiscovererWorker();
void startFsFactoriesAndRefresh();
protected:
virtual void addLocalFsFactory();
void deleteAllTables( medialibrary::sqlite::Connection *dbConn );
class FsFactoryCb : public fs::IFileSystemFactoryCb
{
public:
explicit FsFactoryCb( MediaLibrary* ml );
private:
virtual void onDeviceMounted( const fs::IDevice& deviceFs,
const std::string& newMountpoint ) override;
virtual void onDeviceUnmounted( const fs::IDevice& deviceFs,
const std::string& removedMountpoint ) override;
private:
MediaLibrary* m_ml;
};
protected:
mutable compat::Mutex m_mutex;
std::shared_ptr<sqlite::Connection> m_dbConnection;
Settings m_settings;
bool m_initialized;
bool m_networkDiscoveryEnabled;
std::atomic_bool m_discovererIdle;
std::atomic_bool m_parserIdle;
/* All fs factory callbacks must outlive the fs factory itself, since
* it might invoke some of the callback interface methods during teardown
*/
mutable FsFactoryCb m_fsFactoryCb;
const std::string m_dbPath;
const std::string m_mlFolderPath;
const std::string m_thumbnailPath;
......@@ -394,11 +367,7 @@ protected:
IMediaLibraryCb* m_callback;
// External device lister
std::vector<std::shared_ptr<fs::IFileSystemFactory>> m_fsFactories;
// Device lister will invoke fs factories through IDeviceListerCb so
// the device lister must be destroyed before the fs factories
std::unordered_map<std::string, DeviceListerPtr> m_deviceListers;
FsHolder m_fsHolder;
// User provided parser services
std::vector<std::shared_ptr<parser::IParserService>> m_services;
......
/*****************************************************************************
* Media Library
*****************************************************************************
* Copyright (C) 2021 Hugo Beauzée-Luyssen, Videolabs, VideoLAN
*
* Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
*
* 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.
*****************************************************************************/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "FsHolder.h"
#include "Device.h"
#include "database/SqliteTransaction.h"
#include "logging/Logger.h"
#include "medialibrary/filesystem/IDevice.h"
#include "factory/DeviceListerFactory.h"
#include "discoverer/DiscovererWorker.h"
#include "parser/Parser.h"
#ifdef HAVE_LIBVLC
# include "utils/VLCInstance.h"
# include "filesystem/libvlc/DeviceLister.h"
#endif
#include <algorithm>
namespace medialibrary
{
FsHolder::FsHolder( MediaLibrary* ml )
: m_ml( ml )
, m_fsFactoryCb( ml )
, m_networkDiscoveryEnabled( false )
, m_started( false )
{
auto devLister = factory::createDeviceLister();
if ( devLister != nullptr )
m_deviceListers["file://"] = std::move( devLister );
#ifdef HAVE_LIBVLC
auto lanSds = VLCInstance::get().mediaDiscoverers( VLC::MediaDiscoverer::Category::Lan );
auto deviceLister = std::make_shared<fs::libvlc::DeviceLister>( "smb://" );
for ( const auto& sd : lanSds )
deviceLister->addSD( sd.name() );
m_deviceListers["smb://"] = std::move( deviceLister );
#endif
}
bool FsHolder::addFsFactory( std::shared_ptr<fs::IFileSystemFactory> fsFactory )
{
std::lock_guard<compat::Mutex> lock{ m_mutex };
auto it = std::find_if( cbegin( m_fsFactories ),
cend( m_fsFactories ),
[&fsFactory]( const std::shared_ptr<fs::IFileSystemFactory>& fsf ) {
return fsFactory->scheme() == fsf->scheme();
});
if ( it != cend( m_fsFactories ) )
return false;
m_fsFactories.push_back( std::move( fsFactory ) );
return true;
}
void FsHolder::registerDeviceLister( const std::string& scheme,
DeviceListerPtr lister )
{
std::lock_guard<compat::Mutex> lock{ m_mutex };
m_deviceListers[scheme] = std::move( lister );
}
DeviceListerPtr FsHolder::deviceLister( const std::string& scheme ) const
{
std::lock_guard<compat::Mutex> lock{ m_mutex };
auto it = m_deviceListers.find( scheme );
if ( it == cend( m_deviceListers ) )
return nullptr;
return it->second;
}
bool FsHolder::setNetworkEnabled( bool enabled )
{
auto expected = !enabled;
if ( m_networkDiscoveryEnabled.compare_exchange_strong( expected,
enabled ) == false )
{
/* If the value didn't change, that's not a failure */
return true;
}
if ( m_started.load( std::memory_order_acquire ) == false )
return true;
auto affected = false;
std::unique_ptr<sqlite::Transaction> t;
if ( enabled == false )
t = m_ml->getConn()->newTransaction();
for ( auto fsFactory : m_fsFactories )
{
if ( fsFactory->isNetworkFileSystem() == false )
continue;
if ( enabled == true )
{
if ( fsFactory->start( &m_fsFactoryCb ) == true )
{
fsFactory->refreshDevices();
affected = true;
}
}
else
{
auto devices = Device::fetchByScheme( m_ml, fsFactory->scheme() );
for ( const auto& d : devices )
d->setPresent( false );
fsFactory->stop();
affected = true;
}
}
if ( t != nullptr )
t->commit();
return affected;
}
bool FsHolder::isNetworkEnabled() const
{
return m_networkDiscoveryEnabled.load( std::memory_order_acquire );
}
void FsHolder::startFsFactoriesAndRefresh()
{
std::lock_guard<compat::Mutex> lock( m_mutex );
for ( const auto& fsFactory : m_fsFactories )
{
/*
* We only want to start the fs factory if it is a local one, or if
* it's a network one and network discovery is enabled
*/
if ( m_networkDiscoveryEnabled == true ||
fsFactory->isNetworkFileSystem() == false )
{
fsFactory->start( &m_fsFactoryCb );
fsFactory->refreshDevices();
}
}
auto devices = Device::fetchAll( m_ml );
for ( const auto& d : devices )
{
auto fsFactory = fsFactoryForMrlLocked( d->scheme() );
m_ml->refreshDevice( *d, fsFactory.get() );
}
}
void FsHolder::stopNetworkFsFactories()
{
std::lock_guard<compat::Mutex> lock( m_mutex );
for ( auto& fsFactory : m_fsFactories )
{
if ( fsFactory->isNetworkFileSystem() == false ||
fsFactory->isStarted() == false )
continue;
fsFactory->stop();
}
}