Album.cpp 19.8 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 28
#include <algorithm>

29
#include "Album.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
30
#include "AlbumTrack.h"
31
#include "Artist.h"
32
#include "Genre.h"
33
#include "Media.h"
34
#include "Thumbnail.h"
35

36
#include "database/SqliteTools.h"
37

38 39 40
namespace medialibrary
{

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
41
const std::string policy::AlbumTable::Name = "Album";
42
const std::string policy::AlbumTable::PrimaryKeyColumn = "id_album";
43
int64_t Album::* const policy::AlbumTable::PrimaryKey = &Album::m_id;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
44

45 46
Album::Album(MediaLibraryPtr ml, sqlite::Row& row)
    : m_ml( ml )
47
{
48 49
    row >> m_id
        >> m_title
50
        >> m_artistId
51
        >> m_releaseYear
52
        >> m_shortSummary
53
        >> m_thumbnailId
54
        >> m_nbTracks
55
        >> m_duration
56
        >> m_isPresent;
57 58
}

59
Album::Album( MediaLibraryPtr ml, const std::string& title, int64_t thumbnailId )
60 61
    : m_ml( ml )
    , m_id( 0 )
62
    , m_title( title )
63
    , m_artistId( 0 )
64
    , m_releaseYear( ~0u )
65
    , m_thumbnailId( thumbnailId )
66
    , m_nbTracks( 0 )
67
    , m_duration( 0 )
68
    , m_isPresent( true )
69 70 71
{
}

72 73 74
Album::Album( MediaLibraryPtr ml, const Artist* artist )
    : m_ml( ml )
    , m_id( 0 )
75 76
    , m_artistId( artist->id() )
    , m_releaseYear( ~0u )
77
    , m_thumbnailId( 0 )
78
    , m_nbTracks( 0 )
79
    , m_duration( 0 )
80
    , m_isPresent( true )
81 82 83
{
}

84
int64_t Album::id() const
85 86
{
    return m_id;
87 88
}

89
const std::string& Album::title() const
90
{
91
    return m_title;
92 93
}

94
unsigned int Album::releaseYear() const
95
{
96 97
    if ( m_releaseYear == ~0U )
        return 0;
98 99 100
    return m_releaseYear;
}

101
bool Album::setReleaseYear( unsigned int date, bool force )
102
{
103 104 105 106 107 108 109 110 111 112 113 114
    if ( date == m_releaseYear )
        return true;
    if ( force == false )
    {
        if ( m_releaseYear != ~0u && date != m_releaseYear )
        {
            // If we already have set the date back to 0, don't do it again.
            if ( m_releaseYear == 0 )
                return true;
            date = 0;
        }
    }
115 116
    static const std::string req = "UPDATE " + policy::AlbumTable::Name
            + " SET release_year = ? WHERE id_album = ?";
117
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, date, m_id ) == false )
118 119 120 121 122
        return false;
    m_releaseYear = date;
    return true;
}

123
const std::string& Album::shortSummary() const
124 125 126 127
{
    return m_shortSummary;
}

128 129
bool Album::setShortSummary( const std::string& summary )
{
130
    static const std::string req = "UPDATE " + policy::AlbumTable::Name
131
            + " SET short_summary = ? WHERE id_album = ?";
132
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, summary, m_id ) == false )
133 134 135 136 137
        return false;
    m_shortSummary = summary;
    return true;
}

138
const std::string& Album::artworkMrl() const
139
{
140 141 142 143 144 145 146 147 148 149 150 151
    if ( m_thumbnailId == 0 )
        return Thumbnail::EmptyMrl;

    auto lock = m_thumbnail.lock();
    if ( m_thumbnail.isCached() == false )
    {
        auto thumbnail = Thumbnail::fetch( m_ml, m_thumbnailId );
        if ( thumbnail == nullptr )
            return Thumbnail::EmptyMrl;
        m_thumbnail = std::move( thumbnail );
    }
    return m_thumbnail.get()->mrl();
152 153
}

