Media.cpp 21.2 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
#if HAVE_CONFIG_H
# include "config.h"
#endif

27
#include <algorithm>
28
#include <cassert>
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
29 30
#include <cstdlib>
#include <cstring>
31
#include <ctime>
32

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
33
#include "Album.h"
34
#include "AlbumTrack.h"
35
#include "Artist.h"
36
#include "AudioTrack.h"
37
#include "Media.h"
38
#include "File.h"
39
#include "Folder.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
40
#include "Label.h"
41
#include "logging/Logger.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
42
#include "Movie.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
43
#include "ShowEpisode.h"
44
#include "database/SqliteTools.h"
45
#include "VideoTrack.h"
46
#include "filesystem/IFile.h"
47 48
#include "filesystem/IDirectory.h"
#include "filesystem/IDevice.h"
49
#include "utils/Filename.h"
50

51 52 53
namespace medialibrary
{

54
const std::string policy::MediaTable::Name = "Media";
55
const std::string policy::MediaTable::PrimaryKeyColumn = "id_media";
56
int64_t Media::* const policy::MediaTable::PrimaryKey = &Media::m_id;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
57

58 59
const std::string policy::MediaMetadataTable::Name = "MediaMetadata";

60 61
Media::Media( MediaLibraryPtr ml, sqlite::Row& row )
    : m_ml( ml )
62
    , m_changed( false )
63
{
64 65
    row >> m_id
        >> m_type
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
66
        >> m_subType
67 68
        >> m_duration
        >> m_playCount
69
        >> m_lastPlayedDate
70
        >> m_insertionDate
71
        >> m_releaseDate
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
        >> m_thumbnail
73
        >> m_title
74
        >> m_filename
75 76
        >> m_isFavorite
        >> m_isPresent;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
77 78
}

79 80 81
Media::Media( MediaLibraryPtr ml, const std::string& title, Type type )
    : m_ml( ml )
    , m_id( 0 )
82
    , m_type( type )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
83
    , m_subType( SubType::Unknown )
84
    , m_duration( -1 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
85
    , m_playCount( 0 )
86
    , m_lastPlayedDate( 0 )
87
    , m_insertionDate( time( nullptr ) )
88
    , m_releaseDate( 0 )
89
    , m_title( title )
90 91
    // When creating a Media, meta aren't parsed, and therefor, is the filename
    , m_filename( title )
92
    , m_isFavorite( false )
93
    , m_isPresent( true )
94
    , m_changed( false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
95
{
96 97
}

98
std::shared_ptr<Media> Media::create( MediaLibraryPtr ml, Type type, const std::string& fileName )
99
{
100
    auto self = std::make_shared<Media>( ml, fileName, type );
101
    static const std::string req = "INSERT INTO " + policy::MediaTable::Name +
102
            "(type, insertion_date, title, filename) VALUES(?, ?, ?, ?)";
103

104
    if ( insert( ml, self, req, type, self->m_insertionDate, self->m_title, self->m_filename ) == false )
105
        return nullptr;
106
    return self;
107 108
}

109
AlbumTrackPtr Media::albumTrack() const
110
{
111 112
    if ( m_subType != SubType::AlbumTrack )
        return nullptr;
113 114
    auto lock = m_albumTrack.lock();

115
    if ( m_albumTrack.isCached() == false )
116
        m_albumTrack = AlbumTrack::fromMedia( m_ml, m_id );
117
    return m_albumTrack.get();
118 119
}

120
void Media::setAlbumTrack( AlbumTrackPtr albumTrack )
121
{
122
    auto lock = m_albumTrack.lock();
123
    m_albumTrack = albumTrack;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
124 125
    m_subType = SubType::AlbumTrack;
    m_changed = true;
126 127
}

128
int64_t Media::duration() const
129 130 131 132
{
    return m_duration;
}

133
void Media::setDuration( int64_t duration )
134
{
135 136
    if ( m_duration == duration )
        return;
137
    m_duration = duration;
138
    m_changed = true;
139 140
}

141
ShowEpisodePtr Media::showEpisode() const
142
{
143 144
    if ( m_subType != SubType::ShowEpisode )
        return nullptr;
145

146 147
    auto lock = m_showEpisode.lock();
    if ( m_showEpisode.isCached() == false )
148
        m_showEpisode = ShowEpisode::fromMedia( m_ml, m_id );
149
    return m_showEpisode.get();
150 151
}

152
void Media::setShowEpisode( ShowEpisodePtr episode )
153
{
154 155
    auto lock = m_showEpisode.lock();
    m_showEpisode = episode;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
156 157
    m_subType = SubType::ShowEpisode;
    m_changed = true;
158 159
}

160
std::vector<LabelPtr> Media::labels()
161
{
162
    static const std::string req = "SELECT l.* FROM " + policy::LabelTable::Name + " l "
163
            "INNER JOIN LabelFileRelation lfr ON lfr.label_id = l.id_label "
164
            "WHERE lfr.media_id = ?";
165
    return Label::fetchAll<ILabel>( m_ml, req, m_id );
166 167
}

168
int Media::playCount() const
169
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
170
    return m_playCount;
171 172
}

173
bool Media::increasePlayCount()
174
{
175 176 177 178 179
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
            "play_count = ?, last_played_date = ? WHERE id_media = ?";
    auto lastPlayedDate = time( nullptr );
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_playCount + 1, lastPlayedDate, m_id ) == false )
        return false;
180
    m_playCount++;
181 182
    m_lastPlayedDate = lastPlayedDate;
    return true;
183 184
}

