Task.cpp 15.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2015 - 2017 Hugo Beauzée-Luyssen, Videolabs
 *
 * Authors: Hugo Beauzée-Luyssen<hugo@beauzee.fr>
 *          Alexandre Fernandez <nerf@boboop.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "Task.h"

30 31
#include "medialibrary/filesystem/IFile.h"
#include "medialibrary/filesystem/IDirectory.h"
32
#include "File.h"
33 34
#include "Folder.h"
#include "Playlist.h"
35
#include "Media.h"
36 37
#include "parser/Task.h"
#include "utils/Filename.h"
38
#include "utils/Url.h"
39

40 41
#include <algorithm>

42 43 44
namespace medialibrary
{

45 46 47 48
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;

49 50 51
namespace parser
{

52 53 54
Task::Task( MediaLibraryPtr ml, sqlite::Row& row )
    : currentService( 0 )
    , m_ml( ml )
55
{
56
    std::string mrl;
57
    unsigned int parentPlaylistIndex;
58 59 60 61 62 63 64 65
    row >> m_id
        >> m_step
        >> m_retryCount
        >> mrl
        >> m_fileId
        >> m_parentFolderId
        >> m_parentPlaylistId
        >> parentPlaylistIndex;
66
    m_item = Item{ this, std::move( mrl ), parentPlaylistIndex };
67 68
}

69
Task::Task( MediaLibraryPtr ml, std::shared_ptr<fs::IFile> fileFs,
70
            std::shared_ptr<Folder> parentFolder,
71
            std::shared_ptr<fs::IDirectory> parentFolderFs,
72
            std::shared_ptr<Playlist> parentPlaylist,
73
            unsigned int parentPlaylistIndex )
74
    : currentService( 0 )
75
    , m_ml( ml )
76
    , m_step( Step::None )
77
    , m_fileId( 0 )
78
    , m_item( this, std::move( fileFs ), std::move( parentFolder ),
79
              std::move( parentFolderFs ), std::move( parentPlaylist ), parentPlaylistIndex )
80 81 82
{
}

83
void Task::markStepCompleted( Step stepCompleted )
84
{
85
    m_step = static_cast<Step>( static_cast<uint8_t>( m_step ) |
86
                                      static_cast<uint8_t>( stepCompleted ) );
87 88
}

89 90 91 92
bool Task::saveParserStep()
{
    static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET step = ?, "
            "retry_count = 0 WHERE id_task = ?";
93
    return sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_step, m_id );
94 95
}

96 97 98 99 100 101 102
bool Task::resetRetryCountOnSuccess()
{
    static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
            "retry_count = 0 WHERE id_task = ?";
    return sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_id );
}

103 104
bool Task::isCompleted() const
{
105
    using StepType = typename std::underlying_type<Step>::type;
106
    return ( static_cast<StepType>( m_step ) &
107 108
             static_cast<StepType>( Step::Completed ) ) ==
             static_cast<StepType>( Step::Completed );
109 110
}

111
bool Task::isStepCompleted( Step step ) const
112 113 114 115 116 117 118 119 120 121 122
{
    return ( static_cast<uint8_t>( m_step ) & static_cast<uint8_t>( step ) ) != 0;
}

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 );
}

123
bool Task::updateFileId( int64_t fileId )
124
{
125 126 127 128 129 130
    // When restoring a task, we will invoke ITaskCb::updateFileId while the
    // task already knows the fileId (since we're using it to restore the file instance)
    // In this case, bail out. Otherwise, it is not expected for the task to change
    // its associated file during the processing.
    if ( m_fileId == fileId && fileId != 0 )
        return true ;
131
    assert( m_fileId == 0 );
132
    assert( fileId != 0 );
133 134
    static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
            "file_id = ? WHERE id_task = ?";
135
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, fileId, m_id ) == false )
136
        return false;
137
    m_fileId = fileId;
138 139 140
    return true;
}

141 142 143 144 145
int64_t Task::id() const
{
    return m_id;
}