154
bool Album::setArtworkMrl( const std::string& artworkMrl, Thumbnail::Origin origin )
155
{
156 157
    if ( m_thumbnailId != 0 )
        return Thumbnail::setMrlFromPrimaryKey( m_ml, m_thumbnail, m_thumbnailId,
158
                                                artworkMrl, origin );
159 160 161 162 163 164 165 166

    std::unique_ptr<sqlite::Transaction> t;
    if ( sqlite::Transaction::transactionInProgress() == false )
        t = m_ml->getConn()->newTransaction();
    auto lock = m_thumbnail.lock();
    m_thumbnail = Thumbnail::create( m_ml, artworkMrl, Thumbnail::Origin::Album );
    if ( m_thumbnail.get() == nullptr )
        return false;
167
    static const std::string req = "UPDATE " + policy::AlbumTable::Name
168 169
            + " SET thumbnail_id = ? WHERE id_album = ?";
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, m_thumbnail.get()->id(), m_id ) == false )
170
        return false;
171 172 173
    m_thumbnailId = m_thumbnail.get()->id();
    if ( t != nullptr )
        t->commit();
174 175 176
    return true;
}

177
std::string Album::orderTracksBy( SortingCriteria sort, bool desc )
178 179 180 181
{
    std::string req = " ORDER BY ";
    switch ( sort )
    {
182
    case SortingCriteria::Alpha:
183 184
        req += "med.title";
        break;
185
    case SortingCriteria::Duration:
186 187
        req += "med.duration";
        break;
188
    case SortingCriteria::ReleaseDate:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
189
        req += "med.release_date";
190 191
        break;
    default:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
192 193 194 195
        if ( desc == true )
            req += "att.disc_number DESC, att.track_number DESC, med.filename";
        else
            req += "att.disc_number, att.track_number, med.filename";
196 197 198 199 200 201 202 203
        break;
    }

    if ( desc == true )
        req += " DESC";
    return req;
}

204
std::string Album::orderBy( SortingCriteria sort, bool desc )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
205 206 207 208
{
    std::string req = " ORDER BY ";
    switch ( sort )
    {
209
    case SortingCriteria::ReleaseDate:
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
210 211 212 213 214
        if ( desc == true )
            req += "release_year DESC, title";
        else
            req += "release_year, title";
        break;
215
    case SortingCriteria::Duration:
216 217 218 219
        req += "duration";
        if ( desc == true )
            req += " DESC";
        break;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
220 221 222 223 224 225 226 227 228
    default:
        req += "title";
        if ( desc == true )
            req += " DESC";
        break;
    }
    return req;
}

229
std::vector<MediaPtr> Album::tracks( SortingCriteria sort, bool desc ) const
230
{
231 232
    // This doesn't return the cached version, because it would be fairly complicated, if not impossible or
    // counter productive, to maintain a cache that respects all orderings.
233 234
    std::string req = "SELECT med.* FROM " + policy::MediaTable::Name + " med "
        " INNER JOIN " + policy::AlbumTrackTable::Name + " att ON att.media_id = med.id_media "
235
        " WHERE att.album_id = ? AND med.is_present != 0";
236
    req += orderTracksBy( sort, desc );
237
    return Media::fetchAll<IMedia>( m_ml, req, m_id );
238 239
}

240
std::vector<MediaPtr> Album::tracks( GenrePtr genre, SortingCriteria sort, bool desc ) const
241 242 243
{
    if ( genre == nullptr )
        return {};
244
    std::string req = "SELECT med.* FROM " + policy::MediaTable::Name + " med "
245
            " INNER JOIN " + policy::AlbumTrackTable::Name + " att ON att.media_id = med.id_media "
246
            " WHERE att.album_id = ? AND med.is_present != 0"
247
            " AND genre_id = ?";
248
    req += orderTracksBy( sort, desc );
249
    return Media::fetchAll<IMedia>( m_ml, req, m_id, genre->id() );
250 251
}

