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

Add basic folder support

parent 92f5e465
......@@ -42,7 +42,9 @@ class IFile
virtual bool addAudioTrack( const std::string& codec, unsigned int bitrate,
unsigned int sampleRate, unsigned int nbChannels ) = 0;
virtual bool audioTracks( std::vector<AudioTrackPtr>& tracks ) = 0;
/// Returns wether the file has been added as a stand alone file (true), or as
/// part of a folder (false)
virtual bool isStandAlone() = 0;
virtual bool isReady() const = 0;
};
......
#pragma once
#include "Types.h"
#include <vector>
class IFolder
{
public:
virtual ~IFolder() = default;
virtual unsigned int id() const = 0;
virtual const std::string& path() = 0;
virtual std::vector<FilePtr> files() = 0;
};
......@@ -32,10 +32,14 @@ class IMediaLibrary
public:
virtual ~IMediaLibrary() {}
virtual bool initialize( const std::string& dbPath ) = 0;
/// Adds a stand alone file
virtual FilePtr addFile( const std::string& path ) = 0;
/// Adds a folder and all the files it contains
virtual FolderPtr addFolder( const std::string& path ) = 0;
virtual FilePtr file( const std::string& path ) = 0;
virtual bool deleteFile( const std::string& mrl ) = 0;
virtual bool deleteFile( FilePtr file ) = 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;
......
......@@ -6,6 +6,7 @@
class IAlbum;
class IAlbumTrack;
class IFile;
class IFolder;
class ILabel;
class IMetadataService;
class IMovie;
......@@ -17,6 +18,7 @@ class IVideoTrack;
struct sqlite3;
typedef std::shared_ptr<IFile> FilePtr;
typedef std::shared_ptr<IFolder> FolderPtr;
typedef std::shared_ptr<ILabel> LabelPtr;
typedef std::shared_ptr<IAlbum> AlbumPtr;
typedef std::shared_ptr<IAlbumTrack> AlbumTrackPtr;
......
......@@ -6,24 +6,39 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
EnableCpp11()
add_definitions("-Wall -Wextra -pedantic")
if(UNIX)
set(ARCH_FOLDER "unix")
endif()
list(APPEND HEADERS_LIST
${CMAKE_SOURCE_DIR}/include/IShow.h
${CMAKE_SOURCE_DIR}/include/IShowEpisode.h
${CMAKE_SOURCE_DIR}/include/IAlbum.h
${CMAKE_SOURCE_DIR}/include/IAlbumTrack.h
${CMAKE_SOURCE_DIR}/include/IFile.h
${CMAKE_SOURCE_DIR}/include/IFolder.h
${CMAKE_SOURCE_DIR}/include/IMediaLibrary.h
${CMAKE_SOURCE_DIR}/include/ILabel.h
${CMAKE_SOURCE_DIR}/include/IAudioTrack.h
${CMAKE_SOURCE_DIR}/include/IVideoTrack.h
${CMAKE_SOURCE_DIR}/include/IMovie.h
SqliteTools.h
filesystem/IDirectory.h
filesystem/IFile.h
filesystem/${ARCH_FOLDER}/Directory.h
filesystem/${ARCH_FOLDER}/File.h
)
include_directories("${CMAKE_SOURCE_DIR}/include")
include_directories("${CMAKE_SOURCE_DIR}/src")
list(APPEND SRC_LIST ${HEADERS_LIST}
MediaLibrary.cpp
File.cpp
Folder.cpp
Album.cpp
Show.cpp
Label.cpp
......@@ -36,6 +51,10 @@ list(APPEND SRC_LIST ${HEADERS_LIST}
Parser.cpp
metadata_services/VLCMetadataService.cpp
filesystem/Factory.cpp
filesystem/${ARCH_FOLDER}/Directory.cpp
filesystem/${ARCH_FOLDER}/File.cpp
)
find_package(Sqlite3 REQUIRED)
......
......@@ -6,6 +6,7 @@
#include "AlbumTrack.h"
#include "AudioTrack.h"
#include "File.h"
#include "Folder.h"
#include "Label.h"
#include "Movie.h"
#include "ShowEpisode.h"
......@@ -27,10 +28,12 @@ File::File( DBConnection dbConnection, sqlite3_stmt* stmt )
m_showEpisodeId = sqlite3_column_int( stmt, 5 );
m_mrl = (const char*)sqlite3_column_text( stmt, 6 );
m_movieId = Traits<unsigned int>::Load( stmt, 7 );
m_folderId = Traits<unsigned int>::Load( stmt, 8 );
m_isReady = m_type != UnknownType;
}
File::File( const std::string& mrl )
File::File( const std::string& mrl, unsigned int folderId )
: m_id( 0 )
, m_type( UnknownType )
, m_duration( 0 )
......@@ -39,18 +42,28 @@ File::File( const std::string& mrl )
, m_showEpisodeId( 0 )
, m_mrl( mrl )
, m_movieId( 0 )
, m_folderId( folderId )
, m_isReady( false )
{
}
FilePtr File::create( DBConnection dbConnection, const std::string& mrl )
FilePtr File::create( DBConnection dbConnection, const std::string& mrl, unsigned int folderId )
{
auto self = std::make_shared<File>( mrl );
auto self = std::make_shared<File>( mrl, folderId );
static const std::string req = "INSERT INTO " + policy::FileTable::Name +
"(mrl) VALUES(?)";
bool pKey = _Cache::insert( dbConnection, self, req, mrl );
if ( pKey == false )
return nullptr;
"(mrl, folder_id) VALUES(?, ?)";
//FIXME: Consider having a ForeignKey type that will handle the '0' special case
if ( folderId != 0 )
{
if ( _Cache::insert( dbConnection, self, req, mrl, folderId ) == false )
return nullptr;
}
else
{
if ( _Cache::insert( dbConnection, self, req, mrl, nullptr ) == false )
return nullptr;
}
self->m_dbConnection = dbConnection;
return self;
}
......@@ -185,6 +198,11 @@ bool File::audioTracks( std::vector<AudioTrackPtr>& tracks )
return SqliteTools::fetchAll<AudioTrack>( m_dbConnection, req, tracks, m_id );
}
bool File::isStandAlone()
{
return m_folderId == 0;
}
bool File::isReady() const
{
return m_isReady;
......@@ -212,12 +230,15 @@ bool File::createTable( DBConnection connection )
"show_episode_id UNSIGNED INTEGER,"
"mrl TEXT UNIQUE ON CONFLICT FAIL,"
"movie_id UNSIGNED INTEGER,"
"folder_id UNSIGNED INTEGER,"
"FOREIGN KEY (album_track_id) REFERENCES " + policy::AlbumTrackTable::Name
+ "(id_track) ON DELETE CASCADE,"
"FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name
+ "(id_episode) ON DELETE CASCADE,"
"FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name
+ "(id_movie) ON DELETE CASCADE"
+ "(id_movie) ON DELETE CASCADE,"
"FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
+ "(id_folder) ON DELETE CASCADE"
")";
if ( SqliteTools::executeRequest( connection, req ) == false )
return false;
......
......@@ -40,9 +40,9 @@ class File : public IFile, public Cache<File, IFile, policy::FileTable, policy::
// shall be well-formed, and private constructor would prevent that.
// There might be a way with a user-defined allocator, but we'll see that later...
File(DBConnection dbConnection , sqlite3_stmt* stmt);
File( const std::string& mrl );
File(const std::string& mrl , unsigned int folderId);
static FilePtr create( DBConnection dbConnection, const std::string& mrl );
static FilePtr create(DBConnection dbConnection, const std::string& mrl , unsigned int folderId);
static bool createTable( DBConnection connection );
virtual unsigned int id() const;
......@@ -63,6 +63,7 @@ class File : public IFile, public Cache<File, IFile, policy::FileTable, policy::
virtual bool videoTracks( std::vector<VideoTrackPtr>& tracks );
virtual bool addAudioTrack(const std::string& codec, unsigned int bitrate , unsigned int sampleRate, unsigned int nbChannels);
virtual bool audioTracks( std::vector<AudioTrackPtr>& tracks );
virtual bool isStandAlone() override;
virtual bool isReady() const;
void setReady();
......@@ -79,6 +80,7 @@ class File : public IFile, public Cache<File, IFile, policy::FileTable, policy::
unsigned int m_showEpisodeId;
std::string m_mrl;
unsigned int m_movieId;
unsigned int m_folderId;
// Auto fetched related properties
Album* m_album;
......
#include "Folder.h"
#include "File.h"
#include "SqliteTools.h"
namespace policy
{
const std::string FolderTable::Name = "Folder";
const std::string FolderTable::CacheColumn = "id_folder";
unsigned int Folder::* const FolderTable::PrimaryKey = &Folder::m_id;
}
Folder::Folder( DBConnection dbConnection, sqlite3_stmt* stmt )
: m_dbConection( dbConnection )
{
m_id = Traits<unsigned int>::Load( stmt, 0 );
m_path = Traits<std::string>::Load( stmt, 1 );
}
Folder::Folder( const std::string& path )
: m_path( path )
{
}
bool Folder::createTable(DBConnection connection)
{
std::string req = "CREATE TABLE IF NOT EXISTS " + policy::FolderTable::Name + "("
+ policy::FolderTable::CacheColumn + " INTEGER PRIMARY KEY AUTOINCREMENT,"
"path TEXT UNIQUE ON CONFLICT FAIL"
")";
return SqliteTools::executeRequest( connection, req );
}
FolderPtr Folder::create(DBConnection connection, const std::string& path)
{
auto self = std::make_shared<Folder>( path );
static const std::string req = "INSERT INTO " + policy::FolderTable::Name +
"(path) VALUES(?)";
if ( _Cache::insert( connection, self, req, path ) == false )
return nullptr;
self->m_dbConection = connection;
return self;
}
unsigned int Folder::id() const
{
return m_id;
}
const std::string& Folder::path()
{
return m_path;
}
std::vector<FilePtr> Folder::files()
{
static const std::string req = "SELECT f.* FROM " + policy::FileTable::Name +
" WHERE f.id_folder = ?";
auto res = std::vector<FilePtr>{};
SqliteTools::fetchAll<File>( m_dbConection, req, res, m_id );
return res;
}
#pragma once
#include "Cache.h"
#include "IFolder.h"
#include <sqlite3.h>
class Folder;
namespace policy
{
struct FolderTable
{
static const std::string Name;
static const std::string CacheColumn;
static unsigned int Folder::*const PrimaryKey;
};
}
class Folder : public IFolder, public Cache<Folder, IFolder, policy::FolderTable>
{
using _Cache = Cache<Folder, IFolder, policy::FolderTable>;
public:
Folder(DBConnection dbConnection, sqlite3_stmt* stmt);
Folder( const std::string& path );
static bool createTable( DBConnection connection );
static FolderPtr create( DBConnection connection, const std::string& path );
virtual unsigned int id() const override;
virtual const std::string& path() override;
virtual std::vector<FilePtr> files() override;
private:
DBConnection m_dbConection;
unsigned int m_id;
std::string m_path;
friend class Cache<Folder, IFolder, policy::FolderTable>;
friend class policy::FolderTable;
};
......@@ -5,6 +5,7 @@
#include "AlbumTrack.h"
#include "AudioTrack.h"
#include "File.h"
#include "Folder.h"
#include "MediaLibrary.h"
#include "IMetadataService.h"
#include "Label.h"
......@@ -15,6 +16,9 @@
#include "SqliteTools.h"
#include "VideoTrack.h"
#include "filesystem/IDirectory.h"
#include "filesystem/IFile.h"
MediaLibrary::MediaLibrary()
: m_parser( new Parser )
{
......@@ -23,6 +27,7 @@ MediaLibrary::MediaLibrary()
MediaLibrary::~MediaLibrary()
{
File::clear();
Folder::clear();
Label::clear();
Album::clear();
AlbumTrack::clear();
......@@ -43,6 +48,7 @@ bool MediaLibrary::initialize(const std::string& dbPath)
if ( SqliteTools::executeRequest( DBConnection(m_dbConnection), "PRAGMA foreign_keys = ON" ) == false )
return false;
return ( File::createTable( m_dbConnection ) &&
Folder::createTable( m_dbConnection ) &&
Label::createTable( m_dbConnection ) &&
Album::createTable( m_dbConnection ) &&
AlbumTrack::createTable( m_dbConnection ) &&
......@@ -66,12 +72,26 @@ FilePtr MediaLibrary::file( const std::string& path )
FilePtr MediaLibrary::addFile( const std::string& path )
{
auto file = File::create( m_dbConnection, path );
auto file = File::create( m_dbConnection, path, 0 );
if ( file == nullptr )
return nullptr;
return file;
}
FolderPtr MediaLibrary::addFolder( const std::string& path )
{
auto folder = Folder::create( m_dbConnection, path );
if ( folder == nullptr )
return nullptr;
auto dir = fs::createDirectory( path );
for ( auto& f : dir->files() )
{
if ( File::create( m_dbConnection, f->fullPath(), folder->id() ) == nullptr )
std::cerr << "Failed to add file " << f->fullPath() << " to the media library" << std::endl;
}
return folder;
}
bool MediaLibrary::deleteFile( const std::string& mrl )
{
return File::destroy( m_dbConnection, mrl );
......@@ -82,6 +102,11 @@ bool MediaLibrary::deleteFile( FilePtr file )
return File::destroy( m_dbConnection, std::static_pointer_cast<File>( file ) );
}
bool MediaLibrary::deleteFolder( FolderPtr folder )
{
return Folder::destroy( m_dbConnection, std::static_pointer_cast<Folder>( folder ) );
}
LabelPtr MediaLibrary::createLabel( const std::string& label )
{
return Label::create( m_dbConnection, label );
......
......@@ -17,8 +17,10 @@ class MediaLibrary : public IMediaLibrary
virtual bool files( std::vector<FilePtr>& res );
virtual FilePtr file( const std::string& path );
virtual FilePtr addFile( const std::string& path );
virtual FolderPtr addFolder( const std::string& path ) override;
virtual bool deleteFile( const std::string& mrl );
virtual bool deleteFile( FilePtr file );
virtual bool deleteFolder( FolderPtr folder ) override;
virtual LabelPtr createLabel( const std::string& label );
virtual bool deleteLabel(const std::string& text );
......
......@@ -196,6 +196,7 @@ class SqliteTools
} while ( res == SQLITE_ROW );
if ( res != SQLITE_DONE )
{
std::cerr << "Failed to execute <" << req << '>' << std::endl;
std::cerr << "Invalid result: " <<
#if SQLITE_VERSION_NUMBER >= 3007015
sqlite3_errstr( res )
......
......@@ -34,6 +34,7 @@ include_directories(${LIBVLC_INCLUDE_DIR})
list(APPEND TEST_SRCS
Files.cpp
Folders.cpp
Labels.cpp
Albums.cpp
Tests.cpp
......
......@@ -38,6 +38,7 @@ TEST_F( Files, Create )
ASSERT_EQ( f->playCount(), 0 );
ASSERT_EQ( f->albumTrack(), nullptr );
ASSERT_EQ( f->showEpisode(), nullptr );
ASSERT_TRUE( f->isStandAlone() );
std::vector<std::shared_ptr<IFile>> files;
bool success = ml->files( files );
......@@ -58,6 +59,7 @@ TEST_F( Files, Fetch )
f2 = ml->file( "/dev/null" );
ASSERT_EQ( f->mrl(), f2->mrl() );
ASSERT_TRUE( f2->isStandAlone() );
}
TEST_F( Files, Delete )
......
#include "gtest/gtest.h"
#include "IFile.h"
#include "IFolder.h"
#include "IMediaLibrary.h"
class Folders : public testing::Test
{
public:
static std::unique_ptr<IMediaLibrary> ml;
protected:
virtual void SetUp()
{
ml.reset( MediaLibraryFactory::create() );
bool res = ml->initialize( "test.db" );
ASSERT_TRUE( res );
}
virtual void TearDown()
{
ml.reset();
unlink("test.db");
}
};
std::unique_ptr<IMediaLibrary> Folders::ml;
TEST_F( Folders, Add )
{
auto f = ml->addFolder( "." );
auto files = std::vector<FilePtr>{};
bool res = ml->files( files );
ASSERT_TRUE( res );
ASSERT_EQ( files.size(), 2u );
ASSERT_FALSE( files[0]->isStandAlone() );
}
TEST_F( Folders, Delete )
{
auto f = ml->addFolder( "." );
auto files = std::vector<FilePtr>{};
bool res = ml->files( files );
ASSERT_TRUE( res );
ASSERT_EQ( files.size(), 2u );
ml->deleteFolder( f );
res = ml->files( files );
ASSERT_TRUE( res );
ASSERT_EQ( files.size(), 0u );
}
TEST_F( Folders, Load )
{
auto f = ml->addFolder( "." );
auto files = std::vector<FilePtr>{};
bool res = ml->files( files );
ASSERT_TRUE( res );
ASSERT_EQ( files.size(), 2u );
SetUp();
res = ml->files( files );
ASSERT_TRUE( res );
ASSERT_EQ( files.size(), 2u );
ASSERT_FALSE( files[0]->isStandAlone() );
ASSERT_FALSE( files[1]->isStandAlone() );
}
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