Playlist.cpp 10.2 KB
Newer Older
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
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

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
27 28 29 30
#include "Playlist.h"

#include "Media.h"

31 32
#include "database/SqliteQuery.h"

33 34
#include <algorithm>

35 36 37
namespace medialibrary
{

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
38 39 40 41
namespace policy
{
const std::string PlaylistTable::Name = "Playlist";
const std::string PlaylistTable::PrimaryKeyColumn = "id_playlist";
42
int64_t Playlist::* const PlaylistTable::PrimaryKey = &Playlist::m_id;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
43 44
}

45
Playlist::Playlist( MediaLibraryPtr ml, sqlite::Row& row )
46
    : m_ml( ml )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
47 48
{
    row >> m_id
49
        >> m_name
50
        >> m_fileId
51 52
        >> m_creationDate
        >> m_artworkMrl;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
53 54
}

55
Playlist::Playlist( MediaLibraryPtr ml, const std::string& name )
56 57
    : m_ml( ml )
    , m_id( 0 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
58
    , m_name( name )
59
    , m_fileId( 0 )
60
    , m_creationDate( time( nullptr ) )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
61 62 63
{
}

64
std::shared_ptr<Playlist> Playlist::create( MediaLibraryPtr ml, const std::string& name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
65
{
66
    auto self = std::make_shared<Playlist>( ml, name );
67
    static const std::string req = "INSERT INTO " + policy::PlaylistTable::Name +
68
            "(name, file_id, creation_date, artwork_mrl) VALUES(?, ?, ?, ?)";
69 70
    try
    {
71
        if ( insert( ml, self, req, name, nullptr, self->m_creationDate, self->m_artworkMrl ) == false )
72 73 74 75 76 77 78 79
            return nullptr;
        return self;
    }
    catch( sqlite::errors::ConstraintViolation& ex )
    {
        LOG_WARN( "Failed to create playlist: ", ex.what() );
    }
    return nullptr;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
80 81
}

82
int64_t Playlist::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96
{
    return m_id;
}

const std::string& Playlist::name() const
{
    return m_name;
}

bool Playlist::setName( const std::string& name )
{
    if ( name == m_name )
        return true;
    static const std::string req = "UPDATE " + policy::PlaylistTable::Name + " SET name = ? WHERE id_playlist = ?";
97
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, name, m_id ) == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
98 99 100 101 102
        return false;
    m_name = name;
    return true;
}

103 104 105 106 107
unsigned int Playlist::creationDate() const
{
    return m_creationDate;
}

108 109 110 111 112
const std::string& Playlist::artworkMrl() const
{
    return m_artworkMrl;
}

113
Query<IMedia> Playlist::media() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
114
{
115
    static const std::string req = "FROM " + policy::MediaTable::Name + " m "
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
116
            "LEFT JOIN PlaylistMediaRelation pmr ON pmr.media_id = m.id_media "
117
            "WHERE pmr.playlist_id = ? AND m.is_present != 0 "
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
118
            "ORDER BY pmr.position";
119
    curateNullMediaID();
120
    return make_query<Media, IMedia>( m_ml, "m.*", req, m_id );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
121 122
}

123 124 125
Query<IMedia> Playlist::searchMedia( const std::string& pattern,
                                     const QueryParameters* params ) const
{
126
    curateNullMediaID();
127 128 129
    return Media::searchInPlaylist( m_ml, pattern, m_id, params );
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
void Playlist::curateNullMediaID() const
{
    auto dbConn = m_ml->getConn();
    auto t = dbConn->newTransaction();
    std::string req = "SELECT rowid, mrl FROM PlaylistMediaRelation "
            "WHERE media_id IS NULL "
            "AND playlist_id = ?";
    sqlite::Statement stmt{ dbConn->handle(), req };
    stmt.execute( m_id );
    std::string updateReq = "UPDATE PlaylistMediaRelation SET media_id = ? WHERE rowid = ?";
    auto mediaNotFound = false;

    for ( sqlite::Row row = stmt.row(); row != nullptr; row = stmt.row() )
    {
        int64_t rowId;
        std::string mrl;
        row >> rowId >> mrl;
        auto media = m_ml->media( mrl );
        if ( media != nullptr )
        {
            LOG_INFO( "Updating playlist item mediaId (playlist: ", m_id,
                      "; mrl: ", mrl, ')' );
            sqlite::Tools::executeUpdate( dbConn, updateReq, media->id(), rowId );
        }
        else
        {
            LOG_INFO( "Can't restore media association for media ", mrl,
                      " in playlist ", m_id, ". Media will be removed from the playlist" );
            mediaNotFound = true;
        }
    }
    if ( mediaNotFound )
    {
        // Batch all deletion at once instead of doing it during the loop
        std::string deleteReq = "DELETE FROM PlaylistMediaRelation "
                "WHERE playlist_id = ? AND media_id IS NULL";
        sqlite::Tools::executeDelete( dbConn, deleteReq, m_id );
    }
    t->commit();
}

171
bool Playlist::append( const IMedia& media )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
172
{
173
    return add( media, 0 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
174 175
}

176
bool Playlist::add( const IMedia& media, unsigned int position )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
177
{
178 179
    static const std::string req = "INSERT INTO PlaylistMediaRelation"
            "(media_id, mrl, playlist_id, position) VALUES(?, ?, ?, ?)";
180 181
    try
    {
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
        // position isn't a foreign key, but we want it to be passed as NULL if it equals to 0
        // When the position is NULL, the insertion triggers takes care of
        // counting the number of records to auto append.
        auto files = media.files();
        assert( files.size() > 0 );
        auto mainFile = std::find_if( begin( files ), end( files ), []( const FilePtr& f) {
            return f->type() == IFile::Type::Main;
        });
        if ( mainFile == end( files ) )
        {
            LOG_ERROR( "Can't add a media without any files to a playlist" );
            return false;
        }
        return sqlite::Tools::executeInsert( m_ml->getConn(), req, media.id(),
                                             (*mainFile)->mrl(), m_id,
                                             sqlite::ForeignKey{ position } );
198 199 200 201 202 203
    }
    catch (const sqlite::errors::ConstraintViolation& ex)
    {
        LOG_WARN( "Rejected playlist insertion: ", ex.what() );
        return false;
    }
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
204 205
}

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
bool Playlist::append( int64_t mediaId )
{
    auto media = m_ml->media( mediaId );
    if ( media == nullptr )
        return false;
    return append( *media );
}

bool Playlist::add(const int64_t mediaId, unsigned int position)
{
    auto media = m_ml->media( mediaId );
    if ( media == nullptr )
        return false;
    return add( *media, position );
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
// Attach file object to Playlist
std::shared_ptr<File> Playlist::addFile( const fs::IFile& fileFs, int64_t parentFolderId,
                                         bool isFolderFsRemovable )
{
    assert( m_fileId == 0 );
    assert( sqlite::Transaction::transactionInProgress() == true );

    auto file = File::createFromPlaylist( m_ml, m_id, fileFs, parentFolderId, isFolderFsRemovable);
    if ( file == nullptr )
        return nullptr;
    static const std::string req = "UPDATE " + policy::PlaylistTable::Name + " SET file_id = ? WHERE id_playlist = ?";
    if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, file->id(), m_id ) == false )
        return nullptr;
    m_fileId = file->id();
    return file;
}

239
bool Playlist::move( int64_t mediaId, unsigned int position )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
240 241 242 243 244
{
    if ( position == 0 )
        return false;
    static const std::string req = "UPDATE PlaylistMediaRelation SET position = ? WHERE "
            "playlist_id = ? AND media_id = ?";
245
    return sqlite::Tools::executeUpdate( m_ml->getConn(), req, position, m_id, mediaId );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
246 247
}

248
bool Playlist::remove( int64_t mediaId )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
249 250
{
    static const std::string req = "DELETE FROM PlaylistMediaRelation WHERE playlist_id = ? AND media_id = ?";
251
    return sqlite::Tools::executeDelete( m_ml->getConn(), req, m_id, mediaId );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
252 253
}

254
void Playlist::createTable( sqlite::Connection* dbConn )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
255
{
256 257 258 259 260
    std::string reqs[] = {
        #include "database/tables/Playlist_v14.sql"
    };
    for ( const auto& req : reqs )
        sqlite::Tools::executeRequest( dbConn, req );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
261 262
}

263
void Playlist::createTriggers( sqlite::Connection* dbConn )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
264
{
265 266 267 268 269
    std::string reqs[] = {
        #include "database/tables/Playlist_triggers_v14.sql"
    };
    for ( const auto& req : reqs )
        sqlite::Tools::executeRequest( dbConn, req );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
270 271
}

272
Query<IPlaylist> Playlist::search( MediaLibraryPtr ml, const std::string& name,
273
                                   const QueryParameters* params )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
274
{
275
    std::string req = "FROM " + policy::PlaylistTable::Name + " WHERE id_playlist IN "
276
            "(SELECT rowid FROM " + policy::PlaylistTable::Name + "Fts WHERE name MATCH '*' || ? || '*')";
277
    req += sortRequest( params );
278
    return make_query<Playlist, IPlaylist>( ml, "*", req, name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
279
}
280

281
Query<IPlaylist> Playlist::listAll( MediaLibraryPtr ml, const QueryParameters* params )
282
{
283
    std::string req = "FROM " + policy::PlaylistTable::Name;
284
    req += sortRequest( params );
285
    return make_query<Playlist, IPlaylist>( ml, "*", req );
286 287
}

288
void Playlist::clearExternalPlaylistContent(MediaLibraryPtr ml)
289
{
290 291 292 293 294 295 296
    // We can't delete all external playlist as such, since this would cause the
    // deletion of the associated task through the Task.playlist_id Playlist.id_playlist
    // foreign key, and therefor they wouldn't be rescanned.
    // Instead, flush the playlist content.
    const std::string req = "DELETE FROM PlaylistMediaRelation WHERE playlist_id IN ("
            "SELECT id_playlist FROM " + policy::PlaylistTable::Name + " WHERE "
            "file_id IS NOT NULL)";
297 298 299
    sqlite::Tools::executeDelete( ml->getConn(), req );
}

300
std::string Playlist::sortRequest( const QueryParameters* params )
301 302
{
    std::string req = " ORDER BY ";
303
    SortingCriteria sort = params != nullptr ? params->sort : SortingCriteria::Default;
304 305
    switch ( sort )
    {
306
    case SortingCriteria::InsertionDate:
307 308 309 310 311 312
        req += "creation_date";
        break;
    default:
        req += "name";
        break;
    }
313
    if ( params != nullptr && params->desc == true )
314
        req += " DESC";
315
    return req;
316 317
}

318
}