Media.cpp 19.9 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 283 284 285 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 321 322 323 324 325 326 327 328 329
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 = ?";
        sqlite::Statement stmt( m_ml->getConn()->getConn(), req );
        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 );
        }
    }
    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 );
}

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

330 331 332 333 334 335 336 337
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
338
void Media::setThumbnail(const std::string& thumbnail )
339
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
340
    if ( m_thumbnail == thumbnail )
341
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
342
    m_thumbnail = thumbnail;
343 344 345 346 347 348
    m_changed = true;
}

bool Media::save()
{
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
349
            "type = ?, subtype = ?, duration = ?, release_date = ?,"
350
            "thumbnail = ?, title = ? WHERE id_media = ?";
351 352
    if ( m_changed == false )
        return true;
353
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_type, m_subType, m_duration,
354
                                       m_releaseDate, m_thumbnail, m_title, m_id ) == false )
355 356 357 358
    {
        return false;
    }
    m_changed = false;
359 360 361
    return true;
}

362
std::shared_ptr<File> Media::addFile( const fs::IFile& fileFs, Folder& parentFolder, fs::IDirectory& parentFolderFs, IFile::Type type )
363
{
364
    auto file = File::create( m_ml, m_id, type, fileFs, parentFolder.id(), parentFolderFs.device()->isRemovable() );
365 366 367 368 369 370
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
371 372
}

373
FilePtr Media::addExternalMrl( const std::string& mrl , IFile::Type type )
374
{
375
    auto file = File::create( m_ml, m_id, type, mrl );
376 377 378 379 380 381 382 383
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
}

384
void Media::removeFile( File& file )
385
{
386 387 388
    file.destroy();
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
389
        return;
390
    m_files.get().erase( std::remove_if( begin( m_files.get() ), end( m_files.get() ), [&file]( const FilePtr& f ) {
391 392
        return f->id() == file.id();
    }));
393 394
}

395
std::vector<MediaPtr> Media::listAll( MediaLibraryPtr ml, IMedia::Type type, SortingCriteria sort, bool desc )
396
{
397
    std::string req;
398
    if ( sort == SortingCriteria::LastModificationDate || sort == SortingCriteria::FileSize )
399 400 401 402
    {
        req = "SELECT m.* FROM " + policy::MediaTable::Name + " m INNER JOIN "
                + policy::FileTable::Name + " f ON m.id_media = f.media_id"
                " WHERE m.type = ?"
403
                " AND f.type = ?";
404 405 406 407
        if ( sort == SortingCriteria::LastModificationDate )
            req += " ORDER BY f.last_modification_date";
        else
            req += " ORDER BY f.size";
408 409
        if ( desc == true )
            req += " DESC";
410
        return fetchAll<IMedia>( ml, req, type, File::Type::Main );
411 412 413 414
    }
    req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY ";
    switch ( sort )
    {
415
    case SortingCriteria::Duration:
416 417
        req += "duration";
        break;
418
    case SortingCriteria::InsertionDate:
419 420
        req += "insertion_date";
        break;
421
    case SortingCriteria::ReleaseDate:
422 423 424
        req += "release_date";
        break;
    default:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
425
        req += "title";
426 427 428 429 430 431
        break;
    }
    if ( desc == true )
        req += " DESC";

    return fetchAll<IMedia>( ml, req, type );
432 433
}

434
int64_t Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
435 436 437 438
{
    return m_id;
}

439
IMedia::Type Media::type()
440 441 442 443
{
    return m_type;
}

444
IMedia::SubType Media::subType() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
445 446 447 448
{
    return m_subType;
}

449
void Media::setType( Type type )
450
{
451
    if ( m_type == type )
452
        return;
453
    m_type = type;
454
    m_changed = true;
455 456
}