252
std::vector<MediaPtr> Album::cachedTracks() const
253 254 255
{
    auto lock = m_tracks.lock();
    if ( m_tracks.isCached() == false )
256
        m_tracks = tracks( SortingCriteria::Default, false );
257 258 259
    return m_tracks.get();
}

260
std::shared_ptr<AlbumTrack> Album::addTrack( std::shared_ptr<Media> media, unsigned int trackNb,
261
                                             unsigned int discNumber, int64_t artistId, Genre* genre )
262
{
263 264
    auto track = AlbumTrack::create( m_ml, m_id, media, trackNb, discNumber, artistId,
                                     genre != nullptr ? genre->id() : 0, media->duration() );
265 266
    if ( track == nullptr )
        return nullptr;
267
    media->setAlbumTrack( track );
268 269
    if ( genre != nullptr )
        genre->updateCachedNbTracks( 1 );
270
    // Assume the media will be saved by the caller
271
    m_nbTracks++;
272 273
    if ( media->duration() > 0 )
        m_duration += media->duration();
274
    auto lock = m_tracks.lock();
275 276 277 278 279 280 281
    // Don't assume we have always have a valid value in m_tracks.
    // While it's ok to assume that if we are currently parsing the album, we
    // have a valid cache tracks, this isn't true when restarting an interrupted parsing.
    // The nbTracks value will be correct however. If it's equal to one, it means we're inserting
    // the first track in this album
    if ( m_tracks.isCached() == false && m_nbTracks == 1 )
        m_tracks.markCached();
282 283
    if ( m_tracks.isCached() == true )
        m_tracks.get().push_back( media );
284
    return track;
285 286
}

287 288 289 290 291
unsigned int Album::nbTracks() const
{
    return m_nbTracks;
}

292 293 294 295 296
unsigned int Album::duration() const
{
    return m_duration;
}

297
ArtistPtr Album::albumArtist() const
298 299 300
{
    if ( m_artistId == 0 )
        return nullptr;
301 302 303 304
    auto lock = m_albumArtist.lock();
    if ( m_albumArtist.isCached() == false )
        m_albumArtist = Artist::fetch( m_ml, m_artistId );
    return m_albumArtist.get();
305 306
}

307
bool Album::setAlbumArtist( std::shared_ptr<Artist> artist )
308 309 310 311 312 313 314
{
    if ( m_artistId == artist->id() )
        return true;
    if ( artist->id() == 0 )
        return false;
    static const std::string req = "UPDATE " + policy::AlbumTable::Name + " SET "
            "artist_id = ? WHERE id_album = ?";
315
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, artist->id(), m_id ) == false )
316
        return false;
317 318
    if ( m_artistId != 0 )
    {
319 320 321
        if ( m_albumArtist.isCached() == false )
            albumArtist();
        m_albumArtist.get()->updateNbAlbum( -1 );
322
    }
323
    m_artistId = artist->id();
324
    m_albumArtist = artist;
325
    artist->updateNbAlbum( 1 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
326 327
    static const std::string ftsReq = "UPDATE " + policy::AlbumTable::Name + "Fts SET "
            " artist = ? WHERE rowid = ?";
328
    sqlite::Tools::executeUpdate( m_ml->getConn(), ftsReq, artist->name(), m_id );
329 330 331
    return true;
}

332
std::vector<ArtistPtr> Album::artists( bool desc ) const
333
{
334
    std::string req = "SELECT art.* FROM " + policy::ArtistTable::Name + " art "
335
            "INNER JOIN AlbumArtistRelation aar ON aar.artist_id = art.id_artist "
336 337 338
            "WHERE aar.album_id = ? ORDER BY art.name";
    if ( desc == true )
        req += " DESC";
339
    return Artist::fetchAll<IArtist>( m_ml, req, m_id );
340 341
}

