Media.cpp 13.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
#include <cassert>
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
24 25
#include <cstdlib>
#include <cstring>
26
#include <ctime>
27

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
28
#include "Album.h"
29
#include "AlbumTrack.h"
30
#include "Artist.h"
31
#include "AudioTrack.h"
32
#include "Media.h"
33
#include "Folder.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
34
#include "Label.h"
35
#include "logging/Logger.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
36
#include "Movie.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
37
#include "ShowEpisode.h"
38
#include "database/SqliteTools.h"
39
#include "VideoTrack.h"
40
#include "filesystem/IFile.h"
41

42 43 44
const std::string policy::MediaTable::Name = "Media";
const std::string policy::MediaTable::CacheColumn = "mrl";
unsigned int Media::* const policy::MediaTable::PrimaryKey = &Media::m_id;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
45

46
Media::Media( DBConnection dbConnection, sqlite3_stmt* stmt )
47 48 49 50
    : m_dbConnection( dbConnection )
{
    m_id = sqlite3_column_int( stmt, 0 );
    m_type = (Type)sqlite3_column_int( stmt, 1 );
51
    m_duration = sqlite::Traits<int64_t>::Load( stmt, 2 );
52
    m_albumTrackId = sqlite3_column_int( stmt, 3 );
53
    m_playCount = sqlite3_column_int( stmt, 4 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
54
    m_showEpisodeId = sqlite3_column_int( stmt, 5 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
55
    m_mrl = (const char*)sqlite3_column_text( stmt, 6 );
56 57
    m_movieId = sqlite::Traits<unsigned int>::Load( stmt, 7 );
    m_folderId = sqlite::Traits<unsigned int>::Load( stmt, 8 );
58
    m_lastModificationDate = sqlite::Traits<unsigned int>::Load( stmt, 9 );
59
    m_snapshot = sqlite::Traits<std::string>::Load( stmt, 10 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
60
    m_isParsed = sqlite::Traits<bool>::Load( stmt, 11 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
61
    m_name = sqlite::Traits<std::string>::Load( stmt, 12 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
62 63
}

64
Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& name, Type type )
65
    : m_id( 0 )
66
    , m_type( type )
67
    , m_duration( -1 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
68 69 70
    , m_albumTrackId( 0 )
    , m_playCount( 0 )
    , m_showEpisodeId( 0 )
71
    , m_mrl( file->fullPath() )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
    , m_movieId( 0 )
73
    , m_folderId( folderId )
74
    , m_lastModificationDate( file->lastModificationDate() )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
75
    , m_isParsed( false )
76
    , m_name( name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
77
{
78 79
}

80
std::shared_ptr<Media> Media::create( DBConnection dbConnection, Type type, const fs::IFile* file, unsigned int folderId )
81
{
82 83
    auto self = std::make_shared<Media>( file, folderId, file->name(), type );
    static const std::string req = "INSERT INTO " + policy::MediaTable::Name +
84
            "(type, mrl, folder_id, last_modification_date, name) VALUES(?, ?, ?, ?, ?)";
85

86 87
    using type_t = std::underlying_type<Type>::type;
    if ( _Cache::insert( dbConnection, self, req, static_cast<type_t>( type ), self->m_mrl, sqlite::ForeignKey( folderId ),
88
                         self->m_lastModificationDate, self->m_name ) == false )
89
        return nullptr;
90 91
    self->m_dbConnection = dbConnection;
    return self;
92 93
}

94
AlbumTrackPtr Media::albumTrack()
95
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
96
    if ( m_albumTrack == nullptr && m_albumTrackId != 0 )
97
    {
98
        m_albumTrack = AlbumTrack::fetch( m_dbConnection, m_albumTrackId );
99 100
    }
    return m_albumTrack;
101 102
}

103
bool Media::setAlbumTrack( AlbumTrackPtr albumTrack )
104
{
105 106
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET album_track_id = ? "
            "WHERE id_media = ?";
107
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, albumTrack->id(), m_id ) == false )
108 109 110 111 112 113
        return false;
    m_albumTrackId = albumTrack->id();
    m_albumTrack = albumTrack;
    return true;
}

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
bool Media::addArtist( ArtistPtr artist )
{
    static const std::string req = "INSERT INTO MediaArtistRelation VALUES(?, ?)";
    // If track's ID is 0, the request will fail due to table constraints
    sqlite::ForeignKey artistForeignKey( artist != nullptr ? artist->id() : 0 );
    return sqlite::Tools::executeRequest( m_dbConnection, req, m_id, artistForeignKey );
}

std::vector<ArtistPtr> Media::artists() const
{
    static const std::string req = "SELECT art.* FROM " + policy::ArtistTable::Name + " art "
            "LEFT JOIN MediaArtistRelation mar ON mar.id_artist = art.id_artist "
            "WHERE mar.id_media = ?";
    return Artist::fetchAll( m_dbConnection, req, m_id );
}

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

135
bool Media::setDuration( int64_t duration )
136
{
137 138
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET duration = ? "
            "WHERE id_media = ?";
139 140 141 142 143 144
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, duration, m_id ) == false )
        return false;
    m_duration = duration;
    return true;
}

145
std::shared_ptr<IShowEpisode> Media::showEpisode()
146
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
147
    if ( m_showEpisode == nullptr && m_showEpisodeId != 0 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
148
    {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
149
        m_showEpisode = ShowEpisode::fetch( m_dbConnection, m_showEpisodeId );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
150 151
    }
    return m_showEpisode;
152 153
}

154
bool Media::setShowEpisode(ShowEpisodePtr showEpisode)
155
{
156 157
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET show_episode_id = ?  WHERE id_media = ?";
158
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, showEpisode->id(), m_id ) == false )
159 160 161 162 163 164
        return false;
    m_showEpisodeId = showEpisode->id();
    m_showEpisode = showEpisode;
    return true;
}

