MediaLibrary.cpp 24.6 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
/*****************************************************************************
 * 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.
 *****************************************************************************/

23 24 25 26
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

27 28
#include <algorithm>
#include <functional>
29
#include <sys/stat.h>
30

31 32
#include "Album.h"
#include "AlbumTrack.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
33
#include "Artist.h"
34
#include "AudioTrack.h"
35
#include "discoverer/DiscovererWorker.h"
36
#include "utils/ModificationsNotifier.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
37
#include "Device.h"
38
#include "File.h"
39
#include "Folder.h"
40
#include "Genre.h"
41
#include "History.h"
42
#include "Media.h"
43
#include "MediaLibrary.h"
44
#include "Label.h"
45
#include "logging/Logger.h"
46
#include "Movie.h"
47
#include "parser/Parser.h"
48
#include "Playlist.h"
49 50
#include "Show.h"
#include "ShowEpisode.h"
51
#include "database/SqliteTools.h"
52
#include "database/SqliteConnection.h"
53
#include "utils/Filename.h"
54
#include "VideoTrack.h"
55

56 57 58
// Discoverers:
#include "discoverer/FsDiscoverer.h"

59 60 61
// Metadata services:
#include "metadata_services/vlc/VLCMetadataService.h"
#include "metadata_services/vlc/VLCThumbnailer.h"
62
#include "metadata_services/MetadataParser.h"
63

64 65
// FileSystem
#include "factory/DeviceListerFactory.h"
66
#include "factory/FileSystemFactory.h"
67
#include "factory/NetworkFileSystemFactory.h"
68
#include "filesystem/IDevice.h"
69