146 147 148 149 150
Task::Item& Task::item()
{
    return m_item;
}

151 152 153
Task::Item::Item( ITaskCb* taskCb, std::string mrl, unsigned int subitemPosition )
    : m_taskCb( taskCb )
    , m_mrl( std::move( mrl ) )
154
    , m_duration( 0 )
155
    , m_parentPlaylistIndex( subitemPosition )
156 157 158
{
}

159
Task::Item::Item( ITaskCb* taskCb, std::shared_ptr<fs::IFile> fileFs,
160 161 162
                  std::shared_ptr<Folder> parentFolder,
                  std::shared_ptr<fs::IDirectory> parentFolderFs,
                  std::shared_ptr<Playlist> parentPlaylist, unsigned int parentPlaylistIndex  )
163 164
    : m_taskCb( taskCb )
    , m_mrl( fileFs->mrl() )
165 166 167 168 169 170
    , m_duration( 0 )
    , m_fileFs( std::move( fileFs ) )
    , m_parentFolder( std::move( parentFolder ) )
    , m_parentFolderFs( std::move( parentFolderFs ) )
    , m_parentPlaylist( std::move( parentPlaylist ) )
    , m_parentPlaylistIndex( parentPlaylistIndex )
171 172 173
{
}

174
std::string Task::Item::meta( Task::Item::Metadata type ) const
175 176 177 178 179 180 181
{
    auto it = m_metadata.find( type );
    if ( it == end( m_metadata ) )
        return std::string{};
    return it->second;
}

182
void Task::Item::setMeta( Task::Item::Metadata type, std::string value )
183 184 185 186
{
    m_metadata[type] = std::move( value );
}

187 188 189 190 191
const std::string& Task::Item::mrl() const
{
    return m_mrl;
}

192 193 194 195 196
void Task::Item::setMrl( std::string mrl )
{
    m_mrl = std::move( mrl );
}

197
size_t Task::Item::nbSubItems() const
198
{
199 200 201
    return m_subItems.size();
}

202
const IItem& Task::Item::subItem( unsigned int index ) const
203 204
{
    return m_subItems[index];
205 206
}

207
IItem& Task::Item::createSubItem( std::string mrl, unsigned int playlistIndex )
208
{
209
    m_subItems.emplace_back( nullptr, std::move( mrl ), playlistIndex );
210
    return m_subItems.back();
211 212
}

213 214 215 216 217 218 219 220 221 222
int64_t Task::Item::duration() const
{
    return m_duration;
}

void Task::Item::setDuration( int64_t duration )
{
    m_duration = duration;
}

223 224 225 226 227 228 229 230 231 232
const std::vector<Task::Item::Track>& Task::Item::tracks() const
{
    return m_tracks;
}

void Task::Item::addTrack(Task::Item::Track t)
{
    m_tracks.emplace_back( std::move( t ) );
}

233
MediaPtr Task::Item::media()
234 235 236 237
{
    return m_media;
}

238
void Task::Item::setMedia( MediaPtr media )
239 240 241 242
{
    m_media = std::move( media );
}

243
FilePtr Task::Item::file()
244 245 246 247
{
    return m_file;
}

248
bool Task::Item::setFile( FilePtr file)
249 250
{
    m_file = std::move( file );
251 252
    assert( m_taskCb != nullptr );
    return m_taskCb->updateFileId( m_file->id() );
253 254
}

255
FolderPtr Task::Item::parentFolder()
256 257 258 259 260 261 262 263 264 265 266 267 268 269
{
    return m_parentFolder;
}

std::shared_ptr<fs::IFile> Task::Item::fileFs()
{
    return m_fileFs;
}

std::shared_ptr<fs::IDirectory> Task::Item::parentFolderFs()
{
    return m_parentFolderFs;
}

270
PlaylistPtr Task::Item::parentPlaylist()
271 272 273 274 275 276 277 278 279
{
    return m_parentPlaylist;
}