342
bool Album::addArtist( std::shared_ptr<Artist> artist )
343
{
344
    static const std::string req = "INSERT OR IGNORE INTO AlbumArtistRelation VALUES(?, ?)";
345 346
    if ( m_id == 0 || artist->id() == 0 )
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
347
        LOG_ERROR("Both artist & album need to be inserted in database before being linked together" );
348 349
        return false;
    }
350
    return sqlite::Tools::executeInsert( m_ml->getConn(), req, m_id, artist->id() ) != 0;
351 352
}

353 354
bool Album::removeArtist(Artist* artist)
{
355
    static const std::string req = "DELETE FROM AlbumArtistRelation WHERE album_id = ? "
356
            "AND id_artist = ?";
357
    return sqlite::Tools::executeDelete( m_ml->getConn(), req, m_id, artist->id() );
358 359
}

360
void Album::createTable( sqlite::Connection* dbConnection )
361
{
362
    const std::string req = "CREATE TABLE IF NOT EXISTS " +
363 364
            policy::AlbumTable::Name +
            "("
365
                "id_album INTEGER PRIMARY KEY AUTOINCREMENT,"
366
                "title TEXT COLLATE NOCASE,"
367
                "artist_id UNSIGNED INTEGER,"
368
                "release_year UNSIGNED INTEGER,"
369
                "short_summary TEXT,"
370
                "thumbnail_id UNSIGNED INT,"
371
                "nb_tracks UNSIGNED INTEGER DEFAULT 0,"
372
                "duration UNSIGNED INTEGER NOT NULL DEFAULT 0,"
373 374
                "is_present BOOLEAN NOT NULL DEFAULT 1,"
                "FOREIGN KEY( artist_id ) REFERENCES " + policy::ArtistTable::Name
375 376 377
                + "(id_artist) ON DELETE CASCADE,"
                "FOREIGN KEY(thumbnail_id) REFERENCES " + policy::ThumbnailTable::Name
                + "(id_thumbnail)"
378
            ")";
379
    const std::string reqRel = "CREATE TABLE IF NOT EXISTS AlbumArtistRelation("
380 381 382 383
                "album_id INTEGER,"
                "artist_id INTEGER,"
                "PRIMARY KEY (album_id, artist_id),"
                "FOREIGN KEY(album_id) REFERENCES " + policy::AlbumTable::Name + "("
384
                    + policy::AlbumTable::PrimaryKeyColumn + ") ON DELETE CASCADE,"
385
                "FOREIGN KEY(artist_id) REFERENCES " + policy::ArtistTable::Name + "("
386
                    + policy::ArtistTable::PrimaryKeyColumn + ") ON DELETE CASCADE"
387
            ")";
388
    const std::string vtableReq = "CREATE VIRTUAL TABLE IF NOT EXISTS "
389
                + policy::AlbumTable::Name + "Fts USING FTS3("
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
390 391 392
                "title,"
                "artist"
            ")";
393

394 395 396
    sqlite::Tools::executeRequest( dbConnection, req );
    sqlite::Tools::executeRequest( dbConnection, reqRel );
    sqlite::Tools::executeRequest( dbConnection, vtableReq );
397 398
}