185 186 187 188 189
bool Media::isFavorite() const
{
    return m_isFavorite;
}

190
bool Media::setFavorite( bool favorite )
191
{
192
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET is_favorite = ? WHERE id_media = ?";
193
    if ( m_isFavorite == favorite )
194 195 196
        return true;
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, favorite, m_id ) == false )
        return false;
197
    m_isFavorite = favorite;
198
    return true;
199 200
}

201
const std::vector<FilePtr>& Media::files() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
202
{
203 204 205 206 207
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
    {
        static const std::string req = "SELECT * FROM " + policy::FileTable::Name
                + " WHERE media_id = ?";
208
        m_files = File::fetchAll<IFile>( m_ml, req, m_id );
209 210
    }
    return m_files;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
211 212
}

213
MoviePtr Media::movie() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
214
{
215 216 217
    if ( m_subType != SubType::Movie )
        return nullptr;

218 219
    auto lock = m_movie.lock();

220
    if ( m_movie.isCached() == false )
221
        m_movie = Movie::fromMedia( m_ml, m_id );
222
    return m_movie.get();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
223 224
}

225
void Media::setMovie(MoviePtr movie)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
226
{
227
    auto lock = m_movie.lock();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
228
    m_movie = movie;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
229 230
    m_subType = SubType::Movie;
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
231 232
}

233 234
bool Media::addVideoTrack(const std::string& codec, unsigned int width, unsigned int height, float fps,
                          const std::string& language, const std::string& description )
235
{
236
    return VideoTrack::create( m_ml, codec, width, height, fps, m_id, language, description ) != nullptr;
237 238
}

239
std::vector<VideoTrackPtr> Media::videoTracks()
240
{
241 242
    static const std::string req = "SELECT * FROM " + policy::VideoTrackTable::Name +
            " WHERE media_id = ?";
243
    return VideoTrack::fetchAll<IVideoTrack>( m_ml, req, m_id );
244 245
}

246
bool Media::addAudioTrack( const std::string& codec, unsigned int bitrate,
247 248
                          unsigned int sampleRate, unsigned int nbChannels,
                          const std::string& language, const std::string& desc )
249
{
250
    return AudioTrack::create( m_ml, codec, bitrate, sampleRate, nbChannels, language, desc, m_id ) != nullptr;
251 252
}

