media_library.cpp 10.2 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 23 24 25 26
/*****************************************************************************
 * Copyright © 2015 VideoLAN, VideoLabs SAS
 *****************************************************************************
 *
 * 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 General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 *****************************************************************************/
/*
 * By committing to this project, you allow VideoLAN and VideoLabs to relicense
 * the code to a different OSI approved license, in case it is required for
 * compatibility with the Store
 *****************************************************************************/

27 28
#include "IMediaLibrary.h"

29
#include <mutex>
30 31
#include <Ecore.h>

32
#include "common.h"
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
33

34
#include "IFile.h"
35
#include "ILogger.h"
36
#include "IVideoTrack.h"
37

38
#include "media_library.hpp"
39
#include "system_storage.h"
40

41 42
static media_item* fileToMediaItem( FilePtr file );

43 44 45
class TizenLogger : public ILogger
{
    virtual void Error( const std::string& msg ) override
46 47 48
    {
        dlog_print( DLOG_ERROR, "medialibrary", msg.c_str() );
    }
49 50

    virtual void Warning( const std::string& msg ) override
51 52 53
    {
        dlog_print( DLOG_WARN, "medialibrary", msg.c_str() );
    }
54 55

    virtual void Info( const std::string& msg ) override
56 57 58
    {
        dlog_print( DLOG_INFO, "medialibrary", msg.c_str() );
    }
59 60
};

61
struct media_library : public IMediaLibraryCb
62
{
63
public:
64
    media_library();
65

66
    // IMediaLibraryCb
67
    virtual void onFileAdded( FilePtr file ) override;
68
    virtual void onFileUpdated( FilePtr file ) override;
69

70 71 72
    virtual void onDiscoveryStarted( const std::string& entryPoint ) override;
    virtual void onDiscoveryCompleted( const std::string& entryPoint ) override;

73 74 75
    void registerOnChange(media_library_file_list_changed_cb cb, void* cbUserData);
    void unregisterOnChange(media_library_file_list_changed_cb cb, void* cbUserData);

76
public:
77 78
    std::unique_ptr<IMediaLibrary> ml;
    std::unique_ptr<TizenLogger> logger;
79 80 81

private:
    void onChange();
82 83 84 85 86

private:
    // Holds the number of discoveries ongoing
    // This gets incremented by the caller thread (most likely the main loop)
    // and gets decremented by the discovery thread, hence the need for atomic
87
    int m_nbDiscovery;
88 89
    // Holds the number of changes since last call to fileListChangedCb.
    // This can be accessed from both the discovery & metadata threads
90 91
    int m_nbElemChanged;
    std::mutex m_mutex;
92
    std::vector<std::pair<media_library_file_list_changed_cb, void*>> m_onChangeCb;
93 94
};

95
media_library::media_library()
96
    : ml( MediaLibraryFactory::create() )
97 98
    , m_nbDiscovery( 0 )
    , m_nbElemChanged( 0 )
99
{
100 101
    if ( ml == nullptr )
        throw std::runtime_error( "Failed to initialize MediaLibrary" );
102 103
}

104
void
105
media_library::onFileAdded( FilePtr file )
106
{
107 108
    //FIXME: This seems fishy if no discovery is in progress and some media gets updated.
    //This is very unlikely to happen for a while though.
109
    std::unique_lock<std::mutex> lock( m_mutex );
110
    if ( ++m_nbElemChanged >= 50 )
111
    {
112
        LOGI("Enough changes to trigger an update.");
113
        onChange();
114 115
        m_nbElemChanged = 0;
    }
116 117
}

118 119 120 121 122 123
void
media_library::onFileUpdated( FilePtr file )
{
    LOGW("Ignoring [%s] update", file->mrl().c_str());
}

124 125 126 127
void
media_library::onDiscoveryStarted( const std::string& entryPoint )
{
    LOGI( "Starting [%s] discovery", entryPoint.c_str() );
128 129
    std::unique_lock<std::mutex> lock( m_mutex );
    m_nbDiscovery++;
130 131 132 133 134 135
}

void
media_library::onDiscoveryCompleted( const std::string& entryPoint )
{
    LOGI("Completed [%s] discovery", entryPoint.c_str() );
136
    std::unique_lock<std::mutex> lock( m_mutex );
137 138 139 140 141 142 143
    if ( --m_nbDiscovery == 0 )
    {
        // If this is the last discovery, and some files got updated, send a final update

        if ( m_nbElemChanged != 0 )
        {
            m_nbElemChanged = 0;
144
            LOGI("Changes detected, sending update to listeners");
145
            onChange();
146 147 148
        }
        LOGI( "Completed all active discovery operations" );
    }
149 150
}

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
void
media_library::registerOnChange(media_library_file_list_changed_cb cb, void* cbUserData)
{
    m_onChangeCb.emplace_back(cb, cbUserData);
}

void
media_library::unregisterOnChange(media_library_file_list_changed_cb cb, void* cbUserData)
{
    auto ite = end(m_onChangeCb);
    for (auto it = begin(m_onChangeCb); it != ite; ++it)
    {
        if ((*it).first == cb && (*it).second == cb)
        {
            m_onChangeCb.erase(it);
            return;
        }
    }
}
void
media_library::onChange()
{
    for (auto &p : m_onChangeCb)
    {
        ecore_main_loop_thread_safe_call_async( p.first, p.second );
    }
}


180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
media_library *
media_library_create(application *p_app)
{
    try
    {
        return new media_library;
    }
    catch (std::exception& ex)
    {
        LOGE( "%s", ex.what() );
        return nullptr;
    }
}