399
void Album::createTriggers( sqlite::Connection* dbConnection )
400
{
401 402
    const std::string indexReq = "CREATE INDEX IF NOT EXISTS album_artist_id_idx ON " +
            policy::AlbumTable::Name + "(artist_id)";
403 404 405 406
    static const std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_album_present AFTER UPDATE OF "
            "is_present ON " + policy::AlbumTrackTable::Name +
            " BEGIN "
            " UPDATE " + policy::AlbumTable::Name + " SET is_present="
407 408
                "(SELECT EXISTS("
                    "SELECT id_track FROM " + policy::AlbumTrackTable::Name +
409
                    " WHERE album_id=new.album_id AND is_present != 0 LIMIT 1"
410
                ") )"
411 412
                "WHERE id_album=new.album_id;"
            " END";
413
    static const std::string deleteTriggerReq = "CREATE TRIGGER IF NOT EXISTS delete_album_track AFTER DELETE ON "
414 415
             + policy::AlbumTrackTable::Name +
            " BEGIN "
416 417 418
            " UPDATE " + policy::AlbumTable::Name +
            " SET"
                " nb_tracks = nb_tracks - 1,"
419
                " duration = duration - old.duration"
420
                " WHERE id_album = old.album_id;"
421
            " DELETE FROM " + policy::AlbumTable::Name +
422
                " WHERE id_album=old.album_id AND nb_tracks = 0;"
423
            " END";
424
    static const std::string updateAddTrackTriggerReq = "CREATE TRIGGER IF NOT EXISTS add_album_track"
425 426 427
            " AFTER INSERT ON " + policy::AlbumTrackTable::Name +
            " BEGIN"
            " UPDATE " + policy::AlbumTable::Name +
428
            " SET duration = duration + new.duration,"
429
            " nb_tracks = nb_tracks + 1"
430 431
            " WHERE id_album = new.album_id;"
            " END";
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
432 433 434 435 436 437 438 439 440 441 442 443 444 445
    static const std::string vtriggerInsert = "CREATE TRIGGER IF NOT EXISTS insert_album_fts AFTER INSERT ON "
            + policy::AlbumTable::Name +
            // Skip unknown albums
            " WHEN new.title IS NOT NULL"
            " BEGIN"
            " INSERT INTO " + policy::AlbumTable::Name + "Fts(rowid, title) VALUES(new.id_album, new.title);"
            " END";
    static const std::string vtriggerDelete = "CREATE TRIGGER IF NOT EXISTS delete_album_fts BEFORE DELETE ON "
            + policy::AlbumTable::Name +
            // Unknown album probably won't be deleted, but better safe than sorry
            " WHEN old.title IS NOT NULL"
            " BEGIN"
            " DELETE FROM " + policy::AlbumTable::Name + "Fts WHERE rowid = old.id_album;"
            " END";
446
    sqlite::Tools::executeRequest( dbConnection, indexReq );
447 448 449 450 451
    sqlite::Tools::executeRequest( dbConnection, triggerReq );
    sqlite::Tools::executeRequest( dbConnection, deleteTriggerReq );
    sqlite::Tools::executeRequest( dbConnection, updateAddTrackTriggerReq );
    sqlite::Tools::executeRequest( dbConnection, vtriggerInsert );
    sqlite::Tools::executeRequest( dbConnection, vtriggerDelete );
452 453
}

454
std::shared_ptr<Album> Album::create( MediaLibraryPtr ml, const std::string& title, int64_t thumbnailId )
455
{
456
    auto album = std::make_shared<Album>( ml, title, thumbnailId );
457
    static const std::string req = "INSERT INTO " + policy::AlbumTable::Name +
458 459
            "(id_album, title, thumbnail_id) VALUES(NULL, ?, ?)";
    if ( insert( ml, album, req, title, sqlite::ForeignKey( thumbnailId ) ) == false )
460 461
        return nullptr;
    return album;
462
}
463