165
std::vector<std::shared_ptr<ILabel> > Media::labels()
166
{
167
    static const std::string req = "SELECT l.* FROM " + policy::LabelTable::Name + " l "
168
            "LEFT JOIN LabelFileRelation lfr ON lfr.id_label = l.id_label "
169
            "WHERE lfr.id_media = ?";
170
    return Label::fetchAll( m_dbConnection, req, m_id );
171 172
}

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

178
const std::string& Media::mrl() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
179 180 181 182
{
    return m_mrl;
}

183
MoviePtr Media::movie()
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
184 185 186 187 188 189 190 191
{
    if ( m_movie == nullptr && m_movieId != 0 )
    {
        m_movie = Movie::fetch( m_dbConnection, m_movieId );
    }
    return m_movie;
}

192
bool Media::setMovie( MoviePtr movie )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
193
{
194 195
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET movie_id = ? WHERE id_media = ?";
196
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, movie->id(), m_id ) == false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
197 198 199 200 201 202
        return false;
    m_movie = movie;
    m_movieId = movie->id();
    return true;
}

203
bool Media::addVideoTrack(const std::string& codec, unsigned int width, unsigned int height, float fps)
204 205 206 207 208 209 210 211 212 213
{
    static const std::string req = "INSERT INTO VideoTrackFileRelation VALUES(?, ?)";

    auto track = VideoTrack::fetch( m_dbConnection, codec, width, height, fps );
    if ( track == nullptr )
    {
        track = VideoTrack::create( m_dbConnection, codec, width, height, fps );
        if ( track == nullptr )
            return false;
    }
214
    return sqlite::Tools::executeRequest( m_dbConnection, req, track->id(), m_id );
215 216
}

217
std::vector<VideoTrackPtr> Media::videoTracks()
218 219 220
{
    static const std::string req = "SELECT t.* FROM " + policy::VideoTrackTable::Name +
            " t LEFT JOIN VideoTrackFileRelation vtfr ON vtfr.id_track = t.id_track"
221
            " WHERE vtfr.id_media = ?";
222
    return VideoTrack::fetchAll( m_dbConnection, req, m_id );
223 224
}

225
bool Media::addAudioTrack( const std::string& codec, unsigned int bitrate,
226
                          unsigned int sampleRate, unsigned int nbChannels )
227 228 229
{
    static const std::string req = "INSERT INTO AudioTrackFileRelation VALUES(?, ?)";

230
    auto track = AudioTrack::fetch( m_dbConnection, codec, bitrate, sampleRate, nbChannels );
231 232
    if ( track == nullptr )
    {
233
        track = AudioTrack::create( m_dbConnection, codec, bitrate, sampleRate, nbChannels );
234 235 236
        if ( track == nullptr )
            return false;
    }
237
    return sqlite::Tools::executeRequest( m_dbConnection, req, track->id(), m_id );
238 239
}

240
std::vector<AudioTrackPtr> Media::audioTracks()
241 242 243
{
    static const std::string req = "SELECT t.* FROM " + policy::AudioTrackTable::Name +
            " t LEFT JOIN AudioTrackFileRelation atfr ON atfr.id_track = t.id_track"
244
            " WHERE atfr.id_media = ?";
245
    return AudioTrack::fetchAll( m_dbConnection, req, m_id );
246 247
}

248
const std::string &Media::snapshot()
249 250 251 252
{
    return m_snapshot;
}

253
bool Media::setSnapshot(const std::string &snapshot)
254
{
255 256
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET snapshot = ? WHERE id_media = ?";
257 258 259 260 261 262
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, snapshot, m_id ) == false )
        return false;
    m_snapshot = snapshot;
    return true;
}

263
bool Media::isStandAlone()
264 265 266 267
{
    return m_folderId == 0;
}

268
unsigned int Media::lastModificationDate()
269 270 271 272
{
    return m_lastModificationDate;
}

273
bool Media::isParsed() const
274
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
275
    return m_isParsed;
276 277
}

278
bool Media::markParsed()
279
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
280 281
    if ( m_isParsed == true )
        return true;