70 71 72
namespace medialibrary
{

73
const std::vector<std::string> MediaLibrary::supportedVideoExtensions {
74 75 76 77
    // Videos
    "avi", "3gp", "amv", "asf", "divx", "dv", "flv", "gxf",
    "iso", "m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov",
    "mp2", "mp4", "mpeg", "mpeg1", "mpeg2", "mpeg4", "mpg",
78
    "mts", "mxf", "nsv", "nuv", "ogm", "ogv", "ogx", "ps",
79 80 81 82
    "rec", "rm", "rmvb", "tod", "ts", "vob", "vro", "webm", "wmv"
};

const std::vector<std::string> MediaLibrary::supportedAudioExtensions {
83 84 85 86 87 88 89 90
    // Audio
    "a52", "aac", "ac3", "aiff", "amr", "aob", "ape",
    "dts", "flac", "it", "m4a", "m4p", "mid", "mka", "mlp",
    "mod", "mp1", "mp2", "mp3", "mpc", "oga", "ogg", "oma",
    "rmi", "s3m", "spx", "tta", "voc", "vqf", "w64", "wav",
    "wma", "wv", "xa", "xm"
};

91
const uint32_t MediaLibrary::DbModelVersion = 2;
92

93
MediaLibrary::MediaLibrary()
94 95
    : m_callback( nullptr )
    , m_verbosity( LogLevel::Error )
96
    , m_initialized( false )
97
{
98
    Log::setLogLevel( m_verbosity );
99 100
}

101 102
MediaLibrary::~MediaLibrary()
{
103
    // Explicitely stop the discoverer, to avoid it writting while tearing down.
104 105
    if ( m_discoverer != nullptr )
        m_discoverer->stop();
106 107
    if ( m_parser != nullptr )
        m_parser->stop();
108
    Media::clear();
109
    Folder::clear();
110 111 112 113 114
    Label::clear();
    Album::clear();
    AlbumTrack::clear();
    Show::clear();
    ShowEpisode::clear();
115
    Movie::clear();
116
    VideoTrack::clear();
117
    AudioTrack::clear();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
118
    Artist::clear();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
119
    Device::clear();
120
    File::clear();
121
    Playlist::clear();
122
    History::clear();
123
    Genre::clear();
124 125
}

126 127
bool MediaLibrary::createAllTables()
{
128 129 130 131 132
    // 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.

133
    auto t = m_dbConnection->newTransaction();
134
    auto res = Device::createTable( m_dbConnection.get() ) &&
135
        Folder::createTable( m_dbConnection.get() ) &&
136
        Media::createTable( m_dbConnection.get() ) &&
137
        File::createTable( m_dbConnection.get() ) &&
138
        Label::createTable( m_dbConnection.get() ) &&
139
        Playlist::createTable( m_dbConnection.get() ) &&
140
        Genre::createTable( m_dbConnection.get() ) &&
141 142
        Album::createTable( m_dbConnection.get() ) &&
        AlbumTrack::createTable( m_dbConnection.get() ) &&
143
        Album::createTriggers( m_dbConnection.get() ) &&
144 145 146 147 148
        Show::createTable( m_dbConnection.get() ) &&
        ShowEpisode::createTable( m_dbConnection.get() ) &&
        Movie::createTable( m_dbConnection.get() ) &&
        VideoTrack::createTable( m_dbConnection.get() ) &&
        AudioTrack::createTable( m_dbConnection.get() ) &&
149
        Artist::createTable( m_dbConnection.get() ) &&
150
        Artist::createDefaultArtists( m_dbConnection.get() ) &&
151
        Artist::createTriggers( m_dbConnection.get() ) &&
152
        Media::createTriggers( m_dbConnection.get() ) &&
153
        Playlist::createTriggers( m_dbConnection.get() ) &&
154
        History::createTable( m_dbConnection.get() ) &&
155 156 157 158 159 160 161
        Settings::createTable( m_dbConnection.get() );
    if ( res == false )
        return false;
    t->commit();
    return true;
}

162 163
void MediaLibrary::registerEntityHooks()
{
164
    if ( m_modificationNotifier == nullptr )
165 166
        return;

167 168 169 170 171
    m_dbConnection->registerUpdateHook( policy::MediaTable::Name,
                                        [this]( SqliteConnection::HookReason reason, int64_t rowId ) {
        if ( reason != SqliteConnection::HookReason::Delete )
            return;
        Media::removeFromCache( rowId );
172
        m_modificationNotifier->notifyMediaRemoval( rowId );
173
    });
174 175 176 177 178 179 180
    m_dbConnection->registerUpdateHook( policy::ArtistTable::Name,
                                        [this]( SqliteConnection::HookReason reason, int64_t rowId ) {
        if ( reason != SqliteConnection::HookReason::Delete )
            return;
        Artist::removeFromCache( rowId );
        m_modificationNotifier->notifyArtistRemoval( rowId );
    });
181 182 183 184 185 186 187
    m_dbConnection->registerUpdateHook( policy::AlbumTable::Name,
                                        [this]( SqliteConnection::HookReason reason, int64_t rowId ) {
        if ( reason != SqliteConnection::HookReason::Delete )
            return;
        Album::removeFromCache( rowId );
        m_modificationNotifier->notifyAlbumRemoval( rowId );
    });
188 189 190 191 192 193 194
    m_dbConnection->registerUpdateHook( policy::AlbumTrackTable::Name,
                                        [this]( SqliteConnection::HookReason reason, int64_t rowId ) {
        if ( reason != SqliteConnection::HookReason::Delete )
            return;
        AlbumTrack::removeFromCache( rowId );
        m_modificationNotifier->notifyAlbumTrackRemoval( rowId );
    });
195 196 197 198 199 200 201
    m_dbConnection->registerUpdateHook( policy::PlaylistTable::Name,
                                        [this]( SqliteConnection::HookReason reason, int64_t rowId ) {
        if ( reason != SqliteConnection::HookReason::Delete )
            return;
        Playlist::removeFromCache( rowId );
        m_modificationNotifier->notifyPlaylistRemoval( rowId );
    });
202 203 204
}


205 206 207 208 209
bool MediaLibrary::validateSearchPattern( const std::string& pattern )
{
    return pattern.size() >= 3;
}

210
bool MediaLibrary::initialize( const std::string& dbPath, const std::string& thumbnailPath, IMediaLibraryCb* mlCallback )
211
{
212 213
    if ( m_initialized == true )
        return true;
214 215 216 217
    if ( m_deviceLister == nullptr )
    {
        m_deviceLister = factory::createDeviceLister();
        if ( m_deviceLister == nullptr )
218 219
        {
            LOG_ERROR( "No available IDeviceLister was found." );
220
            return false;
221
        }
222
    }
223
    addLocalFsFactory();
224 225 226
#ifdef _WIN32
    if ( mkdir( thumbnailPath.c_str() ) != 0 )
#else
227
    if ( mkdir( thumbnailPath.c_str(), S_IRWXU ) != 0 )
228
#endif
229 230 231 232 233
    {
        if ( errno != EEXIST )
            throw std::runtime_error( std::string( "Failed to create thumbnail directory: " ) +
                                      strerror( errno ) );
    }
234
    m_thumbnailPath = thumbnailPath;
235 236 237
    m_callback = mlCallback;
    m_dbConnection.reset( new SqliteConnection( dbPath ) );

238 239 240
    // Give a chance to test overloads to reject the creation of a notifier
    startDeletionNotifier();
    // Which allows us to register hooks, or not, depending on the presence of a notifier
241 242
    registerEntityHooks();

243
    if ( createAllTables() == false )
244
    {
245
        LOG_ERROR( "Failed to create database structure" );
246 247
        return false;
    }
248 249 250
    if ( m_settings.load( m_dbConnection.get() ) == false )
        return false;
    if ( m_settings.dbModelVersion() != DbModelVersion )
251 252 253 254
    {
        if ( updateDatabaseModel( m_settings.dbModelVersion() ) == false )
            return false;
    }
255 256
    startDiscoverer();
    startParser();
257
    m_initialized = true;
258
    return true;
259 260
}

261 262 263 264 265 266
void MediaLibrary::setVerbosity(LogLevel v)
{
    m_verbosity = v;
    Log::setLogLevel( v );
}

267 268 269 270 271
MediaPtr MediaLibrary::media( int64_t mediaId ) const
{
    return Media::fetch( this, mediaId );
}

272 273
MediaPtr MediaLibrary::media( const std::string& mrl ) const
{
274
    LOG_INFO( "Fetching media from mrl: ", mrl );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
275
    auto file = File::fromExternalMrl( this, mrl );
276 277 278 279 280
    if ( file != nullptr )
    {
        LOG_INFO( "Found external media: ", mrl );
        return file->media();
    }
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
281
    auto fsFactory = fsFactoryForMrl( mrl );
282
    if ( fsFactory == nullptr )
283
    {
284 285
        LOG_WARN( "Failed to create FS factory for path ", mrl );
        return nullptr;
286
    }
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
287
    auto device = fsFactory->createDeviceFromMrl( mrl );
288 289 290 291 292 293
    if ( device == nullptr )
    {
        LOG_WARN( "Failed to create a device associated with mrl ", mrl );
        return nullptr;
    }
    if ( device->isRemovable() == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
294
        file = File::fromMrl( this, mrl );
295 296
    else
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
297
        auto folder = Folder::fromMrl( this, utils::file::directory( mrl ) );
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
        if ( folder == nullptr )
        {
            LOG_WARN( "Failed to find folder containing ", mrl );
            return nullptr;
        }
        if ( folder->isPresent() == false )
        {
            LOG_INFO( "Found a folder containing ", mrl, " but it is not present" );
            return nullptr;
        }
        file = File::fromFileName( this, utils::file::fileName( mrl ), folder->id() );
    }
    if ( file == nullptr )
    {
        LOG_WARN( "Failed to fetch file for ", mrl, "(device ", device->uuid(), " was ",
                  device->isRemovable() ? "NOT" : "", "removable)");
        return nullptr;
    }
    return file->media();
}

319 320
MediaPtr MediaLibrary::addMedia( const std::string& mrl )
{
321
    auto t = m_dbConnection->newTransaction();
322 323 324
    auto media = Media::create( this, IMedia::Type::Unknown, utils::file::fileName( mrl ) );
    if ( media == nullptr )
        return nullptr;
325 326 327
    if ( media->addExternalMrl( mrl, IFile::Type::Main ) == nullptr )
        return nullptr;
    t->commit();
328 329 330
    return media;
}

331
std::vector<MediaPtr> MediaLibrary::audioFiles( SortingCriteria sort, bool desc ) const
332
{
333
    return Media::listAll( this, IMedia::Type::Audio, sort, desc );
334 335
}

336
std::vector<MediaPtr> MediaLibrary::videoFiles( SortingCriteria sort, bool desc ) const
337
{
338
    return Media::listAll( this, IMedia::Type::Video, sort, desc );
339 340
}

341
std::shared_ptr<Media> MediaLibrary::addFile( const fs::IFile& fileFs, Folder& parentFolder, fs::IDirectory& parentFolderFs )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
342
{
343
    auto type = IMedia::Type::Unknown;
344
    auto ext = fileFs.extension();
345 346 347 348 349
    auto predicate = [ext](const std::string& v) {
        return strcasecmp(v.c_str(), ext.c_str()) == 0;
    };

    if ( std::find_if( begin( supportedVideoExtensions ), end( supportedVideoExtensions ),
350 351 352
                    predicate ) == end( supportedVideoExtensions ) &&
         std::find_if( begin( supportedAudioExtensions ), end( supportedAudioExtensions ),
                         predicate ) == end( supportedAudioExtensions ) )
353
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
354
        LOG_INFO( "Rejecting file ", fileFs.mrl(), " due to its extension" );
355
        return nullptr;
356
    }
357

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
358
    LOG_INFO( "Adding ", fileFs.mrl() );
359
    auto mptr = Media::create( this, type, fileFs.name() );
360 361
    if ( mptr == nullptr )
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
362
        LOG_ERROR( "Failed to add media ", fileFs.mrl(), " to the media library" );
363 364
        return nullptr;
    }