253
std::vector<AudioTrackPtr> Media::audioTracks()
254
{
255 256
    static const std::string req = "SELECT * FROM " + policy::AudioTrackTable::Name +
            " WHERE media_id = ?";
257
    return AudioTrack::fetchAll<IAudioTrack>( m_ml, req, m_id );
258 259
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
260
const std::string &Media::thumbnail()
261
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
262
    return m_thumbnail;
263 264
}

265 266 267 268 269
unsigned int Media::insertionDate() const
{
    return m_insertionDate;
}

270 271 272 273 274
unsigned int Media::releaseDate() const
{
    return m_releaseDate;
}

275 276 277 278 279 280 281 282
const IMediaMetadata& Media::metadata( IMedia::MetadataType type ) const
{
    auto lock = m_metadata.lock();
    if ( m_metadata.isCached() == false )
    {
        std::vector<MediaMetadata> res;
        static const std::string req = "SELECT * FROM " + policy::MediaMetadataTable::Name +
                " WHERE id_media = ?";
283 284 285
        auto conn = m_ml->getConn();
        auto ctx = conn->acquireReadContext();
        sqlite::Statement stmt( conn->getConn(), req );
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
        stmt.execute( m_id );
        for ( sqlite::Row row = stmt.row(); row != nullptr; row = stmt.row() )
        {
            assert( row.load<int64_t>( 0 ) == m_id );
            res.emplace_back( row.load<decltype(MediaMetadata::m_type)>( 1 ),
                              row.load<decltype(MediaMetadata::m_value)>( 2 ) );
        }
        m_metadata = res;
    }
    auto it = std::find_if( begin( m_metadata.get() ), end( m_metadata.get() ), [type](const MediaMetadata& m ) {
        return m.m_type == type;
    });
    if ( it == end( m_metadata.get() ) )
    {
        m_metadata.get().emplace_back( type );
        return *m_metadata.get().rbegin();
    }
    return (*it);
}

bool Media::setMetadata( IMedia::MetadataType type, const std::string& value )
{
    {
        auto lock = m_metadata.lock();
        if ( m_metadata.isCached() == true )
        {
            auto it = std::find_if( begin( m_metadata.get() ), end( m_metadata.get() ), [type](const MediaMetadata& m ) {
                return m.m_type == type;
            });
            if ( it != end( m_metadata.get() ) )
                (*it).m_value = value;
            else
                m_metadata.get().emplace_back( type, value );
        }
    }
321 322 323 324 325 326 327 328 329 330 331
    try
    {
        static const std::string req = "INSERT OR REPLACE INTO " + policy::MediaMetadataTable::Name +
                "(id_media, type, value) VALUES(?, ?, ?)";
        return sqlite::Tools::executeInsert( m_ml->getConn(), req, m_id, type, value );
    }
    catch ( const sqlite::errors::Generic& ex )
    {
        LOG_ERROR( "Failed to update media metadata: ", ex.what() );
        return false;
    }
332 333 334 335 336 337 338 339
}

bool Media::setMetadata( IMedia::MetadataType type, int64_t value )
{
    auto str = std::to_string( value );
    return setMetadata( type, str );
}

340 341 342 343 344 345 346 347
void Media::setReleaseDate( unsigned int date )
{
    if ( m_releaseDate == date )
        return;
    m_releaseDate = date;
    m_changed = true;
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
348
void Media::setThumbnail(const std::string& thumbnail )
349
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
350
    if ( m_thumbnail == thumbnail )
351
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
352
    m_thumbnail = thumbnail;
353 354 355 356 357 358
    m_changed = true;
}

bool Media::save()
{
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
359
            "type = ?, subtype = ?, duration = ?, release_date = ?,"
360
            "thumbnail = ?, title = ? WHERE id_media = ?";
361 362
    if ( m_changed == false )
        return true;
363
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_type, m_subType, m_duration,
364
                                       m_releaseDate, m_thumbnail, m_title, m_id ) == false )
