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

Add History support

parent 1beca96a
/*****************************************************************************
* Media Library
*****************************************************************************
* Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
*
* 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.
*****************************************************************************/
#pragma once
#include "Types.h"
class IHistoryEntry
{
public:
virtual ~IHistoryEntry() = default;
virtual MediaPtr media() const = 0;
virtual const std::string& mrl() const = 0;
virtual unsigned int insertionDate() const = 0;
};
...@@ -104,6 +104,13 @@ class IMediaLibrary ...@@ -104,6 +104,13 @@ class IMediaLibrary
virtual std::vector<PlaylistPtr> playlists() = 0; virtual std::vector<PlaylistPtr> playlists() = 0;
virtual bool deletePlaylist( unsigned int playlistId ) = 0; virtual bool deletePlaylist( unsigned int playlistId ) = 0;
/**
* History
*/
virtual bool addToHistory( MediaPtr media ) = 0;
virtual bool addToHistory( const std::string& mrl ) = 0;
virtual std::vector<HistoryPtr> history() const = 0;
/** /**
* @brief discover Launch a discovery on the provided entry point. * @brief discover Launch a discovery on the provided entry point.
* The actuall discovery will run asynchronously, meaning this method will immediatly return. * The actuall discovery will run asynchronously, meaning this method will immediatly return.
......
...@@ -30,6 +30,7 @@ class IAlbumTrack; ...@@ -30,6 +30,7 @@ class IAlbumTrack;
class IAudioTrack; class IAudioTrack;
class IDiscoverer; class IDiscoverer;
class IFile; class IFile;
class IHistoryEntry;
class IMedia; class IMedia;
class ILabel; class ILabel;
class IMetadataService; class IMetadataService;
...@@ -54,6 +55,7 @@ typedef std::shared_ptr<IAudioTrack> AudioTrackPtr; ...@@ -54,6 +55,7 @@ typedef std::shared_ptr<IAudioTrack> AudioTrackPtr;
typedef std::shared_ptr<IVideoTrack> VideoTrackPtr; typedef std::shared_ptr<IVideoTrack> VideoTrackPtr;
typedef std::shared_ptr<IArtist> ArtistPtr; typedef std::shared_ptr<IArtist> ArtistPtr;
typedef std::shared_ptr<IPlaylist> PlaylistPtr; typedef std::shared_ptr<IPlaylist> PlaylistPtr;
typedef std::shared_ptr<IHistoryEntry> HistoryPtr;
typedef SqliteConnection* DBConnection; typedef SqliteConnection* DBConnection;
......
...@@ -68,6 +68,7 @@ list(APPEND SRC_LIST ${HEADERS_LIST} ...@@ -68,6 +68,7 @@ list(APPEND SRC_LIST ${HEADERS_LIST}
Device.cpp Device.cpp
File.cpp File.cpp
Playlist.cpp Playlist.cpp
History.cpp
parser/Parser.cpp parser/Parser.cpp
parser/ParserService.cpp parser/ParserService.cpp
......
/*****************************************************************************
* Media Library
*****************************************************************************
* Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
*
* 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.
*****************************************************************************/
#include "History.h"
#include "Media.h"
#include "database/SqliteTools.h"
namespace policy
{
const std::string HistoryTable::Name = "History";
const std::string HistoryTable::PrimaryKeyColumn = "id_record";
unsigned int History::* const HistoryTable::PrimaryKey = &History::m_id;
}
constexpr unsigned int History::MaxEntries;
History::History( DBConnection dbConn, sqlite::Row& row )
{
row >> m_id
>> m_mrl
>> m_mediaId
>> m_date;
if ( m_mediaId != 0 )
{
m_media = Media::load( dbConn, row );
}
}
bool History::createTable( DBConnection dbConnection )
{
static const std::string req = "CREATE TABLE IF NOT EXISTS " + policy::HistoryTable::Name +
"("
"id_record INTEGER PRIMARY KEY AUTOINCREMENT,"
"mrl TEXT UNIQUE ON CONFLICT REPLACE,"
"media_id INTEGER UNIQUE ON CONFLICT REPLACE,"
"insertion_date UNSIGNED INT DEFAULT (strftime('%s', 'now')),"
"FOREIGN KEY (media_id) REFERENCES " + policy::MediaTable::Name
+ "(id_media) ON DELETE CASCADE"
")";
static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS limit_nb_records AFTER INSERT ON "
+ policy::HistoryTable::Name +
" BEGIN "
"DELETE FROM " + policy::HistoryTable::Name + " WHERE id_record in "
"(SELECT id_record FROM " + policy::HistoryTable::Name +
" ORDER BY insertion_date DESC LIMIT -1 OFFSET " + std::to_string( MaxEntries ) + ");"
" END";
return sqlite::Tools::executeRequest( dbConnection, req ) &&
sqlite::Tools::executeRequest( dbConnection, triggerReq );
}
bool History::insert(DBConnection dbConn, const IMedia& media )
{
static const std::string req = "INSERT INTO " + policy::HistoryTable::Name + "(media_id)"
"VALUES(?)";
return sqlite::Tools::insert( dbConn, req, media.id() ) != 0;
}
bool History::insert( DBConnection dbConn, const std::string& mrl )
{
static const std::string req = "INSERT INTO " + policy::HistoryTable::Name + "(mrl)"
"VALUES(?)";
return sqlite::Tools::insert( dbConn, req, mrl ) != 0;
}
std::vector<std::shared_ptr<IHistoryEntry> > History::fetch( DBConnection dbConn )
{
static const std::string req = "SELECT * FROM " + policy::HistoryTable::Name + " h "
" LEFT OUTER JOIN " + policy::MediaTable::Name + " m ON m.id_media = h.media_id"
" ORDER BY insertion_date DESC";
return sqlite::Tools::fetchAll<History, IHistoryEntry>( dbConn, req );
}
MediaPtr History::media() const
{
return m_media;
}
const std::string& History::mrl() const
{
return m_mrl;
}
unsigned int History::insertionDate() const
{
return m_date;
}
/*****************************************************************************
* Media Library
*****************************************************************************
* Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
*
* 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.
*****************************************************************************/
#pragma once
#include "Types.h"
#include "database/DatabaseHelpers.h"
#include "IHistoryEntry.h"
#include <vector>
#include <string>
class History;
class Media;
namespace policy
{
struct HistoryTable
{
static const std::string Name;
static const std::string PrimaryKeyColumn;
static unsigned int History::* const PrimaryKey;
};
}
class History : public IHistoryEntry, public DatabaseHelpers<History, policy::HistoryTable>
{
public:
History( DBConnection dbConn, sqlite::Row& row );
static bool createTable( DBConnection dbConnection );
static bool insert( DBConnection dbConn, const IMedia& media );
static bool insert( DBConnection dbConn, const std::string& mrl );
static std::vector<std::shared_ptr<IHistoryEntry>> fetch(DBConnection dbConn);
virtual MediaPtr media() const override;
virtual const std::string& mrl() const override;
virtual unsigned int insertionDate() const override;
static constexpr unsigned int MaxEntries = 100u;
private:
unsigned int m_id;
std::string m_mrl;
unsigned int m_mediaId;
unsigned int m_date;
std::shared_ptr<Media> m_media;
friend policy::HistoryTable;
};
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "Device.h" #include "Device.h"
#include "File.h" #include "File.h"
#include "Folder.h" #include "Folder.h"
#include "History.h"
#include "Media.h" #include "Media.h"
#include "MediaLibrary.h" #include "MediaLibrary.h"
#include "Label.h" #include "Label.h"
...@@ -105,6 +106,7 @@ MediaLibrary::~MediaLibrary() ...@@ -105,6 +106,7 @@ MediaLibrary::~MediaLibrary()
Device::clear(); Device::clear();
File::clear(); File::clear();
Playlist::clear(); Playlist::clear();
History::clear();
// Explicitely release the connection's TLS // Explicitely release the connection's TLS
if ( m_dbConnection != nullptr ) if ( m_dbConnection != nullptr )
m_dbConnection->release(); m_dbConnection->release();
...@@ -138,6 +140,7 @@ bool MediaLibrary::createAllTables() ...@@ -138,6 +140,7 @@ bool MediaLibrary::createAllTables()
Artist::createTriggers( m_dbConnection.get() ) && Artist::createTriggers( m_dbConnection.get() ) &&
Media::createTriggers( m_dbConnection.get() ) && Media::createTriggers( m_dbConnection.get() ) &&
Playlist::createTriggers( m_dbConnection.get() ) && Playlist::createTriggers( m_dbConnection.get() ) &&
History::createTable( m_dbConnection.get() ) &&
Settings::createTable( m_dbConnection.get() ); Settings::createTable( m_dbConnection.get() );
if ( res == false ) if ( res == false )
return false; return false;
...@@ -373,6 +376,23 @@ bool MediaLibrary::deletePlaylist( unsigned int playlistId ) ...@@ -373,6 +376,23 @@ bool MediaLibrary::deletePlaylist( unsigned int playlistId )
return Playlist::destroy( m_dbConnection.get(), playlistId ); return Playlist::destroy( m_dbConnection.get(), playlistId );
} }
bool MediaLibrary::addToHistory( MediaPtr media )
{
if ( media == nullptr )
return false;
return History::insert( m_dbConnection.get(), *media );
}
bool MediaLibrary::addToHistory( const std::string& mrl )
{
return History::insert( m_dbConnection.get(), mrl );
}
std::vector<HistoryPtr> MediaLibrary::history() const
{
return History::fetch( m_dbConnection.get() );
}
void MediaLibrary::startParser() void MediaLibrary::startParser()
{ {
m_parser.reset( new Parser( m_dbConnection.get(), this, m_callback ) ); m_parser.reset( new Parser( m_dbConnection.get(), this, m_callback ) );
......
...@@ -82,6 +82,10 @@ class MediaLibrary : public IMediaLibrary ...@@ -82,6 +82,10 @@ class MediaLibrary : public IMediaLibrary
virtual std::vector<PlaylistPtr> playlists() override; virtual std::vector<PlaylistPtr> playlists() override;
virtual bool deletePlaylist( unsigned int playlistId ) override; virtual bool deletePlaylist( unsigned int playlistId ) override;
virtual bool addToHistory( MediaPtr media );
virtual bool addToHistory( const std::string& mrl );
virtual std::vector<HistoryPtr> history() const;
virtual void discover( const std::string& entryPoint ) override; virtual void discover( const std::string& entryPoint ) override;
bool banFolder( const std::string& path ) override; bool banFolder( const std::string& path ) override;
bool unbanFolder( const std::string& path ) override; bool unbanFolder( const std::string& path ) override;
......
/*****************************************************************************
* Media Library
*****************************************************************************
* Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
*
* 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.
*****************************************************************************/
#include "Tests.h"
#include "IHistoryEntry.h"
#include "History.h"
#include "Media.h"
class HistoryTest : public Tests
{
};
TEST_F( HistoryTest, InsertMrl )
{
ml->addToHistory( "upnp://stream" );
auto hList = ml->history();
ASSERT_EQ( 1u, hList.size() );
auto h = hList[0];
ASSERT_EQ( nullptr, h->media() );
ASSERT_EQ( h->mrl(), "upnp://stream" );
ASSERT_NE( 0u, h->insertionDate() );
}
TEST_F( HistoryTest, InsertMedia )
{
auto media = ml->addFile( "media.mkv" );
ml->addToHistory( media );
auto hList = ml->history();
ASSERT_EQ( 1u, hList.size() );
auto h = hList[0];
ASSERT_NE( nullptr, h->media() );
ASSERT_EQ( media->id(), h->media()->id() );
ASSERT_EQ( h->mrl(), "" );
ASSERT_NE( 0u, h->insertionDate() );
}
TEST_F( HistoryTest, MaxEntries )
{
for ( auto i = 0u; i < History::MaxEntries; ++i )
{
ml->addToHistory( std::to_string( i ) );
}
auto hList = ml->history();
ASSERT_EQ( History::MaxEntries, hList.size() );
ml->addToHistory( "new-media" );
hList = ml->history();
ASSERT_EQ( History::MaxEntries, hList.size() );
ASSERT_EQ( "1", hList[99]->mrl() );
}
TEST_F( HistoryTest, Ordering )
{
ml->addToHistory( "first-stream" );
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
ml->addToHistory( "second-stream" );
auto hList = ml->history();
ASSERT_EQ( 2u, hList.size() );
ASSERT_EQ( hList[0]->mrl(), "second-stream" );
ASSERT_EQ( hList[1]->mrl(), "first-stream" );
}
TEST_F( HistoryTest, UpdateInsertionDate )
{
ml->addToHistory( "stream" );
auto hList = ml->history();
ASSERT_EQ( 1u, hList.size() );
auto date = hList[0]->insertionDate();
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
ml->addToHistory( "stream" );
hList = ml->history();
ASSERT_EQ( 1u, hList.size() );
ASSERT_NE( date, hList[0]->insertionDate() );
}
TEST_F( HistoryTest, DeleteMedia )
{
auto m = ml->addFile( "media.mkv" );
ml->addToHistory( m );
auto hList = ml->history();
ASSERT_EQ( 1u, hList.size() );
auto f = m->files()[0];
m->removeFile( static_cast<File&>( *f ) );
hList = ml->history();
ASSERT_EQ( 0u, hList.size() );
}
...@@ -14,6 +14,7 @@ list(APPEND TEST_SRCS ...@@ -14,6 +14,7 @@ list(APPEND TEST_SRCS
unittest/DeviceTests.cpp unittest/DeviceTests.cpp
unittest/FileTests.cpp unittest/FileTests.cpp
unittest/PlaylistTests.cpp unittest/PlaylistTests.cpp
unittest/HistoryTests.cpp
mocks/FileSystem.h mocks/FileSystem.h
mocks/FileSystem.cpp mocks/FileSystem.cpp
......
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