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

Album: Add search

parent f3a80076
......@@ -117,6 +117,7 @@ class IMediaLibrary
*/
virtual std::vector<MediaPtr> searchAlbumTracks( const std::string& title ) const = 0;
virtual std::vector<PlaylistPtr> searchPlaylists( const std::string& name ) const = 0;
virtual std::vector<AlbumPtr> searchAlbums( const std::string& pattern ) const = 0;
/**
* @brief discover Launch a discovery on the provided entry point.
......
......@@ -222,6 +222,9 @@ bool Album::setAlbumArtist( Artist* artist )
}
m_artistId = artist->id();
artist->updateNbAlbum( 1 );
static const std::string ftsReq = "UPDATE " + policy::AlbumTable::Name + "Fts SET "
" artist = ? WHERE rowid = ?";
sqlite::Tools::executeUpdate( m_dbConnection, ftsReq, artist->name(), m_id );
return true;
}
......@@ -276,8 +279,13 @@ bool Album::createTable(DBConnection dbConnection )
"FOREIGN KEY(artist_id) REFERENCES " + policy::ArtistTable::Name + "("
+ policy::ArtistTable::PrimaryKeyColumn + ") ON DELETE CASCADE"
")";
static const std::string vtableReq = "CREATE VIRTUAL TABLE " + policy::AlbumTable::Name + "Fts USING FTS3("
"title,"
"artist"
")";
return sqlite::Tools::executeRequest( dbConnection, req ) &&
sqlite::Tools::executeRequest( dbConnection, reqRel );
sqlite::Tools::executeRequest( dbConnection, reqRel ) &&
sqlite::Tools::executeRequest( dbConnection, vtableReq );
}
bool Album::createTriggers(DBConnection dbConnection)
......@@ -289,7 +297,23 @@ bool Album::createTriggers(DBConnection dbConnection)
"(SELECT COUNT(id_track) FROM " + policy::AlbumTrackTable::Name + " WHERE album_id=new.album_id AND is_present=1) "
"WHERE id_album=new.album_id;"
" END";
return sqlite::Tools::executeRequest( dbConnection, triggerReq );
static const std::string vtriggerInsert = "CREATE TRIGGER IF NOT EXISTS insert_album_fts AFTER INSERT ON "
+ policy::AlbumTable::Name +
// Skip unknown albums
" WHEN new.title IS NOT NULL"
" BEGIN"
" INSERT INTO " + policy::AlbumTable::Name + "Fts(rowid, title) VALUES(new.id_album, new.title);"
" END";
static const std::string vtriggerDelete = "CREATE TRIGGER IF NOT EXISTS delete_album_fts BEFORE DELETE ON "
+ policy::AlbumTable::Name +
// Unknown album probably won't be deleted, but better safe than sorry
" WHEN old.title IS NOT NULL"
" BEGIN"
" DELETE FROM " + policy::AlbumTable::Name + "Fts WHERE rowid = old.id_album;"
" END";
return sqlite::Tools::executeRequest( dbConnection, triggerReq ) &&
sqlite::Tools::executeRequest( dbConnection, vtriggerInsert ) &&
sqlite::Tools::executeRequest( dbConnection, vtriggerDelete );
}
std::shared_ptr<Album> Album::create(DBConnection dbConnection, const std::string& title )
......@@ -313,3 +337,11 @@ std::shared_ptr<Album> Album::createUnknownAlbum( DBConnection dbConnection, con
album->m_dbConnection = dbConnection;
return album;
}
std::vector<AlbumPtr> Album::search( DBConnection dbConn, const std::string& pattern )
{
static const std::string req = "SELECT * FROM " + policy::AlbumTable::Name + " WHERE id_album IN "
"(SELECT rowid FROM " + policy::AlbumTable::Name + "Fts WHERE " +
policy::AlbumTable::Name + "Fts MATCH ?)";
return fetchAll<IAlbum>( dbConn, req, pattern + "*" );
}
......@@ -98,6 +98,12 @@ class Album : public IAlbum, public DatabaseHelpers<Album, policy::AlbumTable>
static bool createTriggers( DBConnection dbConnection );
static std::shared_ptr<Album> create(DBConnection dbConnection, const std::string& title );
static std::shared_ptr<Album> createUnknownAlbum( DBConnection dbConnection, const Artist* artist );
///
/// \brief search search for an album, through its albumartist or title
/// \param pattern A pattern representing the title, or the name of the main artist
/// \return
///
static std::vector<AlbumPtr> search(DBConnection dbConn, const std::string& pattern );
protected:
DBConnection m_dbConnection;
......
......@@ -415,6 +415,11 @@ std::vector<PlaylistPtr> MediaLibrary::searchPlaylists( const std::string& name
return Playlist::search( m_dbConnection.get(), name );
}
std::vector<AlbumPtr> MediaLibrary::searchAlbums( const std::string& pattern ) const
{
return Album::search( m_dbConnection.get(), pattern );
}
void MediaLibrary::startParser()
{
m_parser.reset( new Parser( m_dbConnection.get(), this, m_callback ) );
......
......@@ -92,6 +92,7 @@ class MediaLibrary : public IMediaLibrary
virtual std::vector<MediaPtr> searchAlbumTracks( const std::string& title ) const override;
virtual std::vector<PlaylistPtr> searchPlaylists( const std::string& name ) const override;
virtual std::vector<AlbumPtr> searchAlbums( const std::string& pattern ) const override;
virtual void discover( const std::string& entryPoint ) override;
bool banFolder( const std::string& path ) override;
......
......@@ -232,3 +232,77 @@ TEST_F( Albums, AlbumArtist )
ASSERT_NE( albumArtist, nullptr );
ASSERT_EQ( albumArtist->name(), artist->name() );
}
TEST_F( Albums, SearchByTitle )
{
ml->createAlbum( "sea otters" );
ml->createAlbum( "pangolins of fire" );
auto albums = ml->searchAlbums( "otte" );
ASSERT_EQ( 1u, albums.size() );
}
TEST_F( Albums, SearchByArtist )
{
auto a = ml->createAlbum( "sea otters" );
auto artist = ml->createArtist( "pangolins" );
a->setAlbumArtist( artist.get() );
auto albums = ml->searchAlbums( "pangol" );
ASSERT_EQ( 1u, albums.size() );
}
TEST_F( Albums, SearchNoDuplicate )
{
auto a = ml->createAlbum( "sea otters" );
auto artist = ml->createArtist( "otters" );
a->setAlbumArtist( artist.get() );
auto albums = ml->searchAlbums( "otters" );
ASSERT_EQ( 1u, albums.size() );
}
TEST_F( Albums, SearchNoUnknownAlbum )
{
auto artist = ml->createArtist( "otters" );
auto album = artist->unknownAlbum();
ASSERT_NE( nullptr, album );
auto albums = ml->searchAlbums( "otters" );
ASSERT_EQ( 0u, albums.size() );
// Can't search by name since there is no name set for unknown albums
}
TEST_F( Albums, SearchAfterDeletion )
{
auto a = ml->createAlbum( "sea otters" );
auto albums = ml->searchAlbums( "sea" );
ASSERT_EQ( 1u, albums.size() );
ml->deleteAlbum( a->id() );
albums = ml->searchAlbums( "sea" );
ASSERT_EQ( 0u, albums.size() );
}
TEST_F( Albums, SearchAfterArtistUpdate )
{
auto a = ml->createAlbum( "sea otters" );
auto artist = ml->createArtist( "pangolin of fire" );
auto artist2 = ml->createArtist( "pangolin of ice" );
a->setAlbumArtist( artist.get() );
auto albums = ml->searchAlbums( "fire" );
ASSERT_EQ( 1u, albums.size() );
albums = ml->searchAlbums( "ice" );
ASSERT_EQ( 0u, albums.size() );
a->setAlbumArtist( artist2.get() );
albums = ml->searchAlbums( "fire" );
ASSERT_EQ( 0u, albums.size() );
albums = ml->searchAlbums( "ice" );
ASSERT_EQ( 1u, albums.size() );
}
......@@ -24,6 +24,7 @@
#include <memory>
#include "Album.h"
#include "File.h"
#include "filesystem/IFile.h"
#include "filesystem/IDirectory.h"
......@@ -124,3 +125,8 @@ std::shared_ptr<Playlist> MediaLibraryTester::playlist(unsigned int playlistId)
{
return Playlist::fetch( m_dbConnection.get(), playlistId );
}
void MediaLibraryTester::deleteAlbum( unsigned int albumId )
{
Album::destroy( m_dbConnection.get(), albumId );
}
......@@ -37,6 +37,7 @@ public:
std::shared_ptr<Folder> folder( const std::string& path );
std::shared_ptr<Media> addFile( const std::string& path );
std::shared_ptr<Playlist> playlist( unsigned int playlistId );
void deleteAlbum( unsigned int albumId );
private:
std::unique_ptr<fs::IDirectory> dummyDirectory;
......
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