unsigned int Task::Item::parentPlaylistIndex() const
{
    return m_parentPlaylistIndex;
}

280
bool Task::restoreLinkedEntities()
281
{
282
    LOG_INFO("Restoring linked entities of task ", m_id);
283 284
    // MRL will be empty if the task has been resumed from unparsed files
    // parentFolderId == 0 indicates an external file
285 286
    auto mrl = m_item.mrl();
    if ( mrl.empty() == true && m_parentFolderId == 0 )
287 288 289 290 291
    {
        LOG_WARN( "Aborting & removing external file task (#", m_id, ')' );
        destroy( m_ml, m_id );
        return false;
    }
292 293
    // First of all, we need to know if the file has been created already
    // ie. have we run the MetadataParser service, at least partially
294
    std::shared_ptr<File> file;
295
    if ( m_fileId != 0 )
296
        file = File::fetch( m_ml, m_fileId );
297

298 299
    // We might re-create tasks without mrl to ease the handling of files on
    // external storage.
300
    if ( mrl.empty() == true )
301 302 303 304
    {
        // but we expect those to be created from an existing file after a
        // partial/failed migration. If we don't have a file nor an mrl, we
        // can't really process it.
305
        if ( file == nullptr )
306 307 308 309
        {
            assert( !"Can't process a file without a file nor an mrl" );
            return false;
        }
310
        auto folder = Folder::fetch( m_ml, file->folderId() );
311 312 313 314 315 316 317 318 319 320
        if ( folder == nullptr )
        {
            assert( !"Can't file the folder associated with a file" );
            // If the folder can't be found in db while the file can, it looks
            // a lot like a sporadic failure, since file are deleted through
            // triggers when its parent folder gets deleted. Just postpone this.
            return false;
        }
        if ( folder->isPresent() == false )
        {
321
            LOG_WARN( "Postponing rescan of removable file ", file->rawMrl(),
322 323 324
                      " until the device containing it is present again" );
            return false;
        }
325
        setMrl( file->mrl() );
326
    }
327
    auto fsFactory = m_ml->fsFactoryForMrl( mrl );
328 329 330
    if ( fsFactory == nullptr )
        return false;

331
    std::shared_ptr<fs::IDirectory> parentFolderFs;
332 333
    try
    {
334
        parentFolderFs = fsFactory->createDirectory( utils::file::directory( mrl ) );
335 336 337 338
    }
    catch ( const std::system_error& ex )
    {
        LOG_ERROR( "Failed to restore task: ", ex.what() );
339
        return false;
340
    }
341

342
    std::shared_ptr<fs::IFile> fileFs;
343 344
    try
    {
345 346 347
        auto files = parentFolderFs->files();
        auto it = std::find_if( begin( files ), end( files ), [&mrl]( std::shared_ptr<fs::IFile> f ) {
            return f->mrl() == mrl;
348 349 350
        });
        if ( it == end( files ) )
        {
351
            LOG_ERROR( "Failed to restore fs::IFile associated with ", mrl );
352 353
            return false;
        }
354
        fileFs = std::move( *it );
355 356
    }
    catch ( const std::system_error& ex )
357
    {
358 359
        // If we never found the file yet, we can delete the task. It will be
        // recreated upon next discovery
360
        if ( file == nullptr )
361
        {
362
            LOG_WARN( "Failed to restore file system instances for mrl ", mrl, "."
363 364 365 366 367 368 369 370 371
                      " Removing the task until it gets detected again." );
            destroy( m_ml, m_id );
        }
        else
        {
            // Otherwise we need to postpone it, although most likely we will
            // detect that the file is now missing, and we won't try to restore
            // this task until it comes back (since the task restoration request
            // includes the file.is_present flag)
372
            LOG_WARN( "Failed to restore file system instances for mrl ", mrl, "."
373 374
                      " Postponing the task." );
        }
375 376 377
        return false;
    }

378 379 380 381 382 383 384
    std::shared_ptr<Folder> parentFolder = Folder::fetch( m_ml, m_parentFolderId );
    if ( parentFolder == nullptr )
    {
        LOG_ERROR( "Failed to restore parent folder #", m_parentFolderId );
        return false;
    }
    std::shared_ptr<Playlist> parentPlaylist;
385
    if ( m_parentPlaylistId != 0 )
386 387 388 389 390 391 392 393
    {
        parentPlaylist = Playlist::fetch( m_ml, m_parentPlaylistId );
        if ( parentPlaylist == nullptr )
        {
            LOG_ERROR( "Failed to restore parent playlist #", m_parentPlaylistId );
            return false;
        }
    }
394

395
    m_item = Item{ this, std::move( fileFs ), std::move( parentFolder ),
396 397 398 399 400 401 402
                   std::move( parentFolderFs ), std::move( parentPlaylist ),
                   m_item.parentPlaylistIndex() };
    if ( file != nullptr )
    {
        m_item.setMedia( file->media() );
        m_item.setFile( std::move( file ) );
    }
403 404 405
    return true;
}

