[vlc-devel] [PATCH 03/18] qt: add ServicesDiscoveryModel

Prince Gupta guptaprince8832 at gmail.com
Wed Sep 23 19:40:01 CEST 2020


---
 modules/gui/qt/Makefile.am                    |   3 +
 modules/gui/qt/maininterface/mainui.cpp       |   2 +
 .../gui/qt/network/servicesdiscoverymodel.cpp | 264 ++++++++++++++++++
 .../gui/qt/network/servicesdiscoverymodel.hpp | 124 ++++++++
 4 files changed, 393 insertions(+)
 create mode 100644 modules/gui/qt/network/servicesdiscoverymodel.cpp
 create mode 100644 modules/gui/qt/network/servicesdiscoverymodel.hpp

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index 12d41fc14f..92e8269f0b 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -174,6 +174,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/network/networkmediamodel.hpp \
 	gui/qt/network/networksourcelistener.cpp \
 	gui/qt/network/networksourcelistener.hpp \
+	gui/qt/network/servicesdiscoverymodel.cpp \
+	gui/qt/network/servicesdiscoverymodel.hpp \
 	gui/qt/player/input_models.cpp gui/qt/player/input_models.hpp \
 	gui/qt/player/player_controller.cpp gui/qt/player/player_controller.hpp gui/qt/player/player_controller_p.hpp \
 	gui/qt/player/playercontrolbarmodel.cpp gui/qt/player/playercontrolbarmodel.hpp \
@@ -321,6 +323,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/network/networkdevicemodel.moc.cpp \
 	gui/qt/network/networksourcesmodel.moc.cpp \
 	gui/qt/network/networkmediamodel.moc.cpp \
+	gui/qt/network/servicesdiscoverymodel.moc.cpp \
 	gui/qt/player/input_models.moc.cpp \
 	gui/qt/player/player_controller.moc.cpp \
 	gui/qt/player/playercontrolbarmodel.moc.cpp \
diff --git a/modules/gui/qt/maininterface/mainui.cpp b/modules/gui/qt/maininterface/mainui.cpp
index 2ce8b8d2af..80716c910f 100644
--- a/modules/gui/qt/maininterface/mainui.cpp
+++ b/modules/gui/qt/maininterface/mainui.cpp
@@ -32,6 +32,7 @@
 #include "network/networkmediamodel.hpp"
 #include "network/networkdevicemodel.hpp"
 #include "network/networksourcesmodel.hpp"
+#include "network/servicesdiscoverymodel.hpp"
 
 #include "maininterface/main_interface.hpp"
 
@@ -167,6 +168,7 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<NetworkMediaModel>( "org.videolan.medialib", 0, 1, "NetworkMediaModel");
         qmlRegisterType<NetworkDeviceModel>( "org.videolan.medialib", 0, 1, "NetworkDeviceModel");
         qmlRegisterType<NetworkSourcesModel>( "org.videolan.medialib", 0, 1, "NetworkSourcesModel");
+        qmlRegisterType<ServicesDiscoveryModel>( "org.videolan.medialib", 0, 1, "ServicesDiscoveryModel");
         qmlRegisterType<MlFoldersModel>( "org.videolan.medialib", 0, 1, "MLFolderModel");
 
         //expose base object, they aren't instanciable from QML side