bool
195
media_library_start( media_library* p_media_library)
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
{
    auto appDataCStr = std::unique_ptr<char, void(*)(void*)>( system_storage_appdata_get(), &free );
    std::string appData( appDataCStr.get() );
    if ( appDataCStr == nullptr )
    {
        LOGE( "Failed to fetch application data directory" );
        return false;
    }
    p_media_library->logger.reset( new TizenLogger );
    p_media_library->ml->setLogger( p_media_library->logger.get() );
    return p_media_library->ml->initialize( appData + "vlc.db", appData + "/snapshots", p_media_library );
}

void
media_library_delete(media_library* p_media_library)
{
    delete p_media_library;
}

void
media_library_discover( media_library* p_ml, const char* psz_location )
{
    p_ml->ml->discover( psz_location );
}

static media_item*
fileToMediaItem( FilePtr file )
223
{
224 225 226 227 228 229 230 231 232 233 234
    auto type = MEDIA_ITEM_TYPE_UNKNOWN;
    switch ( file->type() )
    {
    case IFile::Type::VideoType:
        type = MEDIA_ITEM_TYPE_VIDEO;
        break;
    case IFile::Type::AudioType:
        type = MEDIA_ITEM_TYPE_AUDIO;
        break;
    default:
        LOGW( "Unknown file type: %d", file->type() );
235
        return nullptr;
236 237 238 239 240 241 242 243
    }
    auto mi = media_item_create( file->mrl().c_str(), type );
    if ( mi == nullptr )
    {
        //FIXME: What should we do? This won't be run again until the next time
        //we restore the media library. Also, do we care? This is likely E_NOMEM, so we
        //might have bigger problems than a missing file...
        LOGE( "Failed to create media_item for file %s", file->mrl().c_str() );
244
        return nullptr;
245
    }
246
    media_item_set_meta(mi, MEDIA_ITEM_META_TITLE, file->name().c_str());
247 248 249 250
    // If the file hasn't been parsed yet, there's no change we can do something
    // usefull past this point, we will try again after onFileUpdated gets called.
    if ( file->isParsed() == false )
        return mi;
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    mi->i_duration = file->duration();
    if ( file->type() == IFile::Type::VideoType )
    {
        auto vtracks = file->videoTracks();
        if ( vtracks.size() != 0 )
        {
            if ( vtracks.size() > 1 )
                LOGW( "Ignoring file [%s] extra video tracks for file description", file->mrl().c_str() );
            auto vtrack = vtracks[0];
            mi->i_w = vtrack->width();
            mi->i_h = vtrack->height();
        }
        else
        {
            // Assume a media library problem and just let it go.
            LOGW( "Adding video file [%s] with no video tracks detected.", file->mrl().c_str() );
        }
    }
    else if ( file->type() == IFile::Type::AudioType )
    {
        // So far, nothing to do here.
    }
273
    return mi;
274 275
}

276
struct ml_callback_context
277
{
278 279
    ml_callback_context( media_library* ml, media_library_list_cb c, void* p_user_data )
        : p_ml( ml ), cb( c ), list(nullptr), p_data( p_user_data ) {}
280
    media_library* p_ml;
281 282
    media_library_list_cb cb;
    Eina_List* list;
283 284
    void* p_data;
};
285

286
void
287
intermediate_list_callback( void* p_data )
288
{
289 290 291 292 293 294 295 296 297
    auto ctx = reinterpret_cast<ml_callback_context*>( p_data );
    ctx->cb( ctx->list, ctx->p_data );
    delete ctx;
}

void
media_library_get_audio_files( media_library* p_ml, media_library_list_cb cb, void* p_user_data )
{
    auto ctx = new ml_callback_context( p_ml, cb, p_user_data );
298

299
    ecore_thread_run( [](void* data, Ecore_Thread* ) {
300
        auto ctx = reinterpret_cast<ml_callback_context*>( data );
301 302 303 304 305 306 307 308 309
        auto files = ctx->p_ml->ml->audioFiles();
        Eina_List *list = nullptr;
        for ( auto& f : files )
        {
            auto elem = fileToMediaItem( f );
            if ( elem == nullptr )
                continue;
            list = eina_list_append( list, elem );
        }
310 311
        ctx->list = list;
        ecore_main_loop_thread_safe_call_async( intermediate_list_callback, ctx );
312
    }, nullptr, nullptr, ctx );
313 314
}

Thomas Guillem's avatar
Thomas Guillem committed
315
void
316
media_library_get_video_files( media_library* p_ml, media_library_list_cb cb, void* p_user_data )
317
{
318
    auto ctx = new ml_callback_context( p_ml, cb, p_user_data );
319

320
    ecore_thread_run( [](void* data, Ecore_Thread* ) {
321
        auto ctx = reinterpret_cast<ml_callback_context*>( data );
322 323 324 325 326 327 328 329 330
        auto files = ctx->p_ml->ml->videoFiles();
        Eina_List *list = nullptr;
        for ( auto& f : files )
        {
            auto elem = fileToMediaItem( f );
            if ( elem == nullptr )
                continue;
            list = eina_list_append( list, elem );
        }
331 332
        ctx->list = list;
        ecore_main_loop_thread_safe_call_async( intermediate_list_callback, ctx );
333
    }, nullptr, nullptr, ctx );
334
}
335 336 337 338 339 340 341 342 343 344 345 346

void
media_library_register_on_change(media_library* ml, media_library_file_list_changed_cb cb, void* p_data)
{
    ml->registerOnChange(cb, p_data);
}

void
media_library_unregister_on_change(media_library* ml, media_library_file_list_changed_cb cb, void* p_data)
{
    ml->unregisterOnChange(cb, p_data);
}