365
    // For now, assume all media are made of a single file
366
    auto file = mptr->addFile( fileFs, parentFolder, parentFolderFs, File::Type::Main );
367
    if ( file == nullptr )
368
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
369
        LOG_ERROR( "Failed to add file ", fileFs.mrl(), " to media #", mptr->id() );
370
        Media::destroy( this, mptr->id() );
371 372
        return nullptr;
    }
373
    if ( m_parser != nullptr )
374 375
        m_parser->parse( mptr, file );
    return mptr;
376 377
}

378
bool MediaLibrary::deleteFolder( const Folder& folder )
379
{
380
    if ( Folder::destroy( this, folder.id() ) == false )
381
        return false;
382
    Media::clear();
383
    return true;
384 385
}

386 387
LabelPtr MediaLibrary::createLabel( const std::string& label )
{
388
    return Label::create( this, label );
389
}
390 391 392

bool MediaLibrary::deleteLabel( LabelPtr label )
{
393
    return Label::destroy( this, label->id() );
394
}
395

396
AlbumPtr MediaLibrary::album( int64_t id ) const
397
{
398
    return Album::fetch( this, id );
399 400
}

401
std::shared_ptr<Album> MediaLibrary::createAlbum( const std::string& title, const std::string& artworkMrl )
402
{
403
    return Album::create( this, title, artworkMrl );
404 405
}

