...
 
Commits (78)
......@@ -66,7 +66,6 @@ class IMedia
// This is likely to be used for album arts as well.
virtual const std::string& snapshot() = 0;
virtual unsigned int insertionDate() const = 0;
virtual bool isAvailable() const = 0;
};
#endif // IFILE_H
......@@ -89,18 +89,6 @@ class IMediaLibrary
* Calling this after initialize() is not a supported scenario.
*/
virtual void setFsFactory( std::shared_ptr<factory::IFileSystem> fsFactory ) = 0;
///
/// \brief addFile Adds a file to the media library.
/// \param path The absolute path to this file
/// \param parentFolder The parent folder, or nullptr to add this file as
/// a stand alone file.
/// \return The newly created file, or nullptr in case of error
///
virtual MediaPtr file( const std::string& path ) = 0;
/// Adds a folder and all the files it contains
virtual FolderPtr folder( const std::string& path ) = 0;
virtual bool deleteFolder( FolderPtr folder ) = 0;
virtual LabelPtr createLabel( const std::string& label ) = 0;
virtual bool deleteLabel( LabelPtr label ) = 0;
......
......@@ -42,7 +42,6 @@ class IArtist;
class SqliteConnection;
typedef std::shared_ptr<IMedia> MediaPtr;
typedef std::shared_ptr<IFolder> FolderPtr;
typedef std::shared_ptr<ILabel> LabelPtr;
typedef std::shared_ptr<IAlbum> AlbumPtr;
typedef std::shared_ptr<IAlbumTrack> AlbumTrackPtr;
......
......@@ -29,6 +29,7 @@ namespace fs
{
class IDirectory;
class IFile;
class IDevice;
}
namespace factory
......@@ -47,5 +48,11 @@ namespace factory
/// \param fileName an absolute path to a file
///
virtual std::unique_ptr<fs::IFile> createFile( const std::string& fileName ) = 0;
///
/// \brief createDevice creates a representation of a device
/// \param uuid The device UUID
/// \return A representation of the device, or nullptr if the device is currently unavailable.
///
virtual std::shared_ptr<fs::IDevice> createDevice( const std::string& uuid ) = 0;
};
}
......@@ -22,20 +22,17 @@
#pragma once
#include "Types.h"
#include <string>
#include <vector>
class IFolder
namespace fs
{
class IDevice
{
public:
virtual ~IFolder() = default;
virtual unsigned int id() const = 0;
virtual const std::string& path() const = 0;
// This will only returns the files in this immediate folder
virtual std::vector<MediaPtr> files() = 0;
virtual std::vector<FolderPtr> folders() = 0;
virtual unsigned int lastModificationDate() = 0;
virtual bool isRemovable() = 0;
virtual FolderPtr parent() = 0;
virtual ~IDevice() = default;
virtual const std::string& uuid() const = 0;
virtual bool isRemovable() const = 0;
virtual bool isPresent() const = 0;
virtual const std::string& mountpoint() const = 0;
};
}
......@@ -29,6 +29,7 @@
namespace fs
{
class IFile;
class IDevice;
class IDirectory
{
......@@ -40,7 +41,6 @@ namespace fs
virtual const std::vector<std::string>& files() = 0;
/// Returns a list of absolute path to this folder subdirectories
virtual const std::vector<std::string>& dirs() = 0;
virtual unsigned int lastModificationDate() const = 0;
virtual bool isRemovable() const = 0;
virtual std::shared_ptr<IDevice> device() const = 0;
};
}
......@@ -35,7 +35,6 @@ unsigned int Album::* const policy::AlbumTable::PrimaryKey = &Album::m_id;
Album::Album(DBConnection dbConnection, sqlite::Row& row)
: m_dbConnection( dbConnection )
, m_tracksCached( false )
{
row >> m_id
>> m_title
......@@ -44,7 +43,8 @@ Album::Album(DBConnection dbConnection, sqlite::Row& row)
>> m_shortSummary
>> m_artworkUrl
>> m_lastSyncDate
>> m_nbTracks;
>> m_nbTracks
>> m_isPresent;
}
Album::Album(const std::string& title )
......@@ -54,7 +54,7 @@ Album::Album(const std::string& title )
, m_releaseYear( ~0u )
, m_lastSyncDate( 0 )
, m_nbTracks( 0 )
, m_tracksCached( false )
, m_isPresent( true )
{
}
......@@ -64,7 +64,7 @@ Album::Album( const Artist* artist )
, m_releaseYear( ~0u )
, m_lastSyncDate( 0 )
, m_nbTracks( 0 )
, m_tracksCached( false )
, m_isPresent( true )
{
}
......@@ -114,7 +114,7 @@ const std::string& Album::shortSummary() const
bool Album::setShortSummary( const std::string& summary )
{
static const std::string& req = "UPDATE " + policy::AlbumTable::Name
static const std::string req = "UPDATE " + policy::AlbumTable::Name
+ " SET short_summary = ? WHERE id_album = ?";
if ( sqlite::Tools::executeUpdate( m_dbConnection, req, summary, m_id ) == false )
return false;
......@@ -129,7 +129,7 @@ const std::string& Album::artworkUrl() const
bool Album::setArtworkUrl( const std::string& artworkUrl )
{
static const std::string& req = "UPDATE " + policy::AlbumTable::Name
static const std::string req = "UPDATE " + policy::AlbumTable::Name
+ " SET artwork_url = ? WHERE id_album = ?";
if ( sqlite::Tools::executeUpdate( m_dbConnection, req, artworkUrl, m_id ) == false )
return false;
......@@ -144,16 +144,10 @@ time_t Album::lastSyncDate() const
std::vector<MediaPtr> Album::tracks() const
{
std::lock_guard<std::mutex> lock( m_tracksLock );
if ( m_tracksCached == true )
return m_tracks;
static const std::string req = "SELECT med.* FROM " + policy::MediaTable::Name + " med "
" LEFT JOIN " + policy::AlbumTrackTable::Name + " att ON att.media_id = med.id_media "
" WHERE att.album_id = ? ORDER BY att.disc_number, att.track_number";
m_tracks = Media::fetchAll<IMedia>( m_dbConnection, req, m_id );
m_tracksCached = true;
return m_tracks;
" WHERE att.album_id = ? AND med.is_present = 1 ORDER BY att.disc_number, att.track_number";
return Media::fetchAll<IMedia>( m_dbConnection, req, m_id );
}
std::shared_ptr<AlbumTrack> Album::addTrack(std::shared_ptr<Media> media, unsigned int trackNb, unsigned int discNumber )
......@@ -167,14 +161,9 @@ std::shared_ptr<AlbumTrack> Album::addTrack(std::shared_ptr<Media> media, unsign
return nullptr;
static const std::string req = "UPDATE " + policy::AlbumTable::Name +
" SET nb_tracks = nb_tracks + 1 WHERE id_album = ?";
std::lock_guard<std::mutex> lock( m_tracksLock );
if ( sqlite::Tools::executeUpdate( m_dbConnection, req, m_id ) == false )
return nullptr;
m_nbTracks++;
// Invalide the cache instead of trying to maintain it from here.
// Keeping the ordering consistent while adding items is going to be hard
// once we start to expose multiple sorting criteria
m_tracksCached = false;
t->commit();
return track;
}
......@@ -249,7 +238,8 @@ bool Album::createTable(DBConnection dbConnection )
"short_summary TEXT,"
"artwork_url TEXT,"
"last_sync_date UNSIGNED INTEGER,"
"nb_tracks UNSIGNED INTEGER DEFAULT 0"
"nb_tracks UNSIGNED INTEGER DEFAULT 0,"
"is_present BOOLEAN NOT NULL DEFAULT 1"
")";
static const std::string reqRel = "CREATE TABLE IF NOT EXISTS AlbumArtistRelation("
"id_album INTEGER,"
......@@ -264,6 +254,18 @@ bool Album::createTable(DBConnection dbConnection )
sqlite::Tools::executeRequest( dbConnection, reqRel );
}
bool Album::createTriggers(DBConnection dbConnection)
{
static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_album_present AFTER UPDATE OF "
"is_present ON " + policy::AlbumTrackTable::Name +
" BEGIN "
" UPDATE " + policy::AlbumTable::Name + " SET is_present="
"(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 );
}
std::shared_ptr<Album> Album::create(DBConnection dbConnection, const std::string& title )
{
auto album = std::make_shared<Album>( title );
......
......@@ -83,6 +83,7 @@ class Album : public IAlbum, public DatabaseHelpers<Album, policy::AlbumTable>
bool removeArtist( Artist* artist );
static bool createTable( DBConnection dbConnection );
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 );
......@@ -96,10 +97,7 @@ class Album : public IAlbum, public DatabaseHelpers<Album, policy::AlbumTable>
std::string m_artworkUrl;
time_t m_lastSyncDate;
unsigned int m_nbTracks;
mutable std::vector<MediaPtr> m_tracks;
mutable bool m_tracksCached;
mutable std::mutex m_tracksLock;
bool m_isPresent;
friend struct policy::AlbumTable;
};
......
......@@ -40,7 +40,8 @@ AlbumTrack::AlbumTrack(DBConnection dbConnection, sqlite::Row& row )
>> m_trackNumber
>> m_albumId
>> m_releaseYear
>> m_discNumber;
>> m_discNumber
>> m_isPresent;
}
//FIXME: constify media
......@@ -51,6 +52,7 @@ AlbumTrack::AlbumTrack( Media* media, unsigned int trackNumber, unsigned int alb
, m_albumId( albumId )
, m_releaseYear( 0 )
, m_discNumber( discNumber )
, m_isPresent( true )
{
}
......@@ -87,12 +89,19 @@ bool AlbumTrack::createTable( DBConnection dbConnection )
"album_id UNSIGNED INTEGER NOT NULL,"
"release_year UNSIGNED INTEGER,"
"disc_number UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"FOREIGN KEY (media_id) REFERENCES " + policy::MediaTable::Name + "(id_media)"
" ON DELETE CASCADE, "
"FOREIGN KEY (album_id) REFERENCES Album(id_album) "
" ON DELETE CASCADE"
")";
return sqlite::Tools::executeRequest( dbConnection, req );
static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_track_present AFTER UPDATE OF is_present "
"ON " + policy::MediaTable::Name +
" BEGIN"
" UPDATE " + policy::AlbumTrackTable::Name + " SET is_present = new.is_present WHERE media_id = new.id_media;"
" END";
return sqlite::Tools::executeRequest( dbConnection, req ) &&
sqlite::Tools::executeRequest( dbConnection, triggerReq );
}
std::shared_ptr<AlbumTrack> AlbumTrack::create(DBConnection dbConnection, unsigned int albumId, Media* media, unsigned int trackNb, unsigned int discNumber )
......
......@@ -75,6 +75,7 @@ class AlbumTrack : public IAlbumTrack, public DatabaseHelpers<AlbumTrack, policy
unsigned int m_albumId;
unsigned int m_releaseYear;
unsigned int m_discNumber;
bool m_isPresent;
std::weak_ptr<Album> m_album;
......
......@@ -39,13 +39,15 @@ Artist::Artist( DBConnection dbConnection, sqlite::Row& row )
>> m_name
>> m_shortBio
>> m_artworkUrl
>> m_nbAlbums;
>> m_nbAlbums
>> m_isPresent;
}
Artist::Artist( const std::string& name )
: m_id( 0 )
, m_name( name )
, m_nbAlbums( 0 )
, m_isPresent( true )
{
}
......@@ -89,7 +91,7 @@ std::vector<MediaPtr> Artist::media() const
{
static const std::string req = "SELECT med.* FROM " + policy::MediaTable::Name + " med "
"LEFT JOIN MediaArtistRelation mar ON mar.id_media = med.id_media "
"WHERE mar.id_artist = ?";
"WHERE mar.id_artist = ? AND med.is_present = 1";
return Media::fetchAll<IMedia>( m_dbConnection, req, m_id );
}
else
......@@ -169,7 +171,8 @@ bool Artist::createTable( DBConnection dbConnection )
"name TEXT COLLATE NOCASE UNIQUE ON CONFLICT FAIL,"
"shortbio TEXT,"
"artwork_url TEXT,"
"nb_albums UNSIGNED INT DEFAULT 0"
"nb_albums UNSIGNED INT DEFAULT 0,"
"is_present BOOLEAN NOT NULL DEFAULT 1"
")";
static const std::string reqRel = "CREATE TABLE IF NOT EXISTS MediaArtistRelation("
"id_media INTEGER NOT NULL,"
......@@ -184,6 +187,18 @@ bool Artist::createTable( DBConnection dbConnection )
sqlite::Tools::executeRequest( dbConnection, reqRel );
}
bool Artist::createTriggers(DBConnection dbConnection)
{
static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS has_album_present AFTER UPDATE OF "
"is_present ON " + policy::AlbumTable::Name +
" BEGIN "
" UPDATE " + policy::ArtistTable::Name + " SET is_present="
"(SELECT COUNT(id_album) FROM " + policy::AlbumTable::Name + " WHERE artist_id=new.artist_id AND is_present=1) "
"WHERE id_artist=new.artist_id;"
" END";
return sqlite::Tools::executeRequest( dbConnection, triggerReq );
}
bool Artist::createDefaultArtists( DBConnection dbConnection )
{
// Don't rely on Artist::create, since we want to insert or do nothing here.
......
......@@ -59,6 +59,7 @@ public:
std::shared_ptr<Album> unknownAlbum();
static bool createTable( DBConnection dbConnection );
static bool createTriggers( DBConnection dbConnection );
static bool createDefaultArtists( DBConnection dbConnection );
static std::shared_ptr<Artist> create( DBConnection dbConnection, const std::string& name );
......@@ -69,6 +70,7 @@ private:
std::string m_shortBio;
std::string m_artworkUrl;
unsigned int m_nbAlbums;
bool m_isPresent;
friend struct policy::ArtistTable;
};
......@@ -9,7 +9,7 @@ EnableCpp11()
include(FindPkgConfig)
pkg_check_modules(EVAS evas)
add_definitions("-Wall -Wextra -pedantic")
add_definitions("-Wall -Wextra -pedantic -O0")
if(UNIX)
set(ARCH_FOLDER "unix")
......@@ -23,7 +23,6 @@ list(APPEND HEADERS_LIST
${CMAKE_SOURCE_DIR}/include/IAlbum.h
${CMAKE_SOURCE_DIR}/include/IAlbumTrack.h
${CMAKE_SOURCE_DIR}/include/IMedia.h
${CMAKE_SOURCE_DIR}/include/IFolder.h
${CMAKE_SOURCE_DIR}/include/IMediaLibrary.h
${CMAKE_SOURCE_DIR}/include/ILabel.h
${CMAKE_SOURCE_DIR}/include/IAudioTrack.h
......@@ -44,7 +43,7 @@ list(APPEND HEADERS_LIST
discoverer/FsDiscoverer.h
factory/FileSystem.h
Utils.h
utils/Filename.h
)
include_directories("${CMAKE_SOURCE_DIR}/include")
......@@ -66,16 +65,19 @@ list(APPEND SRC_LIST ${HEADERS_LIST}
Parser.cpp
Artist.cpp
Settings.cpp
Device.cpp
factory/FileSystem.cpp
filesystem/common/CommonFile.cpp
filesystem/${ARCH_FOLDER}/Directory.cpp
filesystem/${ARCH_FOLDER}/File.cpp
filesystem/${ARCH_FOLDER}/Device.cpp
discoverer/FsDiscoverer.cpp
discoverer/DiscovererWorker.cpp
Utils.cpp
utils/Filename.cpp
database/SqliteConnection.cpp
database/SqliteTransaction.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 "Device.h"
const std::string policy::DeviceTable::Name = "Device";
const std::string policy::DeviceTable::PrimaryKeyColumn = "id_device";
unsigned int Device::* const policy::DeviceTable::PrimaryKey = &Device::m_id;
Device::Device( DBConnection dbConnection, sqlite::Row& row )
: m_dbConn( dbConnection )
{
row >> m_id
>> m_uuid
>> m_isRemovable
>> m_isPresent;
//FIXME: It's probably a bad idea to load "isPresent" for DB. This field should
//only be here for sqlite triggering purposes
}
Device::Device( const std::string& uuid, bool isRemovable )
: m_uuid( uuid )
, m_isRemovable( isRemovable )
// Assume we can't add an absent device
, m_isPresent( true )
{
}
unsigned int Device::id() const
{
return m_id;
}
const std::string&Device::uuid() const
{
return m_uuid;
}
bool Device::isRemovable() const
{
return m_isRemovable;
}
bool Device::isPresent() const
{
return m_isPresent;
}
void Device::setPresent(bool value)
{
static const std::string req = "UPDATE " + policy::DeviceTable::Name +
" SET is_present = ? WHERE id_device = ?";
if ( sqlite::Tools::executeUpdate( m_dbConn, req, value, m_id ) == false )
return;
m_isPresent = value;
}
std::shared_ptr<Device> Device::create( DBConnection dbConnection, const std::string& uuid, bool isRemovable )
{
static const std::string req = "INSERT INTO " + policy::DeviceTable::Name
+ "(uuid, is_removable, is_present) VALUES(?, ?, ?)";
auto self = std::make_shared<Device>( uuid, isRemovable );
if ( insert( dbConnection, self, req, uuid, isRemovable, self->isPresent() ) == false )
return nullptr;
self->m_dbConn = dbConnection;
return self;
}
bool Device::createTable(DBConnection connection)
{
std::string req = "CREATE TABLE IF NOT EXISTS " + policy::DeviceTable::Name + "("
"id_device INTEGER PRIMARY KEY AUTOINCREMENT,"
"uuid TEXT UNIQUE ON CONFLICT FAIL,"
"is_removable BOOLEAN,"
"is_present BOOLEAN"
")";
return sqlite::Tools::executeRequest( connection, req );
}
std::shared_ptr<Device> Device::fromUuid( DBConnection dbConnection, const std::string& uuid )
{
static const std::string req = "SELECT * FROM " + policy::DeviceTable::Name +
" WHERE uuid = ?";
return fetch( dbConnection, req, uuid );
}
/*****************************************************************************
* 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"
class Device;
namespace policy
{
struct DeviceTable
{
static const std::string Name;
static const std::string PrimaryKeyColumn;
static unsigned int Device::*const PrimaryKey;
};
}
class Device : public DatabaseHelpers<Device, policy::DeviceTable>
{
public:
Device( const std::string& uuid, bool isRemovable );
Device( DBConnection dbConnection, sqlite::Row& row );
unsigned int id() const;
const std::string& uuid() const;
bool isRemovable() const;
bool isPresent() const;
void setPresent( bool value );
static std::shared_ptr<Device> create( DBConnection dbConnection, const std::string& uuid, bool isRemovable );
static bool createTable( DBConnection connection );
static std::shared_ptr<Device> fromUuid( DBConnection dbConnection, const std::string& uuid );
private:
DBConnection m_dbConn;
// This is a database ID
unsigned int m_id;
// This is a unique ID on the system side, in the /dev/disk/by-uuid sense.
// It can be a name or what not, depending on the OS.
std::string m_uuid;
bool m_isRemovable;
bool m_isPresent;
friend struct policy::DeviceTable;
};
......@@ -21,10 +21,15 @@
*****************************************************************************/
#include "Folder.h"
#include "Device.h"
#include "Media.h"
#include "database/SqliteTools.h"
#include "filesystem/IDirectory.h"
#include "filesystem/IDevice.h"
#include "utils/Filename.h"
#include <unordered_map>
namespace policy
{
......@@ -33,24 +38,28 @@ namespace policy
unsigned int Folder::* const FolderTable::PrimaryKey = &Folder::m_id;
}
Folder::Folder(DBConnection dbConnection, sqlite::Row& row )
std::shared_ptr<factory::IFileSystem> Folder::FsFactory;
Folder::Folder( DBConnection dbConnection, sqlite::Row& row )
: m_dbConection( dbConnection )
{
row >> m_id
>> m_path
>> m_parent
>> m_lastModificationDate
>> m_isRemovable
>> m_isBlacklisted;
>> m_isBlacklisted
>> m_deviceId
>> m_isPresent
>> m_isRemovable;
}
Folder::Folder( const std::string& path, time_t lastModificationDate, bool isRemovable, unsigned int parent )
Folder::Folder( const std::string& path, unsigned int parent, unsigned int deviceId, bool isRemovable )
: m_id( 0 )
, m_path( path )
, m_parent( parent )
, m_lastModificationDate( lastModificationDate )
, m_isRemovable( isRemovable )
, m_isBlacklisted( false )
, m_deviceId( deviceId )
, m_isPresent( true )
, m_isRemovable( isRemovable )
{
}
......@@ -59,40 +68,109 @@ bool Folder::createTable(DBConnection connection)
std::string req = "CREATE TABLE IF NOT EXISTS " + policy::FolderTable::Name +
"("
"id_folder INTEGER PRIMARY KEY AUTOINCREMENT,"
"path TEXT UNIQUE ON CONFLICT FAIL,"
"path TEXT,"
"id_parent UNSIGNED INTEGER,"
"last_modification_date UNSIGNED INTEGER,"
"is_removable INTEGER,"
"is_blacklisted INTEGER,"
"device_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"FOREIGN KEY (id_parent) REFERENCES " + policy::FolderTable::Name +
"(id_folder) ON DELETE CASCADE"
"(id_folder) ON DELETE CASCADE,"
"FOREIGN KEY (device_id) REFERENCES " + policy::DeviceTable::Name +
"(id_device) ON DELETE CASCADE,"
"UNIQUE(path, device_id) ON CONFLICT FAIL"
")";
return sqlite::Tools::executeRequest( connection, req );
std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_device_present AFTER UPDATE OF is_present ON "
+ policy::DeviceTable::Name +
" BEGIN"
" UPDATE " + policy::FolderTable::Name + " SET is_present = new.is_present WHERE device_id = new.id_device;"
" END";
return sqlite::Tools::executeRequest( connection, req ) &&
sqlite::Tools::executeRequest( connection, triggerReq );
}
std::shared_ptr<Folder> Folder::create( DBConnection connection, const std::string& path, time_t lastModificationDate, bool isRemovable, unsigned int parentId )
std::shared_ptr<Folder> Folder::create( DBConnection connection, const std::string& fullPath, unsigned int parentId, Device& device, fs::IDevice& deviceFs )
{
auto self = std::make_shared<Folder>( path, lastModificationDate, isRemovable, parentId );
std::string path;
if ( device.isRemovable() == true )
path = utils::file::removePath( fullPath, deviceFs.mountpoint() );
else
path = fullPath;
auto self = std::make_shared<Folder>( path, parentId, device.id(), device.isRemovable() );
static const std::string req = "INSERT INTO " + policy::FolderTable::Name +
"(path, id_parent, last_modification_date, is_removable) VALUES(?, ?, ?, ?)";
if ( insert( connection, self, req, path, sqlite::ForeignKey( parentId ),
lastModificationDate, isRemovable ) == false )
"(path, id_parent, device_id, is_removable) VALUES(?, ?, ?, ?)";
if ( insert( connection, self, req, path, sqlite::ForeignKey( parentId ), device.id(), device.isRemovable() ) == false )
return nullptr;
self->m_dbConection = connection;
if ( device.isRemovable() == true )
{
self->m_deviceMountpoint = deviceFs.mountpoint();
self->m_fullPath = self->m_deviceMountpoint.get() + path;
}
return self;
}
bool Folder::blacklist( DBConnection connection, const std::string& path )
bool Folder::blacklist( DBConnection connection, const std::string& fullPath )
{
auto f = fromPath( connection, fullPath );
if ( f != nullptr )
{
// Let the foreign key destroy everything beneath this folder
destroy( connection, f->id() );
}
auto folderFs = FsFactory->createDirectory( fullPath );
if ( folderFs == nullptr )
return false;
auto deviceFs = folderFs->device();
auto device = Device::fromUuid( connection, deviceFs->uuid() );
if ( device == nullptr )
device = Device::create( connection, deviceFs->uuid(), deviceFs->isRemovable() );
std::string path;
if ( deviceFs->isRemovable() == true )
path = utils::file::removePath( fullPath, deviceFs->mountpoint() );
else
path = fullPath;
static const std::string req = "INSERT INTO " + policy::FolderTable::Name +
"(path, id_parent, is_blacklisted) VALUES(?, ?, ?)";
return sqlite::Tools::insert( connection, req, path, nullptr, true ) != 0;
"(path, id_parent, is_blacklisted, device_id, is_removable) VALUES(?, ?, ?, ?, ?)";
return sqlite::Tools::insert( connection, req, path, nullptr, true, device->id(), deviceFs->isRemovable() ) != 0;
}
std::shared_ptr<Folder> Folder::fromPath( DBConnection conn, const std::string& path )
void Folder::setFileSystemFactory( std::shared_ptr<factory::IFileSystem> fsFactory )
{
const std::string req = "SELECT * FROM " + policy::FolderTable::Name + " WHERE path = ? AND is_blacklisted IS NULL";
return fetch( conn, req, path );
FsFactory = fsFactory;
}
std::shared_ptr<Folder> Folder::fromPath( DBConnection conn, const std::string& fullPath )
{
auto folderFs = FsFactory->createDirectory( fullPath );
if ( folderFs == nullptr )
return nullptr;
auto deviceFs = folderFs->device();
if ( deviceFs == nullptr )
{
LOG_ERROR( "Failed to get device containing an existing folder: ", fullPath );
return nullptr;
}
if ( deviceFs->isRemovable() == false )
{
const std::string req = "SELECT * FROM " + policy::FolderTable::Name + " WHERE path = ? AND is_removable = 0"
" AND is_blacklisted IS NULL";
return fetch( conn, req, fullPath );
}
const std::string req = "SELECT * FROM " + policy::FolderTable::Name + " WHERE path = ? AND device_id = ? "
"AND is_blacklisted IS NULL";
auto device = Device::fromUuid( conn, deviceFs->uuid() );
// We are trying to find a folder. If we don't know the device it's on, we don't know the folder.
if ( device == nullptr )
return nullptr;
auto path = utils::file::removePath( fullPath, deviceFs->mountpoint() );
auto folder = fetch( conn, req, path, device->id() );
if ( folder == nullptr )
return nullptr;
folder->m_deviceMountpoint = deviceFs->mountpoint();
folder->m_fullPath = folder->m_deviceMountpoint.get() + path;
return folder;
}
unsigned int Folder::id() const
......@@ -102,7 +180,18 @@ unsigned int Folder::id() const
const std::string& Folder::path() const
{
return m_path;
if ( m_isRemovable == false )
return m_path;
auto lock = m_deviceMountpoint.lock();
if ( m_deviceMountpoint.isCached() == true )
return m_fullPath;
auto device = Device::fetch( m_dbConection, m_deviceId );
auto deviceFs = FsFactory->createDevice( device->uuid() );
m_deviceMountpoint = deviceFs->mountpoint();
m_fullPath = m_deviceMountpoint.get() + m_path;
return m_fullPath;
}
std::vector<MediaPtr> Folder::files()
......@@ -112,34 +201,38 @@ std::vector<MediaPtr> Folder::files()
return Media::fetchAll<IMedia>( m_dbConection, req, m_id );
}
std::vector<FolderPtr> Folder::folders()
std::vector<std::shared_ptr<Folder>> Folder::folders()
{
static const std::string req = "SELECT * FROM " + policy::FolderTable::Name +
" WHERE id_parent = ? AND is_blacklisted IS NULL";
return fetchAll<IFolder>( m_dbConection, req, m_id );
return fetchAll( m_dbConection, m_id );
}
FolderPtr Folder::parent()
std::shared_ptr<Folder> Folder::parent()
{
return fetch( m_dbConection, m_parent );
}
unsigned int Folder::lastModificationDate()
unsigned int Folder::deviceId() const
{
return m_lastModificationDate;
return m_deviceId;
}
bool Folder::setLastModificationDate( unsigned int lastModificationDate )
bool Folder::isPresent() const
{
static const std::string req = "UPDATE " + policy::FolderTable::Name +
" SET last_modification_date = ? WHERE id_folder = ?";
if ( sqlite::Tools::executeUpdate( m_dbConection, req, lastModificationDate, m_id ) == false )
return false;
m_lastModificationDate = lastModificationDate;
return true;
return m_isPresent;
}
bool Folder::isRemovable()
std::vector<std::shared_ptr<Folder>> Folder::fetchAll( DBConnection dbConn, unsigned int parentFolderId )
{
return m_isRemovable;
if ( parentFolderId == 0 )
{
static const std::string req = "SELECT * FROM " + policy::FolderTable::Name
+ " WHERE id_parent IS NULL AND is_blacklisted is NULL AND is_present = 1";
return DatabaseHelpers::fetchAll<Folder>( dbConn, req );
}
else
{
static const std::string req = "SELECT * FROM " + policy::FolderTable::Name
+ " WHERE id_parent = ? AND is_blacklisted is NULL AND is_present = 1";
return DatabaseHelpers::fetchAll<Folder>( dbConn, req, parentFolderId );
}
}
......@@ -23,11 +23,13 @@
#pragma once
#include "database/DatabaseHelpers.h"
#include "IFolder.h"
#include "factory/IFileSystem.h"
#include "utils/Cache.h"
#include <sqlite3.h>
class Folder;
class Device;
namespace fs
{
......@@ -45,36 +47,54 @@ struct FolderTable
}
class Folder : public IFolder, public DatabaseHelpers<Folder, policy::FolderTable>
// This doesn't publicly expose the DatabaseHelper inheritance in order to force
// the user to go through Folder's overloads, as they take care of the device mountpoint
// fetching & path composition
class Folder : public DatabaseHelpers<Folder, policy::FolderTable>
{
public:
Folder( DBConnection dbConnection, sqlite::Row& row );
Folder(const std::string& path, time_t lastModificationDate, bool isRemovable, unsigned int parent );
Folder( const std::string& path, unsigned int parent , unsigned int deviceId , bool isRemovable );
static bool createTable( DBConnection connection );
static std::shared_ptr<Folder> create(DBConnection connection, const std::string& path, time_t lastModificationDate, bool isRemovable, unsigned int parentId );
static bool blacklist( DBConnection connection, const std::string& path );
static std::shared_ptr<Folder> create( DBConnection connection, const std::string& path, unsigned int parentId, Device& device, fs::IDevice& deviceFs );
static bool blacklist(DBConnection connection, const std::string& fullPath );
static std::vector<std::shared_ptr<Folder>> fetchAll( DBConnection dbConn, unsigned int parentFolderId );
///
/// \brief setFileSystemFactory Sets a file system factory to be used when building IDevices
/// This is assumed to be called once, before any discovery/reloading process is launched.
/// \param fsFactory The factory to be used
///
static void setFileSystemFactory( std::shared_ptr<factory::IFileSystem> fsFactory );
static std::shared_ptr<Folder> fromPath( DBConnection conn, const std::string& path );
static std::shared_ptr<Folder> fromPath( DBConnection conn, const std::string& fullPath );
virtual unsigned int id() const override;
virtual const std::string& path() const override;
virtual std::vector<MediaPtr> files() override;
virtual std::vector<FolderPtr> folders() override;
virtual FolderPtr parent() override;
virtual unsigned int lastModificationDate() override;
bool setLastModificationDate(unsigned int lastModificationDate);
bool isRemovable();
unsigned int id() const;
const std::string& path() const;
std::vector<MediaPtr> files();
std::vector<std::shared_ptr<Folder>> folders();
std::shared_ptr<Folder> parent();
unsigned int deviceId() const;
bool isPresent() const;
private:
static std::shared_ptr<factory::IFileSystem> FsFactory;
private:
DBConnection m_dbConection;
unsigned int m_id;
// This contains the path relative to the device mountpoint (ie. excluding it)
std::string m_path;
unsigned int m_parent;
unsigned int m_lastModificationDate;
bool m_isRemovable;
bool m_isBlacklisted;
unsigned int m_deviceId;
bool m_isPresent;
bool m_isRemovable;
mutable Cache<std::string> m_deviceMountpoint;
// This contains the full path, including device mountpoint.
mutable std::string m_fullPath;
friend struct policy::FolderTable;
};
......@@ -38,6 +38,7 @@
#include "database/SqliteTools.h"
#include "VideoTrack.h"
#include "filesystem/IFile.h"
#include "utils/Filename.h"
const std::string policy::MediaTable::Name = "Media";
const std::string policy::MediaTable::PrimaryKeyColumn = "id_media";
......@@ -60,36 +61,41 @@ Media::Media( DBConnection dbConnection, sqlite::Row& row )
>> m_insertionDate
>> m_snapshot
>> m_isParsed
>> m_title;
>> m_title
>> m_isPresent
>> m_isRemovable;
}
Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& title, Type type )
Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& title, Type type, bool isRemovable )
: m_id( 0 )
, m_type( type )
, m_duration( -1 )
, m_playCount( 0 )
, m_showEpisodeId( 0 )
, m_mrl( file->fullPath() )
, m_mrl( isRemovable == true ? file->name() : file->fullPath() )
, m_movieId( 0 )
, m_folderId( folderId )
, m_lastModificationDate( file->lastModificationDate() )
, m_insertionDate( time( nullptr ) )
, m_isParsed( false )
, m_title( title )
, m_isPresent( true )
, m_isRemovable( isRemovable )
, m_changed( false )
{
}
std::shared_ptr<Media> Media::create( DBConnection dbConnection, Type type, const fs::IFile* file, unsigned int folderId )
std::shared_ptr<Media> Media::create( DBConnection dbConnection, Type type, const fs::IFile* file, unsigned int folderId, bool isRemovable )
{
auto self = std::make_shared<Media>( file, folderId, file->name(), type );
auto self = std::make_shared<Media>( file, folderId, file->name(), type, isRemovable );
static const std::string req = "INSERT INTO " + policy::MediaTable::Name +
"(type, mrl, folder_id, last_modification_date, insertion_date, title) VALUES(?, ?, ?, ?, ?, ?)";
"(type, mrl, folder_id, last_modification_date, insertion_date, title, is_removable) VALUES(?, ?, ?, ?, ?, ?, ?)";
if ( insert( dbConnection, self, req, type, self->m_mrl, sqlite::ForeignKey( folderId ),
self->m_lastModificationDate, self->m_insertionDate, self->m_title) == false )
self->m_lastModificationDate, self->m_insertionDate, self->m_title, isRemovable ) == false )
return nullptr;
self->m_dbConnection = dbConnection;
self->m_fullPath = file->fullPath();
return self;
}
......@@ -175,7 +181,17 @@ void Media::increasePlayCount()
const std::string& Media::mrl() const
{
return m_mrl;
if ( m_isRemovable == false )
return m_mrl;
auto lock = m_fullPath.lock();
if ( m_fullPath.isCached() )
return m_fullPath;
auto folder = Folder::fetch( m_dbConnection, m_folderId );
if ( folder == nullptr )
return m_mrl;
m_fullPath = folder->path() + m_mrl;
return m_fullPath;
}
MoviePtr Media::movie()
......@@ -232,11 +248,6 @@ unsigned int Media::insertionDate() const
return m_insertionDate;
}
bool Media::isAvailable() const
{
return true;
}
void Media::setSnapshot( const std::string& snapshot )
{
if ( m_snapshot == snapshot )
......@@ -264,11 +275,6 @@ bool Media::save()
return true;
}
bool Media::isStandAlone()
{
return m_folderId == 0;
}
unsigned int Media::lastModificationDate()
{
return m_lastModificationDate;
......@@ -326,7 +332,7 @@ bool Media::createTable( DBConnection connection )
"duration INTEGER,"
"play_count UNSIGNED INTEGER,"
"show_episode_id UNSIGNED INTEGER,"
"mrl TEXT UNIQUE ON CONFLICT FAIL,"
"mrl TEXT,"
"artist TEXT,"
"movie_id UNSIGNED INTEGER,"
"folder_id UNSIGNED INTEGER,"
......@@ -335,14 +341,23 @@ bool Media::createTable( DBConnection connection )
"snapshot TEXT,"
"parsed BOOLEAN NOT NULL DEFAULT 0,"
"title TEXT,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name
+ "(id_episode) ON DELETE CASCADE,"
"FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name
+ "(id_movie) ON DELETE CASCADE,"
"FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
+ "(id_folder) ON DELETE CASCADE"
+ "(id_folder) ON DELETE CASCADE,"
"UNIQUE( mrl, folder_id ) ON CONFLICT FAIL"
")";
return sqlite::Tools::executeRequest( connection, req );
std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_folder_present AFTER UPDATE OF is_present ON "
+ policy::FolderTable::Name +
" BEGIN"
" UPDATE " + policy::MediaTable::Name + " SET is_present = new.is_present WHERE folder_id = new.id_folder;"
" END";
return sqlite::Tools::executeRequest( connection, req ) &&
sqlite::Tools::executeRequest( connection, triggerReq );
}
bool Media::addLabel( LabelPtr label )
......
......@@ -28,7 +28,7 @@
#include "IMedia.h"
#include "database/DatabaseHelpers.h"
#include "utils/Cache.h"
class Album;
class ShowEpisode;
......@@ -36,11 +36,6 @@ class AlbumTrack;
class Media;
namespace sqlite
{
class Row;
}
namespace policy
{
struct MediaTable
......@@ -59,9 +54,9 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
// 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(const fs::IFile* file, unsigned int folderId, const std::string &title, Type type);
Media( const fs::IFile* file, unsigned int folderId, const std::string &title, Type type, bool isRemovable );
static std::shared_ptr<Media> create(DBConnection dbConnection, Type type, const fs::IFile* file , unsigned int folderId);
static std::shared_ptr<Media> create( DBConnection dbConnection, Type type, const fs::IFile* file , unsigned int folderId, bool isRemovable );
static bool createTable( DBConnection connection );
virtual unsigned int id() const override;
......@@ -93,11 +88,9 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
virtual std::vector<AudioTrackPtr> audioTracks() override;
virtual const std::string& snapshot() override;
virtual unsigned int insertionDate() const override;
virtual bool isAvailable() const override;
void setSnapshot( const std::string& snapshot );
bool save();
bool isStandAlone();
unsigned int lastModificationDate();
/// Explicitely mark a file as fully parsed, meaning no metadata service needs to run anymore.
......@@ -124,12 +117,15 @@ class Media : public IMedia, public DatabaseHelpers<Media, policy::MediaTable>
std::string m_snapshot;
bool m_isParsed;
std::string m_title;
bool m_isPresent;
bool m_isRemovable;
// Auto fetched related properties
AlbumTrackPtr m_albumTrack;
ShowEpisodePtr m_showEpisode;
MoviePtr m_movie;
bool m_changed;
mutable Cache<std::string> m_fullPath;
friend struct policy::MediaTable;
};
......
......@@ -31,6 +31,7 @@
#include "AudioTrack.h"
#include "discoverer/DiscovererWorker.h"
#include "Media.h"
#include "Device.h"
#include "Folder.h"
#include "MediaLibrary.h"
#include "IMetadataService.h"
......@@ -42,7 +43,7 @@
#include "ShowEpisode.h"
#include "database/SqliteTools.h"
#include "database/SqliteConnection.h"
#include "Utils.h"
#include "utils/Filename.h"
#include "VideoTrack.h"
// Discoverers:
......@@ -54,6 +55,7 @@
#include "filesystem/IDirectory.h"
#include "filesystem/IFile.h"
#include "filesystem/IDevice.h"
#include "factory/FileSystem.h"
const std::vector<std::string> MediaLibrary::supportedVideoExtensions {
......@@ -77,8 +79,7 @@ const std::vector<std::string> MediaLibrary::supportedAudioExtensions {
const uint32_t MediaLibrary::DbModelVersion = 1;
MediaLibrary::MediaLibrary()
: m_discoverer( new DiscovererWorker )
, m_verbosity( LogLevel::Error )
: m_verbosity( LogLevel::Error )
{
Log::setLogLevel( m_verbosity );
}
......@@ -86,7 +87,8 @@ MediaLibrary::MediaLibrary()
MediaLibrary::~MediaLibrary()
{
// Explicitely stop the discoverer, to avoid it writting while tearing down.
m_discoverer->stop();
if ( m_discoverer != nullptr )
m_discoverer->stop();
Media::clear();
Folder::clear();
Label::clear();
......@@ -98,6 +100,7 @@ MediaLibrary::~MediaLibrary()
VideoTrack::clear();
AudioTrack::clear();
Artist::clear();
Device::clear();
// Explicitely release the connection's TLS
if ( m_dbConnection != nullptr )
m_dbConnection->release();
......@@ -112,40 +115,23 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
{
if ( m_fsFactory == nullptr )
m_fsFactory.reset( new factory::FileSystemFactory );
Folder::setFileSystemFactory( m_fsFactory );
m_snapshotPath = snapshotPath;
m_callback = mlCallback;
m_dbConnection.reset( new SqliteConnection( dbPath ) );
m_parser.reset( new Parser( m_dbConnection.get(), m_callback ) );
if ( mlCallback != nullptr )
{
const char* args[] = {
"-vv",
};
m_vlcInstance = VLC::Instance( sizeof(args) / sizeof(args[0]), args );
m_vlcInstance.logSet([this](int lvl, const libvlc_log_t*, std::string msg) {
if ( m_verbosity != LogLevel::Verbose )
return ;
if ( lvl == LIBVLC_ERROR )
Log::Error( msg );
else if ( lvl == LIBVLC_WARNING )
Log::Warning( msg );
else
Log::Info( msg );
});
auto vlcService = std::unique_ptr<VLCMetadataService>( new VLCMetadataService( m_vlcInstance, m_dbConnection.get(), m_fsFactory ) );
auto thumbnailerService = std::unique_ptr<VLCThumbnailer>( new VLCThumbnailer( m_vlcInstance ) );
addMetadataService( std::move( vlcService ) );
addMetadataService( std::move( thumbnailerService ) );
}
auto t = m_dbConnection->newTransaction();
if ( ( Media::createTable( m_dbConnection.get() ) &&
// We need to create the tables in order of triggers creation
// Device is the "root of all evil". When a device is modified,
// we will trigger an update on folder, which will trigger
// an update on files, and so on.
if ( ( Device::createTable( m_dbConnection.get() ) &&
Folder::createTable( m_dbConnection.get() ) &&
Media::createTable( m_dbConnection.get() ) &&
Label::createTable( m_dbConnection.get() ) &&
Album::createTable( m_dbConnection.get() ) &&
AlbumTrack::createTable( m_dbConnection.get() ) &&
Album::createTriggers( m_dbConnection.get() ) &&
Show::createTable( m_dbConnection.get() ) &&
ShowEpisode::createTable( m_dbConnection.get() ) &&
Movie::createTable( m_dbConnection.get() ) &&
......@@ -153,6 +139,7 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
AudioTrack::createTable( m_dbConnection.get() ) &&
Artist::createTable( m_dbConnection.get() ) &&
Artist::createDefaultArtists( m_dbConnection.get() ) &&
Artist::createTriggers( m_dbConnection.get() ) &&
Settings::createTable( m_dbConnection.get() ) ) == false )
{
LOG_ERROR( "Failed to create database structure" );
......@@ -164,10 +151,8 @@ bool MediaLibrary::initialize( const std::string& dbPath, const std::string& sna
if ( m_settings.dbModelVersion() != DbModelVersion )
return false;
t->commit();
m_discoverer->setCallback( m_callback );
m_discoverer->addDiscoverer( std::unique_ptr<IDiscoverer>( new FsDiscoverer( m_fsFactory, this, m_dbConnection.get() ) ) );
m_discoverer->reload();
m_parser->start();
startDiscoverer();
startParser();
return true;
}
......@@ -179,31 +164,24 @@ void MediaLibrary::setVerbosity(LogLevel v)
std::vector<MediaPtr> MediaLibrary::files()
{
return Media::fetchAll<IMedia>( m_dbConnection.get() );
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE is_present = 1";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req );
}
std::vector<MediaPtr> MediaLibrary::audioFiles()
{
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? ORDER BY title";
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY title";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::AudioType );
}
std::vector<MediaPtr> MediaLibrary::videoFiles()
{
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? ORDER BY title";
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY title";
return Media::fetchAll<IMedia>( m_dbConnection.get(), req, IMedia::Type::VideoType );
}
MediaPtr MediaLibrary::file( const std::string& path )
{
static const std::string req = "SELECT * FROM " + policy::MediaTable::Name +
" WHERE mrl = ?";
return Media::fetch( m_dbConnection.get(), req, path );
}
std::shared_ptr<Media> MediaLibrary::addFile( const std::string& path, FolderPtr parentFolder )
std::shared_ptr<Media> MediaLibrary::addFile( const std::string& path, Folder& parentFolder, fs::IDirectory& parentFolderFs )