Media.cpp 11 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
#include "utils/Filename.h"
42

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

47
Media::Media( DBConnection dbConnection, sqlite::Row& row )
48
    : m_dbConnection( dbConnection )
49
    , m_changed( false )
50
{
51 52
    row >> m_id
        >> m_type
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
53
        >> m_subType
54 55
        >> m_duration
        >> m_playCount
56 57
        >> m_progress
        >> m_rating
58 59 60
        >> m_mrl
        >> m_folderId
        >> m_lastModificationDate
61
        >> m_insertionDate
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
62
        >> m_thumbnail
63
        >> m_isParsed
64
        >> m_title
65 66
        >> m_isPresent
        >> m_isRemovable;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
67 68
}

69
Media::Media( const fs::IFile* file, unsigned int folderId, const std::string& title, Type type, bool isRemovable )
70
    : m_id( 0 )
71
    , m_type( type )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
    , m_subType( SubType::Unknown )
73
    , m_duration( -1 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
74
    , m_playCount( 0 )
75 76
    , m_progress( .0f )
    , m_rating( -1 )
77
    , m_mrl( isRemovable == true ? file->name() : file->fullPath() )
78
    , m_folderId( folderId )
79
    , m_lastModificationDate( file->lastModificationDate() )
80
    , m_insertionDate( time( nullptr ) )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
81
    , m_isParsed( false )
82
    , m_title( title )
83
    , m_isPresent( true )
84
    , m_isRemovable( isRemovable )
85
    , m_changed( false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
86
{
87 88
}

89
std::shared_ptr<Media> Media::create( DBConnection dbConnection, Type type, const fs::IFile* file, unsigned int folderId, bool isRemovable )
90
{
91
    auto self = std::make_shared<Media>( file, folderId, file->name(), type, isRemovable );
92
    static const std::string req = "INSERT INTO " + policy::MediaTable::Name +
93
            "(type, mrl, folder_id, last_modification_date, insertion_date, title, is_removable) VALUES(?, ?, ?, ?, ?, ?, ?)";
94

95
    if ( insert( dbConnection, self, req, type, self->m_mrl, sqlite::ForeignKey( folderId ),
96
                         self->m_lastModificationDate, self->m_insertionDate, self->m_title, isRemovable ) == false )
97
        return nullptr;
98
    self->m_dbConnection = dbConnection;
99
    self->m_fullPath = file->fullPath();
100
    return self;
101 102
}

103
AlbumTrackPtr Media::albumTrack() const
104
{
105 106
    auto lock = m_albumTrack.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
107
    if ( m_albumTrack.isCached() == false && m_subType == SubType::AlbumTrack )
108 109
        m_albumTrack = AlbumTrack::fromMedia( m_dbConnection, m_id );
    return m_albumTrack.get();
110 111
}

112
void Media::setAlbumTrack( AlbumTrackPtr albumTrack )
113
{
114
    auto lock = m_albumTrack.lock();
115
    m_albumTrack = albumTrack;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
116 117
    m_subType = SubType::AlbumTrack;
    m_changed = true;
118 119
}

120
int64_t Media::duration() const
121 122 123 124
{
    return m_duration;
}

125
void Media::setDuration( int64_t duration )
126
{
127 128
    if ( m_duration == duration )
        return;
129
    m_duration = duration;
130
    m_changed = true;
131 132
}

133
ShowEpisodePtr Media::showEpisode() const
134
{
135 136
    auto lock = m_showEpisode.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
137
    if ( m_showEpisode.isCached() == false && m_subType == SubType::ShowEpisode )
138 139
        m_showEpisode = ShowEpisode::fromMedia( m_dbConnection, m_id );
    return m_showEpisode.get();
140 141
}

142
void Media::setShowEpisode( ShowEpisodePtr episode )
143
{
144 145
    auto lock = m_showEpisode.lock();
    m_showEpisode = episode;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
146 147
    m_subType = SubType::ShowEpisode;
    m_changed = true;
148 149
}

150
std::vector<LabelPtr> Media::labels()
151
{
152
    static const std::string req = "SELECT l.* FROM " + policy::LabelTable::Name + " l "
153 154
            "LEFT JOIN LabelFileRelation lfr ON lfr.label_id = l.id_label "
            "WHERE lfr.media_id = ?";
155
    return Label::fetchAll<ILabel>( m_dbConnection, req, m_id );
156 157
}

158
int Media::playCount() const
159
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
160
    return m_playCount;
161 162
}

163 164 165 166 167 168
void Media::increasePlayCount()
{
    m_playCount++;
    m_changed = true;
}

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
float Media::progress() const
{
    return m_progress;
}

void Media::setProgress( float progress )
{
    if ( progress == m_progress || progress < 0 || progress > 1.0 )
        return;
    m_progress = progress;
    m_changed = true;
}

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

void Media::setRating( int rating )
{
    if ( m_rating == rating )
        return;
    m_rating = rating;
    m_changed = true;
}

195
const std::string& Media::mrl() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
196
{
197 198 199
    if ( m_isRemovable == false )
        return m_mrl;

200 201 202 203 204 205 206 207
    auto lock = m_fullPath.lock();
    if ( m_fullPath.isCached() )
        return m_fullPath;
    auto folder = Folder::fetch( m_dbConnection, m_folderId );
    if ( folder == nullptr )
        return m_mrl;
    m_fullPath = folder->path() + m_mrl;
    return m_fullPath;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
208 209
}

210
MoviePtr Media::movie() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
211
{
212 213
    auto lock = m_movie.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
214
    if ( m_movie.isCached() == false && m_subType == SubType::Movie )
215 216
        m_movie = Movie::fromMedia( m_dbConnection, m_id );
    return m_movie.get();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
217 218
}

219
void Media::setMovie(MoviePtr movie)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
220
{
221
    auto lock = m_movie.lock();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
222
    m_movie = movie;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
223 224
    m_subType = SubType::Movie;
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
225 226
}

227
bool Media::addVideoTrack(const std::string& codec, unsigned int width, unsigned int height, float fps)
228
{
229
    return VideoTrack::create( m_dbConnection, codec, width, height, fps, m_id ) != nullptr;
230 231
}

232
std::vector<VideoTrackPtr> Media::videoTracks()
233
{
234 235
    static const std::string req = "SELECT * FROM " + policy::VideoTrackTable::Name +
            " WHERE media_id = ?";
236
    return VideoTrack::fetchAll<IVideoTrack>( m_dbConnection, req, m_id );
237 238
}

239
bool Media::addAudioTrack( const std::string& codec, unsigned int bitrate,
240 241
                          unsigned int sampleRate, unsigned int nbChannels,
                          const std::string& language, const std::string& desc )
242
{
243
    return AudioTrack::create( m_dbConnection, codec, bitrate, sampleRate, nbChannels, language, desc, m_id ) != nullptr;
244 245
}

246
std::vector<AudioTrackPtr> Media::audioTracks()
247
{
248 249
    static const std::string req = "SELECT * FROM " + policy::AudioTrackTable::Name +
            " WHERE media_id = ?";
250
    return AudioTrack::fetchAll<IAudioTrack>( m_dbConnection, req, m_id );
251 252
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
253
const std::string &Media::thumbnail()
254
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
255
    return m_thumbnail;
256 257
}

258 259 260 261 262
unsigned int Media::insertionDate() const
{
    return m_insertionDate;
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
263
void Media::setThumbnail(const std::string& thumbnail )
264
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
265
    if ( m_thumbnail == thumbnail )
266
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
267
    m_thumbnail = thumbnail;
268 269 270 271 272 273
    m_changed = true;
}

bool Media::save()
{
    static const std::string req = "UPDATE " + policy::MediaTable::Name + " SET "
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
274
            "type = ?, subtype = ?, duration = ?, play_count = ?, progress = ?, rating = ?,"
275
            "last_modification_date = ?, thumbnail = ?, parsed = ?,"
276
            "title = ? WHERE id_media = ?";
277 278
    if ( m_changed == false )
        return true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
279
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, m_type, m_subType, m_duration, m_playCount,
280
                                       m_progress, m_rating, m_lastModificationDate,
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
281
                                       m_thumbnail, m_isParsed, m_title, m_id ) == false )