406
std::vector<AlbumPtr> MediaLibrary::albums( SortingCriteria sort, bool desc ) const
407
{
408
    return Album::listAll( this, sort, desc );
409 410
}

411
std::vector<GenrePtr> MediaLibrary::genres( SortingCriteria sort, bool desc ) const
412
{
413
    return Genre::listAll( this, sort, desc );
414 415
}

416 417 418 419 420
GenrePtr MediaLibrary::genre(int64_t id) const
{
    return Genre::fetch( this, id );
}

421
ShowPtr MediaLibrary::show( const std::string& name ) const
422 423 424
{
    static const std::string req = "SELECT * FROM " + policy::ShowTable::Name
            + " WHERE name = ?";
425
    return Show::fetch( this, req, name );
426 427
}

428
std::shared_ptr<Show> MediaLibrary::createShow(const std::string& name)
429
{
430
    return Show::create( this, name );
431 432
}

433
MoviePtr MediaLibrary::movie( const std::string& title ) const
434 435 436
{
    static const std::string req = "SELECT * FROM " + policy::MovieTable::Name
            + " WHERE title = ?";
437
    return Movie::fetch( this, req, title );
438 439
}

440
std::shared_ptr<Movie> MediaLibrary::createMovie( Media& media, const std::string& title )
441
{
442
    auto movie = Movie::create( this, media.id(), title );
443
    media.setMovie( movie );
444
    media.save();
445
    return movie;
446 447
}