457
const std::string &Media::title() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
458
{
459
    return m_title;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
460 461
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
462
void Media::setTitleBuffered( const std::string &title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
463
{
464 465
    if ( m_title == title )
        return;
466
    m_title = title;
467
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
468 469
}

470
bool Media::createTable( DBConnection connection )
471
{
472 473
    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
474
            "type INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
475
            "subtype INTEGER,"
476
            "duration INTEGER DEFAULT -1,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
477
            "play_count UNSIGNED INTEGER,"
478
            "last_played_date UNSIGNED INTEGER,"
479
            "insertion_date UNSIGNED INTEGER,"
480
            "release_date UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
481
            "thumbnail TEXT,"
482
            "title TEXT COLLATE NOCASE,"
483
            "filename TEXT,"
484
            "is_favorite BOOLEAN NOT NULL DEFAULT 0,"
485
            "is_present BOOLEAN NOT NULL DEFAULT 1"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
486
            ")";
487
    const std::string indexReq = "CREATE INDEX IF NOT EXISTS index_last_played_date ON "
488
            + policy::MediaTable::Name + "(last_played_date DESC)";
489
    const std::string vtableReq = "CREATE VIRTUAL TABLE IF NOT EXISTS "
490
                + policy::MediaTable::Name + "Fts USING FTS3("
491 492
                "title,"
                "labels"
493
            ")";
494 495 496 497 498 499
    const std::string metadataReq = "CREATE TABLE IF NOT EXISTS " + policy::MediaMetadataTable::Name + "("
            "id_media INTEGER,"
            "type INTEGER,"
            "value TEXT,"
            "PRIMARY KEY (id_media, type)"
            ")";
500
    return sqlite::Tools::executeRequest( connection, req ) &&
501
            sqlite::Tools::executeRequest( connection, indexReq ) &&
502 503
            sqlite::Tools::executeRequest( connection, vtableReq ) &&
            sqlite::Tools::executeRequest( connection, metadataReq );
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
}

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;";
522 523 524 525

    static const std::string vtableInsertTrigger = "CREATE TRIGGER IF NOT EXISTS insert_media_fts"
            " AFTER INSERT ON " + policy::MediaTable::Name +
            " BEGIN"
526
            " INSERT INTO " + policy::MediaTable::Name + "Fts(rowid,title,labels) VALUES(new.id_media, new.title, '');"
527 528
            " 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
529
            " BEFORE DELETE ON " + policy::MediaTable::Name +
530 531 532 533 534 535 536 537
            " 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";
538
    return sqlite::Tools::executeRequest( connection, triggerReq ) &&
539 540 541 542
            sqlite::Tools::executeRequest( connection, triggerReq2 ) &&
            sqlite::Tools::executeRequest( connection, vtableInsertTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableDeleteTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableUpdateTitleTrigger2 );
543
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
544

545
bool Media::addLabel( LabelPtr label )
546
{
547
    if ( m_id == 0 || label->id() == 0 )
548
    {
549
        LOG_ERROR( "Both file & label need to be inserted in database before being linked together" );
550 551 552
        return false;
    }
    const char* req = "INSERT INTO LabelFileRelation VALUES(?, ?)";
553
    if ( sqlite::Tools::executeInsert( m_ml->getConn(), req, label->id(), m_id ) == 0 )
554 555 556
        return false;
    const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
        "SET labels = labels || ' ' || ? WHERE rowid = ?";
557
    return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
558 559
}

560
bool Media::removeLabel( LabelPtr label )
561
{
562
    if ( m_id == 0 || label->id() == 0 )
563
    {
564
        LOG_ERROR( "Can't unlink a label/file not inserted in database" );
565 566
        return false;
    }
567
    const char* req = "DELETE FROM LabelFileRelation WHERE label_id = ? AND media_id = ?";
568
    if ( sqlite::Tools::executeDelete( m_ml->getConn(), req, label->id(), m_id ) == false )
569 570 571
        return false;
    const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
            "SET labels = TRIM(REPLACE(labels, ?, '')) WHERE rowid = ?";
572
    return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
573
}
574 575


576
std::vector<MediaPtr> Media::search( MediaLibraryPtr ml, const std::string& title )
577 578 579
{
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE"
            " id_media IN (SELECT rowid FROM " + policy::MediaTable::Name + "Fts"
580
            " WHERE " + policy::MediaTable::Name + "Fts MATCH '*' || ? || '*')"
581
            "AND is_present = 1";
582
    return Media::fetchAll<IMedia>( ml, req, title );
583
}
584

585
std::vector<MediaPtr> Media::fetchHistory( MediaLibraryPtr ml )
586 587 588
{
    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";
589
    return fetchAll<IMedia>( ml, req );
590
}
591

592
void Media::clearHistory( MediaLibraryPtr ml )
593 594
{
    auto dbConn = ml->getConn();
595 596
    // There should already be an active transaction, from MediaLibrary::clearHistory
    assert( sqlite::Transaction::transactionInProgress() == true );
597 598
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
            "play_count = 0,"
599 600 601
            "last_played_date = NULL";
    static const std::string flushProgress = "DELETE FROM " + policy::MediaMetadataTable::Name +
            " WHERE type = ?";
602 603
    // Clear the entire cache since quite a few items are now containing invalid info.
    clear();
604
    sqlite::Tools::executeUpdate( dbConn, req );
605
    sqlite::Tools::executeDelete( dbConn, flushProgress, IMedia::MetadataType::Progress );
606 607
}

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
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;
}

623
}