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

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

47
const std::string policy::MediaTable::Name = "Media";
48
const std::string policy::MediaTable::PrimaryKeyColumn = "id_media";
49
unsigned int Media::* const policy::MediaTable::PrimaryKey = &Media::m_id;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
50

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

67
Media::Media( const std::string& title, Type type )
68
    : m_id( 0 )
69
    , m_type( type )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
70
    , m_subType( SubType::Unknown )
71
    , m_duration( -1 )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
72
    , m_playCount( 0 )
73 74
    , m_progress( .0f )
    , m_rating( -1 )
75
    , m_insertionDate( time( nullptr ) )
76
    , m_title( title )
77
    , m_changed( false )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
78
{
79 80
}

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

87
    if ( insert( dbConnection, self, req, type, self->m_insertionDate, self->m_title ) == false )
88
        return nullptr;
89 90
    self->m_dbConnection = dbConnection;
    return self;
91 92
}

93
AlbumTrackPtr Media::albumTrack() const
94
{
95 96
    auto lock = m_albumTrack.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
97
    if ( m_albumTrack.isCached() == false && m_subType == SubType::AlbumTrack )
98 99
        m_albumTrack = AlbumTrack::fromMedia( m_dbConnection, m_id );
    return m_albumTrack.get();
100 101
}

102
void Media::setAlbumTrack( AlbumTrackPtr albumTrack )
103
{
104
    auto lock = m_albumTrack.lock();
105
    m_albumTrack = albumTrack;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
106 107
    m_subType = SubType::AlbumTrack;
    m_changed = true;
108 109
}

110
int64_t Media::duration() const
111 112 113 114
{
    return m_duration;
}

115
void Media::setDuration( int64_t duration )
116
{
117 118
    if ( m_duration == duration )
        return;
119
    m_duration = duration;
120
    m_changed = true;
121 122
}

123
ShowEpisodePtr Media::showEpisode() const
124
{
125 126
    auto lock = m_showEpisode.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
127
    if ( m_showEpisode.isCached() == false && m_subType == SubType::ShowEpisode )
128 129
        m_showEpisode = ShowEpisode::fromMedia( m_dbConnection, m_id );
    return m_showEpisode.get();
130 131
}

132
void Media::setShowEpisode( ShowEpisodePtr episode )
133
{
134 135
    auto lock = m_showEpisode.lock();
    m_showEpisode = episode;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
136 137
    m_subType = SubType::ShowEpisode;
    m_changed = true;
138 139
}

140
std::vector<LabelPtr> Media::labels()
141
{
142
    static const std::string req = "SELECT l.* FROM " + policy::LabelTable::Name + " l "
143 144
            "LEFT JOIN LabelFileRelation lfr ON lfr.label_id = l.id_label "
            "WHERE lfr.media_id = ?";
145
    return Label::fetchAll<ILabel>( m_dbConnection, req, m_id );
146 147
}

148
int Media::playCount() const
149
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
150
    return m_playCount;
151 152
}

153 154 155 156 157 158
void Media::increasePlayCount()
{
    m_playCount++;
    m_changed = true;
}

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
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;
}

185
const std::vector<FilePtr>& Media::files() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
186
{
187 188 189 190 191 192 193 194
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
    {
        static const std::string req = "SELECT * FROM " + policy::FileTable::Name
                + " WHERE media_id = ?";
        m_files = File::fetchAll<IFile>( m_dbConnection, req, m_id );
    }
    return m_files;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
195 196
}

197
MoviePtr Media::movie() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
198
{
199 200
    auto lock = m_movie.lock();

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
201
    if ( m_movie.isCached() == false && m_subType == SubType::Movie )
202 203
        m_movie = Movie::fromMedia( m_dbConnection, m_id );
    return m_movie.get();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
204 205
}

206
void Media::setMovie(MoviePtr movie)
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
207
{
208
    auto lock = m_movie.lock();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
209
    m_movie = movie;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
210 211
    m_subType = SubType::Movie;
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
212 213
}

214
bool Media::addVideoTrack(const std::string& codec, unsigned int width, unsigned int height, float fps)
215
{
216
    return VideoTrack::create( m_dbConnection, codec, width, height, fps, m_id ) != nullptr;
217 218
}

