Media.cpp 17.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
/*****************************************************************************
 * 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
Media::Media( MediaLibraryPtr ml, sqlite::Row& row )
    : m_ml( ml )
60
    , m_changed( false )
61
{
62 63
    row >> m_id
        >> m_type
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
64
        >> m_subType
65 66
        >> m_duration
        >> m_playCount
67
        >> m_lastPlayedDate
68 69
        >> m_progress
        >> m_rating
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 88
    , m_progress( .0f )
    , m_rating( -1 )
89
    , m_insertionDate( time( nullptr ) )
90
    , m_releaseDate( 0 )
91
    , m_title( title )
92 93
    // When creating a Media, meta aren't parsed, and therefor, is the filename
    , m_filename( title )
94
    , m_isFavorite( false )
95
    , m_isPresent( true )
96
    , m_changed( false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
97
{
98 99
}

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

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

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

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

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

130
int64_t Media::duration() const
131 132 133 134
{
    return m_duration;
}

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

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

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

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

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

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

175
bool Media::increasePlayCount()
176
{
177 178 179 180 181
    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;
182
    m_playCount++;
183 184
    m_lastPlayedDate = lastPlayedDate;
    return true;
185 186
}

187 188 189 190 191
float Media::progress() const
{
    return m_progress;
}

192
bool Media::setProgress( float progress )
193
{
194
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET progress = ? WHERE id_media = ?";
195
    if ( progress == m_progress || progress < 0 || progress > 1.0 )
196 197 198
        return true;
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, progress, m_id ) == false )
        return false;
199
    m_progress = progress;
200
    return true;
201 202 203 204 205 206 207
}

int Media::rating() const
{
    return m_rating;
}

208
bool Media::setRating( int rating )
209
{
210
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET rating = ? WHERE id_media = ?";
211
    if ( m_rating == rating )
212 213 214
        return true;
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, rating, m_id ) == false )
        return false;
215
    m_rating = rating;
216
    return true;
217 218
}

219 220 221 222 223
bool Media::isFavorite() const
{
    return m_isFavorite;
}

224
bool Media::setFavorite( bool favorite )
225
{
226
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET is_favorite = ? WHERE id_media = ?";
227
    if ( m_isFavorite == favorite )
228 229 230
        return true;
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, favorite, m_id ) == false )
        return false;
231
    m_isFavorite = favorite;
232
    return true;
233 234
}

235
const std::vector<FilePtr>& Media::files() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
236
{
237 238 239 240 241
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
    {
        static const std::string req = "SELECT * FROM " + policy::FileTable::Name
                + " WHERE media_id = ?";
242
        m_files = File::fetchAll<IFile>( m_ml, req, m_id );
243 244
    }
    return m_files;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
245 246
}

247
MoviePtr Media::movie() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
248
{
249 250 251
    if ( m_subType != SubType::Movie )
        return nullptr;

252 253
    auto lock = m_movie.lock();

254
    if ( m_movie.isCached() == false )
255
        m_movie = Movie::fromMedia( m_ml, m_id );
256
    return m_movie.get();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
257 258
}

259
void Media::setMovie(MoviePtr movie)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
260
{
261
    auto lock = m_movie.lock();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
262
    m_movie = movie;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
263 264
    m_subType = SubType::Movie;
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
265 266
}

267 268
bool Media::addVideoTrack(const std::string& codec, unsigned int width, unsigned int height, float fps,
                          const std::string& language, const std::string& description )
269
{
270
    return VideoTrack::create( m_ml, codec, width, height, fps, m_id, language, description ) != nullptr;
271 272
}

273
std::vector<VideoTrackPtr> Media::videoTracks()
274
{
275 276
    static const std::string req = "SELECT * FROM " + policy::VideoTrackTable::Name +
            " WHERE media_id = ?";
277
    return VideoTrack::fetchAll<IVideoTrack>( m_ml, req, m_id );
278 279
}

280
bool Media::addAudioTrack( const std::string& codec, unsigned int bitrate,
281 282
                          unsigned int sampleRate, unsigned int nbChannels,
                          const std::string& language, const std::string& desc )
283
{
284
    return AudioTrack::create( m_ml, codec, bitrate, sampleRate, nbChannels, language, desc, m_id ) != nullptr;
285 286
}

287
std::vector<AudioTrackPtr> Media::audioTracks()
288
{
289 290
    static const std::string req = "SELECT * FROM " + policy::AudioTrackTable::Name +
            " WHERE media_id = ?";
291
    return AudioTrack::fetchAll<IAudioTrack>( m_ml, req, m_id );
292 293
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
294
const std::string &Media::thumbnail()
295
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
296
    return m_thumbnail;
297 298
}

299 300 301 302 303
unsigned int Media::insertionDate() const
{
    return m_insertionDate;
}

304 305 306 307 308 309 310 311 312 313 314 315 316
unsigned int Media::releaseDate() const
{
    return m_releaseDate;
}

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
317
void Media::setThumbnail(const std::string& thumbnail )
318
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
319
    if ( m_thumbnail == thumbnail )
320
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
321
    m_thumbnail = thumbnail;
322 323 324 325 326 327
    m_changed = true;
}

bool Media::save()
{
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
328
            "type = ?, subtype = ?, duration = ?, progress = ?, release_date = ?,"
329
            "thumbnail = ?, title = ? WHERE id_media = ?";
330 331
    if ( m_changed == false )
        return true;
332
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_type, m_subType, m_duration,
333
                                       m_progress, m_releaseDate, m_thumbnail, m_title, m_id ) == false )
334 335 336 337
    {
        return false;
    }
    m_changed = false;
338 339 340
    return true;
}

341
std::shared_ptr<File> Media::addFile( const fs::IFile& fileFs, Folder& parentFolder, fs::IDirectory& parentFolderFs, IFile::Type type )
342
{
343
    auto file = File::create( m_ml, m_id, type, fileFs, parentFolder.id(), parentFolderFs.device()->isRemovable() );
344 345 346 347 348 349
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
350 351
}

352
void Media::removeFile( File& file )
353
{
354 355 356
    file.destroy();
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
357
        return;
358
    m_files.get().erase( std::remove_if( begin( m_files.get() ), end( m_files.get() ), [&file]( const FilePtr& f ) {
359 360
        return f->id() == file.id();
    }));
361 362
}

363
std::vector<MediaPtr> Media::listAll( MediaLibraryPtr ml, IMedia::Type type, SortingCriteria sort, bool desc )
364
{
365
    std::string req;
366
    if ( sort == SortingCriteria::LastModificationDate || sort == SortingCriteria::FileSize )
367 368 369 370
    {
        req = "SELECT m.* FROM " + policy::MediaTable::Name + " m INNER JOIN "
                + policy::FileTable::Name + " f ON m.id_media = f.media_id"
                " WHERE m.type = ?"
371 372 373 374 375
                " AND ( f.type = ? OR f.type = ? )";
        if ( sort == SortingCriteria::LastModificationDate )
            req += " ORDER BY f.last_modification_date";
        else
            req += " ORDER BY f.size";
376 377 378 379 380 381 382
        if ( desc == true )
            req += " DESC";
        return fetchAll<IMedia>( ml, req, type, File::Type::Entire, File::Type::Main );
    }
    req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ? AND is_present = 1 ORDER BY ";
    switch ( sort )
    {
383
    case SortingCriteria::Duration:
384 385
        req += "duration";
        break;
386
    case SortingCriteria::InsertionDate:
387 388
        req += "insertion_date";
        break;
389
    case SortingCriteria::ReleaseDate:
390 391 392
        req += "release_date";
        break;
    default:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
393
        req += "title";
394 395 396 397 398 399
        break;
    }
    if ( desc == true )
        req += " DESC";

    return fetchAll<IMedia>( ml, req, type );
400 401
}

402
int64_t Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
403 404 405 406
{
    return m_id;
}

407
IMedia::Type Media::type()
408 409 410 411
{
    return m_type;
}

412
IMedia::SubType Media::subType() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
413 414 415 416
{
    return m_subType;
}

417
void Media::setType( Type type )
418
{
419
    if ( m_type == type )
420
        return;
421
    m_type = type;
422
    m_changed = true;
423 424
}

425
const std::string &Media::title() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
426
{
427
    return m_title;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
428 429
}

430
void Media::setTitle( const std::string &title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
431
{
432 433
    if ( m_title == title )
        return;
434
    m_title = title;
435
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
436 437
}

438
bool Media::createTable( DBConnection connection )
439
{
440 441
    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
442
            "type INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
443
            "subtype INTEGER,"
444
            "duration INTEGER DEFAULT -1,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
445
            "play_count UNSIGNED INTEGER,"
446
            "last_played_date UNSIGNED INTEGER,"
447 448
            "progress REAL,"
            "rating INTEGER DEFAULT -1,"
449
            "insertion_date UNSIGNED INTEGER,"
450
            "release_date UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
451
            "thumbnail TEXT,"
452
            "title TEXT COLLATE NOCASE,"
453
            "filename TEXT,"
454
            "is_favorite BOOLEAN NOT NULL DEFAULT 0,"
455
            "is_present BOOLEAN NOT NULL DEFAULT 1"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
456
            ")";
457
    const std::string indexReq = "CREATE INDEX IF NOT EXISTS index_last_played_date ON "
458
            + policy::MediaTable::Name + "(last_played_date DESC)";
459
    const std::string vtableReq = "CREATE VIRTUAL TABLE IF NOT EXISTS "
460
                + policy::MediaTable::Name + "Fts USING FTS3("
461 462
                "title,"
                "labels"
463 464
            ")";
    return sqlite::Tools::executeRequest( connection, req ) &&
465
            sqlite::Tools::executeRequest( connection, indexReq ) &&
466
            sqlite::Tools::executeRequest( connection, vtableReq );
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
}

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;";
485 486 487 488

    static const std::string vtableInsertTrigger = "CREATE TRIGGER IF NOT EXISTS insert_media_fts"
            " AFTER INSERT ON " + policy::MediaTable::Name +
            " BEGIN"
489
            " INSERT INTO " + policy::MediaTable::Name + "Fts(rowid,title,labels) VALUES(new.id_media, new.title, '');"
490 491
            " 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
492
            " BEFORE DELETE ON " + policy::MediaTable::Name +
493 494 495 496 497 498 499 500
            " 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";
501
    return sqlite::Tools::executeRequest( connection, triggerReq ) &&
502 503 504 505
            sqlite::Tools::executeRequest( connection, triggerReq2 ) &&
            sqlite::Tools::executeRequest( connection, vtableInsertTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableDeleteTrigger ) &&
            sqlite::Tools::executeRequest( connection, vtableUpdateTitleTrigger2 );
506
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
507

508
bool Media::addLabel( LabelPtr label )
509
{
510
    if ( m_id == 0 || label->id() == 0 )
511
    {
512
        LOG_ERROR( "Both file & label need to be inserted in database before being linked together" );
513 514 515
        return false;
    }
    const char* req = "INSERT INTO LabelFileRelation VALUES(?, ?)";
516
    if ( sqlite::Tools::executeInsert( m_ml->getConn(), req, label->id(), m_id ) == 0 )
517 518 519
        return false;
    const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
        "SET labels = labels || ' ' || ? WHERE rowid = ?";
520
    return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
521 522
}

523
bool Media::removeLabel( LabelPtr label )
524
{
525
    if ( m_id == 0 || label->id() == 0 )
526
    {
527
        LOG_ERROR( "Can't unlink a label/file not inserted in database" );
528 529
        return false;
    }
530
    const char* req = "DELETE FROM LabelFileRelation WHERE label_id = ? AND media_id = ?";
531
    if ( sqlite::Tools::executeDelete( m_ml->getConn(), req, label->id(), m_id ) == false )
532 533 534
        return false;
    const std::string reqFts = "UPDATE " + policy::MediaTable::Name + "Fts "
            "SET labels = TRIM(REPLACE(labels, ?, '')) WHERE rowid = ?";
535
    return sqlite::Tools::executeUpdate( m_ml->getConn(), reqFts, label->name(), m_id );
536
}
537 538


539
std::vector<MediaPtr> Media::search( MediaLibraryPtr ml, const std::string& title )
540 541 542
{
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE"
            " id_media IN (SELECT rowid FROM " + policy::MediaTable::Name + "Fts"
543
            " WHERE " + policy::MediaTable::Name + "Fts MATCH '*' || ? || '*')"
544
            "AND is_present = 1";
545
    return Media::fetchAll<IMedia>( ml, req, title );
546
}
547

548
std::vector<MediaPtr> Media::fetchHistory( MediaLibraryPtr ml )
549 550 551
{
    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";
552
    return fetchAll<IMedia>( ml, req );
553
}
554

555
void Media::clearHistory( MediaLibraryPtr ml )
556 557 558 559 560 561 562 563
{
    auto dbConn = ml->getConn();
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
            "play_count = 0,"
            "last_played_date = NULL,"
            "progress = 0";
    // Clear the entire cache since quite a few items are now containing invalid info.
    clear();
564
    sqlite::Tools::executeUpdate( dbConn, req );
565 566
}

567
}