406 407
void Task::setMrl( std::string newMrl )
{
408
    if ( m_item.mrl() == newMrl )
409 410 411 412 413
        return;
    static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
            "mrl = ? WHERE id_task = ?";
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, newMrl, m_id ) == false )
        return;
414
    m_item.setMrl( std::move( newMrl ) );
415 416
}

417 418 419 420 421 422 423 424 425 426 427
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,"
428
        "UNIQUE(mrl, parent_playlist_id) ON CONFLICT FAIL,"
429 430 431 432 433 434 435 436 437 438 439 440 441
        "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 "
442
            "retry_count = 0 WHERE step & ? != ?";
443 444
    sqlite::Tools::executeUpdate( ml->getConn(), req, Step::Completed,
                                  Step::Completed);
445 446 447 448 449 450
}

void Task::resetParsing( MediaLibraryPtr ml )
{
    static const std::string req = "UPDATE " + policy::TaskTable::Name + " SET "
            "retry_count = 0, step = ?";
451
    sqlite::Tools::executeUpdate( ml->getConn(), req, Step::None );
452 453
}

454
std::vector<std::shared_ptr<Task>> Task::fetchUncompleted( MediaLibraryPtr ml )
455 456 457
{
    static const std::string req = "SELECT * FROM " + policy::TaskTable::Name + " t"
        " LEFT JOIN " + policy::FileTable::Name + " f ON f.id_file = t.file_id"
458
        " WHERE step & ? != ? AND retry_count < 3 AND (f.is_present != 0 OR "
459
        " t.file_id IS NULL)";
460 461
    return Task::fetchAll<Task>( ml, req, Step::Completed,
                                 Step::Completed );
462 463 464 465 466 467 468
}

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 )
{
469 470 471 472
    auto parentFolderId = parentFolder->id();
    auto parentPlaylistId = parentPlaylist.first != nullptr ? parentPlaylist.first->id() : 0;
    auto parentPlaylistIndex = parentPlaylist.second;

473 474 475 476 477 478
    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(?, ?, ?, ?)";
479 480 481
    if ( insert( ml, self, req, self->m_item.mrl(), parentFolderId,
                 sqlite::ForeignKey( parentPlaylistId ),
                 parentPlaylistIndex ) == false )
482 483
        return nullptr;
    return self;
484 485
}

486 487 488 489 490 491
void Task::recoverUnscannedFiles( MediaLibraryPtr ml )
{
    static const std::string req = "INSERT INTO " + policy::TaskTable::Name +
            "(file_id, parent_folder_id)"
            " SELECT id_file, folder_id FROM " + policy::FileTable::Name +
            " f LEFT JOIN " + policy::TaskTable::Name + " t"
492 493
            " ON t.file_id = f.id_file WHERE t.file_id IS NULL"
            " AND f.folder_id IS NOT NULL";
494 495 496
    sqlite::Tools::executeInsert( ml->getConn(), req );
}

497 498 499
}

}