282 283 284 285
    {
        return false;
    }
    m_changed = false;
286 287 288
    return true;
}

289
unsigned int Media::lastModificationDate()
290 291 292 293
{
    return m_lastModificationDate;
}

294
bool Media::isParsed() const
295
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
296
    return m_isParsed;
297 298
}

299
void Media::markParsed()
300
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
301
    if ( m_isParsed == true )
302
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
303
    m_isParsed = true;
304
    m_changed = true;
305 306
}

307
unsigned int Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
308 309 310 311
{
    return m_id;
}

312
IMedia::Type Media::type()
313 314 315 316
{
    return m_type;
}

317
void Media::setType( Type type )
318
{
319 320
    if ( m_type != type )
        return;
321
    m_type = type;
322
    m_changed = true;
323 324
}

325
const std::string &Media::title()
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
326
{
327
    return m_title;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
328 329
}

330
void Media::setTitle( const std::string &title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
331
{
332 333
    if ( m_title == title )
        return;
334
    m_title = title;
335
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
336 337
}

338
bool Media::createTable( DBConnection connection )
339
{
340 341
    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
342
            "type INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
343
            "subtype INTEGER,"
344
            "duration INTEGER DEFAULT -1,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
345
            "play_count UNSIGNED INTEGER,"
346 347
            "progress REAL,"
            "rating INTEGER DEFAULT -1,"
348
            "mrl TEXT,"
349
            "folder_id UNSIGNED INTEGER,"
350
            "last_modification_date UNSIGNED INTEGER,"
351
            "insertion_date UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
352
            "thumbnail TEXT,"
353
            "parsed BOOLEAN NOT NULL DEFAULT 0,"
354
            "title TEXT,"
355
            "is_present BOOLEAN NOT NULL DEFAULT 1,"
356
            "is_removable BOOLEAN NOT NULL,"
357
            "FOREIGN KEY (folder_id) REFERENCES " + policy::FolderTable::Name
358 359
            + "(id_folder) ON DELETE CASCADE,"
            "UNIQUE( mrl, folder_id ) ON CONFLICT FAIL"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
360
            ")";
361 362 363 364 365 366 367
    std::string triggerReq = "CREATE TRIGGER IF NOT EXISTS is_folder_present AFTER UPDATE OF is_present ON "
            + policy::FolderTable::Name +
            " BEGIN"
            " UPDATE " + policy::MediaTable::Name + " SET is_present = new.is_present WHERE folder_id = new.id_folder;"
            " END";
    return sqlite::Tools::executeRequest( connection, req ) &&
            sqlite::Tools::executeRequest( connection, triggerReq );
368
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
369

370
bool Media::addLabel( LabelPtr label )
371
{
372
    if ( m_id == 0 || label->id() == 0 )
373
    {
374
        LOG_ERROR( "Both file & label need to be inserted in database before being linked together" );
375 376 377
        return false;
    }
    const char* req = "INSERT INTO LabelFileRelation VALUES(?, ?)";
378
    return sqlite::Tools::insert( m_dbConnection, req, label->id(), m_id ) != 0;
379 380
}

381
bool Media::removeLabel( LabelPtr label )
382
{
383
    if ( m_id == 0 || label->id() == 0 )
384
    {
385
        LOG_ERROR( "Can't unlink a label/file not inserted in database" );
386 387
        return false;
    }
388
    const char* req = "DELETE FROM LabelFileRelation WHERE label_id = ? AND media_id = ?";
389
    return sqlite::Tools::executeDelete( m_dbConnection, req, label->id(), m_id );
390
}