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

Rework stream history

It's now using the regular Media table & requests
parent 8575a814
......@@ -26,7 +26,6 @@ libmedialibrary_la_HEADERS = \
include/medialibrary/IAudioTrack.h \
include/medialibrary/IFile.h \
include/medialibrary/IGenre.h \
include/medialibrary/IHistoryEntry.h \
include/medialibrary/ILabel.h \
include/medialibrary/ILogger.h \
include/medialibrary/IMedia.h \
......@@ -68,7 +67,6 @@ libmedialibrary_la_SOURCES = \
src/File.cpp \
src/Folder.cpp \
src/Genre.cpp \
src/History.cpp \
src/Label.cpp \
src/Media.cpp \
src/MediaLibrary.cpp \
......@@ -140,7 +138,6 @@ noinst_HEADERS = \
src/filesystem/win32/File.h \
src/Folder.h \
src/Genre.h \
src/History.h \
src/Label.h \
src/logging/IostreamLogger.h \
src/logging/Logger.h \
......@@ -293,7 +290,6 @@ unittest_SOURCES = \
test/unittest/FsUtilsTests.cpp \
test/unittest/UrlTests.cpp \
test/unittest/GenreTests.cpp \
test/unittest/HistoryTests.cpp \
test/unittest/LabelTests.cpp \
test/unittest/MediaTests.cpp \
test/unittest/MovieTests.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.
*****************************************************************************/
#pragma once
#include <string>
#include "medialibrary/Types.h"
namespace medialibrary
{
class IHistoryEntry
{
public:
virtual ~IHistoryEntry() = default;
virtual MediaPtr media() const = 0;
virtual unsigned int insertionDate() const = 0;
};
}
......@@ -311,9 +311,8 @@ class IMediaLibrary
/**
* History
*/
virtual bool addToStreamHistory( MediaPtr media ) = 0;
virtual Query<IHistoryEntry> lastStreamsPlayed() const = 0;
virtual Query<IMedia> lastMediaPlayed() const = 0;
virtual Query<IMedia> history() const = 0;
virtual Query<IMedia> streamHistory() const = 0;
/**
* @brief clearHistory will clear both streams history & media history.
* @return true in case of success, false otherwise. The database will stay untouched in case
......
......@@ -32,7 +32,6 @@ class IAlbumTrack;
class IAudioTrack;
class IFile;
class IGenre;
class IHistoryEntry;
class IMedia;
class ILabel;
class IMetadataService;
......@@ -64,7 +63,6 @@ using ArtistPtr = std::shared_ptr<IArtist>;
using AudioTrackPtr = std::shared_ptr<IAudioTrack>;
using FilePtr = std::shared_ptr<IFile>;
using GenrePtr = std::shared_ptr<IGenre>;
using HistoryPtr = std::shared_ptr<IHistoryEntry>;
using LabelPtr = std::shared_ptr<ILabel>;
using MediaPtr = std::shared_ptr<IMedia>;
using MoviePtr = std::shared_ptr<IMovie>;
......
/*****************************************************************************
* 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.
*****************************************************************************/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "History.h"
#include "Media.h"
#include "database/SqliteTools.h"
#include "database/SqliteQuery.h"
namespace medialibrary
{
namespace policy
{
const std::string HistoryTable::Name = "History";
const std::string HistoryTable::PrimaryKeyColumn = "id_media";
int64_t History::* const HistoryTable::PrimaryKey = &History::m_mediaId;
}
constexpr unsigned int History::MaxEntries;
History::History( MediaLibraryPtr ml, sqlite::Row& row )
: m_media( Media::load( ml, row ) )
{
// In case we load the media from cache, we won't advance in columns
row.advanceToColumn( row.nbColumns() - 1 );
row >> m_date;
}
void History::createTable( sqlite::Connection* dbConnection )
{
const std::string req = "CREATE TABLE IF NOT EXISTS " + policy::HistoryTable::Name +
"("
"id_media INTEGER PRIMARY KEY,"
"insertion_date UNSIGNED INT NOT NULL,"
"FOREIGN KEY (id_media) REFERENCES " + policy::MediaTable::Name +
"(id_media) ON DELETE CASCADE"
")";
// Don't index the id_media field, we don't want to select history records using the media_id
sqlite::Tools::executeRequest( dbConnection, req );
}
void History::createTriggers(sqlite::Connection* dbConnection)
{
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_media in "
"(SELECT id_media FROM " + policy::HistoryTable::Name +
" ORDER BY insertion_date DESC LIMIT -1 OFFSET " + std::to_string( MaxEntries ) + ");"
" END";
sqlite::Tools::executeRequest( dbConnection, triggerReq );
}
bool History::insert( sqlite::Connection* dbConn, int64_t mediaId )
{
static const std::string req = "INSERT OR REPLACE INTO " + policy::HistoryTable::Name +
"(id_media, insertion_date) VALUES(?, strftime('%s', 'now'))";
return sqlite::Tools::executeInsert( dbConn, req, mediaId ) != 0;
}
Query<IHistoryEntry> History::fetch( MediaLibraryPtr ml )
{
static const std::string req = "FROM " + policy::MediaTable::Name + " f "
"INNER JOIN " + policy::HistoryTable::Name + " h ON h.id_media = f.id_media "
"ORDER BY h.insertion_date DESC";
return make_query<History, IHistoryEntry>( ml, "f.*, h.insertion_date", req );
}
void History::clearStreams( MediaLibraryPtr ml )
{
static const std::string req = "DELETE FROM " + policy::HistoryTable::Name;
sqlite::Tools::executeRequest( ml->getConn(), req );
}
MediaPtr History::media() const
{
return m_media;
}
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 "medialibrary/IHistoryEntry.h"
#include <vector>
#include <string>
namespace medialibrary
{
class History;
class Media;
namespace policy
{
struct HistoryTable
{
static const std::string Name;
static const std::string PrimaryKeyColumn;
static int64_t History::* const PrimaryKey;
};
}
class History : public IHistoryEntry, public DatabaseHelpers<History, policy::HistoryTable, cachepolicy::Uncached<History>>
{
public:
History( MediaLibraryPtr ml, sqlite::Row& row );
static void createTable( sqlite::Connection* dbConnection );
static void createTriggers( sqlite::Connection* dbConnection );
static bool insert( sqlite::Connection* dbConn, int64_t mediaId );
static Query<IHistoryEntry> fetch( MediaLibraryPtr ml );
static void clearStreams( MediaLibraryPtr ml );
virtual MediaPtr media() const override;
virtual unsigned int insertionDate() const override;
static constexpr unsigned int MaxEntries = 20u;
private:
MediaPtr m_media;
int64_t m_mediaId;
unsigned int m_date;
friend policy::HistoryTable;
};
}
......@@ -732,16 +732,26 @@ Query<IMedia> Media::searchInPlaylist( MediaLibraryPtr ml, const std::string& pa
Query<IMedia> Media::fetchHistory( MediaLibraryPtr ml )
{
static const std::string req = "FROM " + policy::MediaTable::Name + " WHERE last_played_date IS NOT NULL"
static const std::string req = "FROM " + policy::MediaTable::Name +
" WHERE last_played_date IS NOT NULL"
" AND type != ?"
" ORDER BY last_played_date DESC LIMIT 100";
return make_query<Media, IMedia>( ml, "*", req );
return make_query<Media, IMedia>( ml, "*", req, IMedia::Type::Stream );
}
Query<IMedia> Media::fetchStreamHistory(MediaLibraryPtr ml)
{
static const std::string req = "FROM " + policy::MediaTable::Name +
" WHERE last_played_date IS NOT NULL"
" AND type = ?"
" ORDER BY last_played_date DESC LIMIT 100";
return make_query<Media, IMedia>( ml, "*", req, IMedia::Type::Stream );
}
void Media::clearHistory( MediaLibraryPtr ml )
{
auto dbConn = ml->getConn();
// There should already be an active transaction, from MediaLibrary::clearHistory
assert( sqlite::Transaction::transactionInProgress() == true );
auto t = dbConn->newTransaction();
static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
"play_count = 0,"
"last_played_date = NULL";
......@@ -753,6 +763,7 @@ void Media::clearHistory( MediaLibraryPtr ml )
static_cast<MDType>( IMedia::MetadataType::Progress ) );
sqlite::Tools::executeUpdate( dbConn, req );
t->commit();
}
}
......@@ -140,6 +140,7 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
static Query<IMedia> searchInPlaylist( MediaLibraryPtr ml, const std::string& pattern,
int64_t playlistId, const QueryParameters* params );
static Query<IMedia> fetchHistory( MediaLibraryPtr ml );
static Query<IMedia> fetchStreamHistory( MediaLibraryPtr ml );
static void clearHistory( MediaLibraryPtr ml );
......
......@@ -41,7 +41,6 @@
#include "File.h"
#include "Folder.h"
#include "Genre.h"
#include "History.h"
#include "Media.h"
#include "MediaLibrary.h"
#include "Label.h"
......@@ -138,7 +137,6 @@ void MediaLibrary::clearCache()
Device::clear();
File::clear();
Playlist::clear();
History::clear();
Genre::clear();
Thumbnail::clear();
}
......@@ -167,7 +165,6 @@ void MediaLibrary::createAllTables()
AudioTrack::createTable( m_dbConnection.get() );
Artist::createTable( m_dbConnection.get() );
Artist::createDefaultArtists( m_dbConnection.get() );
History::createTable( m_dbConnection.get() );
Settings::createTable( m_dbConnection.get() );
parser::Task::createTable( m_dbConnection.get() );
Metadata::createTable( m_dbConnection.get() );
......@@ -184,7 +181,6 @@ void MediaLibrary::createAllTriggers()
File::createTriggers( m_dbConnection.get() );
Genre::createTriggers( m_dbConnection.get() );
Playlist::createTriggers( m_dbConnection.get() );
History::createTriggers( m_dbConnection.get() );
Label::createTriggers( m_dbConnection.get() );
Show::createTriggers( m_dbConnection.get() );
}
......@@ -675,27 +671,14 @@ bool MediaLibrary::deletePlaylist( int64_t playlistId )
}
}
bool MediaLibrary::addToStreamHistory( MediaPtr media )
Query<IMedia> MediaLibrary::history() const
{
try
{
return History::insert( getConn(), media->id() );
}
catch ( const sqlite::errors::Generic& ex )
{
LOG_ERROR( "Failed to add stream to history: ", ex.what() );
return false;
}
}
Query<IHistoryEntry> MediaLibrary::lastStreamsPlayed() const
{
return History::fetch( this );
return Media::fetchHistory( this );
}
Query<IMedia> MediaLibrary::lastMediaPlayed() const
Query<IMedia> MediaLibrary::streamHistory() const
{
return Media::fetchHistory( this );
return Media::fetchStreamHistory( this );
}
bool MediaLibrary::clearHistory()
......@@ -703,10 +686,7 @@ bool MediaLibrary::clearHistory()
try
{
return sqlite::Tools::withRetries( 3, [this]() {
auto t = getConn()->newTransaction();
Media::clearHistory( this );
History::clearStreams( this );
t->commit();
return true;
});
}
......
......@@ -113,9 +113,8 @@ class MediaLibrary : public IMediaLibrary, public IDeviceListerCb
virtual PlaylistPtr playlist( int64_t id ) const override;
virtual bool deletePlaylist( int64_t playlistId ) override;
virtual bool addToStreamHistory( MediaPtr media ) override;
virtual Query<IHistoryEntry> lastStreamsPlayed() const override;
virtual Query<IMedia> lastMediaPlayed() const override;
virtual Query<IMedia> history() const override;
virtual Query<IMedia> streamHistory() const override;
virtual bool clearHistory() override;
virtual Query<IMedia> searchMedia( const std::string& title,
......
/*****************************************************************************
* 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.
*****************************************************************************/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "Tests.h"
#include "medialibrary/IHistoryEntry.h"
#include "History.h"
#include "Media.h"
#include "compat/Thread.h"
class HistoryTest : public Tests
{
};
TEST_F( HistoryTest, InsertMrl )
{
auto m = ml->addMedia( "upnp://stream" );
ml->addToStreamHistory( m );
auto hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 1u, hList.size() );
auto h = hList[0];
ASSERT_EQ( h->media()->files()[0]->mrl(), "upnp://stream" );
ASSERT_NE( 0u, h->insertionDate() );
}
TEST_F( HistoryTest, MaxEntries )
{
for ( auto i = 0u; i < History::MaxEntries; ++i )
{
auto m = ml->addMedia( "http://media" + std::to_string( i ) );
ml->addToStreamHistory( m );
}
auto hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( History::MaxEntries, hList.size() );
auto m = ml->addMedia("smb://new-media" );
ml->addToStreamHistory( m );
hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( History::MaxEntries, hList.size() );
}
TEST_F( HistoryTest, Ordering )
{
auto m = ml->addMedia( "first-stream" );
ml->addToStreamHistory( m );
compat::this_thread::sleep_for( std::chrono::seconds( 1 ) );
auto m2 = ml->addMedia( "second-stream" );
ml->addToStreamHistory( m2 );
auto hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 2u, hList.size() );
ASSERT_EQ( hList[0]->media()->id(), m2->id() );
ASSERT_EQ( hList[1]->media()->id(), m->id() );
}
TEST_F( HistoryTest, UpdateInsertionDate )
{
auto m = ml->addMedia( "stream" );
ml->addToStreamHistory( m );
auto hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 1u, hList.size() );
auto date = hList[0]->insertionDate();
compat::this_thread::sleep_for( std::chrono::seconds( 1 ) );
ml->addToStreamHistory( m );
hList = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 1u, hList.size() );
ASSERT_NE( date, hList[0]->insertionDate() );
}
TEST_F( HistoryTest, ClearStreamHistory )
{
auto m = ml->addMedia( "f00" );
ml->addToStreamHistory( m );
auto m2 = ml->addMedia( "bar" );
ml->addToStreamHistory( m2 );
auto history = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 2u, history.size() );
ml->clearHistory();
history = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 0u, history.size() );
Reload();
history = ml->lastStreamsPlayed()->all();
ASSERT_EQ( 0u, history.size() );
}
......@@ -330,12 +330,12 @@ TEST_F( Medias, History )
{
auto m = std::static_pointer_cast<Media>( ml->addMedia( "media.mkv" ) );
auto history = ml->lastMediaPlayed()->all();
auto history = ml->history()->all();
ASSERT_EQ( 0u, history.size() );
m->increasePlayCount();
m->save();
history = ml->lastMediaPlayed()->all();
history = ml->history()->all();
ASSERT_EQ( 1u, history.size() );
ASSERT_EQ( m->id(), history[0]->id() );
......@@ -343,32 +343,49 @@ TEST_F( Medias, History )
auto m2 = std::static_pointer_cast<Media>( ml->addMedia( "media2.mkv" ) );
m2->increasePlayCount();
history = ml->lastMediaPlayed()->all();
history = ml->history()->all();
ASSERT_EQ( 2u, history.size() );
ASSERT_EQ( m2->id(), history[0]->id() );
ASSERT_EQ( m->id(), history[1]->id() );
}
TEST_F( Medias, StreamHistory )
{
auto m1 = std::static_pointer_cast<Media>( ml->addStream( "http://media.org/sample.mkv" ) );
auto m2 = std::static_pointer_cast<Media>( ml->addStream( "http://media.org/sample2.mkv" ) );
auto m3 = std::static_pointer_cast<Media>( ml->addMedia( "localfile.mkv" ) );
m1->increasePlayCount();
m2->increasePlayCount();
m3->increasePlayCount();
auto history = ml->streamHistory()->all();
ASSERT_EQ( 2u, history.size() );
history = ml->history()->all();
ASSERT_EQ( 1u, history.size() );
}
TEST_F( Medias, ClearHistory )
{
auto m = std::static_pointer_cast<Media>( ml->addMedia( "media.mkv" ) );
auto history = ml->lastMediaPlayed()->all();
auto history = ml->history()->all();
ASSERT_EQ( 0u, history.size() );
m->increasePlayCount();
m->save();
history = ml->lastMediaPlayed()->all();
history = ml->history()->all();
ASSERT_EQ( 1u, history.size() );
ASSERT_TRUE( ml->clearHistory() );
history = ml->lastMediaPlayed()->all();
history = ml->history()->all();
ASSERT_EQ( 0u, history.size() );
Reload();
history = ml->lastMediaPlayed()->all();
history = ml->history()->all();
ASSERT_EQ( 0u, history.size() );
}
......
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