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

Split Task progress out of the File class & entities

This is now handled by a specific task table in DB
parent 0f1b0d8b
......@@ -40,7 +40,6 @@ int64_t File::* const policy::FileTable::PrimaryKey = &File::m_id;
File::File( MediaLibraryPtr ml, sqlite::Row& row )
: m_ml( ml )
{
bool dummyNbRetries;
row >> m_id
>> m_mediaId
>> m_playlistId
......@@ -48,8 +47,6 @@ File::File( MediaLibraryPtr ml, sqlite::Row& row )
>> m_type
>> m_lastModificationDate
>> m_size
>> m_parserSteps
>> dummyNbRetries
>> m_folderId
>> m_isPresent
>> m_isRemovable
......@@ -65,7 +62,6 @@ File::File( MediaLibraryPtr ml, int64_t mediaId, int64_t playlistId, Type type,
, m_type( type )
, m_lastModificationDate( file.lastModificationDate() )
, m_size( file.size() )
, m_parserSteps( parser::Task::ParserStep::None )
, m_folderId( folderId )
, m_isPresent( true )
, m_isRemovable( isRemovable )
......@@ -83,7 +79,6 @@ File::File(MediaLibraryPtr ml, int64_t mediaId, int64_t playlistId, IFile::Type
, m_type( type )
, m_lastModificationDate( 0 )
, m_size( 0 )
, m_parserSteps( parser::Task::ParserStep::Completed )
, m_folderId( 0 )
, m_isPresent( true )
, m_isRemovable( false )
......@@ -133,39 +128,6 @@ bool File::isExternal() const
return m_isExternal;
}
void File::markStepCompleted( parser::Task::ParserStep step )
{
m_parserSteps = static_cast<parser::Task::ParserStep>( static_cast<uint8_t>( m_parserSteps ) |
static_cast<uint8_t>( step ) );
}
void File::markStepUncompleted(parser::Task::ParserStep step)
{
m_parserSteps = static_cast<parser::Task::ParserStep>( static_cast<uint8_t>( m_parserSteps ) &
( ~ static_cast<uint8_t>( step ) ) );
}
bool File::saveParserStep()
{
static const std::string req = "UPDATE " + policy::FileTable::Name + " SET parser_step = ?, "
"parser_retries = 0 WHERE id_file = ?";
if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_parserSteps, m_id ) == false )
return false;
return true;
}
parser::Task::ParserStep File::parserStep() const
{
return m_parserSteps;
}
void File::startParserStep()
{
static const std::string req = "UPDATE " + policy::FileTable::Name + " SET "
"parser_retries = parser_retries + 1 WHERE id_file = ?";
sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_id );
}
std::shared_ptr<Media> File::media() const
{
if ( m_mediaId == 0 )
......@@ -200,8 +162,6 @@ void File::createTable( sqlite::Connection* dbConnection )
"type UNSIGNED INTEGER,"
"last_modification_date UNSIGNED INT,"
"size UNSIGNED INT,"
"parser_step INTEGER NOT NULL DEFAULT 0,"
"parser_retries INTEGER NOT NULL DEFAULT 0,"
"folder_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
......@@ -316,25 +276,5 @@ std::shared_ptr<File> File::fromExternalMrl( MediaLibraryPtr ml, const std::stri
return file;
}
std::vector<std::shared_ptr<File>> File::fetchUnparsed( MediaLibraryPtr ml )
{
static const std::string req = "SELECT * FROM " + policy::FileTable::Name
+ " WHERE parser_step != ? AND is_present != 0 AND folder_id IS NOT NULL AND parser_retries < 3";
return File::fetchAll<File>( ml, req, parser::Task::ParserStep::Completed );
}
void File::resetRetryCount( MediaLibraryPtr ml )
{
static const std::string req = "UPDATE " + policy::FileTable::Name + " SET "
"parser_retries = 0 WHERE parser_step != ? AND is_present != 0 AND folder_id IS NOT NULL";
sqlite::Tools::executeUpdate( ml->getConn(), req, parser::Task::ParserStep::Completed );
}
void File::resetParsing( MediaLibraryPtr ml )
{
static const std::string req = "UPDATE " + policy::FileTable::Name + " SET "
"parser_retries = 0, parser_step = ?";
sqlite::Tools::executeUpdate( ml->getConn(), req, parser::Task::ParserStep::None );
}
}
......@@ -58,21 +58,7 @@ public:
virtual unsigned int lastModificationDate() const override;
virtual unsigned int size() const override;
virtual bool isExternal() const override;
/*
* We need to decouple the current parser state and the saved one.
* For instance, metadata extraction won't save anything in DB, so while
* we might want to know that it's been processed and metadata have been
* extracted, in case we were to restart the parsing, we would need to
* extract the same information again
*/
void markStepCompleted( parser::Task::ParserStep step );
void markStepUncompleted( parser::Task::ParserStep step );
bool saveParserStep();
parser::Task::ParserStep parserStep() const;
/**
* @brief startParserStep Do some internal book keeping to avoid restarting a step too many time
*/
void startParserStep();
std::shared_ptr<Media> media() const;
bool destroy();
int64_t folderId();
......@@ -110,10 +96,6 @@ public:
*/
static std::shared_ptr<File> fromExternalMrl( MediaLibraryPtr ml, const std::string& mrl );
static std::vector<std::shared_ptr<File>> fetchUnparsed( MediaLibraryPtr ml );
static void resetRetryCount( MediaLibraryPtr ml );
static void resetParsing( MediaLibraryPtr ml );
private:
MediaLibraryPtr m_ml;
......@@ -126,7 +108,6 @@ private:
Type m_type;
std::time_t m_lastModificationDate;
unsigned int m_size;
parser::Task::ParserStep m_parserSteps;
int64_t m_folderId;
bool m_isPresent;
bool m_isRemovable;
......
......@@ -156,6 +156,7 @@ void MediaLibrary::createAllTables()
Artist::createDefaultArtists( m_dbConnection.get() );
History::createTable( m_dbConnection.get() );
Settings::createTable( m_dbConnection.get() );
parser::Task::createTable( m_dbConnection.get() );
}
void MediaLibrary::createAllTriggers()
......@@ -423,9 +424,10 @@ void MediaLibrary::addDiscoveredFile( std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<fs::IDirectory> parentFolderFs,
std::pair<std::shared_ptr<Playlist>, unsigned int> parentPlaylist )
{
if ( m_parser != nullptr )
m_parser->parse( std::move( fileFs ), std::move( parentFolder ),
std::move( parentFolderFs ), std::move( parentPlaylist ) );
auto task = parser::Task::create( this, std::move( fileFs ), std::move( parentFolder ),
std::move( parentFolderFs ), std::move( parentPlaylist ) );
if ( task != nullptr && m_parser != nullptr )
m_parser->parse( task );
}
bool MediaLibrary::deleteFolder( const Folder& folder )
......@@ -907,7 +909,7 @@ bool MediaLibrary::forceParserRetry()
{
try
{
File::resetRetryCount( this );
parser::Task::resetRetryCount( this );
return true;
}
catch ( const sqlite::errors::Generic& ex )
......@@ -1093,7 +1095,7 @@ void MediaLibrary::forceRescan()
Show::deleteAll( this );
VideoTrack::deleteAll( this );
AudioTrack::deleteAll( this );
File::resetParsing( this );
parser::Task::deleteAll( this );
clearCache();
Artist::createDefaultArtists( getConn() );
t->commit();
......
......@@ -39,4 +39,54 @@
"UPDATE " + ArtistTable::Name + " SET nb_tracks = ("
"SELECT COUNT(id_track) FROM " + AlbumTrackTable::Name + " WHERE "
"artist_id = " + ArtistTable::Name + ".id_artist"
")"
")",
"CREATE TEMPORARY TABLE " + FileTable::Name + "_backup("
"id_file INTEGER PRIMARY KEY AUTOINCREMENT,"
"media_id INT NOT NULL,"
"playlist_id UNSIGNED INT DEFAULT NULL,"
"mrl TEXT,"
"type UNSIGNED INTEGER,"
"last_modification_date UNSIGNED INT,"
"size UNSIGNED INT,"
"parser_step INTEGER NOT NULL DEFAULT 0,"
"parser_retries INTEGER NOT NULL DEFAULT 0,"
"folder_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"is_external BOOLEAN NOT NULL,"
"FOREIGN KEY (media_id) REFERENCES " + MediaTable::Name + "(id_media) ON DELETE CASCADE,"
"FOREIGN KEY (playlist_id) REFERENCES " + PlaylistTable::Name + "(id_playlist) ON DELETE CASCADE,"
"FOREIGN KEY (folder_id) REFERENCES " + FolderTable::Name + "(id_folder) ON DELETE CASCADE,"
"UNIQUE( mrl, folder_id ) ON CONFLICT FAIL);",
"INSERT INTO " + FileTable::Name + "_backup SELECT * FROM " + FileTable::Name + ";",
"DROP TABLE " + FileTable::Name + ";",
"CREATE TABLE " + FileTable::Name + "(id_file INTEGER PRIMARY KEY AUTOINCREMENT,"
"media_id UNSIGNED INT DEFAULT NULL,"
"playlist_id UNSIGNED INT DEFAULT NULL,"
"mrl TEXT,"
"type UNSIGNED INTEGER,"
"last_modification_date UNSIGNED INT,"
"size UNSIGNED INT,"
// Removed parsed_step & parser_retries
"folder_id UNSIGNED INTEGER,"
"is_present BOOLEAN NOT NULL DEFAULT 1,"
"is_removable BOOLEAN NOT NULL,"
"is_external BOOLEAN NOT NULL,"
"FOREIGN KEY (media_id) REFERENCES " + MediaTable::Name + "(id_media) ON DELETE CASCADE,"
"FOREIGN KEY (playlist_id) REFERENCES " + PlaylistTable::Name + "(id_playlist) ON DELETE CASCADE,"
"FOREIGN KEY (folder_id) REFERENCES " + FolderTable::Name + "(id_folder) ON DELETE CASCADE,"
"UNIQUE( mrl, folder_id ) ON CONFLICT FAIL);",
"INSERT INTO " + FileTable::Name + "("
"id_file, media_id, playlist_id, mrl, type, last_modification_date, size, folder_id,"
"is_present, is_removable, is_external)"
" SELECT "
"id_file, media_id, playlist_id, mrl, type, last_modification_date, size, folder_id,"
"is_present, is_removable, is_external"
" FROM " + FileTable::Name + "_backup;",
"DROP TABLE " + FileTable::Name + "_backup;",
......@@ -91,7 +91,7 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
assert( task.file != nullptr );
task.markStepCompleted( parser::Task::ParserStep::Completed );
task.file->saveParserStep();
task.saveParserStep();
return parser::Task::Status::Success;
}
......@@ -119,8 +119,6 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
return parser::Task::Status::Fatal;
}
t->commit();
// Synchronize file step tracker with task
task.markStepCompleted( task.step );
}
// Voluntarily trigger an exception for a valid, but less common case, to avoid database overhead
catch ( sqlite::errors::ConstraintViolation& ex )
......@@ -156,7 +154,8 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
if ( alreadyInParser == true )
{
task.step = parser::Task::ParserStep::Completed; // Let the worker drop this duplicate task
// Let the worker drop this duplicate task
task.markStepCompleted( parser::Task::ParserStep::Completed );
return parser::Task::Status::Success;
}
......@@ -227,7 +226,7 @@ parser::Task::Status MetadataParser::run( parser::Task& task )
// we're analyzing an audio file
if ( isAudio == true && utils::file::schemeIs( "attachment://", task.media->thumbnail() ) == false )
task.markStepCompleted( parser::Task::ParserStep::Thumbnailer );
if ( task.file->saveParserStep() == false )
if ( task.saveParserStep() == false )
return parser::Task::Status::Fatal;
m_notifier->notifyMediaCreation( task.media );
return parser::Task::Status::Success;
......@@ -808,8 +807,7 @@ void MetadataParser::flush()
bool MetadataParser::isCompleted( const parser::Task& task ) const
{
// We always need to run this task if the metadata extraction isn't completed
return ( static_cast<uint8_t>( task.step ) &
static_cast<uint8_t>( parser::Task::ParserStep::MetadataAnalysis ) ) != 0;
return task.isStepCompleted( parser::Task::ParserStep::MetadataAnalysis );
}
}
......@@ -68,8 +68,7 @@ bool VLCThumbnailer::initialize()
bool VLCThumbnailer::isCompleted( const parser::Task& task ) const
{
return ( static_cast<uint8_t>( task.file->parserStep() ) &
static_cast<uint8_t>( parser::Task::ParserStep::Thumbnailer ) ) != 0;
return task.isStepCompleted( parser::Task::ParserStep::Thumbnailer );
}
parser::Task::Status VLCThumbnailer::run( parser::Task& task )
......@@ -110,7 +109,7 @@ parser::Task::Status VLCThumbnailer::run( parser::Task& task )
if ( media->type() == Media::Type::Audio )
{
task.markStepCompleted( parser::Task::ParserStep::Thumbnailer );
file->saveParserStep();
task.saveParserStep();
LOG_INFO( file->mrl(), " type has changed to Audio. Skipping thumbnail generation" );
return parser::Task::Status::Success;
}
......@@ -153,7 +152,7 @@ parser::Task::Status VLCThumbnailer::run( parser::Task& task )
m_notifier->notifyMediaModification( task.media );
auto t = m_ml->getConn()->newTransaction();
if ( media->save() == false || file->saveParserStep() == false )
if ( media->save() == false || task.saveParserStep() == false )
return parser::Task::Status::Fatal;
t->commit();
return parser::Task::Status::Success;
......
......@@ -57,30 +57,11 @@ void Parser::addService( ServicePtr service )
m_services.push_back( std::move( service ) );
}
void Parser::parse( std::shared_ptr<File> file, std::shared_ptr<Media> media,
const std::string& mrl )
void Parser::parse( std::shared_ptr<parser::Task> task )
{
if ( m_services.empty() == true )
return;
m_services[0]->parse( std::unique_ptr<parser::Task>( new parser::Task(
std::move( file ),
std::move( media ),
mrl ) ) );
m_opToDo += m_services.size();
updateStats();
}
void Parser::parse( std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<Folder> parentFolder,
std::shared_ptr<fs::IDirectory> parentFolderFs,
std::pair<std::shared_ptr<Playlist>, unsigned int> parentPlaylist )
{
if ( m_services.empty() == true )
return;
std::string mrl = fileFs->mrl();
m_services[0]->parse( std::unique_ptr<parser::Task>( new parser::Task(
std::move( fileFs ), std::move( parentFolder ), std::move( parentFolderFs ),
std::move( parentPlaylist.first ), parentPlaylist.second, mrl ) ) );
m_services[0]->parse( std::move( task ) );
m_opToDo += m_services.size();
updateStats();
}
......@@ -125,11 +106,12 @@ void Parser::restore()
if ( m_services.empty() == true )
return;
auto files = File::fetchUnparsed( m_ml );
LOG_INFO( "Resuming parsing on ", files.size(), " mrl" );
for ( auto& f : files )
auto tasks = parser::Task::fetchUnparsed( m_ml );
LOG_INFO( "Resuming parsing on ", tasks.size(), " tasks" );
for ( auto& t : tasks )
{
parse( f, f->media(), f->mrl() );
t->restoreLinkedEntities();
parse( t );
}
}
......@@ -153,21 +135,23 @@ void Parser::updateStats()
}
}
void Parser::done( std::unique_ptr<parser::Task> t, parser::Task::Status status )
void Parser::done( std::shared_ptr<parser::Task> t, parser::Task::Status status )
{
++m_opDone;
auto serviceIdx = ++t->currentService;
if ( status == parser::Task::Status::TemporaryUnavailable ||
status == parser::Task::Status::Fatal ||
( t->file != nullptr && t->file->parserStep() == parser::Task::ParserStep::Completed ) )
status == parser::Task::Status::Fatal || t->isCompleted() )
{
if ( serviceIdx < m_services.size() )
{
// We won't process the next tasks, so we need to keep the number of "todo" operations coherent:
m_opToDo -= m_services.size() - serviceIdx;
}
// If the task is now completed, there is no need to store it in database anymore
if ( t->isCompleted() )
t->removeFromDB();
updateStats();
return;
}
......
......@@ -38,7 +38,7 @@ class IParserCb
{
public:
virtual ~IParserCb() = default;
virtual void done( std::unique_ptr<parser::Task> task, parser::Task::Status status ) = 0;
virtual void done( std::shared_ptr<parser::Task> task, parser::Task::Status status ) = 0;
virtual void onIdleChanged( bool isIdle ) = 0;
};
......@@ -50,12 +50,7 @@ public:
Parser( MediaLibrary* ml );
virtual ~Parser();
void addService( ServicePtr service );
void parse( std::shared_ptr<File> file, std::shared_ptr<Media> media,
const std::string& mrl );
void parse( std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<Folder> parentFolder,
std::shared_ptr<fs::IDirectory> parentFolderFs,
std::pair<std::shared_ptr<Playlist>, unsigned int> parentPlaylist );
void parse( std::shared_ptr<parser::Task> task );
void start();
void pause();
void resume();
......@@ -66,7 +61,8 @@ public:
private:
void updateStats();
virtual void done( std::unique_ptr<parser::Task> task, parser::Task::Status status ) override;
virtual void done( std::shared_ptr<parser::Task> task,
parser::Task::Status status ) override;
virtual void onIdleChanged( bool idle ) override;
private:
......
......@@ -86,7 +86,7 @@ void ParserService::stop()
}
}
void ParserService::parse( std::unique_ptr<parser::Task> t )
void ParserService::parse( std::shared_ptr<parser::Task> t )
{
if ( m_threads.size() == 0 )
{
......@@ -152,7 +152,7 @@ void ParserService::mainloop()
while ( m_stopParser == false )
{
std::unique_ptr<parser::Task> task;
std::shared_ptr<parser::Task> task;
{
std::unique_lock<compat::Mutex> lock( m_lock );
if ( m_tasks.empty() == true || m_paused == true )
......@@ -191,8 +191,7 @@ void ParserService::mainloop()
status = parser::Task::Status::Fatal;
else
{
if ( task->file != nullptr )
task->file->startParserStep(); // FIXME ?
task->startParserStep();
status = run( *task );
auto duration = std::chrono::steady_clock::now() - chrono;
LOG_INFO( "Done executing ", serviceName, " task on ", task->mrl, " in ",
......
......@@ -59,7 +59,7 @@ public:
/// \brief stop Effectively wait the the underlying threads to join.
///
void stop();
void parse( std::unique_ptr<parser::Task> t );
void parse( std::shared_ptr<parser::Task> t );
void initialize( MediaLibrary* mediaLibrary, IParserCb* parserCb );
bool isIdle() const;
///
......@@ -96,7 +96,7 @@ private:
std::atomic_bool m_idle;
compat::ConditionVariable m_cond;
compat::ConditionVariable m_idleCond;
std::queue<std::unique_ptr<parser::Task>> m_tasks;
std::queue<std::shared_ptr<parser::Task>> m_tasks;
std::vector<compat::Thread> m_threads;
compat::Mutex m_lock;
};
......
......@@ -28,53 +28,187 @@
#include "Task.h"
#include "filesystem/IFile.h"
#include "filesystem/IDirectory.h"
#include "File.h"
#include "Folder.h"
#include "Playlist.h"
#include "parser/Task.h"
#include "utils/Filename.h"
namespace medialibrary
{
const std::string policy::TaskTable::Name = "Task";
const std::string policy::TaskTable::PrimaryKeyColumn = "id_task";
int64_t parser::Task::* const policy::TaskTable::PrimaryKey = &parser::Task::m_id;
namespace parser
{
Task::Task( std::shared_ptr<File> file, std::shared_ptr<Media> media,
std::string mrl )
: media( std::move( media ) )
, file( std::move( file ) )
, mrl( std::move( mrl ) )
, currentService( 0 )
, step( this->file->parserStep() )
Task::Task( MediaLibraryPtr ml, sqlite::Row& row )
: currentService( 0 )
, m_ml( ml )
{
row >> m_id
>> m_step
>> m_retryCount
>> mrl
>> m_fileId
>> m_parentFolderId
>> m_parentPlaylistId
>> parentPlaylistIndex;
}
Task::Task( std::shared_ptr<fs::IFile> fileFs,
Task::Task( MediaLibraryPtr ml, std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<Folder> parentFolder,
std::shared_ptr<fs::IDirectory> parentFolderFs,
std::shared_ptr<Playlist> parentPlaylist,
unsigned int parentPlaylistIndex,
std::string mrl )
unsigned int parentPlaylistIndex )
: fileFs( std::move( fileFs ) )
, parentFolder( std::move( parentFolder ) )
, parentFolderFs( std::move( parentFolderFs ) )
, parentPlaylist( std::move( parentPlaylist ) )
, parentPlaylistIndex( parentPlaylistIndex )
, mrl( std::move( mrl ) )
, mrl( this->fileFs->mrl() )
, currentService( 0 )
, step( ParserStep::None )
, m_ml( ml )
, m_step( ParserStep::None )
{
}
void Task::markStepCompleted( ParserStep stepCompleted )
{
step = static_cast<ParserStep>( static_cast<uint8_t>( step ) | static_cast<uint8_t>( stepCompleted ) );
if ( file != nullptr )
file->markStepCompleted( stepCompleted );
m_step = static_cast<ParserStep>( static_cast<uint8_t>( m_step ) |
static_cast<uint8_t>( stepCompleted ) );
}
void Task::markStepUncompleted( ParserStep stepUncompleted )
{
step = static_cast<ParserStep>( static_cast<uint8_t>( step ) & ( ~ static_cast<uint8_t>( stepUncompleted ) ) );
m_step = static_cast<ParserStep>( static_cast<uint8_t>( m_step ) &
( ~ static_cast<uint8_t>( stepUncompleted ) ) );
}
bool Task::saveParserStep()
{
static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET step = ?, "
"retry_count = 0 WHERE id_task = ?";
if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_step, m_id ) == false )
return false;
return true;
}
bool Task::isCompleted() const
{
return m_step == ParserStep::Completed;
}
bool Task::isStepCompleted( Task::ParserStep step ) const
{
return ( static_cast<uint8_t>( m_step ) & static_cast<uint8_t>( step ) ) != 0;
}
bool Task::removeFromDB()
{
return destroy( m_ml, m_id );
}
void Task::startParserStep()
{
static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
"retry_count = retry_count + 1 WHERE id_task = ?";
sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_id );
}
bool Task::restoreLinkedEntities( )
{
auto fsFactory = m_ml->fsFactoryForMrl( mrl );
if ( fsFactory == nullptr )
return false;
parentFolderFs = fsFactory->createDirectory( utils::file::directory( mrl ) );
if ( parentFolderFs == nullptr )
return false;
auto files = parentFolderFs->files();
auto it = std::find_if( begin( files ), end( files ), [this]( std::shared_ptr<fs::IFile> f ) {
return f->mrl() == mrl;
});
if ( it == end( files ) )
{
LOG_ERROR( "Failed to restore fs::IFile associated with ", mrl );
return false;
}
fileFs = *it;
file = File::fetch( m_ml, m_fileId );
if ( file != nullptr )
file->markStepUncompleted( stepUncompleted );
media = file->media();
parentFolder = Folder::fetch( m_ml, m_parentFolderId );
if ( m_parentPlaylistId != 0 )
parentPlaylist = Playlist::fetch( m_ml, m_parentPlaylistId );
return true;
}
void Task::createTable( sqlite::Connection* dbConnection )
{
std::string req = "CREATE TABLE IF NOT EXISTS " + policy::TaskTable::Name + "("
"id_task INTEGER PRIMARY KEY AUTOINCREMENT,"
"step INTEGER NOT NULL DEFAULT 0,"
"retry_count INTEGER NOT NULL DEFAULT 0,"
"mrl TEXT,"
"file_id UNSIGNED INTEGER,"
"parent_folder_id UNSIGNED INTEGER,"
"parent_playlist_id INTEGER,"
"parent_playlist_index UNSIGNED INTEGER,"
"FOREIGN KEY (parent_folder_id) REFERENCES " + policy::FolderTable::Name
+ "(id_folder) ON DELETE CASCADE,"
"FOREIGN KEY (file_id) REFERENCES " + policy::FileTable::Name
+ "(id_file) ON DELETE CASCADE,"
"FOREIGN KEY (parent_playlist_id) REFERENCES " + policy::PlaylistTable::Name
+ "(id_playlist) ON DELETE CASCADE"
")";
sqlite::Tools::executeRequest( dbConnection, req );
}
void Task::resetRetryCount( MediaLibraryPtr ml )
{
static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
"retry_count = 0 WHERE step != ? AND is_present != 0";
sqlite::Tools::executeUpdate( ml->getConn(), req, parser::Task::ParserStep::Completed );
}
void Task::resetParsing( MediaLibraryPtr ml )
{
static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
"retry_count = 0, step = ?";
sqlite::Tools::executeUpdate( ml->getConn(), req, parser::Task::ParserStep::None );
}
std::vector<std::shared_ptr<Task>> Task::fetchUnparsed( MediaLibraryPtr ml )
{
static const std::string req = "SELECT * FROM " + policy::TaskTable::Name + " t"
" LEFT JOIN " + policy::FileTable::Name + " f ON f.id_file = t.file_id"
" WHERE step != ? AND retry_count < 3 AND (f.is_present != 0 OR "
" t.file_id IS NULL)";
return Task::fetchAll<Task>( ml, req, parser::Task::ParserStep::Completed );
}
std::shared_ptr<Task>
Task::create( MediaLibraryPtr ml, std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<Folder> parentFolder, std::shared_ptr<fs::IDirectory> parentFolderFs,
std::pair<std::shared_ptr<Playlist>, unsigned int> parentPlaylist )
{
std::shared_ptr<Task> self = std::make_shared<Task>( ml, std::move( fileFs ),
std::move( parentFolder ), std::move( parentFolderFs ),
std::move( parentPlaylist.first ), parentPlaylist.second );
const std::string req = "INSERT INTO " + policy::TaskTable::Name +
"(mrl, parent_folder_id, parent_playlist_id, parent_playlist_index) "
"VALUES(?, ?, ?, ?)";
if ( insert( ml, self, req, self->mrl, self->parentFolder->id(), sqlite::ForeignKey(
self->parentPlaylist ? self->parentPlaylist->id() : 0 ),
self->parentPlaylistIndex ) == false )
return nullptr;
return self;
}
}
......
......@@ -29,6 +29,8 @@
#include <string>
#include <vlcpp/vlc.hpp>
#include "database/DatabaseHelpers.h"
namespace medialibrary
{
......@@ -45,8 +47,23 @@ class Playlist;
namespace parser
{
class Task;
}
struct Task
namespace policy
{
struct TaskTable
{
static const std::string Name;
static const std::string PrimaryKeyColumn;
static int64_t parser::Task::*const PrimaryKey;
};
}
namespace parser
{
struct Task : public DatabaseHelpers<Task, policy::TaskTable, cachepolicy::Uncached<Task>>
{
enum class Status
{
......@@ -77,17 +94,33 @@ struct Task
* The Media is provided as a parameter to avoid this to implicitely query
* the database for the media associated to the provided file
*/
Task( std::shared_ptr<File> file, std::shared_ptr<Media> media,
std::string mrl );
Task( std::shared_ptr<fs::IFile> fileFs,
Task( MediaLibraryPtr ml, sqlite::Row& row );
Task( MediaLibraryPtr ml, std::shared_ptr<fs::IFile> fileFs,
std::shared_ptr<Folder> parentFolder,
std::shared_ptr<fs::IDirectory> parentFolderFs,
std::shared_ptr<Playlist> parentPlaylist,
unsigned int parentPlaylistIndex,
std::string mrl );