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

Implement caching

parent 7fd486c7
#ifndef IALBUM_H
#define IALBUM_H
#include <memory>
#include <string>
#include <vector>
......@@ -16,7 +17,7 @@ class IAlbum
virtual unsigned int releaseYear() = 0;
virtual const std::string& shortSummary() = 0;
virtual const std::string& artworkUrl() = 0;
virtual const std::vector<IAlbumTrack*>& tracks() = 0;
virtual const std::vector<std::shared_ptr<IAlbumTrack>>& tracks() = 0;
};
#endif // IALBUM_H
#ifndef IALBUMTRACK_H
#define IALBUMTRACK_H
#include <memory>
#include <string>
class IAlbum;
......@@ -13,7 +14,7 @@ class IAlbumTrack
virtual const std::string& genre() = 0;
virtual const std::string& title() = 0;
virtual unsigned int trackNumber() = 0;
virtual IAlbum* album() = 0;
virtual std::shared_ptr<IAlbum> album() = 0;
};
#endif // IALBUMTRACK_H
......@@ -2,7 +2,7 @@
#define IFILE_H
#include <vector>
#include <memory>
#include "ITrackInformation.h"
class IAlbumTrack;
......@@ -16,15 +16,14 @@ class IFile
virtual ~IFile() {}
virtual unsigned int id() const = 0;
virtual IAlbumTrack* albumTrack() = 0;
virtual std::shared_ptr<IAlbumTrack> albumTrack() = 0;
virtual unsigned int duration() = 0;
virtual IShowEpisode* showEpisode() = 0;
virtual std::shared_ptr<IShowEpisode> showEpisode() = 0;
virtual int playCount() = 0;
virtual const std::string& mrl() = 0;
virtual ILabel* addLabel( const std::string& label ) = 0;
virtual bool removeLabel( const ILabel* label ) = 0;
virtual std::vector<ILabel*> labels() = 0;
virtual std::shared_ptr<ILabel> addLabel( const std::string& label ) = 0;
virtual bool removeLabel( const std::shared_ptr<ILabel>& label ) = 0;
virtual const std::vector<std::shared_ptr<ILabel>>& labels() = 0;
};
#endif // IFILE_H
#ifndef ILABEL_H
#define ILABEL_H
#include <memory>
#include <vector>
class IFile;
......@@ -12,7 +13,7 @@ class ILabel
virtual unsigned int id() const = 0;
virtual const std::string& name() = 0;
virtual std::vector<IFile*> files() = 0;
virtual std::vector<std::shared_ptr<IFile>>& files() = 0;
virtual bool link( IFile* file ) = 0;
virtual bool unlink( IFile* file ) const = 0;
};
......
......@@ -3,17 +3,20 @@
#include <vector>
#include <string>
#include <memory>
#include "IFile.h"
class IFile;
typedef std::shared_ptr<IFile> FilePtr;
class IMediaLibrary
{
public:
virtual ~IMediaLibrary() {}
virtual bool initialize( const std::string& dbPath ) = 0;
virtual IFile* addFile( const std::string& path ) = 0;
virtual IFile* file( const std::string& path ) = 0;
virtual const std::vector<IFile*>& files() = 0;
virtual FilePtr addFile( const std::string& path ) = 0;
virtual FilePtr file( const std::string& path ) = 0;
virtual bool files( std::vector<FilePtr>& res ) = 0;
};
class MediaLibraryFactory
......
......@@ -3,6 +3,7 @@
class IShow;
#include <memory>
#include <string>
class IShowEpisode
......@@ -17,7 +18,7 @@ class IShowEpisode
virtual unsigned int seasonNuber() = 0;
virtual const std::string& shortSummary() = 0;
virtual const std::string& tvdbId() = 0;
virtual IShow* show() = 0;
virtual std::shared_ptr<IShow> show() = 0;
};
#endif // ISHOWEPISODE_H
......@@ -3,6 +3,9 @@
#include "SqliteTools.h"
const std::string policy::AlbumTable::Name = "Album";
const std::string policy::AlbumTable::CacheColumn = "id_album";
Album::Album(sqlite3* dbConnection, sqlite3_stmt* stmt)
: m_dbConnection( dbConnection )
{
......@@ -39,12 +42,13 @@ time_t Album::lastSyncDate()
return m_lastSyncDate;
}
const std::vector<IAlbumTrack*>&Album::tracks()
const std::vector<std::shared_ptr<IAlbumTrack>>& Album::tracks()
{
if ( m_tracks == NULL )
{
m_tracks = new std::vector<std::shared_ptr<IAlbumTrack>>;
const char* req = "SELECT * FROM AlbumTrack WHERE id_album = ?";
SqliteTools::fetchAll<AlbumTrack>( m_dbConnection, req, m_id, m_tracks );
SqliteTools::fetchAll<AlbumTrack>( m_dbConnection, req, m_id, *m_tracks );
}
return *m_tracks;
}
......@@ -61,9 +65,3 @@ bool Album::createTable( sqlite3* dbConnection )
")";
return SqliteTools::createTable( dbConnection, req );
}
Album* Album::fetch(sqlite3* dbConnection, unsigned int albumTrackId)
{
const char* req = "SELECT * FROM Album WHERE id_album = ?";
return SqliteTools::fetchOne<Album>( dbConnection, req, albumTrackId );
}
#ifndef ALBUM_H
#define ALBUM_H
#include <memory>
#include <sqlite3.h>
#include "Cache.h"
#include "IAlbum.h"
class Album : public IAlbum
namespace policy
{
struct AlbumTable
{
static const std::string Name;
static const std::string CacheColumn;
};
}
class Album : public IAlbum, public Cache<Album, IAlbum, policy::AlbumTable>
{
public:
Album( sqlite3* dbConnection, sqlite3_stmt* stmt );
......@@ -15,10 +26,9 @@ class Album : public IAlbum
virtual const std::string& shortSummary();
virtual const std::string& artworkUrl();
virtual time_t lastSyncDate();
virtual const std::vector<IAlbumTrack*>& tracks();
virtual const std::vector<std::shared_ptr<IAlbumTrack>>& tracks();
static bool createTable( sqlite3* dbConnection );
static Album* fetch( sqlite3* dbConnection, unsigned int albumTrackId );
protected:
sqlite3* m_dbConnection;
......@@ -29,7 +39,9 @@ class Album : public IAlbum
std::string m_artworkUrl;
time_t m_lastSyncDate;
std::vector<IAlbumTrack*>* m_tracks;
std::vector<std::shared_ptr<IAlbumTrack>>* m_tracks;
friend class Cache<Album, IAlbum, policy::AlbumTable>;
};
#endif // ALBUM_H
......@@ -2,6 +2,9 @@
#include "Album.h"
#include "SqliteTools.h"
const std::string policy::AlbumTrackTable::Name = "AlbumTrack";
const std::string policy::AlbumTrackTable::CacheColumn = "id_track";
AlbumTrack::AlbumTrack( sqlite3* dbConnection, sqlite3_stmt* stmt )
: m_dbConnection( dbConnection )
, m_album( NULL )
......@@ -41,12 +44,11 @@ unsigned int AlbumTrack::trackNumber()
return m_trackNumber;
}
IAlbum* AlbumTrack::album()
std::shared_ptr<IAlbum> AlbumTrack::album()
{
if ( m_album == NULL && m_albumId != 0 )
if ( m_album == nullptr && m_albumId != 0 )
{
const char* req = "SELECT * FROM Album WHERE id_album = ?";
m_album = SqliteTools::fetchOne<Album>( m_dbConnection, req, m_albumId );
m_album = Album::fetch( m_dbConnection, m_albumId );
}
return m_album;
}
......@@ -5,18 +5,28 @@
#include <string>
#include "IAlbumTrack.h"
#include "Cache.h"
class Album;
class AlbumTrack : public IAlbumTrack
namespace policy
{
struct AlbumTrackTable
{
static const std::string Name;
static const std::string CacheColumn;
};
}
class AlbumTrack : public IAlbumTrack, public Cache<AlbumTrack, IAlbumTrack, policy::AlbumTrackTable>
{
public:
AlbumTrack( sqlite3* dbConnection, sqlite3_stmt* stmt );
virtual const std::string&genre();
virtual const std::string&title();
virtual const std::string& genre();
virtual const std::string& title();
virtual unsigned int trackNumber();
virtual IAlbum*album();
virtual std::shared_ptr<IAlbum> album();
static bool createTable( sqlite3* dbConnection );
......@@ -28,7 +38,9 @@ class AlbumTrack : public IAlbumTrack
unsigned int m_trackNumber;
unsigned int m_albumId;
Album* m_album;
std::shared_ptr<Album> m_album;
friend class Cache<AlbumTrack, IAlbumTrack, policy::AlbumTrackTable>;
};
#endif // ALBUMTRACK_H
#ifndef CACHE_H
#define CACHE_H
#include <memory>
#include <mutex>
#include <unordered_map>
#include "SqliteTools.h"
template <typename TYPE>
class PrimaryKeyCacheKeyPolicy
{
public:
typedef unsigned int KeyType;
static unsigned int key( const std::shared_ptr<TYPE>& self )
{
return self->id();
}
static unsigned int key( sqlite3_stmt* stmt )
{
return Traits<unsigned int>::Load( stmt, 0 );
}
};
/**
* This utility class eases up the implementation of caching.
* It has a few assumptions about class that will use it:
* - The class is stored in database and has a dedicated type.
* - The class has a static std::string member named TableName, that can be used to generate
* requests
* - The class has a static std::string member named CacheColumn, that holds the name of
* the column that will act as a primary key in the cache.
* - The class has a public method of signature "unsigned int id()" that can be used to
* fetch the primary key
* - The class has 2 contructors:
* - One that takes a sqlite3* representing the active DB connection, and
* a sqlite3_stmt* that holds the row currently being fetched. This CTOR is used by
* the load method, when fetching an existing record which isn't cached yet.
* - One that can be used to instantiate a new record, without parameter restriction
*
* Template parameter:
* - IMPL: The actual implementation. Typically the type that inherits this class
* - INTF: An interface that express the type. This is used when fetching containers, as they
* are not polymorphic.
* - CACHECOLUMNTYPE: The type of the column representing a record in a unique way. Quite
* likely the primary key type
* - CACHECOLUMNINDEX: The index of the column used by the cache, to be able to fetch it
* from a sqlite3_stmt containing a row of the stored entity type.
*
* How to use it:
* - Inherit this class and specify the template parameter accordingly
* - Make this class a friend class of the class you inherit from
* - Implement the 2 static strings defined above.
*/
template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY = PrimaryKeyCacheKeyPolicy<IMPL>>
class Cache
{
public:
static std::shared_ptr<IMPL> fetch( sqlite3* dbConnection, const typename CACHEPOLICY::KeyType& key )
{
std::lock_guard<std::mutex> lock( Mutex );
auto it = Store.find( key );
if ( it != Store.end() )
return it->second;
std::string req = "SELECT * FROM" + TABLEPOLICY::Name +
" WHERE " + TABLEPOLICY::CacheColumn + " = ?";
auto res = SqliteTools::fetchOne<IMPL>( dbConnection, req.c_str(), key );
Store[key] = res;
return res;
}
/*
* Will fetch all elements from the database & cache them.
*
* @param res A reference to the result vector. All existing elements will
* be discarded.
*/
static bool fetchAll( sqlite3* dbConnection, std::vector<std::shared_ptr<INTF>>& res )
{
std::string req = "SELECT * FROM " + TABLEPOLICY::Name;
return SqliteTools::fetchAll<IMPL, INTF>( dbConnection, req.c_str(), res );
}
static std::shared_ptr<IMPL> load( sqlite3* dbConnection, sqlite3_stmt* stmt )
{
auto cacheKey = CACHEPOLICY::key( stmt );
std::lock_guard<std::mutex> lock( Mutex );
auto it = Store.find( cacheKey );
if ( it != Store.end() )
return it->second;
auto inst = std::make_shared<IMPL>( dbConnection, stmt );
Store[cacheKey] = inst;
return inst;
}
/*
* Create a new instance of the cache class.
* This doesn't check for the existence of a similar entity already cached.
*/
template <typename... Args>
static std::shared_ptr<IMPL> create( Args&&... args )
{
auto res = std::make_shared<IMPL>( std::forward<Args>( args )... );
typename CACHEPOLICY::KeyType cacheKey = CACHEPOLICY::key( res );
std::lock_guard<std::mutex> lock( Mutex );
Store[cacheKey] = res;
return res;
}
private:
static std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL>> Store;
static std::mutex Mutex;
};
template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
std::unordered_map<typename CACHEPOLICY::KeyType, std::shared_ptr<IMPL>>
Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Store;
template <typename IMPL, typename INTF, typename TABLEPOLICY, typename CACHEPOLICY>
std::mutex Cache<IMPL, INTF, TABLEPOLICY, CACHEPOLICY>::Mutex;
#endif // CACHE_H
......@@ -9,11 +9,12 @@
#include "ShowEpisode.h"
#include "SqliteTools.h"
const std::string policy::FileTable::Name = "File";
const std::string policy::FileTable::CacheColumn = "mrl";
File::File( sqlite3* dbConnection, sqlite3_stmt* stmt )
: m_dbConnection( dbConnection )
, m_albumTrack( NULL )
, m_showEpisode( NULL )
, m_labels( NULL )
, m_labels( nullptr )
{
m_id = sqlite3_column_int( stmt, 0 );
m_type = (Type)sqlite3_column_int( stmt, 1 );
......@@ -33,9 +34,7 @@ File::File( const std::string& mrl )
, m_playCount( 0 )
, m_showEpisodeId( 0 )
, m_mrl( mrl )
, m_albumTrack( NULL )
, m_showEpisode( NULL )
, m_labels( NULL )
, m_labels( nullptr )
{
}
......@@ -64,9 +63,9 @@ bool File::insert( sqlite3* dbConnection )
return true;
}
IAlbumTrack* File::albumTrack()
std::shared_ptr<IAlbumTrack> File::albumTrack()
{
if ( m_albumTrack == NULL && m_albumTrackId != 0 )
if ( m_albumTrack == nullptr && m_albumTrackId != 0 )
{
const char* req = "SELECT * FROM AlbumTrack WHERE id_track = ?";
m_albumTrack = SqliteTools::fetchOne<AlbumTrack>( m_dbConnection, req, m_albumTrackId );
......@@ -79,24 +78,24 @@ unsigned int File::duration()
return m_duration;
}
IShowEpisode*File::showEpisode()
std::shared_ptr<IShowEpisode> File::showEpisode()
{
if ( m_showEpisode == NULL && m_showEpisodeId != 0 )
if ( m_showEpisode == nullptr && m_showEpisodeId != 0 )
{
const char* req = "SELECT * FROM ShowEpisode WHERE id_episode = ?";
m_showEpisode = SqliteTools::fetchOne<ShowEpisode>( m_dbConnection, req, m_showEpisodeId );
m_showEpisode = ShowEpisode::fetch( m_dbConnection, m_showEpisodeId );
}
return m_showEpisode;
}
std::vector<ILabel*> File::labels()
const std::vector<std::shared_ptr<ILabel>>& File::labels()
{
if ( m_labels == NULL )
if ( m_labels == nullptr )
{
m_labels = new std::vector<std::shared_ptr<ILabel>>;
const char* req = "SELECT l.* FROM Label l "
"LEFT JOIN LabelFileRelation lfr ON lfr.id_label = l.id_label "
"WHERE lfr.id_file = ?";
SqliteTools::fetchAll<Label>( m_dbConnection, req, m_id, m_labels );
SqliteTools::fetchAll<Label>( m_dbConnection, req, m_id, *m_labels );
}
return *m_labels;
}
......@@ -111,24 +110,23 @@ const std::string& File::mrl()
return m_mrl;
}
ILabel* File::addLabel(const std::string& label)
std::shared_ptr<ILabel> File::addLabel(const std::string& label)
{
Label* l = new Label( label );
auto l = Label::create( label );
if ( l->insert( m_dbConnection ) == false )
{
delete l;
return NULL;
return nullptr;
}
l->link( this );
return l;
}
bool File::removeLabel( const ILabel* label )
bool File::removeLabel( const std::shared_ptr<ILabel>& label )
{
if ( m_labels != NULL )
if ( m_labels != false )
{
std::vector<ILabel*>::iterator it = m_labels->begin();
std::vector<ILabel*>::iterator ite = m_labels->end();
auto it = m_labels->begin();
auto ite = m_labels->end();
while ( it != ite )
{
if ( (*it)->id() == label->id() )
......@@ -149,6 +147,7 @@ unsigned int File::id() const
bool File::createTable(sqlite3* connection)
{
//FIXME: File is hardcoded
const char* req = "CREATE TABLE IF NOT EXISTS File("
"id_file INTEGER PRIMARY KEY AUTOINCREMENT,"
"type INTEGER,"
......@@ -160,3 +159,14 @@ bool File::createTable(sqlite3* connection)
")";
return SqliteTools::createTable( connection, req );
}
const std::string& policy::FileCache::key(const std::shared_ptr<File> self )
{
return self->mrl();
}
std::string policy::FileCache::key(sqlite3_stmt* stmt)
{
return Traits<std::string>::Load( stmt, 6 );
}
#ifndef FILE_H
#define FILE_H
#include <sqlite3.h>
#include "IFile.h"
#include "Cache.h"
class Album;
class ShowEpisode;
class AlbumTrack;
class File : public IFile
class File;
namespace policy
{
struct FileTable
{
static const std::string Name;
static const std::string CacheColumn;
};
struct FileCache
{
typedef std::string KeyType;
static const std::string& key(const std::shared_ptr<File> self);
static std::string key( sqlite3_stmt* stmt );
};
}
class File : public IFile, public Cache<File, IFile, policy::FileTable, policy::FileCache>
{
public:
enum Type
{
VideoType, // Any video file, not being a tv show episode
......@@ -21,26 +42,29 @@ class File : public IFile
UnknownType,
};
// Those should be private, however the standard states that the expression
// ::new (pv) T(std::forward(args)...)
// 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(sqlite3* dbConnection , sqlite3_stmt* stmt);
File( const std::string& mrl );
bool insert(sqlite3* dbConnection);
static bool createTable(sqlite3* connection);
virtual unsigned int id() const;
virtual IAlbumTrack* albumTrack();
virtual std::shared_ptr<IAlbumTrack> albumTrack();
virtual unsigned int duration();
virtual IShowEpisode* showEpisode();
virtual std::vector<ILabel*> labels();
virtual std::shared_ptr<IShowEpisode> showEpisode();
virtual const std::vector<std::shared_ptr<ILabel>>& labels();
virtual int playCount();
virtual const std::string& mrl();
virtual ILabel* addLabel( const std::string &label );
virtual bool removeLabel( const ILabel* label );
static bool createTable( sqlite3* connection );
virtual std::shared_ptr<ILabel> addLabel( const std::string &label );
virtual bool removeLabel(const std::shared_ptr<ILabel>& label );
private:
sqlite3* m_dbConnection;
// DB fields:
unsigned int m_id;
Type m_type;
......@@ -52,9 +76,11 @@ class File : public IFile
// Auto fetched related properties
Album* m_album;
AlbumTrack* m_albumTrack;
ShowEpisode* m_showEpisode;
std::vector<ILabel*>* m_labels;
std::shared_ptr<AlbumTrack> m_albumTrack;
std::shared_ptr<ShowEpisode> m_showEpisode;
std::vector<std::shared_ptr<ILabel>>* m_labels;
friend class Cache<File, IFile, policy::FileTable, policy::FileCache>;
};
#endif // FILE_H
......@@ -6,6 +6,9 @@
#include "File.h"
#include "SqliteTools.h"
const std::string policy::LabelTable::Name = "Label";
const std::string policy::LabelTable::CacheColumn = "id_label";
Label::Label( sqlite3* dbConnection, sqlite3_stmt* stmt )
: m_dbConnection( dbConnection )
, m_files( NULL )
......@@ -33,21 +36,22 @@ const std::string& Label::name()
return m_name;
}
std::vector<IFile*> Label::files()
std::vector<std::shared_ptr<IFile>>& Label::files()
{
if ( m_files == NULL )
if ( m_files == nullptr )
{
m_files = new std::vector<std::shared_ptr<IFile>>;
const char* req = "SELECT f.* FROM Files f "
"LEFT JOIN LabelFileRelation lfr ON lfr.id_file = f.id_file "
"WHERE lfr.id_label = ?";
SqliteTools::fetchAll<File>( m_dbConnection, req, m_id, m_files );
SqliteTools::fetchAll<File>( m_dbConnection, req, m_id, *m_files );
}
return *m_files;
}
bool Label::insert( sqlite3* dbConnection )
{
assert( m_dbConnection == NULL );
assert( m_dbConnection == nullptr );
assert( m_id == 0 );
sqlite3_stmt* stmt;
const char* req = "INSERT INTO Label VALUES(NULL, ?)";
......@@ -84,7 +88,7 @@ bool Label::createTable(sqlite3* dbConnection)
bool Label::link( IFile* file )
{
if ( m_dbConnection == NULL || m_id == 0 )
if ( m_dbConnection == nullptr || m_id == 0 )
{
std::cerr << "A label needs to be inserted in database before being linked to a file" << std::endl;
return false;
......@@ -105,7 +109,7 @@ bool Label::link( IFile* file )
bool Label::unlink( IFile* file ) const
{
if ( m_dbConnection == NULL || m_id == 0 )
if ( m_dbConnection == nullptr || m_id == 0 )
{
std::cerr << "Can't unlink a label not inserted in database" << std::endl;
return false;
......
......@@ -4,19 +4,31 @@
#include <sqlite3.h>
#include <string>
#include "ILabel.h"
class File;
class Label : public ILabel
#include "ILabel.h"
#include "Cache.h"
namespace policy
{
struct LabelTable
{
static const std::string Name;
static const std::string CacheColumn;
};
}
class Label : public ILabel, public Cache<Label, ILabel, policy::LabelTable>
{
public:
Label(sqlite3* dbConnection, sqlite3_stmt* stmt);
Label( const std::string& name );
public:
virtual unsigned int id() const;
virtual const std::string& name();
virtual std::vector<IFile*> files();
virtual std::vector<std::shared_ptr<IFile>>& files();
bool insert( sqlite3* dbConnection );
static bool createTable( sqlite3* dbConnection );
......@@ -26,7 +38,9 @@ class Label : public ILabel
sqlite3* m_dbConnection;
unsigned int m_id;
std::string m_name;
std::vector<IFile*>* m_files;
std::vector<std::shared_ptr<IFile>>* m_files;
friend class Cache<Label, ILabel, policy::LabelTable>;
};
#endif // LABEL_H
......@@ -9,7 +9,6 @@
MediaLibrary::MediaLibrary()
: m_dbConnection( NULL )
, m_files( NULL )
{
}
......@@ -32,44 +31,22 @@ bool MediaLibrary::initialize(const std::string& dbPath)
}
const std::vector<IFile*>& MediaLibrary::files()
bool MediaLibrary::files( std::vector<FilePtr>& res )
{
if ( m_files == NULL )
{
const char* req = "SELECT * FROM File";
SqliteTools::fetchAll<File>( m_dbConnection, req, m_files );
}
return *m_files;
return File::fetchAll( m_dbConnection, res );
}
IFile*MediaLibrary::file( const std::string& path )
FilePtr MediaLibrary::file( const std::string& path )
{
if ( m_files == NULL )