365 366 367 368
    {
        return false;
    }
    m_changed = false;
369 370 371
    return true;
}

372
std::shared_ptr<File> Media::addFile( const fs::IFile& fileFs, Folder& parentFolder, fs::IDirectory& parentFolderFs, IFile::Type type )
373
{
374
    auto file = File::create( m_ml, m_id, type, fileFs, parentFolder.id(), parentFolderFs.device()->isRemovable() );
375 376 377 378 379 380
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
381 382
}

383
FilePtr Media::addExternalMrl( const std::string& mrl , IFile::Type type )
384
{
385 386 387 388 389 390 391 392 393 394 395
    FilePtr file;
    try
    {
        file = File::create( m_ml, m_id, type, mrl );
    }
    catch ( const sqlite::errors::Generic& ex )
    {
        LOG_ERROR( "Failed to add media external MRL: ", ex.what() );
        return nullptr;
    }

396 397 398 399 400 401 402 403
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
}

404
void Media::removeFile( File& file )
405
{
406 407 408
    file.destroy();
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
409
        return;
410
    m_files.get().erase( std::remove_if( begin( m_files.get() ), end( m_files.get() ), [&file]( const FilePtr& f ) {
411 412
        return f->id() == file.id();
    }));
413 414
}

415
std::vector<MediaPtr> Media::listAll( MediaLibraryPtr ml, IMedia::Type type, SortingCriteria sort, bool desc )
416
{
417
    std::string req;
418
    if ( sort == SortingCriteria::LastModificationDate || sort == SortingCriteria::FileSize )
419 420 421 422
    {
        req = "SELECT m.* FROM " + policy::MediaTable::Name + " m INNER JOIN "
                + policy::FileTable::Name + " f ON m.id_media = f.media_id"
                " WHERE m.type = ?"
423
                " AND f.type = ?";
424 425 426 427
        if ( sort == SortingCriteria::LastModificationDate )
            req += " ORDER BY f.last_modification_date";
        else
            req += " ORDER BY f.size";
428 429
        if ( desc == true )
            req += " DESC";
430
        return fetchAll<IMedia>( ml, req, type, File::Type::Main );
431 432 433 434
    }
    req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY ";
    switch ( sort )
    {
435
    case SortingCriteria::Duration:
436 437
        req += "duration";
        break;
438
    case SortingCriteria::InsertionDate:
439 440
        req += "insertion_date";
        break;
441
    case SortingCriteria::ReleaseDate:
442 443 444
        req += "release_date";
        break;
    default:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
445
        req += "title";
446 447 448 449 450 451
        break;
    }
    if ( desc == true )
        req += " DESC";

    return fetchAll<IMedia>( ml, req, type );
452 453
}

454
int64_t Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
455 456 457 458
{
    return m_id;
}

459
IMedia::Type Media::type()
460 461 462 463
{
    return m_type;
}

464
IMedia::SubType Media::subType() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
465 466 467 468
{
    return m_subType;
}

469
void Media::setType( Type type )
470
{
471
    if ( m_type == type )
472
        return;
473
    m_type = type;
474
    m_changed = true;
475 476
}

477
const std::string &Media::title() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
478
{
479
    return m_title;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
480 481
}

482 483 484 485 486
bool Media::setTitle( const std::string& title )
{
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET title = ? WHERE id_media = ?";
    if ( m_title == title )
        return true;
487 488 489 490 491 492 493 494
    try
    {
        if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, title, m_id ) == false )
            return false;
    }
    catch ( const sqlite::errors::Generic& ex )
    {
        LOG_ERROR( "Failed to set media title: ", ex.what() );
495
        return false;
496 497
    }

