You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

468 lines
13 KiB

/*****************************************************************************
* Copyright (C) 2021 VLC authors and VideoLAN
*
* 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.
*****************************************************************************/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "mlplaylistlistmodel.hpp"
// VLC includes
#include <vlc_media_library.h>
#include <qt.hpp>
// MediaLibrary includes
#include "mlhelper.hpp"
#include "mlplaylist.hpp"
//-------------------------------------------------------------------------------------------------
// Static variables
// NOTE: We multiply by 2 to cover most dpi settings.
static const int MLPLAYLISTMODEL_COVER_WIDTH = 512 * 2; // 16 / 10 ratio
static const int MLPLAYLISTMODEL_COVER_HEIGHT = 320 * 2;
namespace {
void appendMediaIntoPlaylist(vlc_medialibrary_t* ml, int64_t playlistId, const std::vector<MLItemId>& itemList)
{
vlc_ml_query_params_t query;
memset(&query, 0, sizeof(vlc_ml_query_params_t));
for (auto itemId : itemList)
{
// NOTE: When we have a parent it's a collection of media(s).
if (itemId.type != VLC_ML_PARENT_UNKNOWN)
{
ml_unique_ptr<vlc_ml_media_list_t> list;
list.reset(vlc_ml_list_media_of(ml, &query, itemId.type, itemId.id));
if (!list)
continue;
for (const vlc_ml_media_t & media : ml_range_iterate<vlc_ml_media_t>(list))
vlc_ml_playlist_append(ml, playlistId, media.i_id);
}
// NOTE: Otherwise we add the media directly.
else
vlc_ml_playlist_append(ml, playlistId, itemId.id);
}
}
} //anonymous namespace
//=================================================================================================
// MLPlaylistListModel
//=================================================================================================
/* explicit */ MLPlaylistListModel::MLPlaylistListModel(QObject * parent)
: MLBaseModel(parent)
, m_coverSize(MLPLAYLISTMODEL_COVER_WIDTH, MLPLAYLISTMODEL_COVER_HEIGHT) {}
//-------------------------------------------------------------------------------------------------
/* Q_INVOKABLE */ void MLPlaylistListModel::create(const QString & name, const QVariantList& initialItems)
{
assert(m_mediaLib);
std::vector<MLItemId> itemList;
for (const QVariant & id : initialItems)
{
if (id.canConvert<MLItemId>() == false)
continue;
const MLItemId & itemId = id.value<MLItemId>();
if (itemId.id == 0)
continue;
itemList.push_back(itemId);
}
m_mediaLib->runOnMLThread(this,
//ML thread
[name, itemList](vlc_medialibrary_t* ml)
{
vlc_ml_playlist_t * playlist = vlc_ml_playlist_create(ml, qtu(name));
if (playlist)
{
auto playlistId = playlist->i_id;
vlc_ml_playlist_release(playlist);
appendMediaIntoPlaylist(ml, playlistId, itemList);
}
});
}
/* Q_INVOKABLE */ bool MLPlaylistListModel::append(const MLItemId & playlistId,
const QVariantList & ids)
{
assert(m_mediaLib);
if (unlikely(m_transactionPending))
return false;
m_transactionPending = true;
bool result = true;
std::vector<MLItemId> itemList;
for (const QVariant & id : ids)
{
if (id.canConvert<MLItemId>() == false)
{
result = false;
continue;
}
const MLItemId & itemId = id.value<MLItemId>();
if (itemId.id == 0)
{
result = false;
continue;
}
itemList.push_back(itemId);
}
m_mediaLib->runOnMLThread(this,
//ML thread
[playlistId, itemList](vlc_medialibrary_t* ml)
{
appendMediaIntoPlaylist(ml, playlistId.id, itemList);
},
//UI thread
[this]() {
endTransaction();
});
return result;
}
//-------------------------------------------------------------------------------------------------
/* Q_INVOKABLE */ bool MLPlaylistListModel::deletePlaylists(const QVariantList & ids)
{
assert(m_mediaLib);
if (unlikely(m_transactionPending))
return false;
m_transactionPending = true;
std::vector<MLItemId> itemList;
for (const QVariant & id : ids)
{
if (id.canConvert<MLItemId>() == false)
continue;
const MLItemId & itemId = id.value<MLItemId>();
if (itemId.id == 0)
continue;
itemList.push_back(itemId);
}
m_mediaLib->runOnMLThread(this,
//ML thread
[itemList](vlc_medialibrary_t* ml)
{
for (auto itemId : itemList)
vlc_ml_playlist_delete(ml, itemId.id);
},
//UI thread
[this](){
endTransaction();
});
return true;
}
//-------------------------------------------------------------------------------------------------
/* Q_INVOKABLE */ MLItemId MLPlaylistListModel::getItemId(int index) const
{
if (index < 0 || index >= rowCount())
return MLItemId();
return item(index)->getId();
}
//-------------------------------------------------------------------------------------------------
// QAbstractItemModel implementation
//-------------------------------------------------------------------------------------------------
QHash<int, QByteArray> MLPlaylistListModel::roleNames() const /* override */
{
return
{
{ PLAYLIST_ID, "id" },
{ PLAYLIST_NAME, "name" },
{ PLAYLIST_THUMBNAIL, "thumbnail" },
{ PLAYLIST_DURATION, "duration" },
{ PLAYLIST_COUNT, "count" }
};
}
QVariant MLPlaylistListModel::itemRoleData(MLItem *item, int role) const /* override */
{
MLPlaylist * playlist = static_cast<MLPlaylist *>(item);
if (playlist == nullptr)
return QVariant();
switch (role)
{
// NOTE: This is the condition for QWidget view(s).
case Qt::DisplayRole:
return playlist->getName();
// NOTE: These are the conditions for QML view(s).
case PLAYLIST_ID:
return QVariant::fromValue(playlist->getId());
case PLAYLIST_NAME:
return playlist->getName();
case PLAYLIST_THUMBNAIL:
return getCover(playlist);
case PLAYLIST_DURATION:
return QVariant::fromValue(playlist->getDuration());
case PLAYLIST_COUNT:
return QVariant::fromValue(playlist->getCount());
default:
return QVariant();
}
}
//-------------------------------------------------------------------------------------------------
// QAbstractItemModel reimplementation
//-------------------------------------------------------------------------------------------------
QVariant MLPlaylistListModel::headerData(int section, Qt::Orientation orientation,
int role) const /* override */
{
if (role != Qt::DisplayRole || orientation == Qt::Vertical)
return QVariant();
if (section == 0)
return QVariant::fromValue(qtr("Name"));
else
return QVariant();
}
//-------------------------------------------------------------------------------------------------
// Protected MLBaseModel implementation
//-------------------------------------------------------------------------------------------------
vlc_ml_sorting_criteria_t MLPlaylistListModel::roleToCriteria(int role) const /* override */
{
if (role == PLAYLIST_NAME)
return VLC_ML_SORTING_ALPHA;
else
return VLC_ML_SORTING_DEFAULT;
}
std::unique_ptr<MLBaseModel::BaseLoader> MLPlaylistListModel::createLoader() const /* override */
{
return std::make_unique<Loader>(*this);
}
//-------------------------------------------------------------------------------------------------
// Private functions
//-------------------------------------------------------------------------------------------------
void MLPlaylistListModel::endTransaction()
{
m_transactionPending = false;
if (m_resetAfterTransaction)
{
m_resetAfterTransaction = false;
emit resetRequested();
}
}
QString MLPlaylistListModel::getCover(MLPlaylist * playlist) const
{
QString cover = playlist->getCover();
// NOTE: Making sure we're not already generating a cover.
if (cover.isNull() == false || playlist->hasGenerator())
return cover;
MLItemId playlistId = playlist->getId();
struct Context{
QString cover;
};
playlist->setGenerator(true);
m_mediaLib->runOnMLThread<Context>(this,
//ML thread
[playlistId, coverSize = m_coverSize, coverDefault = m_coverDefault, coverPrefix = m_coverPrefix]
(vlc_medialibrary_t* ml, Context& ctx)
{
CoverGenerator generator{ml, playlistId};
generator.setSize(coverSize);
generator.setDefaultThumbnail(coverDefault);
generator.setPrefix(coverPrefix);
if (generator.cachedFileAvailable())
ctx.cover = generator.cachedFileURL();
else
ctx.cover = generator.execute();
},
//UI thread
[this, playlistId]
(quint64, Context& ctx)
{
int row;
// NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
auto playlist = static_cast<MLPlaylist *>(findInCache(playlistId, &row));
if (!playlist)
return;
playlist->setCover(ctx.cover);
playlist->setGenerator(false);
//we're running in a callback
QModelIndex modelIndex = index(row);
emit const_cast<MLPlaylistListModel*>(this)->dataChanged(modelIndex, modelIndex, { PLAYLIST_THUMBNAIL });
});
return cover;
}
//-------------------------------------------------------------------------------------------------
// Private MLBaseModel reimplementation
//-------------------------------------------------------------------------------------------------
void MLPlaylistListModel::onVlcMlEvent(const MLEvent & event) /* override */
{
switch (event.i_type)
{
case VLC_ML_EVENT_PLAYLIST_UPDATED:
case VLC_ML_EVENT_PLAYLIST_ADDED:
case VLC_ML_EVENT_PLAYLIST_DELETED:
{
if(m_transactionPending)
m_resetAfterTransaction = true;
else
{
m_need_reset = true;
// NOTE: Maybe we should call this from MLBaseModel ?
emit resetRequested();
}
break;
}
default:
break;
}
MLBaseModel::onVlcMlEvent(event);
}
//-------------------------------------------------------------------------------------------------
// Properties
//-------------------------------------------------------------------------------------------------
QSize MLPlaylistListModel::coverSize() const
{
return m_coverSize;
}
void MLPlaylistListModel::setCoverSize(const QSize & size)
{
if (m_coverSize == size)
return;
m_coverSize = size;
emit coverSizeChanged();
}
QString MLPlaylistListModel::coverDefault() const
{
return m_coverDefault;
}
void MLPlaylistListModel::setCoverDefault(const QString & fileName)
{
if (m_coverDefault == fileName)
return;
m_coverDefault = fileName;
emit coverDefaultChanged();
}
QString MLPlaylistListModel::coverPrefix() const
{
return m_coverPrefix;
}
void MLPlaylistListModel::setCoverPrefix(const QString & prefix)
{
if (m_coverPrefix == prefix)
return;
m_coverPrefix = prefix;
emit coverPrefixChanged();
}
//=================================================================================================
// Loader
//=================================================================================================
MLPlaylistListModel::Loader::Loader(const MLPlaylistListModel & model)
: MLBaseModel::BaseLoader(model) {}
size_t MLPlaylistListModel::Loader::count(vlc_medialibrary_t* ml) const /* override */
{
vlc_ml_query_params_t params = getParams().toCQueryParams();
return vlc_ml_count_playlists(ml, &params);
}
std::vector<std::unique_ptr<MLItem>>
MLPlaylistListModel::Loader::load(vlc_medialibrary_t* ml, size_t index, size_t count) const /* override */
{
vlc_ml_query_params_t params = getParams(index, count).toCQueryParams();
ml_unique_ptr<vlc_ml_playlist_list_t> list(vlc_ml_list_playlists(ml, &params));
if (list == nullptr)
return {};
std::vector<std::unique_ptr<MLItem>> result;
for (const vlc_ml_playlist_t & playlist : ml_range_iterate<vlc_ml_playlist_t>(list))
{
result.emplace_back(std::make_unique<MLPlaylist>(&playlist));
}
return result;
}
std::unique_ptr<MLItem>
MLPlaylistListModel::Loader::loadItemById(vlc_medialibrary_t* ml, MLItemId itemId) const
{
assert(itemId.type == VLC_ML_PARENT_PLAYLIST);
ml_unique_ptr<vlc_ml_playlist_t> playlist(vlc_ml_get_playlist(ml, itemId.id));
if (!playlist)
return nullptr;
return std::make_unique<MLPlaylist>(playlist.get());
}