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

Introduce a Genre class

AlbumTracks now store a genre id, instead of a plaintext genre.
parent c6db7629
......@@ -25,8 +25,6 @@
#include "IMediaLibrary.h"
class IAlbum;
class IAlbumTrack
{
public:
......@@ -42,9 +40,9 @@ class IAlbumTrack
* @return
*/
virtual ArtistPtr artist() const = 0;
virtual const std::string& genre() = 0;
virtual GenrePtr genre() = 0;
virtual unsigned int trackNumber() = 0;
virtual std::shared_ptr<IAlbum> album() = 0;
virtual AlbumPtr album() = 0;
/**
* @return Which disc this tracks appears on (or 0 if unspecified)
*/
......
/*****************************************************************************
* 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 <vector>
class IGenre
{
public:
virtual ~IGenre() = default;
virtual unsigned int id() const = 0;
virtual const std::string& name() const = 0;
virtual std::vector<ArtistPtr> artists() const = 0;
virtual std::vector<AlbumTrackPtr> tracks() const = 0;
};
......@@ -96,6 +96,7 @@ class IMediaLibrary
virtual MoviePtr movie( const std::string& title ) = 0;
virtual ArtistPtr artist( unsigned int id ) = 0;
virtual std::vector<ArtistPtr> artists() const = 0;
virtual std::vector<GenrePtr> genres() const = 0;
/***
* Playlists
......
......@@ -30,6 +30,7 @@ class IAlbumTrack;
class IAudioTrack;
class IDiscoverer;
class IFile;
class IGenre;
class IHistoryEntry;
class IMedia;
class ILabel;
......@@ -56,6 +57,7 @@ typedef std::shared_ptr<IVideoTrack> VideoTrackPtr;
typedef std::shared_ptr<IArtist> ArtistPtr;
typedef std::shared_ptr<IPlaylist> PlaylistPtr;
typedef std::shared_ptr<IHistoryEntry> HistoryPtr;
typedef std::shared_ptr<IGenre> GenrePtr;
typedef SqliteConnection* DBConnection;
......
......@@ -24,6 +24,7 @@
#include "Album.h"
#include "Artist.h"
#include "Media.h"
#include "Genre.h"
#include "database/SqliteTools.h"
#include "logging/Logger.h"
......@@ -37,7 +38,7 @@ AlbumTrack::AlbumTrack( DBConnection dbConnection, sqlite::Row& row )
row >> m_id
>> m_mediaId
>> m_artistId
>> m_genre
>> m_genreId
>> m_trackNumber
>> m_albumId
>> m_releaseYear
......@@ -92,7 +93,7 @@ bool AlbumTrack::createTable( DBConnection dbConnection )
"id_track INTEGER PRIMARY KEY AUTOINCREMENT,"
"media_id INTEGER,"
"artist_id UNSIGNED INTEGER,"
"genre TEXT,"
"genre_id INTEGER,"
"track_number UNSIGNED INTEGER,"
"album_id UNSIGNED INTEGER NOT NULL,"
"release_year UNSIGNED INTEGER,"
......@@ -102,6 +103,7 @@ bool AlbumTrack::createTable( DBConnection dbConnection )
" ON DELETE CASCADE,"
"FOREIGN KEY (artist_id) REFERENCES " + policy::ArtistTable::Name + "(id_artist)"
" ON DELETE CASCADE,"
"FOREIGN KEY (genre_id) REFERENCES " + policy::GenreTable::Name + "(id_genre),"
"FOREIGN KEY (album_id) REFERENCES Album(id_album) "
" ON DELETE CASCADE"
")";
......@@ -132,18 +134,34 @@ AlbumTrackPtr AlbumTrack::fromMedia( DBConnection dbConnection, unsigned int med
return fetch( dbConnection, req, mediaId );
}
const std::string& AlbumTrack::genre()
std::vector<AlbumTrackPtr> AlbumTrack::fromGenre( DBConnection dbConn, unsigned int genreId )
{
return m_genre;
static const std::string req = "SELECT * FROM " + policy::AlbumTrackTable::Name +
" WHERE genre_id = ?";
return fetchAll<IAlbumTrack>( dbConn, req, genreId );
}
GenrePtr AlbumTrack::genre()
{
auto l = m_genre.lock();
if ( m_genre.isCached() == false )
{
m_genre = Genre::fetch( m_dbConnection, m_genreId );
}
return m_genre.get();
}
bool AlbumTrack::setGenre(const std::string& genre)
bool AlbumTrack::setGenre( std::shared_ptr<Genre> genre )
{
static const std::string req = "UPDATE " + policy::AlbumTrackTable::Name
+ " SET genre = ? WHERE id_track = ?";
if ( sqlite::Tools::executeUpdate( m_dbConnection, req, genre, m_id ) == false )
+ " SET genre_id = ? WHERE id_track = ?";
if ( sqlite::Tools::executeUpdate( m_dbConnection, req,
sqlite::ForeignKey( genre != nullptr ? genre->id() : 0 ),
m_id ) == false )
return false;
auto l = m_genre.lock();
m_genre = genre;
m_genreId = genre != nullptr ? genre->id() : 0;
return true;
}
......
......@@ -25,6 +25,7 @@
#include <sqlite3.h>
#include <string>
#include <vector>
#include "IAlbumTrack.h"
#include "IMediaLibrary.h"
......@@ -35,6 +36,7 @@ class Album;
class AlbumTrack;
class Artist;
class Media;
class Genre;
namespace policy
{
......@@ -55,8 +57,8 @@ class AlbumTrack : public IAlbumTrack, public DatabaseHelpers<AlbumTrack, policy
virtual unsigned int id() const override;
virtual ArtistPtr artist() const override;
bool setArtist( std::shared_ptr<Artist> artist );
virtual const std::string& genre() override;
bool setGenre( const std::string& genre );
virtual GenrePtr genre() override;
bool setGenre( std::shared_ptr<Genre> genre );
virtual unsigned int trackNumber() override;
virtual unsigned int releaseYear() const override;
bool setReleaseYear( unsigned int year );
......@@ -67,13 +69,14 @@ class AlbumTrack : public IAlbumTrack, public DatabaseHelpers<AlbumTrack, policy
static std::shared_ptr<AlbumTrack> create( DBConnection dbConnection, unsigned int albumId,
unsigned int mediaId, unsigned int trackNb , unsigned int discNumber );
static AlbumTrackPtr fromMedia( DBConnection dbConnection, unsigned int mediaId );
static std::vector<AlbumTrackPtr> fromGenre( DBConnection dbConn, unsigned int genreId );
private:
DBConnection m_dbConnection;
unsigned int m_id;
unsigned int m_mediaId;
unsigned int m_artistId;
std::string m_genre;
unsigned int m_genreId;
unsigned int m_trackNumber;
unsigned int m_albumId;
unsigned int m_releaseYear;
......@@ -82,6 +85,7 @@ class AlbumTrack : public IAlbumTrack, public DatabaseHelpers<AlbumTrack, policy
std::weak_ptr<Album> m_album;
mutable Cache<std::shared_ptr<Artist>> m_artist;
mutable Cache<std::shared_ptr<Genre>> m_genre;
friend struct policy::AlbumTrackTable;
};
......
......@@ -69,6 +69,7 @@ list(APPEND SRC_LIST ${HEADERS_LIST}
File.cpp
Playlist.cpp
History.cpp
Genre.cpp
parser/Parser.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 "Genre.h"
#include "AlbumTrack.h"
namespace policy
{
const std::string GenreTable::Name = "Genre";
const std::string GenreTable::PrimaryKeyColumn = "id_genre";
unsigned int Genre::* const GenreTable::PrimaryKey = &Genre::m_id;
}
Genre::Genre( DBConnection dbConn, sqlite::Row& row )
: m_dbConnection( dbConn )
{
row >> m_id
>> m_name;
}
Genre::Genre( const std::string& name )
: m_name( name )
{
}
unsigned int Genre::id() const
{
return m_id;
}
const std::string& Genre::name() const
{
return m_name;
}
std::vector<ArtistPtr> Genre::artists() const
{
}
std::vector<AlbumTrackPtr> Genre::tracks() const
{
return AlbumTrack::fromGenre( m_dbConnection, m_id );
}
bool Genre::createTable( DBConnection dbConn )
{
static const std::string req = "CREATE TABLE IF NOT EXISTS " + policy::GenreTable::Name +
"("
"id_genre INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT UNIQUE ON CONFLICT FAIL"
")";
return sqlite::Tools::executeRequest( dbConn, req );
}
std::shared_ptr<Genre> Genre::create( DBConnection dbConn, const std::string& name )
{
static const std::string req = "INSERT INTO " + policy::GenreTable::Name + "(name)"
"VALUES(?)";
auto self = std::make_shared<Genre>( name );
if ( insert( dbConn, self, req, name ) == false )
return nullptr;
self->m_dbConnection = dbConn;
return self;
}
std::shared_ptr<Genre> Genre::fromName(DBConnection dbConn, const std::string& name )
{
static const std::string req = "SELECT * FROM " + policy::GenreTable::Name + " WHERE name = ?";
return fetch( dbConn, req, name );
}
/*****************************************************************************
* 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 "IGenre.h"
#include "database/DatabaseHelpers.h"
class Genre;
namespace policy
{
struct GenreTable
{
static const std::string Name;
static const std::string PrimaryKeyColumn;
static unsigned int Genre::*const PrimaryKey;
};
}
class Genre : public IGenre, public DatabaseHelpers<Genre, policy::GenreTable>
{
public:
Genre( DBConnection dbConn, sqlite::Row& row );
Genre( const std::string& name );
virtual unsigned int id() const;
virtual const std::string& name() const override;
virtual std::vector<ArtistPtr> artists() const override;
virtual std::vector<AlbumTrackPtr> tracks() const override;
static bool createTable( DBConnection dbConn );
static std::shared_ptr<Genre> create( DBConnection dbConn, const std::string& name );
static std::shared_ptr<Genre> fromName( DBConnection dbConn, const std::string& name );
private:
DBConnection m_dbConnection;
unsigned int m_id;
std::string m_name;
friend policy::GenreTable;
};
......@@ -63,7 +63,7 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
// ::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...
Media(DBConnection dbConnection , sqlite::Row& row);
Media( DBConnection dbConnection , sqlite::Row& row );
Media( const std::string &title, Type type);
static std::shared_ptr<Media> create( DBConnection dbConnection, Type type, const fs::IFile* file );
......
......@@ -33,6 +33,7 @@
#include "Device.h"
#include "File.h"
#include "Folder.h"
#include "Genre.h"
#include "History.h"
#include "Media.h"
#include "MediaLibrary.h"
......@@ -107,6 +108,7 @@ MediaLibrary::~MediaLibrary()
File::clear();
Playlist::clear();
History::clear();
Genre::clear();
// Explicitely release the connection's TLS
if ( m_dbConnection != nullptr )
m_dbConnection->release();
......@@ -127,6 +129,7 @@ bool MediaLibrary::createAllTables()
File::createTable( m_dbConnection.get() ) &&
Label::createTable( m_dbConnection.get() ) &&
Playlist::createTable( m_dbConnection.get() ) &&
Genre::createTable( m_dbConnection.get() ) &&
Album::createTable( m_dbConnection.get() ) &&
AlbumTrack::createTable( m_dbConnection.get() ) &&
Album::createTriggers( m_dbConnection.get() ) &&
......@@ -302,6 +305,16 @@ std::vector<AlbumPtr> MediaLibrary::albums()
return Album::fetchAll<IAlbum>( m_dbConnection.get(), req );
}
std::vector<GenrePtr> MediaLibrary::genres() const
{
return Genre::fetchAll<IGenre>( m_dbConnection.get() );
}
std::shared_ptr<Genre> MediaLibrary::createGenre( const std::string& name )
{
return Genre::create( m_dbConnection.get(), name );
}
ShowPtr MediaLibrary::show(const std::string& name)
{
static const std::string req = "SELECT * FROM " + policy::ShowTable::Name
......
......@@ -41,6 +41,7 @@ class Movie;
class Show;
class Device;
class Folder;
class Genre;
class MediaLibrary : public IMediaLibrary
{
......@@ -67,6 +68,9 @@ class MediaLibrary : public IMediaLibrary
std::shared_ptr<Album> createAlbum( const std::string& title );
virtual std::vector<AlbumPtr> albums() override;
virtual std::vector<GenrePtr> genres() const override;
std::shared_ptr<Genre> createGenre( const std::string& name );
virtual ShowPtr show( const std::string& name ) override;
std::shared_ptr<Show> createShow( const std::string& name );
......
......@@ -25,6 +25,7 @@
#include "AlbumTrack.h"
#include "Artist.h"
#include "File.h"
#include "Genre.h"
#include "Media.h"
#include "Show.h"
#include "utils/Filename.h"
......@@ -337,7 +338,17 @@ std::shared_ptr<AlbumTrack> MetadataParser::handleTrack( std::shared_ptr<Album>
if ( task.genre.length() != 0 )
{
track->setGenre( task.genre );
auto genre = Genre::fromName( m_dbConn, task.genre );
if ( genre == nullptr )
{
genre = m_ml->createGenre( task.genre );
if ( genre == nullptr )
{
LOG_ERROR( "Failed to create a genre in database" );
return nullptr;
}
}
track->setGenre( genre );
}
if ( task.releaseDate.empty() == false )
{
......
......@@ -272,7 +272,8 @@ void Tests::checkAlbumTracks( const IAlbum* album, const std::vector<MediaPtr>&
}
if ( expectedTrack.HasMember( "genre" ) )
{
if ( strcasecmp( expectedTrack["genre"].GetString(), albumTrack->genre().c_str() ) != 0 )
auto genre = albumTrack->genre();
if ( genre == nullptr || strcasecmp( expectedTrack["genre"].GetString(), genre->name().c_str() ) != 0 )
return ;
}
if ( expectedTrack.HasMember( "releaseYear" ) )
......
......@@ -13,6 +13,7 @@
#include "IAlbumTrack.h"
#include "IAudioTrack.h"
#include "IVideoTrack.h"
#include "IGenre.h"
class MockCallback : public IMediaLibraryCb
{
......
......@@ -95,25 +95,6 @@ TEST_F( Albums, NbTracks )
ASSERT_EQ( tracks.size(), a->nbTracks() );
}
TEST_F( Albums, SetGenre )
{
auto a = ml->createAlbum( "album" );
auto f = ml->addFile( "track.mp3" );
auto t = a->addTrack( f, 1, 0 );
f->save();
t->setGenre( "happy underground post progressive death metal" );
ASSERT_EQ( t->genre(), "happy underground post progressive death metal" );
Reload();
a = std::static_pointer_cast<Album>( ml->album( a->id() ) );
auto tracks = a->tracks();
ASSERT_EQ( tracks.size(), 1u );
auto t2 = tracks[0];
ASSERT_EQ( t->genre(), t2->albumTrack()->genre() );
}
TEST_F( Albums, SetReleaseDate )
{
auto a = ml->createAlbum( "album" );
......
......@@ -26,6 +26,7 @@
#include "AlbumTrack.h"
#include "Artist.h"
#include "Media.h"
#include "Genre.h"
class AlbumTracks : public Tests
{
......@@ -90,3 +91,24 @@ TEST_F( AlbumTracks, SetReleaseYear )
auto t2 = m2->albumTrack();
ASSERT_EQ( t->releaseYear(), t2->releaseYear() );
}
TEST_F( AlbumTracks, SetGenre )
{
auto a = ml->createAlbum( "album" );
auto f = ml->addFile( "track.mp3" );
auto t = a->addTrack( f, 1, 0 );
f->save();
auto genre = ml->createGenre( "happy underground post progressive death metal" );
ASSERT_EQ( nullptr, t->genre() );
t->setGenre( genre );
ASSERT_EQ( t->genre()->name(), "happy underground post progressive death metal" );
Reload();
a = std::static_pointer_cast<Album>( ml->album( a->id() ) );
auto tracks = a->tracks();
ASSERT_EQ( tracks.size(), 1u );
auto t2 = tracks[0];
ASSERT_EQ( t->genre()->id(), t2->albumTrack()->genre()->id() );
}
/*****************************************************************************
* 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 "Genre.h"
#include "Album.h"
#include "AlbumTrack.h"
class Genres : public Tests
{
protected:
std::shared_ptr<Genre> g;
virtual void SetUp() override
{
Tests::SetUp();
g = ml->createGenre( "genre" );
}
};
TEST_F( Genres, Create )
{
ASSERT_NE( nullptr, g );
ASSERT_EQ( "genre", g->name() );
auto tracks = g->tracks();
ASSERT_EQ( 0u, tracks.size() );
}
TEST_F( Genres, List )
{
auto g2 = ml->createGenre( "genre 2" );
ASSERT_NE( nullptr, g2 );
auto genres = ml->genres();
ASSERT_EQ( 2u, genres.size() );
}
TEST_F( Genres, ListAlbumTracks )
{
auto a = ml->createAlbum( "album" );
for ( auto i = 1u; i <= 3; i++ )
{
auto m = ml->addFile( "track" + std::to_string( i ) + ".mp3" );
auto t = a->addTrack( m, i, 1 );
if ( i != 1 )
t->setGenre( g );
}
auto tracks = g->tracks();
ASSERT_EQ( 2u, tracks.size() );
}
......@@ -15,6 +15,7 @@ list(APPEND TEST_SRCS
unittest/FileTests.cpp
unittest/PlaylistTests.cpp
unittest/HistoryTests.cpp
unittest/GenreTests.cpp
mocks/FileSystem.h
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