498 499 500 501
    m_title = title;
    return true;
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
502
void Media::setTitleBuffered( const std::string &title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
503
{
504 505
    if ( m_title == title )
        return;
506
    m_title = title;
507
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
508 509
}

510
bool Media::createTable( DBConnection connection )
511
{
512 513
    std::string req = "CREATE TABLE IF NOT EXISTS " + policy::MediaTable::Name + "("
            "id_media INTEGER PRIMARY KEY AUTOINCREMENT,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
514
            "type INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
515
            "subtype INTEGER,"
516
            "duration INTEGER DEFAULT -1,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
517
            "play_count UNSIGNED INTEGER,"
518
            "last_played_date UNSIGNED INTEGER,"
519
            "insertion_date UNSIGNED INTEGER,"
520
            "release_date UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
521
            "thumbnail TEXT,"
522
            "title TEXT COLLATE NOCASE,"
523
            "filename TEXT,"
524
            "is_favorite BOOLEAN NOT NULL DEFAULT 0,"
525
            "is_present BOOLEAN NOT NULL DEFAULT 1"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
526
            ")";
527
    const std::string indexReq = "CREATE INDEX IF NOT EXISTS index_last_played_date ON "
528
            + policy::MediaTable::Name + "(last_played_date DESC)";
529
    const std::string vtableReq = "CREATE VIRTUAL TABLE IF NOT EXISTS "
530
                + policy::MediaTable::Name + "Fts USING FTS3("
531 532
                "title,"
                "labels"
533
            ")";
534 535 536 537 538 539
    const std::string metadataReq = "CREATE TABLE IF NOT EXISTS " + policy::MediaMetadataTable::Name + "("
            "id_media INTEGER,"
            "type INTEGER,"
            "value TEXT,"
            "PRIMARY KEY (id_media, type)"
            ")";
540
    return sqlite::Tools::executeRequest( connection, req ) &&
541
            sqlite::Tools::executeRequest( connection, indexReq ) &&
542 543
            sqlite::Tools::executeRequest( connection, vtableReq ) &&
            sqlite::Tools::executeRequest( connection, metadataReq );
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
}

bool Media::createTriggers( DBConnection connection )
{
    static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS has_files_present AFTER UPDATE OF "
            "is_present ON " + policy::FileTable::Name +
            " BEGIN "
            " UPDATE " + policy::MediaTable::Name + " SET is_present="
                "(SELECT COUNT(id_file) FROM " + policy::FileTable::Name + " WHERE media_id=new.media_id AND is_present=1) "
                "WHERE id_media=new.media_id;"
            " END;";
    static const std::string triggerReq2 = "CREATE TRIGGER IF NOT EXISTS cascade_file_deletion AFTER DELETE ON "
            + policy::FileTable::Name +
            " BEGIN "
            " DELETE FROM " + policy::MediaTable::Name + " WHERE "
                "(SELECT COUNT(id_file) FROM " + policy::FileTable::Name + " WHERE media_id=old.media_id) = 0"
                " AND id_media=old.media_id;"
            " END;";
562 563 564 565

    static const std::string vtableInsertTrigger = "CREATE TRIGGER IF NOT EXISTS insert_media_fts"
            " AFTER INSERT ON " + policy::MediaTable::Name +
            " BEGIN"
566
            " INSERT INTO " + policy::MediaTable::Name + "Fts(rowid,title,labels) VALUES(new.id_media, new.title, '');"
567 568
            " END";
    static const std::string vtableDeleteTrigger = "CREATE TRIGGER IF NOT EXISTS delete_media_fts"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
569
            " BEFORE DELETE ON " + policy::MediaTable::Name +
570 571 572 573 574 575 576 577
            " BEGIN"
            " DELETE FROM " + policy::MediaTable::Name + "Fts WHERE rowid = old.id_media;"
            " END";
    static const std::string vtableUpdateTitleTrigger2 = "CREATE TRIGGER IF NOT EXISTS update_media_title_fts"
              " AFTER UPDATE OF title ON " + policy::MediaTable::Name +
              " BEGIN"
              " UPDATE " + policy::MediaTable::Name + "Fts SET title = new.title WHERE rowid = new.id_media;"
              " END";
578
    return sqlite::Tools::executeRequest( connection, triggerReq ) &&
579 580 581 582
            sqlite::Tools::executeRequest( connection, triggerReq2 ) &&
            sqlite::Tools::executeRequest( connection, vtableInsertTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableDeleteTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableUpdateTitleTrigger2 );
583
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
584

585
bool Media::addLabel( LabelPtr label )
586
{
587
    if ( m_id == 0 || label->id() == 0 )
588
    {
589
        LOG_ERROR( "Both file & label need to be inserted in database before being linked together" );
590 591
        return false;
    }
592 593 594 595 596 597 598 599 600 601 602 603
    try
    {
        const char* req = "INSERT INTO LabelFileRelation VALUES(?, ?)";
        if ( sqlite::Tools::executeInsert( m_ml->getConn(), req, label->id(), m_id ) == 0 )
            return false;
        const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
            "SET labels = labels || ' ' || ? WHERE rowid = ?";
        return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
    }
    catch ( const sqlite::errors::Generic& ex )
    {
        LOG_ERROR( "Failed to add label: ", ex.what() );
604
        return false;
605
    }
606 607
}

608
bool Media::removeLabel( LabelPtr label )
609
{
610
    if ( m_id == 0 || label->id() == 0 )
611
    {
612
        LOG_ERROR( "Can't unlink a label/file not inserted in database" );
613 614
        return false;
    }
615 616 617 618 619 620 621 622 623 624 625 626
    try
    {
        const char* req = "DELETE FROM LabelFileRelation WHERE label_id = ? AND media_id = ?";
        if ( sqlite::Tools::executeDelete( m_ml->getConn(), req, label->id(), m_id ) == false )
            return false;
        const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
                "SET labels = TRIM(REPLACE(labels, ?, '')) WHERE rowid = ?";
        return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
    }
    catch ( const sqlite::errors::Generic& ex )
    {
        LOG_ERROR( "Failed to remove label: ", ex.what() );
627
        return false;
628
    }
629
}
630 631


632
std::vector<MediaPtr> Media::search( MediaLibraryPtr ml, const std::string& title )
633 634 635
{
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE"
            " id_media IN (SELECT rowid FROM " + policy::MediaTable::Name + "Fts"
636
            " WHERE " + policy::MediaTable::Name + "Fts MATCH '*' || ? || '*')"
637
            "AND is_present = 1";
638
    return Media::fetchAll<IMedia>( ml, req, title );
639
}
640

641
std::vector<MediaPtr> Media::fetchHistory( MediaLibraryPtr ml )
642 643 644
{
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE last_played_date IS NOT NULL"
            " ORDER BY last_played_date DESC LIMIT 100";
645
    return fetchAll<IMedia>( ml, req );
646
}
647

648
void Media::clearHistory( MediaLibraryPtr ml )
649 650
{
    auto dbConn = ml->getConn();
651 652
    // There should already be an active transaction, from MediaLibrary::clearHistory
    assert( sqlite::Transaction::transactionInProgress() == true );
653 654
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
            "play_count = 0,"
655 656 657
            "last_played_date = NULL";
    static const std::string flushProgress = "DELETE FROM " + policy::MediaMetadataTable::Name +
            " WHERE type = ?";
658 659
    // Clear the entire cache since quite a few items are now containing invalid info.
    clear();
660
    sqlite::Tools::executeUpdate( dbConn, req );
661
    sqlite::Tools::executeDelete( dbConn, flushProgress, IMedia::MetadataType::Progress );
662 663
}

664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
bool Media::MediaMetadata::isSet() const
{
    return m_isSet;
}

int64_t Media::MediaMetadata::integer() const
{
    return atoll( m_value.c_str() );
}

const std::string& Media::MediaMetadata::str() const
{
    return m_value;
}

679
}