282 283
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET parsed = ? WHERE id_media = ?";
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
284 285 286 287
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, true, m_id ) == false )
        return false;
    m_isParsed = true;
    return true;
288 289
}

290
unsigned int Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
291 292 293 294
{
    return m_id;
}

295
IMedia::Type Media::type()
296 297 298 299
{
    return m_type;
}

300
bool Media::setType( Type type )
301
{
302 303
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET type = ? WHERE id_media = ?";
304 305 306 307 308 309 310 311
    // We need to convert to an integer representation for the sqlite traits to work properly
    using type_t = std::underlying_type<Type>::type;
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, static_cast<type_t>( type ), m_id ) == false )
        return false;
    m_type = type;
    return true;
}

312
const std::string &Media::name()
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
313 314 315 316
{
    return m_name;
}

317
bool Media::setName( const std::string &name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
318
{
319 320
    static const std::string req = "UPDATE " + policy::MediaTable::Name
            + " SET name = ? WHERE id_media = ?";
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
321 322 323 324 325 326
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, name, m_id ) == false )
        return false;
    m_name = name;
    return true;
}

327
bool Media::createTable( DBConnection connection )
328
{
329 330
    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
331
            "type INTEGER,"
332
            "duration INTEGER,"
333
            "album_track_id UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
334 335
            "play_count UNSIGNED INTEGER,"
            "show_episode_id UNSIGNED INTEGER,"
336
            "mrl TEXT UNIQUE ON CONFLICT FAIL,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
337
            "movie_id UNSIGNED INTEGER,"
338
            "folder_id UNSIGNED INTEGER,"
339
            "last_modification_date UNSIGNED INTEGER,"
340
            "snapshot TEXT,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
341
            "parsed BOOLEAN,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
342
            "name TEXT,"
343
            "FOREIGN KEY (album_track_id) REFERENCES " + policy::AlbumTrackTable::Name
344 345
            + "(id_track) ON DELETE CASCADE,"
            "FOREIGN KEY (show_episode_id) REFERENCES " + policy::ShowEpisodeTable::Name
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
346 347
            + "(id_episode) ON DELETE CASCADE,"
            "FOREIGN KEY (movie_id) REFERENCES " + policy::MovieTable::Name
348 349 350
            + "(id_movie) ON DELETE CASCADE,"
            "FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
            + "(id_folder) ON DELETE CASCADE"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
351
            ")";
352
    if ( sqlite::Tools::executeRequest( connection, req ) == false )
353 354 355
        return false;
    req = "CREATE TABLE IF NOT EXISTS VideoTrackFileRelation("
                "id_track INTEGER,"
356 357
                "id_media INTEGER,"
                "PRIMARY KEY ( id_track, id_media ), "
358 359
                "FOREIGN KEY ( id_track ) REFERENCES " + policy::VideoTrackTable::Name +
                    "(id_track) ON DELETE CASCADE,"
360 361
                "FOREIGN KEY ( id_media ) REFERENCES " + policy::MediaTable::Name
                + "(id_media) ON DELETE CASCADE"
362
            ")";
363
    if ( sqlite::Tools::executeRequest( connection, req ) == false )
364 365 366
        return false;
    req = "CREATE TABLE IF NOT EXISTS AudioTrackFileRelation("
                "id_track INTEGER,"
367 368
                "id_media INTEGER,"
                "PRIMARY KEY ( id_track, id_media ), "
369 370
                "FOREIGN KEY ( id_track ) REFERENCES " + policy::AudioTrackTable::Name +
                    "(id_track) ON DELETE CASCADE,"
371 372
                "FOREIGN KEY ( id_media ) REFERENCES " + policy::MediaTable::Name
                + "(id_media) ON DELETE CASCADE"
373
            ")";
374
    return sqlite::Tools::executeRequest( connection, req );
375
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
376

377
bool Media::addLabel( LabelPtr label )
378
{
379
    if ( m_id == 0 || label->id() == 0 )
380
    {
381
        LOG_ERROR( "Both file & label need to be inserted in database before being linked together" );
382 383 384
        return false;
    }
    const char* req = "INSERT INTO LabelFileRelation VALUES(?, ?)";
385
    return sqlite::Tools::executeRequest( m_dbConnection, req, label->id(), m_id );
386 387
}

388
bool Media::removeLabel( LabelPtr label )
389
{
390
    if ( m_id == 0 || label->id() == 0 )
391
    {
392
        LOG_ERROR( "Can't unlink a label/file not inserted in database" );
393 394
        return false;
    }
395
    const char* req = "DELETE FROM LabelFileRelation WHERE id_label = ? AND id_media = ?";
396
    return sqlite::Tools::executeDelete( m_dbConnection, req, label->id(), m_id );
397
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
398

399
const std::string& policy::MediaCache::key(const std::shared_ptr<Media> self )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
400 401 402 403
{
    return self->mrl();
}

404
std::string policy::MediaCache::key(sqlite3_stmt* stmt)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
405
{
406
    return sqlite::Traits<std::string>::Load( stmt, 6 );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
407
}