219
std::vector<VideoTrackPtr> Media::videoTracks()
220
{
221 222
    static const std::string req = "SELECT * FROM " + policy::VideoTrackTable::Name +
            " WHERE media_id = ?";
223
    return VideoTrack::fetchAll<IVideoTrack>( m_dbConnection, req, m_id );
224 225
}

226
bool Media::addAudioTrack( const std::string& codec, unsigned int bitrate,
227 228
                          unsigned int sampleRate, unsigned int nbChannels,
                          const std::string& language, const std::string& desc )
229
{
230
    return AudioTrack::create( m_dbConnection, codec, bitrate, sampleRate, nbChannels, language, desc, m_id ) != nullptr;
231 232
}

233
std::vector<AudioTrackPtr> Media::audioTracks()
234
{
235 236
    static const std::string req = "SELECT * FROM " + policy::AudioTrackTable::Name +
            " WHERE media_id = ?";
237
    return AudioTrack::fetchAll<IAudioTrack>( m_dbConnection, req, m_id );
238 239
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
240
const std::string &Media::thumbnail()
241
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
242
    return m_thumbnail;
243 244
}

245 246 247 248 249
unsigned int Media::insertionDate() const
{
    return m_insertionDate;
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
250
void Media::setThumbnail(const std::string& thumbnail )
251
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
252
    if ( m_thumbnail == thumbnail )
253
        return;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
254
    m_thumbnail = thumbnail;
255 256 257 258 259 260
    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
261
            "type = ?, subtype = ?, duration = ?, play_count = ?, progress = ?, rating = ?,"
262
            "thumbnail = ?, title = ? WHERE id_media = ?";
263 264
    if ( m_changed == false )
        return true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
265
    if ( sqlite::Tools::executeUpdate( m_dbConnection, req, m_type, m_subType, m_duration, m_playCount,
266
                                       m_progress, m_rating, m_thumbnail, m_title, m_id ) == false )
267 268 269 270
    {
        return false;
    }
    m_changed = false;
271 272 273
    return true;
}

274
std::shared_ptr<File> Media::addFile( const fs::IFile& fileFs, Folder& parentFolder, fs::IDirectory& parentFolderFs )
275
{
276 277 278 279 280 281 282
    auto file = File::create( m_dbConnection, m_id, fileFs, parentFolder.id(), parentFolderFs.device()->isRemovable() );
    if ( file == nullptr )
        return nullptr;
    auto lock = m_files.lock();
    if ( m_files.isCached() )
        m_files.get().push_back( file );
    return file;
283 284
}

285
void Media::removeFile( File& file )
286
{
287 288 289
    file.destroy();
    auto lock = m_files.lock();
    if ( m_files.isCached() == false )
290
        return;
291 292 293
    m_files.get().erase( std::remove_if( begin( m_files.get() ), end( m_files.get() ), [&file]( const FilePtr& f ) {
        return f->id() == file.id();
    }));
294 295
}

296
unsigned int Media::id() const
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
297 298 299 300
{
    return m_id;
}

301
IMedia::Type Media::type()
302 303 304 305
{
    return m_type;
}

306
void Media::setType( Type type )
307
{
308 309
    if ( m_type != type )
        return;
310
    m_type = type;
311
    m_changed = true;
312 313
}

314
const std::string &Media::title()
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
315
{
316
    return m_title;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
317 318
}

319
void Media::setTitle( const std::string &title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
320
{
321 322
    if ( m_title == title )
        return;
323
    m_title = title;
324
    m_changed = true;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
325 326
}

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,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
332
            "subtype INTEGER,"
333
            "duration INTEGER DEFAULT -1,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
334
            "play_count UNSIGNED INTEGER,"
335 336
            "progress REAL,"
            "rating INTEGER DEFAULT -1,"
337
            "insertion_date UNSIGNED INTEGER,"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
338
            "thumbnail TEXT,"
339
            "title TEXT,"
340
            "is_present BOOLEAN NOT NULL DEFAULT 1"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
341
            ")";
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    return sqlite::Tools::executeRequest( connection, req );
}

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;";
    return sqlite::Tools::executeRequest( connection, triggerReq ) &&
            sqlite::Tools::executeRequest( connection, triggerReq2 );
363
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
364

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

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