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

Introduce an IDiscoverer interface

parent 13180fb1
#ifndef IDISCOVERER_H
# define IDISCOVERER_H
#include <string>
#include "Types.h"
#include "filesystem/IDirectory.h"
#include "filesystem/IFile.h"
class IDiscovererCb
{
public:
virtual ~IDiscovererCb() = default;
/**
* @brief onNewFolder Called when the discoverer finds a new directory
* @param folderPath The new directory's path
* @param parent The parent folder, or null if this is the root folder
* @return The newly created folder, or nullptr in case of error (or if the
* directory shall not be browsed further)
*/
virtual FolderPtr onNewFolder( const fs::IDirectory* folder, FolderPtr parent ) = 0;
/**
* @brief onNewFile Called when the discoverer finds a new file
* @param filePath The new file's path
* @param parent The parent folder
* @return true if the file was accepted
*/
virtual FilePtr onNewFile( const fs::IFile* file, FolderPtr parent ) = 0;
};
class IDiscoverer
{
public:
virtual ~IDiscoverer() = default;
virtual bool discover( const std::string& entryPoint ) = 0;
};
#endif // IDISCOVERER_H
......@@ -6,6 +6,7 @@
#include "Types.h"
#include "factory/IFileSystem.h"
#include "IDiscoverer.h"
class IParserCb
{
......@@ -28,18 +29,18 @@ class IParserCb
virtual void onFileDone( FilePtr file ) = 0;
};
class IMediaLibrary
class IMediaLibrary : public IDiscovererCb
{
public:
virtual ~IMediaLibrary() {}
///
/// \brief initialize Initializes the media library.
/// \brief initialize Initializes the media library.
/// This will use the provided discoverer to search for new media asynchronously.
///
/// \param dbPath Path to the database
/// \param fsFactory An instance to a filesystem factory.
/// In case this is a nullptr, a default factory will be used
/// \return true in case of success, false otherwise
///
virtual bool initialize( const std::string& dbPath, std::shared_ptr<factory::IFileSystem> fsFactory = nullptr ) = 0;
virtual bool initialize( const std::string& dbPath, std::shared_ptr<factory::IFileSystem> fsFactory ) = 0;
/// Adds a stand alone file
virtual FilePtr addFile( const std::string& path ) = 0;
virtual FilePtr file( const std::string& path ) = 0;
......@@ -47,11 +48,9 @@ class IMediaLibrary
virtual bool deleteFile( FilePtr file ) = 0;
/// Adds a folder and all the files it contains
virtual FolderPtr addFolder( const std::string& path ) = 0;
virtual FolderPtr folder( const std::string& path ) = 0;
virtual bool deleteFolder( FolderPtr folder ) = 0;
virtual LabelPtr createLabel( const std::string& label ) = 0;
virtual bool deleteLabel( const std::string& label ) = 0;
virtual bool deleteLabel( LabelPtr label ) = 0;
......@@ -73,6 +72,15 @@ class IMediaLibrary
*/
virtual void addMetadataService( std::unique_ptr<IMetadataService> service ) = 0;
virtual void parse( FilePtr file, IParserCb* cb ) = 0;
virtual void addDiscoverer( std::unique_ptr<IDiscoverer> discoverer ) = 0;
/**
* @brief discover Launch a discovery on the provided entry point.
* There no garanty on how this will be processed, or if it will be processed synchronously or not.
* Depending on which discoverer modules where provided, this might or might not work
* @param entryPoint What to discover.
*/
virtual void discover( const std::string& entryPoint ) = 0;
};
class MediaLibraryFactory
......
......@@ -5,6 +5,8 @@
class IAlbum;
class IAlbumTrack;
class IAudioTrack;
class IDiscoverer;
class IFile;
class IFolder;
class ILabel;
......@@ -12,7 +14,6 @@ class IMetadataService;
class IMovie;
class IShow;
class IShowEpisode;
class IAudioTrack;
class IVideoTrack;
struct sqlite3;
......
......@@ -22,18 +22,20 @@ list(APPEND HEADERS_LIST
${CMAKE_SOURCE_DIR}/include/IAudioTrack.h
${CMAKE_SOURCE_DIR}/include/IVideoTrack.h
${CMAKE_SOURCE_DIR}/include/IMovie.h
${CMAKE_SOURCE_DIR}/include/IDiscoverer.h
${CMAKE_SOURCE_DIR}/include/filesystem/IDirectory.h
${CMAKE_SOURCE_DIR}/include/filesystem/IFile.h
${CMAKE_SOURCE_DIR}/include/factory/IFileSystem.h
database/Cache.h
database/SqliteTools.h
filesystem/IDirectory.h
filesystem/IFile.h
filesystem/${ARCH_FOLDER}/Directory.h
filesystem/${ARCH_FOLDER}/File.h
discoverer/FsDiscoverer.h
factory/FileSystem.h
Utils.h
)
......@@ -61,6 +63,9 @@ list(APPEND SRC_LIST ${HEADERS_LIST}
factory/FileSystem.cpp
filesystem/${ARCH_FOLDER}/Directory.cpp
filesystem/${ARCH_FOLDER}/File.cpp
discoverer/FsDiscoverer.cpp
Utils.cpp
)
......
#include <algorithm>
#include <functional>
#include <queue>
#include "Album.h"
#include "AlbumTrack.h"
#include "AudioTrack.h"
......@@ -20,7 +18,6 @@
#include "filesystem/IDirectory.h"
#include "filesystem/IFile.h"
#include "factory/FileSystem.h"
const std::vector<std::string> MediaLibrary::supportedExtensions {
......@@ -90,7 +87,6 @@ bool MediaLibrary::initialize( const std::string& dbPath, std::shared_ptr<factor
return loadFolders();
}
std::vector<FilePtr> MediaLibrary::files()
{
return File::fetchAll( m_dbConnection );
......@@ -110,48 +106,6 @@ FilePtr MediaLibrary::addFile( const std::string& path )
return file;
}
FolderPtr MediaLibrary::addFolder( const std::string& path )
{
std::queue<std::pair<std::string, unsigned int>> folders;
FolderPtr root;
folders.emplace( path, 0 );
while ( folders.empty() == false )
{
std::unique_ptr<fs::IDirectory> dir;
auto currentFolder = folders.front();
folders.pop();
try
{
dir = m_fsFactory->createDirectory( currentFolder.first );
}
catch ( std::runtime_error& )
{
// If the first directory fails to open, stop now.
// Otherwise, assume something went wrong in a subdirectory.
if (root == nullptr)
return nullptr;
continue;
}
auto folder = Folder::create( m_dbConnection, dir.get(), currentFolder.second );
if ( folder == nullptr && root == nullptr )
return nullptr;
if ( root == nullptr )
root = folder;
for ( auto& f : dir->files() )
{
auto fsFile = m_fsFactory->createFile( f );
addFile( fsFile.get(), folder->id() );
}
for ( auto& f : dir->dirs() )
folders.emplace( f, folder->id() );
}
return root;
}
FolderPtr MediaLibrary::folder( const std::string& path )
{
return Folder::fetch( m_dbConnection, path );
......@@ -238,17 +192,41 @@ void MediaLibrary::parse(FilePtr file , IParserCb* cb)
m_parser->parse( file, cb );
}
void MediaLibrary::addDiscoverer(std::unique_ptr<IDiscoverer> discoverer)
{
m_discoverers.emplace_back( std::move( discoverer ) );
}
void MediaLibrary::discover( const std::string &entryPoint )
{
for ( auto& d : m_discoverers )
d->discover( entryPoint );
}
FolderPtr MediaLibrary::onNewFolder( const fs::IDirectory* directory, FolderPtr parent )
{
//FIXME: Since we insert files/folders with a UNIQUE constraint, maybe we should
//just let sqlite try to insert, throw an exception in case the contraint gets violated
//catch it and return nullptr from here.
//We previously were fetching the folder manually here, but that triggers an eroneous entry
//in the cache. This might also be something to fix...
return Folder::create( m_dbConnection, directory,
parent == nullptr ? 0 : parent->id() );
}
FilePtr MediaLibrary::onNewFile( const fs::IFile *file, FolderPtr parent )
{
//FIXME: Same uniqueness comment as onNewFolder above.
return addFile( file, parent == nullptr ? 0 : parent->id() );
}
bool MediaLibrary::loadFolders()
{
//FIXME: This should probably be in a sql transaction
//FIXME: This shouldn't be done for "removable"/network files
static const std::string req = "SELECT * FROM " + policy::FolderTable::Name
+ " WHERE id_parent IS NULL";
auto rootFolders = sqlite::Tools::fetchAll<Folder, IFolder>( m_dbConnection, req );
if ( rootFolders.size() == 0 )
{
std::cout << "No folders in DB" << std::endl;
return true;
}
for ( const auto f : rootFolders )
{
std::cout << "Checking " << f->path();
......@@ -289,7 +267,9 @@ bool MediaLibrary::checkSubfolders( fs::IDirectory* folder, unsigned int parentI
if ( it == end( subFoldersInDB ) )
{
std::cout << "New folder detected" << std::endl;
addFolder( subFolderPath );
//FIXME: In order to add the new folder, we need to use the same discoverer.
// This probably means we need to store which discoverer was used to add which file
// and store discoverers as a map instead of a vector
continue;
}
auto subFolder = m_fsFactory->createDirectory( subFolderPath );
......@@ -350,16 +330,17 @@ void MediaLibrary::checkFiles( fs::IDirectory* folder, unsigned int parentId )
}
}
bool MediaLibrary::addFile( const fs::IFile* file, unsigned int folderId )
FilePtr MediaLibrary::addFile( const fs::IFile* file, unsigned int folderId )
{
if ( std::find( begin( supportedExtensions ), end( supportedExtensions ),
file->extension() ) == end( supportedExtensions ) )
return false;
if ( File::create( m_dbConnection, file, folderId ) == nullptr )
auto fptr = File::create( m_dbConnection, file, folderId );
if ( fptr == nullptr )
{
std::cerr << "Failed to add file " << file->fullPath() << " to the media library" << std::endl;
return false;
return nullptr;
}
return true;
return fptr;
}
......@@ -6,6 +6,7 @@ class Parser;
#include <sqlite3.h>
#include "IMediaLibrary.h"
#include "IDiscoverer.h"
class MediaLibrary : public IMediaLibrary
{
......@@ -20,7 +21,6 @@ class MediaLibrary : public IMediaLibrary
virtual bool deleteFile( const std::string& mrl );
virtual bool deleteFile( FilePtr file );
virtual FolderPtr addFolder( const std::string& path ) override;
virtual FolderPtr folder( const std::string& path ) override;
virtual bool deleteFolder( FolderPtr folder ) override;
......@@ -40,6 +40,12 @@ class MediaLibrary : public IMediaLibrary
virtual void addMetadataService( std::unique_ptr<IMetadataService> service );
virtual void parse( FilePtr file, IParserCb* cb );
virtual void addDiscoverer( std::unique_ptr<IDiscoverer> discoverer ) override;
virtual void discover( const std::string& entryPoint ) override;
// IDiscovererCb implementation
virtual FolderPtr onNewFolder( const fs::IDirectory* directory, FolderPtr parent ) override;
virtual FilePtr onNewFile(const fs::IFile* file, FolderPtr parent ) override;
private:
static const std::vector<std::string> supportedExtensions;
......@@ -47,11 +53,12 @@ class MediaLibrary : public IMediaLibrary
bool loadFolders();
bool checkSubfolders( fs::IDirectory* folder, unsigned int parentId );
void checkFiles( fs::IDirectory* folder, unsigned int parentId );
bool addFile( const fs::IFile* file, unsigned int folderId );
FilePtr addFile( const fs::IFile* file, unsigned int folderId );
private:
std::shared_ptr<sqlite3> m_dbConnection;
std::unique_ptr<Parser> m_parser;
std::shared_ptr<factory::IFileSystem> m_fsFactory;
std::vector<std::unique_ptr<IDiscoverer>> m_discoverers;
};
#endif // MEDIALIBRARY_H
#include "FsDiscoverer.h"
#include "factory/FileSystem.h"
#include <queue>
FsDiscoverer::FsDiscoverer( std::shared_ptr<factory::IFileSystem> fsFactory, IDiscovererCb* discoveryCb )
: m_discoveryCb( discoveryCb )
{
if ( fsFactory != nullptr )
m_fsFactory = fsFactory;
else
m_fsFactory.reset( new factory::FileSystemDefaultFactory );
}
bool FsDiscoverer::discover( const std::string &entryPoint )
{
std::queue<std::pair<std::string, FolderPtr>> folders;
folders.emplace( entryPoint, nullptr );
while ( folders.empty() == false )
{
std::unique_ptr<fs::IDirectory> dir;
auto currentFolder = folders.front();
auto currentPath = currentFolder.first;
auto parent = currentFolder.second;
folders.pop();
try
{
dir = m_fsFactory->createDirectory( currentPath );
}
catch ( std::runtime_error& )
{
// If the first directory fails to open, stop now.
// Otherwise, assume something went wrong in a subdirectory.
if (parent == nullptr)
return false;
continue;
}
auto folder = m_discoveryCb->onNewFolder( dir.get(), parent );
if ( folder == nullptr && parent == nullptr )
return false;
if ( folder == nullptr )
continue;
for ( auto& f : dir->files() )
{
auto fsFile = m_fsFactory->createFile( f );
m_discoveryCb->onNewFile( fsFile.get(), folder );
}
for ( auto& f : dir->dirs() )
folders.emplace( f, folder );
}
return true;
}
#ifndef FS_DISCOVERER_H
# define FS_DISCOVERER_H
#include <memory>
#include "IDiscoverer.h"
#include "factory/IFileSystem.h"
class FsDiscoverer : public IDiscoverer
{
public:
FsDiscoverer(std::shared_ptr<factory::IFileSystem> fsFactory , IDiscovererCb *discoveryCb);
virtual bool discover( const std::string &entryPoint ) override;
private:
std::shared_ptr<factory::IFileSystem> m_fsFactory;
IDiscovererCb* m_discoveryCb;
};
#endif // FS_DISCOVERER_H
......@@ -264,8 +264,7 @@ class Folders : public Tests
TEST_F( Folders, Add )
{
auto f = ml->addFolder( "." );
ASSERT_NE( f, nullptr );
ml->discover( "." );
auto files = ml->files();
......@@ -275,9 +274,9 @@ TEST_F( Folders, Add )
TEST_F( Folders, Delete )
{
auto f = ml->addFolder( "." );
ASSERT_NE( f, nullptr );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
auto folderPath = f->path();
auto files = ml->files();
......@@ -306,8 +305,7 @@ TEST_F( Folders, Delete )
TEST_F( Folders, Load )
{
auto f = ml->addFolder( "." );
ASSERT_NE( f, nullptr );
ml->discover( "." );
Reload();
......@@ -319,8 +317,7 @@ TEST_F( Folders, Load )
TEST_F( Folders, InvalidPath )
{
auto f = ml->addFolder( "/invalid/path" );
ASSERT_EQ( f, nullptr );
ml->discover( "/invalid/path" );
auto files = ml->files();
ASSERT_EQ( files.size(), 0u );
......@@ -328,9 +325,9 @@ TEST_F( Folders, InvalidPath )
TEST_F( Folders, List )
{
auto f = ml->addFolder( "." );
ASSERT_NE( f, nullptr );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
auto files = f->files();
ASSERT_EQ( files.size(), 2u );
......@@ -343,13 +340,15 @@ TEST_F( Folders, List )
TEST_F( Folders, AbsolutePath )
{
auto f = ml->addFolder( "." );
ASSERT_NE( f->path(), "." );
ml->discover( "." );
auto f = ml->folder( "." );
ASSERT_EQ( f, nullptr );
}
TEST_F( Folders, ListFolders )
{
auto f = ml->addFolder( "." );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
auto subFolders = f->folders();
ASSERT_EQ( 1u, subFolders.size() );
......@@ -377,7 +376,8 @@ TEST_F( Folders, ListFolders )
TEST_F( Folders, LastModificationDate )
{
auto f = ml->addFolder( "." );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
ASSERT_NE( 0u, f->lastModificationDate() );
auto subFolders = f->folders();
ASSERT_NE( 0u, subFolders[0]->lastModificationDate() );
......@@ -392,7 +392,7 @@ TEST_F( Folders, LastModificationDate )
TEST_F( Folders, NewFolderWithFile )
{
ml->addFolder( "." );
ml->discover( "." );
ASSERT_EQ( 3u, ml->files().size() );
// Do not watch for live changes
......@@ -410,7 +410,8 @@ TEST_F( Folders, NewFolderWithFile )
TEST_F( Folders, NewFileInSubFolder )
{
auto f = ml->addFolder( "." );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
ASSERT_EQ( 3u, ml->files().size() );
f = ml->folder( mock::FileSystemFactory::SubFolder );
......@@ -432,7 +433,7 @@ TEST_F( Folders, NewFileInSubFolder )
TEST_F( Folders, RemoveFileFromDirectory )
{
ml->addFolder( "." );
ml->discover( "." );
ASSERT_EQ( 3u, ml->files().size() );
// Do not watch for live changes
......@@ -450,7 +451,7 @@ TEST_F( Folders, RemoveFileFromDirectory )
TEST_F( Folders, RemoveDirectory )
{
ml->addFolder( "." );
ml->discover( "." );
ASSERT_EQ( 3u, ml->files().size() );
// Do not watch for live changes
......@@ -468,7 +469,7 @@ TEST_F( Folders, RemoveDirectory )
TEST_F( Folders, UpdateFile )
{
ml->addFolder( "." );
ml->discover( "." );
auto filePath = std::string{ mock::FileSystemFactory::SubFolder } + "subfile.mp4";
auto f = ml->file( filePath );
auto id = f->id();
......@@ -489,7 +490,8 @@ TEST_F( Folders, UpdateFile )
TEST_F( Folders, CheckRemovable )
{
fsMock->dirs[mock::FileSystemFactory::SubFolder]->markRemovable();
auto f = ml->addFolder( "." );
ml->discover( "." );
auto f = ml->folder( mock::FileSystemFactory::Root );
ASSERT_FALSE( f->isRemovable() );
auto subfolder = ml->folder( mock::FileSystemFactory::SubFolder );
ASSERT_TRUE( subfolder->isRemovable() );
......
......@@ -2,6 +2,7 @@
#include "filesystem/IFile.h"
#include "filesystem/IDirectory.h"
#include "Utils.h"
#include "discoverer/FsDiscoverer.h"
class TestEnv : public ::testing::Environment
{
......@@ -85,7 +86,12 @@ void Tests::TearDown()
void Tests::Reload(std::shared_ptr<factory::IFileSystem> fs /* = nullptr */ )
{
ml.reset( MediaLibraryFactory::create() );
bool res = ml->initialize( "test.db", fs ? fs : defaultFs );
if ( fs != nullptr )
{
std::unique_ptr<IDiscoverer> discoverer( new FsDiscoverer( fs, ml.get() ) );
ml->addDiscoverer( std::move( discoverer ) );
}
bool res = ml->initialize( "test.db", fs );
ASSERT_TRUE( res );
}
......
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