448
ArtistPtr MediaLibrary::artist( int64_t id ) const
449
{
450
    return Artist::fetch( this, id );
451 452 453
}

ArtistPtr MediaLibrary::artist( const std::string& name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
454 455
{
    static const std::string req = "SELECT * FROM " + policy::ArtistTable::Name
456
            + " WHERE name = ? AND is_present = 1";
457
    return Artist::fetch( this, req, name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
458 459
}

460
std::shared_ptr<Artist> MediaLibrary::createArtist( const std::string& name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
461
{
462 463
    try
    {
464
        return Artist::create( this, name );
465 466 467 468 469 470
    }
    catch( sqlite::errors::ConstraintViolation &ex )
    {
        LOG_WARN( "ContraintViolation while creating an artist (", ex.what(), ") attempting to fetch it instead" );
        return std::static_pointer_cast<Artist>( artist( name ) );
    }
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
471 472
}

473
std::vector<ArtistPtr> MediaLibrary::artists(SortingCriteria sort, bool desc) const
474
{
475
    return Artist::listAll( this, sort, desc );
476 477
}

478 479
PlaylistPtr MediaLibrary::createPlaylist( const std::string& name )
{
480
    return Playlist::create( this, name );
481 482
}

483
std::vector<PlaylistPtr> MediaLibrary::playlists(SortingCriteria sort, bool desc)
484
{
485
    return Playlist::listAll( this, sort, desc );
486 487
}

488 489 490 491 492
PlaylistPtr MediaLibrary::playlist( int64_t id ) const
{
    return Playlist::fetch( this, id );
}

493
bool MediaLibrary::deletePlaylist( int64_t playlistId )
494
{
495
    return Playlist::destroy( this, playlistId );
496
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
497

498
bool MediaLibrary::addToStreamHistory( MediaPtr media )
499
{
500
    return History::insert( getConn(), media->id() );
501 502 503 504 505 506 507
}

std::vector<HistoryPtr> MediaLibrary::lastStreamsPlayed() const
{
    return History::fetch( this );
}

508 509
std::vector<MediaPtr> MediaLibrary::lastMediaPlayed() const
{
510
    return Media::fetchHistory( this );
511 512
}

513 514
bool MediaLibrary::clearHistory()
{
515
    auto t = getConn()->newTransaction();
516
    Media::clearHistory( this );
517 518 519
    if ( History::clearStreams( this ) == false )
        return false;
    t->commit();
520 521 522
    return true;
}

523
MediaSearchAggregate MediaLibrary::searchMedia( const std::string& title ) const
524
{
525 526
    if ( validateSearchPattern( title ) == false )
        return {};
527
    auto tmp = Media::search( this, title );
528
    MediaSearchAggregate res;
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
    for ( auto& m : tmp )
    {
        switch ( m->subType() )
        {
        case IMedia::SubType::AlbumTrack:
            res.tracks.emplace_back( std::move( m ) );
            break;
        case IMedia::SubType::Movie:
            res.movies.emplace_back( std::move( m ) );
            break;
        case IMedia::SubType::ShowEpisode:
            res.episodes.emplace_back( std::move( m ) );
            break;
        default:
            res.others.emplace_back( std::move( m ) );
            break;
        }
    }
    return res;
548 549
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
550 551
std::vector<PlaylistPtr> MediaLibrary::searchPlaylists( const std::string& name ) const
{
552 553
    if ( validateSearchPattern( name ) == false )
        return {};
554
    return Playlist::search( this, name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
555 556
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
557 558
std::vector<AlbumPtr> MediaLibrary::searchAlbums( const std::string& pattern ) const
{
559 560
    if ( validateSearchPattern( pattern ) == false )
        return {};
561
    return Album::search( this, pattern );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
562 563
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
564 565
std::vector<GenrePtr> MediaLibrary::searchGenre( const std::string& genre ) const
{
566 567
    if ( validateSearchPattern( genre ) == false )
        return {};
568
    return Genre::search( this, genre );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
569 570
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
571 572 573 574
std::vector<ArtistPtr> MediaLibrary::searchArtists(const std::string& name ) const
{
    if ( validateSearchPattern( name ) == false )
        return {};
575
    return Artist::search( this, name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
576 577
}

578
SearchAggregate MediaLibrary::search( const std::string& pattern ) const
579
{
580
    SearchAggregate res;
581 582
    res.albums = searchAlbums( pattern );
    res.artists = searchArtists( pattern );
583
    res.genres = searchGenre( pattern );
584 585 586 587 588
    res.media = searchMedia( pattern );
    res.playlists = searchPlaylists( pattern );
    return res;
}

589 590
void MediaLibrary::startParser()
{
591
    m_parser.reset( new Parser( this ) );
592

593
    auto vlcService = std::unique_ptr<VLCMetadataService>( new VLCMetadataService );
594
    auto metadataService = std::unique_ptr<MetadataParser>( new MetadataParser );
595
    auto thumbnailerService = std::unique_ptr<VLCThumbnailer>( new VLCThumbnailer );
596
    m_parser->addService( std::move( vlcService ) );
597
    m_parser->addService( std::move( metadataService ) );
598
    m_parser->addService( std::move( thumbnailerService ) );
599 600 601 602
    m_parser->start();
}

void MediaLibrary::startDiscoverer()
603
{
604
    m_discoverer.reset( new DiscovererWorker( this ) );
605 606
    for ( const auto& fsFactory : m_fsFactories )
        m_discoverer->addDiscoverer( std::unique_ptr<IDiscoverer>( new FsDiscoverer( fsFactory, this, m_callback ) ) );
607
    m_discoverer->reload();
608 609
}

610 611
void MediaLibrary::startDeletionNotifier()
{
612 613
    m_modificationNotifier.reset( new ModificationNotifier( this ) );
    m_modificationNotifier->start();
614 615
}

616 617 618 619 620
void MediaLibrary::addLocalFsFactory()
{
    m_fsFactories.insert( begin( m_fsFactories ), std::make_shared<factory::FileSystemFactory>( m_deviceLister ) );
}

621 622 623 624 625 626 627 628
bool MediaLibrary::updateDatabaseModel( unsigned int previousVersion )
{
    if ( previousVersion == 1 )
    {
        // Way too much differences, introduction of devices, and almost unused in the wild, just drop everything
        std::string req = "PRAGMA writable_schema = 1;"
                            "delete from sqlite_master;"
                            "PRAGMA writable_schema = 0;";
629
        if ( sqlite::Tools::executeRequest( getConn(), req ) == false )
630 631 632 633 634 635 636 637 638 639 640 641 642 643
            return false;
        if ( createAllTables() == false )
            return false;
        ++previousVersion;
    }
    // To be continued in the future!

    // Safety check: ensure we didn't forget a migration along the way
    assert( previousVersion == DbModelVersion );
    m_settings.setDbModelVersion( DbModelVersion );
    m_settings.save();
    return true;
}

644 645 646 647 648 649
void MediaLibrary::reload()
{
    if ( m_discoverer != nullptr )
        m_discoverer->reload();
}

650 651 652 653 654 655
void MediaLibrary::reload( const std::string& entryPoint )
{
    if ( m_discoverer != nullptr )
        m_discoverer->reload( entryPoint );
}

656 657
void MediaLibrary::pauseBackgroundOperations()
{
658 659
    if ( m_parser != nullptr )
        m_parser->pause();
660 661 662 663
}

void MediaLibrary::resumeBackgroundOperations()
{
664 665
    if ( m_parser != nullptr )
        m_parser->resume();
666 667
}

668 669 670 671 672 673 674 675 676 677
DBConnection MediaLibrary::getConn() const
{
    return m_dbConnection.get();
}

IMediaLibraryCb* MediaLibrary::getCb() const
{
    return m_callback;
}

678 679 680 681 682
std::shared_ptr<ModificationNotifier> MediaLibrary::getNotifier() const
{
    return m_modificationNotifier;
}

683 684 685 686 687 688
IDeviceListerCb* MediaLibrary::setDeviceLister( DeviceListerPtr lister )
{
    m_deviceLister = lister;
    return static_cast<IDeviceListerCb*>( this );
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
689
std::shared_ptr<factory::IFileSystem> MediaLibrary::fsFactoryForMrl( const std::string& mrl ) const
690
{
691 692
    for ( const auto& f : m_fsFactories )
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
693
        if ( f->isMrlSupported( mrl ) )
694 695 696
            return f;
    }
    return nullptr;
697 698
}

699 700
void MediaLibrary::discover( const std::string &entryPoint )
{
701 702
    if ( m_discoverer != nullptr )
        m_discoverer->discover( entryPoint );
703 704
}

705 706 707 708 709 710 711 712 713 714 715 716
void MediaLibrary::setDiscoverNetworkEnabled( bool enabled )
{
    if ( enabled )
    {
        auto it = std::find_if( begin( m_fsFactories ), end( m_fsFactories ), []( const std::shared_ptr<factory::IFileSystem> fs ) {
            return fs->isNetworkFileSystem();
        });
        if ( it == end( m_fsFactories ) )
            m_fsFactories.push_back( std::make_shared<factory::NetworkFileSystemFactory>( "smb", "dsm-sd" ) );
    }
    else
    {
717
        m_fsFactories.erase( std::remove_if( begin( m_fsFactories ), end( m_fsFactories ), []( const std::shared_ptr<factory::IFileSystem> fs ) {
718
            return fs->isNetworkFileSystem();
719
        }) );
720 721 722
    }
}

723 724 725 726 727 728 729
std::vector<FolderPtr> MediaLibrary::entryPoints() const
{
    static const std::string req = "SELECT * FROM " + policy::FolderTable::Name + " WHERE parent_id IS NULL"
            " AND is_blacklisted = 0";
    return Folder::fetchAll<IFolder>( this, req );
}

730 731
bool MediaLibrary::removeEntryPoint( const std::string& entryPoint )
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
732
    auto folder = Folder::fromMrl( this, entryPoint );
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
    if ( folder == nullptr )
    {
        LOG_WARN( "Can't remove unknown entrypoint: ", entryPoint );
        return false;
    }
    // The easy case is that this folder was directly discovered. In which case, we just
    // have to delete it and it won't be discovered again.
    // If it isn't, we also have to ban it to prevent it from reappearing. The Folder::banFolder
    // method already handles the prior deletion
    bool res;
    if ( folder->isRootFolder() == false )
        res = banFolder( entryPoint );
    else
        res = deleteFolder( *folder );
    if ( res == false )
        return false;
    // Force a cache cleanup to avoid stalled media
    Media::clear();
    return true;
}

754
bool MediaLibrary::banFolder( const std::string& path )
755
{
756
    return Folder::blacklist( this, path );
757 758
}

759 760
bool MediaLibrary::unbanFolder( const std::string& path )
{
761
    auto folder = Folder::blacklistedFolder( this, path );
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
    if ( folder == nullptr )
        return false;
    deleteFolder( *folder );

    // We are about to refresh the folder we blacklisted earlier, if we don't have a discoverer, stop early
    if ( m_discoverer == nullptr )
        return true;

    auto parentPath = utils::file::parentDirectory( path );
    // If the parent folder was never added to the media library, the discoverer will reject it.
    // We could check it from here, but that would mean fetching the folder twice, which would be a waste.
    m_discoverer->reload( parentPath );
    return true;
}

777
const std::string& MediaLibrary::thumbnailPath() const
778
{
779
    return m_thumbnailPath;
780 781
}

782 783 784 785 786
void MediaLibrary::setLogger( ILogger* logger )
{
    Log::SetLogger( logger );
}

787
void MediaLibrary::onDevicePlugged( const std::string& uuid, const std::string& mountpoint )
788
{
789
    LOG_INFO( "Device ", uuid, " was plugged and mounted on ", mountpoint );
790 791
    for ( const auto& fsFactory : m_fsFactories )
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
792
        if ( fsFactory->isMrlSupported( "file://" ) )
793 794 795 796 797
        {
            fsFactory->refreshDevices();
            break;
        }
    }
798 799
}

800
void MediaLibrary::onDeviceUnplugged( const std::string& uuid )
801
{
802
    LOG_INFO( "Device ", uuid, " was unplugged" );
803 804
    for ( const auto& fsFactory : m_fsFactories )
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
805
        if ( fsFactory->isMrlSupported( "file://" ) )
806 807 808 809 810
        {
            fsFactory->refreshDevices();
            break;
        }
    }
811 812
}

813
}