diff --git a/modules/gui/qt/network/servicesdiscoverymodel.cpp b/modules/gui/qt/network/servicesdiscoverymodel.cpp
new file mode 100644
index 0000000000..46e078156f
--- /dev/null
+++ b/modules/gui/qt/network/servicesdiscoverymodel.cpp
@@ -0,0 +1,264 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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.
+ *****************************************************************************/
+
+#include "servicesdiscoverymodel.hpp"
+#include <vlc_addons.h>
+
+#include "medialibrary/mlhelper.hpp"
+
+#include "playlist/media.hpp"
+#include "playlist/playlist_controller.hpp"
+
+#include <QPixmap>
+
+ServicesDiscoveryModel::ServicesDiscoveryModel( QObject* parent )
+    : QAbstractListModel( parent )
+{
+}
+
+ServicesDiscoveryModel::~ServicesDiscoveryModel()
+{
+    if ( m_manager )
+    {
+        addons_manager_Delete( m_manager );
+    }
+}
+
+QVariant ServicesDiscoveryModel::data( const QModelIndex& index, int role ) const
+{
+    if (!m_ctx)
+        return {};
+    auto idx = index.row();
+    if ( idx < 0 || (size_t)idx >= m_items.size() )
+        return {};
+    const auto& item = m_items[idx];
+    switch ( role )
+    {
+        case Role::SERVICE_NAME:
+            return item.name;
+        case Role::SERVICE_AUTHOR:
+            return item.author;
+        case Role::SERVICE_SUMMARY:
+            return item.summery;
+        case Role::SERVICE_DESCRIPTION:
+            return item.description;
+        case Role::SERVICE_DOWNLOADS:
+            return QVariant::fromValue( item.entry->i_downloads );
+        case Role::SERVICE_SCORE:
+            return item.entry->i_score / 100;
+        case Role::SERVICE_STATE:
+            return item.entry->e_state;
+        case Role::SERVICE_ARTWORK:
+            return item.artworkUrl;
+        default:
+            return {};
+    }
+}
+
+QHash<int, QByteArray> ServicesDiscoveryModel::roleNames() const
+{
+    return {
+        { Role::SERVICE_NAME, "name" },
+        { Role::SERVICE_AUTHOR, "author"},
+        { Role::SERVICE_SUMMARY, "summary" },
+        { Role::SERVICE_DESCRIPTION, "description" },
+        { Role::SERVICE_DOWNLOADS, "downloads" },
+        { Role::SERVICE_SCORE, "score" },
+        { Role::SERVICE_STATE, "state" },
+        { Role::SERVICE_ARTWORK, "artwork" }
+    };
+}
+
+QMap<QString, QVariant> ServicesDiscoveryModel::getDataAt(int idx)
+{
+    QMap<QString, QVariant> dataDict;
+    QHash<int,QByteArray> roles = roleNames();
+    for (auto role: roles.keys()) {
+        dataDict[roles[role]] = data(index(idx), role);
+    }
+    return dataDict;
+}
+
+void ServicesDiscoveryModel::installService(int idx)
+{
+    if ( idx < 0 || idx >= (int)m_items.size() )
+        return;
+
+    addon_uuid_t uuid;
+    memcpy( uuid, m_items[idx].entry->uuid, sizeof( uuid ) );
+    addons_manager_Install( m_manager, uuid );
+}
+
+void ServicesDiscoveryModel::removeService(int idx)
+{
+    if ( idx < 0 || idx >= (int)m_items.size() )
+        return;
+
+    addon_uuid_t uuid;
+    memcpy( uuid, m_items[idx].entry->uuid, sizeof( uuid ) );
+    addons_manager_Remove( m_manager, uuid );
+}
+
+int ServicesDiscoveryModel::rowCount(const QModelIndex& parent) const
+{
+    if ( parent.isValid() )
+        return 0;
+    return getCount();
+}
+
+int ServicesDiscoveryModel::getCount() const
+{
+    assert( m_items.size() < INT32_MAX );
+    return static_cast<int>( m_items.size() );
+}
+
+void ServicesDiscoveryModel::setCtx(QmlMainContext* ctx)
+{
+    if (ctx) {
+        m_ctx = ctx;
+    }
+    if (m_ctx) {
+        initializeManager();
+    }
+    emit ctxChanged();
+}
+
+void ServicesDiscoveryModel::initializeManager()
+{
+    if ( m_manager )
+        addons_manager_Delete( m_manager );
+
+    struct addons_manager_owner owner =
+    {
+        this,
+        addonFoundCallback,
+        addonsDiscoveryEndedCallback,
+        addonChangedCallback,
+    };
+
+    m_manager = addons_manager_New( VLC_OBJECT( m_ctx->getIntf() ), &owner );
+    assert( m_manager );
+
+    m_parsingPending = true;
+    emit parsingPendingChanged();
+    addons_manager_LoadCatalog( m_manager );
+    addons_manager_Gather( m_manager, "repo://" );
+}
+
+void ServicesDiscoveryModel::addonFoundCallback( addons_manager_t *manager,
+                                        addon_entry_t *entry )
+{
+    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
+        return;
+    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
+    QMetaObject::invokeMethod( me, [me, entryPtr = AddonPtr(entry)]()
+    {
+        me->addonFound( std::move( entryPtr ) );
+    }, Qt::QueuedConnection);
+}
+
+void ServicesDiscoveryModel::addonsDiscoveryEndedCallback( addons_manager_t *manager )
+{
+    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
+    QMetaObject::invokeMethod( me, [me]()
+    {
+        me->discoveryEnded();
+    }, Qt::QueuedConnection);
+}
+
+void ServicesDiscoveryModel::addonChangedCallback( addons_manager_t *manager,
+                                          addon_entry_t *entry )
+{
+    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
+        return;
+    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
+    QMetaObject::invokeMethod( me, [me, entryPtr = AddonPtr(entry)]()
+    {
+        me->addonChanged( std::move( entryPtr ) );
+    }, Qt::QueuedConnection);
+}
+
+void ServicesDiscoveryModel::addonFound( ServicesDiscoveryModel::AddonPtr addon )
+{
+    beginInsertRows( QModelIndex(), getCount(), getCount() );
+    m_items.emplace_back(addon);
+    endInsertRows();
+    emit countChanged();
+}
+
+void ServicesDiscoveryModel::addonChanged( ServicesDiscoveryModel::AddonPtr addon )
+{
+    for ( int r = 0; r < getCount(); ++r )
+    {
+        if ( memcmp( m_items[r].entry->uuid, addon->uuid, sizeof( addon->uuid ) ) )
+            continue;
+
+        m_items[r] = addon;
+        emit dataChanged( index( r, 0 ), index( r, 0 ) );
+    }
+}
+
+void ServicesDiscoveryModel::discoveryEnded()
+{
+    assert( m_parsingPending );
+    m_parsingPending = false;
+    emit parsingPendingChanged();
+}
+
+ServicesDiscoveryModel::Item::Item( ServicesDiscoveryModel::AddonPtr addon )
+{
+    *this = addon;
+}
+
+ServicesDiscoveryModel::Item &ServicesDiscoveryModel::Item::operator=( ServicesDiscoveryModel::AddonPtr addon )
+{
+    name = qfu( addon->psz_name );
+    summery = qfu( addon->psz_summary ).trimmed();
+    description = qfu( addon->psz_description ).trimmed();
+    author = qfu( addon->psz_author );
+    sourceUrl = QUrl( addon->psz_source_uri );
+    entry = addon;
+
+    if ( addon->psz_image_data ) {
+        QDir dir( config_GetUserDir( VLC_CACHE_DIR ) );
+        dir.mkdir("art");
+        dir.cd("art");
+        dir.mkdir("qt-addon-covers");
+        dir.cd("qt-addon-covers");
+
+        QString id = addons_uuid_to_psz( &addon->uuid );
+        QString filename = QString("addon_thumbnail_%1.png").arg(id);
+        QString absoluteFilePath =  dir.absoluteFilePath(filename);
+
+        if ( !QFileInfo::exists( absoluteFilePath )) {
+            QPixmap pixmap;
+            pixmap.loadFromData( QByteArray::fromBase64( QByteArray( addon->psz_image_data ) ),
+                0,
+                Qt::AutoColor
+            );
+            pixmap.save(absoluteFilePath);
+        }
+        artworkUrl = QUrl::fromLocalFile( absoluteFilePath );
+    }
+    else if ( addon->e_flags & ADDON_BROKEN )
+        artworkUrl = QUrl( ":/addons/broken.svg" );
+    else
+        artworkUrl = QUrl( ":/addons/default.svg" );
+
+    return *this;
+}
diff --git a/modules/gui/qt/network/servicesdiscoverymodel.hpp b/modules/gui/qt/network/servicesdiscoverymodel.hpp
new file mode 100644
index 0000000000..0eb5221bdc
--- /dev/null
+++ b/modules/gui/qt/network/servicesdiscoverymodel.hpp
@@ -0,0 +1,124 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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.
+ *****************************************************************************/
+
+#ifndef MLServicesDiscoveryModel_HPP
+#define MLServicesDiscoveryModel_HPP
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <QAbstractListModel>
+
+#include <vlc_media_library.h>
+#include <vlc_media_source.h>
+#include <vlc_threads.h>
+#include <vlc_addons.h>
+#include <vlc_cxx_helpers.hpp>
+
+#include <util/qml_main_context.hpp>
+
+#include <memory>
+
+class ServicesDiscoveryModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+public:
+
+    Q_PROPERTY(QmlMainContext* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged)
+    Q_PROPERTY(bool parsingPending READ getParsingPending NOTIFY parsingPendingChanged)
+    Q_PROPERTY(int count READ getCount NOTIFY countChanged)
+
+    enum State // equivalent to addon_state_t
+    {
+        NOTINSTALLED = 0,
+        INSTALLING,
+        INSTALLED,
+        UNINSTALLING
+    };
+    Q_ENUM(State)
+
+    enum Role {
+        SERVICE_NAME = Qt::UserRole + 1,
+        SERVICE_AUTHOR,
+        SERVICE_SUMMARY,
+        SERVICE_DESCRIPTION,
+        SERVICE_DOWNLOADS,
+        SERVICE_SCORE,
+        SERVICE_STATE,
+        SERVICE_ARTWORK
+    };
+    Q_ENUM(Role);
+
+    explicit ServicesDiscoveryModel(QObject* parent = nullptr);
+    virtual ~ServicesDiscoveryModel() override;
+
+    QVariant data(const QModelIndex& index, int role) const override;
+    QHash<int, QByteArray> roleNames() const override;
+    int rowCount(const QModelIndex& parent) const override;
+
+    void setCtx(QmlMainContext* ctx);
+
+    inline QmlMainContext* getCtx() const { return m_ctx; }
+    inline bool getParsingPending() const { return m_parsingPending; }
+    int getCount() const;
+
+    Q_INVOKABLE QMap<QString, QVariant> getDataAt(int idx);
+    Q_INVOKABLE void installService(int idx);
+    Q_INVOKABLE void removeService(int idx);
+
+signals:
+    void parsingPendingChanged();
+    void countChanged();
+    void ctxChanged();
+
+private:
+    using AddonPtr = vlc_shared_data_ptr_type(addon_entry_t,
+                                addon_entry_Hold, addon_entry_Release);
+
+    void initializeManager();
+    static void addonFoundCallback( addons_manager_t *, struct addon_entry_t * );
+    static void addonsDiscoveryEndedCallback( addons_manager_t * );
+    static void addonChangedCallback( addons_manager_t *, struct addon_entry_t * );
+
+    void addonFound( AddonPtr addon );
+    void addonChanged( AddonPtr addon );
+    void discoveryEnded();
+
+    struct Item
+    {
+        QString name;
+        QString summery;
+        QString description;
+        QString author;
+        QUrl sourceUrl;
+        QUrl artworkUrl;
+        AddonPtr entry;
+
+        Item( AddonPtr addon );
+        Item& operator =( AddonPtr addon );
+    };
+
+    std::vector<Item> m_items;
+    QmlMainContext* m_ctx = nullptr;
+    addons_manager_t* m_manager = nullptr;
+    bool m_parsingPending = false;
+};
+
+#endif // MLServicesDiscoveryModel_HPP
-- 
2.25.1



More information about the vlc-devel mailing list