464
std::shared_ptr<Album> Album::createUnknownAlbum( MediaLibraryPtr ml, const Artist* artist )
465
{
466
    auto album = std::make_shared<Album>( ml, artist );
467 468
    static const std::string req = "INSERT INTO " + policy::AlbumTable::Name +
            "(id_album, artist_id) VALUES(NULL, ?)";
469
    if ( insert( ml, album, req, artist->id() ) == false )
470 471 472
        return nullptr;
    return album;
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
473

474
std::vector<AlbumPtr> Album::search( MediaLibraryPtr ml, const std::string& pattern )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
475 476 477
{
    static const std::string req = "SELECT * FROM " + policy::AlbumTable::Name + " WHERE id_album IN "
            "(SELECT rowid FROM " + policy::AlbumTable::Name + "Fts WHERE " +
478
            policy::AlbumTable::Name + "Fts MATCH '*' || ? || '*')"
479
            "AND is_present != 0";
480
    return fetchAll<IAlbum>( ml, req, pattern );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
481
}
482

483
std::vector<AlbumPtr> Album::fromArtist( MediaLibraryPtr ml, int64_t artistId, SortingCriteria sort, bool desc )
484 485
{
    std::string req = "SELECT * FROM " + policy::AlbumTable::Name + " alb "
486 487 488 489 490
                    "INNER JOIN " + policy::AlbumTrackTable::Name + " att "
                        "ON att.album_id = alb.id_album "
                    "WHERE (att.artist_id = ? OR alb.artist_id = ?) "
                        "AND att.is_present != 0 "
                    "GROUP BY att.album_id ORDER BY ";
491 492
    switch ( sort )
    {
493
    case SortingCriteria::Alpha:
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
        req += "title";
        if ( desc == true )
            req += " DESC";
        break;
    default:
        // When listing albums of an artist, default order is by descending year (with album title
        // discrimination in case 2+ albums went out the same year)
        // This leads to DESC being used for "non-desc" case
        if ( desc == true )
            req += "release_year, title";
        else
            req += "release_year DESC, title";
        break;
    }

509
    return fetchAll<IAlbum>( ml, req, artistId, artistId );
510 511
}

512
std::vector<AlbumPtr> Album::fromGenre( MediaLibraryPtr ml, int64_t genreId, SortingCriteria sort, bool desc)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
513 514 515 516 517 518 519 520
{
    std::string req = "SELECT a.* FROM " + policy::AlbumTable::Name + " a "
            "INNER JOIN " + policy::AlbumTrackTable::Name + " att ON att.album_id = a.id_album "
            "WHERE att.genre_id = ? GROUP BY att.album_id";
    req += orderBy( sort, desc );
    return fetchAll<IAlbum>( ml, req, genreId );
}

521
std::vector<AlbumPtr> Album::listAll( MediaLibraryPtr ml, SortingCriteria sort, bool desc )
522
{
523 524 525 526
    if ( sort == SortingCriteria::Artist )
    {
        std::string req = "SELECT alb.* FROM " + policy::AlbumTable::Name + " alb "
                "INNER JOIN " + policy::ArtistTable::Name + " art ON alb.artist_id = art.id_artist "
527
                "WHERE alb.is_present != 0 "
528 529 530 531 532 533
                "ORDER BY art.name ";
        if ( desc == true )
            req += "DESC ";
        req += ", alb.title";
        return fetchAll<IAlbum>( ml, req );
    }
534 535 536 537 538 539 540 541 542 543 544 545 546
    if ( sort == SortingCriteria::PlayCount )
    {
        std::string req = "SELECT alb.* FROM " + policy::AlbumTable::Name + " alb "
                 "INNER JOIN " + policy::AlbumTrackTable::Name + " t ON alb.id_album = t.album_id "
                 "INNER JOIN " + policy::MediaTable::Name + " m ON t.media_id = m.id_media "
                 "WHERE alb.is_present != 0 "
                 "GROUP BY id_album "
                 "ORDER BY SUM(m.play_count) ";
        if ( desc == false )
            req += "DESC "; // Most played first by default
        req += ", alb.title";
        return fetchAll<IAlbum>( ml, req );
    }
547
    std::string req = "SELECT * FROM " + policy::AlbumTable::Name +
548
                    " WHERE is_present != 0";
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
549
    req += orderBy( sort, desc );
550 551
    return fetchAll<IAlbum>( ml, req );
}
552 553

}