[vlc-commits] [Git][videolan/vlc][master] 4 commits: qt: introduce DeviceSourcesProvider

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Sun Jun 9 16:03:53 UTC 2024



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
c5dc278f by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: introduce DeviceSourcesProvider

helper class to get list of media sources

- - - - -
f46dbd59 by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: introduce WorkerThreadSet

- - - - -
e497bd8f by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: provide WorkerThreadSet from MainCtx

- - - - -
4156b110 by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: move data query from main thread in NetworkDeviceModel

fixes #28648 #27087 #27378

- - - - -


10 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainctx.cpp
- modules/gui/qt/maininterface/mainctx.hpp
- modules/gui/qt/meson.build
- + modules/gui/qt/network/devicesourceprovider.cpp
- + modules/gui/qt/network/devicesourceprovider.hpp
- modules/gui/qt/network/networkdevicemodel.cpp
- modules/gui/qt/network/networkdevicemodel.hpp
- + modules/gui/qt/util/workerthreadset.cpp
- + modules/gui/qt/util/workerthreadset.hpp


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -237,6 +237,8 @@ libqt_plugin_la_SOURCES = \
 	menus/menus.cpp menus/menus.hpp \
 	network/mediatreelistener.cpp \
 	network/mediatreelistener.hpp \
+	network/devicesourceprovider.cpp \
+	network/devicesourceprovider.hpp \
 	network/networkdevicemodel.cpp \
 	network/networkdevicemodel.hpp \
 	network/networksourcesmodel.cpp \
@@ -275,6 +277,7 @@ libqt_plugin_la_SOURCES = \
 	util/asynctask.hpp \
 	util/audio_device_model.cpp  \
 	util/audio_device_model.hpp \
+	util/workerthreadset.hpp util/workerthreadset.cpp \
 	util/base_model.hpp util/base_model_p.hpp util/base_model.cpp \
 	util/color_scheme_model.cpp util/color_scheme_model.hpp \
 	util/color_svg_image_provider.cpp util/color_svg_image_provider.hpp \
@@ -422,6 +425,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	menus/custom_menus.moc.cpp \
 	menus/qml_menu_wrapper.moc.cpp \
 	menus/menus.moc.cpp \
+	network/devicesourceprovider.moc.cpp \
 	network/networkdevicemodel.moc.cpp \
 	network/networksourcesmodel.moc.cpp \
 	network/networkmediamodel.moc.cpp \
@@ -440,6 +444,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	playlist/playlist_model.moc.cpp \
 	util/asynctask.moc.cpp \
 	util/audio_device_model.moc.cpp \
+	util/workerthreadset.moc.cpp \
 	util/base_model.moc.cpp \
 	util/color_scheme_model.moc.cpp \
 	util/color_svg_image_provider.moc.cpp \


=====================================
modules/gui/qt/maininterface/mainctx.cpp
=====================================
@@ -34,6 +34,7 @@
 #include "compositor.hpp"
 #include "util/renderer_manager.hpp"
 #include "util/csdbuttonmodel.hpp"
+#include "util/workerthreadset.hpp"
 
 #include "widgets/native/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
 #include "util/qt_dirs.hpp"                     // toNativeSeparators
@@ -522,6 +523,16 @@ inline void MainCtx::initSystray()
         createSystray();
 }
 
+WorkerThreadSet* MainCtx::workersThreads() const
+{
+    if (!m_workersThreads)
+    {
+        m_workersThreads.reset( new WorkerThreadSet );
+    }
+
+    return m_workersThreads.get();
+}
+
 void MainCtx::setMediaLibraryVisible( bool visible )
 {
     if (m_mediaLibraryVisible == visible)


=====================================
modules/gui/qt/maininterface/mainctx.hpp
=====================================
@@ -67,6 +67,7 @@ class VideoSurfaceProvider;
 class ControlbarProfileModel;
 class SearchCtx;
 class SortCtx;
+class WorkerThreadSet;
 
 namespace vlc {
 namespace playlist {
@@ -277,6 +278,8 @@ public:
 
     Q_INVOKABLE bool useXmasCone() const;
 
+    WorkerThreadSet *workersThreads() const;
+
 protected:
     /* Systray */
     void createSystray();
@@ -354,6 +357,8 @@ protected:
     SearchCtx* m_search = nullptr;
     SortCtx* m_sort = nullptr;
 
+    mutable std::unique_ptr<WorkerThreadSet> m_workersThreads;
+
 public slots:
     void toggleUpdateSystrayMenu();
     void showUpdateSystrayMenu();


=====================================
modules/gui/qt/meson.build
=====================================
@@ -102,6 +102,7 @@ moc_headers = files(
     'menus/custom_menus.hpp',
     'menus/qml_menu_wrapper.hpp',
     'menus/menus.hpp',
+    'network/devicesourceprovider.hpp',
     'network/networkdevicemodel.hpp',
     'network/networksourcesmodel.hpp',
     'network/networkmediamodel.hpp',
@@ -120,6 +121,7 @@ moc_headers = files(
     'playlist/playlist_model.hpp',
     'util/asynctask.hpp',
     'util/audio_device_model.hpp',
+    'util/workerthreadset.hpp',
     'util/base_model.hpp',
     'util/color_scheme_model.hpp',
     'util/color_svg_image_provider.hpp',
@@ -363,6 +365,8 @@ some_sources = files(
     'menus/menus.hpp',
     'network/mediatreelistener.cpp',
     'network/mediatreelistener.hpp',
+    'network/devicesourceprovider.cpp',
+    'network/devicesourceprovider.hpp',
     'network/networkdevicemodel.cpp',
     'network/networkdevicemodel.hpp',
     'network/networksourcesmodel.cpp',
@@ -405,6 +409,8 @@ some_sources = files(
     'util/asynctask.hpp',
     'util/audio_device_model.cpp',
     'util/audio_device_model.hpp',
+    'util/workerthreadset.cpp',
+    'util/workerthreadset.hpp',
     'util/base_model.cpp',
     'util/base_model.hpp',
     'util/base_model_p.hpp',


=====================================
modules/gui/qt/network/devicesourceprovider.cpp
=====================================
@@ -0,0 +1,228 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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 "devicesourceprovider.hpp"
+#include "networkmediamodel.hpp"
+
+
+//handle discovery events from the media source provider
+struct DeviceSourceProvider::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
+    ListenerCb(DeviceSourceProvider* provider, NetworkDeviceModel::MediaSourcePtr mediaSource)
+        : provider(provider)
+        , mediaSource(std::move(mediaSource))
+    {}
+
+    inline void onItemPreparseEnded( MediaTreePtr, input_item_node_t *, enum input_item_preparse_status ) override final {}
+
+    void onItemCleared( MediaTreePtr tree, input_item_node_t* node ) override
+    {
+        if (node != &tree->root)
+            return;
+
+        refresh( node->pp_children, node->i_children, true);
+    }
+
+    void onItemAdded( MediaTreePtr tree, input_item_node_t* parent, input_item_node_t *const children[], size_t count ) override
+    {
+        if (parent != &tree->root)
+            return;
+
+        refresh( children, count, false );
+    }
+
+    void onItemRemoved( MediaTreePtr tree, input_item_node_t * node, input_item_node_t *const children[], size_t count ) override
+    {
+        if (node != &tree->root)
+            return;
+
+        std::vector<SharedInputItem> itemList;
+
+        itemList.reserve( count );
+        for ( auto i = 0u; i < count; ++i )
+            itemList.emplace_back( children[i]->p_item );
+
+        QMetaObject::invokeMethod(provider, [provider = this->provider,
+                                  itemList = std::move(itemList),
+                                  mediaSource = this->mediaSource]()
+        {
+            provider->removeItems(itemList, mediaSource);
+        });
+    }
+
+    void refresh(input_item_node_t * const children[], size_t count,
+                 bool clear)
+    {
+        std::vector<SharedInputItem> itemList;
+
+        itemList.reserve(count);
+        for (size_t i = 0; i < count; i++)
+            itemList.emplace_back(children[i]->p_item);
+
+        QMetaObject::invokeMethod(provider, [provider = this->provider,
+                                  itemList = std::move(itemList),
+                                  mediaSource = this->mediaSource, clear]()
+        {
+            provider->addItems(itemList, mediaSource, clear);
+        });
+    }
+
+    DeviceSourceProvider *provider;
+    MediaSourcePtr mediaSource;
+};
+
+
+DeviceSourceProvider::DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource
+                                           , const QString &sourceName, QObject *parent)
+    : QObject(parent)
+    , m_sdSource {sdSource}
+    , m_sourceName {sourceName}
+{
+}
+
+void DeviceSourceProvider::init(qt_intf_t *intf)
+{
+    using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
+                                          decltype( &vlc_media_source_meta_list_Delete )>;
+
+    auto libvlc = vlc_object_instance(intf);
+
+    auto provider = vlc_media_source_provider_Get( libvlc );
+    SourceMetaPtr providerList( vlc_media_source_provider_List(
+                                    provider,
+                                    static_cast<services_discovery_category_e>(m_sdSource) ),
+                               &vlc_media_source_meta_list_Delete );
+
+    if (!providerList)
+    {
+        emit failed();
+        return;
+    }
+
+    size_t nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+    for ( auto i = 0u; i < nbProviders; ++i )
+    {
+        auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
+        const QString sourceName = qfu( meta->name );
+        if ( m_sourceName != '*' && m_sourceName != sourceName )
+            continue;
+
+        m_name += m_name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+
+        MediaSourcePtr mediaSource(
+                    vlc_media_source_provider_GetMediaSource(provider, meta->name)
+                    , false );
+
+        if ( mediaSource == nullptr )
+            continue;
+
+        std::unique_ptr<MediaTreeListener> l{ new MediaTreeListener(
+            MediaTreePtr{ mediaSource->tree },
+            std::make_unique<DeviceSourceProvider::ListenerCb>(this, mediaSource) ) };
+        if ( l->listener == nullptr )
+            break;
+
+        m_mediaSources.push_back( std::move( mediaSource ) );
+        m_listeners.push_back( std::move( l ) );
+    }
+
+    if ( !m_name.isEmpty() )
+        emit nameUpdated( m_name );
+
+    if ( !m_listeners.empty() )
+        emit itemsUpdated( m_items );
+    else
+        emit failed();
+}
+
+void DeviceSourceProvider::addItems(const std::vector<SharedInputItem> &inputList,
+                                    const MediaSourcePtr &mediaSource, const bool clear)
+{
+    bool dataChanged = false;
+
+    if (clear)
+    {
+        const qsizetype removed = m_items.removeIf([&mediaSource](const NetworkDeviceItemPtr &item)
+        {
+            return item->mediaSource == mediaSource;
+        });
+
+        if (removed > 0)
+            dataChanged = true;
+    }
+
+    for (const SharedInputItem & inputItem : inputList)
+    {
+        auto newItem = std::make_shared<NetworkDeviceItem>(inputItem, mediaSource);
+        auto it = m_items.find(newItem);
+        if (it != m_items.end())
+        {
+            (*it)->mrls.push_back(std::make_pair(newItem->mainMrl, mediaSource));
+        }
+        else
+        {
+            m_items.insert(std::move(newItem));
+            dataChanged = true;
+        }
+    }
+
+    if (dataChanged)
+    {
+        emit itemsUpdated(m_items);
+    }
+}
+
+void DeviceSourceProvider::removeItems(const std::vector<SharedInputItem> &inputList,
+                                       const MediaSourcePtr &mediaSource)
+{
+    bool dataChanged = false;
+    for (const SharedInputItem& p_item : inputList)
+    {
+        auto oldItem = std::make_shared<NetworkDeviceItem>(p_item, mediaSource);
+        NetworkDeviceItemSet::iterator it = m_items.find(oldItem);
+        if (it != m_items.end())
+        {
+            bool found = false;
+
+            const NetworkDeviceItemPtr& item = *it;
+            if (item->mrls.size() > 1)
+            {
+                auto mrlIt = std::find_if(
+                    item->mrls.begin(), item->mrls.end(),
+                    [&oldItem]( const std::pair<QUrl, MediaSourcePtr>& mrl ) {
+                        return mrl.first.matches(oldItem->mainMrl, QUrl::StripTrailingSlash)
+                            && mrl.second == oldItem->mediaSource;
+                    });
+
+                if ( mrlIt != item->mrls.end() )
+                {
+                    found = true;
+                    item->mrls.erase( mrlIt );
+                }
+            }
+
+            if (!found)
+            {
+                m_items.erase(it);
+                dataChanged = true;
+            }
+        }
+    }
+
+    if (dataChanged)
+        emit itemsUpdated(m_items);
+}


=====================================
modules/gui/qt/network/devicesourceprovider.hpp
=====================================
@@ -0,0 +1,123 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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.
+ *****************************************************************************/
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "qt.hpp"
+
+#include <QString>
+#include <QSet>
+#include <vector>
+
+#include "networkdevicemodel.hpp"
+#include "mediatreelistener.hpp"
+
+//represents an entry of the model
+struct NetworkDeviceItem
+{
+    NetworkDeviceItem(const SharedInputItem& item, const NetworkDeviceModel::MediaSourcePtr& mediaSource)
+        : name(qfu(item->psz_name))
+        , mainMrl(QUrl::fromEncoded(item->psz_uri))
+        , protocol(mainMrl.scheme())
+        , type( static_cast<NetworkDeviceModel::ItemType>(item->i_type))
+        , mediaSource(mediaSource)
+        , inputItem(item)
+    {
+        id = qHash(name) ^ qHash(protocol);
+        mrls.push_back(std::make_pair(mainMrl, mediaSource));
+
+        char* artworkUrl = input_item_GetArtworkURL(inputItem.get());
+        if (artworkUrl)
+        {
+            artwork = QString::fromUtf8(artworkUrl);
+            free(artworkUrl);
+        }
+    }
+
+    uint id;
+    QString name;
+    QUrl mainMrl;
+    std::vector<std::pair<QUrl, NetworkDeviceModel::MediaSourcePtr>> mrls;
+    QString protocol;
+    NetworkDeviceModel::ItemType type;
+    NetworkDeviceModel::MediaSourcePtr mediaSource;
+    SharedInputItem inputItem;
+    QString artwork;
+};
+
+using NetworkDeviceItemPtr =std::shared_ptr<NetworkDeviceItem>;
+
+static inline bool operator == (const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b) noexcept
+{
+    return a->id == b->id
+        && QString::compare(a->name, b->name, Qt::CaseInsensitive) == 0
+        && QString::compare(a->protocol, b->protocol, Qt::CaseInsensitive) == 0;
+}
+
+
+static inline std::size_t qHash(const NetworkDeviceItemPtr& s, size_t = 0) noexcept
+{
+    return s->id;
+}
+
+using NetworkDeviceItemSet = QSet<NetworkDeviceItemPtr>;
+
+class DeviceSourceProvider : public QObject
+{
+    Q_OBJECT
+
+public:
+    using MediaSourcePtr = NetworkDeviceModel::MediaSourcePtr;
+
+    DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource,
+                         const QString &sourceName,
+                         QObject *parent = nullptr);
+
+    void init(qt_intf_t *intf);
+
+signals:
+    void failed();
+    void nameUpdated( QString name );
+    void itemsUpdated( NetworkDeviceItemSet items );
+
+private:
+    struct ListenerCb;
+
+    void addItems(const std::vector<SharedInputItem>& inputList,
+                  const MediaSourcePtr& mediaSource,
+                  bool clear);
+
+    void removeItems(const std::vector<SharedInputItem>& inputList,
+                     const MediaSourcePtr& mediaSource);
+
+    NetworkDeviceModel::SDCatType m_sdSource;
+    QString m_sourceName; // '*' -> all sources
+    QString m_name; // source long name
+
+    NetworkDeviceItemSet m_items;
+
+    // destruction of listeners may cause destruction of source 'MediaSource'
+    // maintain a seperate reference of MediaSources to fix cyclic free
+    std::vector<MediaSourcePtr> m_mediaSources;
+
+    std::vector<std::unique_ptr<MediaTreeListener>> m_listeners;
+};


=====================================
modules/gui/qt/network/networkdevicemodel.cpp
=====================================
@@ -17,12 +17,13 @@
  *****************************************************************************/
 
 #include <unordered_set>
+#include <QTimer>
 
 #include "maininterface/mainctx.hpp"
+#include "util/workerthreadset.hpp"
 
-#include "networkdevicemodel.hpp"
+#include "devicesourceprovider.hpp"
 #include "networkmediamodel.hpp"
-#include "mediatreelistener.hpp"
 
 #include "playlist/media.hpp"
 #include "playlist/playlist_controller.hpp"
@@ -33,63 +34,6 @@
 namespace
 {
 
-//represents an entry of the model
-struct NetworkDeviceItem
-{
-    NetworkDeviceItem(const SharedInputItem& item, const NetworkDeviceModel::MediaSourcePtr& mediaSource)
-        : name(qfu(item->psz_name))
-        , mainMrl(QUrl::fromEncoded(item->psz_uri))
-        , protocol(mainMrl.scheme())
-        , type( static_cast<NetworkDeviceModel::ItemType>(item->i_type))
-        , mediaSource(mediaSource)
-        , inputItem(item)
-    {
-        id = qHash(name) ^ qHash(protocol);
-        mrls.push_back(std::make_pair(mainMrl, mediaSource));
-
-        char* artworkUrl = input_item_GetArtworkURL(inputItem.get());
-        if (artworkUrl)
-        {
-            artwork = QString::fromUtf8(artworkUrl);
-            free(artworkUrl);
-        }
-    }
-
-    uint id;
-    QString name;
-    QUrl mainMrl;
-    std::vector<std::pair<QUrl, NetworkDeviceModel::MediaSourcePtr>> mrls;
-    QString protocol;
-    NetworkDeviceModel::ItemType type;
-    NetworkDeviceModel::MediaSourcePtr mediaSource;
-    SharedInputItem inputItem;
-    QString artwork;
-};
-
-using NetworkDeviceItemPtr =  std::shared_ptr<NetworkDeviceItem>;
-using NetworkDeviceModelLoader = LocalListCacheLoader<NetworkDeviceItemPtr>;
-
-//hash and compare function for std::unordered_set
-struct NetworkDeviceItemHash
-{
-    std::size_t operator()(const NetworkDeviceItemPtr& s) const noexcept
-    {
-        return s->id;
-    }
-};
-
-struct NetworkDeviceItemEqual
-{
-    bool operator()(const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b) const noexcept
-    {
-        return a->id == b->id
-            && QString::compare(a->name, b->name, Qt::CaseInsensitive) == 0
-            && QString::compare(a->protocol, b->protocol, Qt::CaseInsensitive) == 0;
-    }
-};
-
-using NetworkDeviceItemSet = std::unordered_set<NetworkDeviceItemPtr, NetworkDeviceItemHash, NetworkDeviceItemEqual>;
-
 bool itemMatchPattern(const NetworkDeviceItemPtr& a, const QString& pattern)
 {
     return a->name.contains(pattern, Qt::CaseInsensitive);
@@ -129,22 +73,6 @@ bool descendingName(const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b
 
 }
 
-//handle discovery events from the media source provider
-struct NetworkDeviceModel::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
-    ListenerCb(NetworkDeviceModel* model, MediaSourcePtr mediaSource)
-        : model(model)
-        , mediaSource(std::move(mediaSource))
-    {}
-
-    void onItemCleared( MediaTreePtr tree, input_item_node_t* node ) override;
-    void onItemAdded( MediaTreePtr tree, input_item_node_t* parent, input_item_node_t *const children[], size_t count ) override;
-    void onItemRemoved( MediaTreePtr tree, input_item_node_t * node, input_item_node_t *const children[], size_t count ) override;
-    inline void onItemPreparseEnded( MediaTreePtr, input_item_node_t *, enum input_item_preparse_status ) override {}
-
-    NetworkDeviceModel *model;
-    MediaSourcePtr mediaSource;
-};
-
 // ListCache specialisation
 
 template<>
@@ -156,6 +84,8 @@ bool ListCache<NetworkDeviceItemPtr>::compareItems(const NetworkDeviceItemPtr& a
 
 // NetworkDeviceModelPrivate
 
+using NetworkDeviceModelLoader = LocalListCacheLoader<NetworkDeviceItemPtr>;
+
 class NetworkDeviceModelPrivate
     : public LocalListBaseModelPrivate<NetworkDeviceItemPtr>
 {
@@ -163,7 +93,6 @@ class NetworkDeviceModelPrivate
 public:
     NetworkDeviceModelPrivate(NetworkDeviceModel * pub)
         : LocalListBaseModelPrivate<NetworkDeviceItemPtr>(pub)
-        , m_items(0, NetworkDeviceItemHash{}, NetworkDeviceItemEqual{})
     {}
 
     NetworkDeviceModelLoader::ItemCompare getSortFunction() const override
@@ -191,54 +120,64 @@ public:
         if (m_qmlInitializing || !q->m_ctx || q->m_sdSource == NetworkDeviceModel::CAT_UNDEFINED || q->m_sourceName.isEmpty())
             return false;
 
-        auto libvlc = vlc_object_instance(q->m_ctx->getIntf());
-
-        m_listeners.clear();
         m_items.clear();
 
+        if (m_sources)
+        {
+            q->disconnect( m_sources );
+            m_sources->deleteLater();
+            m_sources = nullptr;
+        }
+
         q->m_name = QString {};
+        emit q->nameChanged();
 
-        auto provider = vlc_media_source_provider_Get( libvlc );
+        m_sources = new DeviceSourceProvider( q->m_sdSource, q->m_sourceName );
+        q->m_ctx->workersThreads()->assignToWorkerThread( m_sources );
 
-        using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
-                                              decltype( &vlc_media_source_meta_list_Delete )>;
+        // make sure we're not releasing resources on main thread
+        // by clearing copies of model before source provider
+        QObject::connect(q, &QObject::destroyed, m_sources, [sources = m_sources]()
+        {
+            sources->deleteLater();
+        });
 
-        SourceMetaPtr providerList( vlc_media_source_provider_List( provider, static_cast<services_discovery_category_e>(q->m_sdSource) ),
-                                   &vlc_media_source_meta_list_Delete );
-        if ( providerList == nullptr )
-            return false;
+        QObject::connect(m_sources, &DeviceSourceProvider::failed, q,
+                [this]()
+        {
+            m_items.clear();
 
-        auto nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+            m_revision += 1;
+            invalidateCache();
+        });
 
-        for ( auto i = 0u; i < nbProviders; ++i )
+        QObject::connect(m_sources, &DeviceSourceProvider::nameUpdated, q,
+                [q](QString name)
         {
-            auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
-            const QString sourceName = qfu( meta->name );
-            if ( q->m_sourceName != '*' && q->m_sourceName != sourceName )
-                continue;
-
-            q->m_name += q->m_name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+            q->m_name = name;
             emit q->nameChanged();
+        });
 
-            MediaSourcePtr mediaSource(
-                        vlc_media_source_provider_GetMediaSource(provider, meta->name)
-                        , false );
-
-            if ( mediaSource == nullptr )
-                continue;
-            std::unique_ptr<MediaTreeListener> l{ new MediaTreeListener(
-                MediaTreePtr{ mediaSource->tree },
-                std::make_unique<NetworkDeviceModel::ListenerCb>(q, MediaSourcePtr{ mediaSource }) ) };
-            if ( l->listener == nullptr )
-                return false;
-            m_listeners.push_back( std::move( l ) );
-        }
+        QObject::connect(m_sources, &DeviceSourceProvider::itemsUpdated, q,
+                [this](NetworkDeviceItemSet items)
+        {
+            m_items = items;
+
+            m_revision += 1;
+            invalidateCache();
+        });
+
+        QMetaObject::invokeMethod(m_sources,
+                                  [sources = this->m_sources, intf = q->m_ctx->getIntf()]()
+        {
+            sources->init( intf );
+        });
 
         //service discovery don't notify preparse end
         m_loading = false;
         emit q->loadingChanged();
 
-        return m_listeners.empty() == false;
+        return true;
     }
 
     const NetworkDeviceItem* getItemForRow(int row) const
@@ -249,96 +188,6 @@ public:
         return nullptr;
     }
 
-    void addItems(
-        const std::vector<SharedInputItem>& inputList,
-        const MediaSourcePtr& mediaSource,
-        bool clear)
-    {
-        bool dataChanged = false;
-
-        if (clear)
-        {
-            //std::remove_if doesn't work with unordered_set
-            //due to iterators being const
-            for (auto it = m_items.begin(); it != m_items.end(); )
-            {
-                it = std::find_if(
-                    it, m_items.end(),
-                    [&mediaSource](const NetworkDeviceItemPtr& item) {
-                        return item->mediaSource == mediaSource;
-                    });
-
-                if (it != m_items.end())
-                    it = m_items.erase(it);
-            }
-            dataChanged = true;
-        }
-
-        for (const SharedInputItem & inputItem : inputList)
-        {
-            auto newItem = std::make_shared<NetworkDeviceItem>(inputItem, mediaSource);
-            auto it = m_items.find(newItem);
-            if (it != m_items.end())
-            {
-                (*it)->mrls.push_back(std::make_pair(newItem->mainMrl, mediaSource));
-            }
-            else
-            {
-                m_items.emplace(std::move(newItem));
-                dataChanged = true;
-            }
-        }
-
-        if (dataChanged)
-        {
-            m_revision += 1;
-            invalidateCache();
-        }
-    }
-
-    void removeItems(const std::vector<SharedInputItem>& inputList, const MediaSourcePtr& mediaSource)
-    {
-        bool dataChanged = false;
-        for (const SharedInputItem& p_item : inputList)
-        {
-            auto oldItem = std::make_shared<NetworkDeviceItem>(p_item, mediaSource);
-            NetworkDeviceItemSet::iterator it = m_items.find(oldItem);
-            if (it != m_items.end())
-            {
-                bool found = false;
-
-                const NetworkDeviceItemPtr& item = *it;
-                if (item->mrls.size() > 1)
-                {
-                    auto mrlIt = std::find_if(
-                        item->mrls.begin(), item->mrls.end(),
-                        [&oldItem]( const std::pair<QUrl, MediaSourcePtr>& mrl ) {
-                            return mrl.first.matches(oldItem->mainMrl, QUrl::StripTrailingSlash)
-                                && mrl.second == oldItem->mediaSource;
-                        });
-
-                    if ( mrlIt != item->mrls.end() )
-                    {
-                        found = true;
-                        item->mrls.erase( mrlIt );
-                    }
-                }
-
-                if (!found)
-                {
-                    m_items.erase(it);
-                    dataChanged = true;
-                }
-            }
-
-        }
-        if (dataChanged)
-        {
-            m_revision += 1;
-            invalidateCache();
-        }
-    }
-
 public: //LocalListCacheLoader::ModelSource
     std::vector<NetworkDeviceItemPtr> getModelData(const QString& pattern) const override
     {
@@ -363,7 +212,7 @@ public: //LocalListCacheLoader::ModelSource
 
 public:
     NetworkDeviceItemSet m_items;
-    std::vector<std::unique_ptr<MediaTreeListener>> m_listeners;
+    QPointer<DeviceSourceProvider> m_sources {};
 };
 
 NetworkDeviceModel::NetworkDeviceModel( QObject* parent )
@@ -576,55 +425,3 @@ QVariantList NetworkDeviceModel::getItemsForIndexes(const QModelIndexList & inde
     return items;
 }
 
-// NetworkDeviceModel::ListenerCb implementation
-
-void NetworkDeviceModel::ListenerCb::onItemCleared( MediaTreePtr tree, input_item_node_t* node )
-{
-    if (node != &tree->root)
-        return;
-    model->refreshDeviceList( mediaSource, node->pp_children, node->i_children, true );
-}
-
-void NetworkDeviceModel::ListenerCb::onItemAdded( MediaTreePtr tree, input_item_node_t* parent,
-                                                  input_item_node_t *const children[],
-                                                  size_t count )
-{
-    if (parent != &tree->root)
-        return;
-    model->refreshDeviceList( mediaSource, children, count, false );
-}
-
-void NetworkDeviceModel::ListenerCb::onItemRemoved( MediaTreePtr tree, input_item_node_t* node,
-                                                    input_item_node_t *const children[],
-                                                    size_t count )
-{
-    if (node != &tree->root)
-        return;
-
-    std::vector<SharedInputItem> itemList;
-
-    itemList.reserve( count );
-    for ( auto i = 0u; i < count; ++i )
-        itemList.emplace_back( children[i]->p_item );
-
-    QMetaObject::invokeMethod(model, [model=model, mediaSource=mediaSource, itemList=std::move(itemList)]() {
-        model->d_func()->removeItems(itemList, mediaSource);
-    }, Qt::QueuedConnection);
-}
-
-void NetworkDeviceModel::refreshDeviceList(MediaSourcePtr mediaSource,
-                                           input_item_node_t * const children[], size_t count,
-                                           bool clear)
-{
-    std::vector<SharedInputItem> itemList;
-
-    itemList.reserve(count);
-    for (size_t i = 0; i < count; i++)
-        itemList.emplace_back(children[i]->p_item);
-
-    QMetaObject::invokeMethod(this, [this, clear, itemList = std::move(itemList), mediaSource]() mutable
-    {
-        Q_D(NetworkDeviceModel);
-        d->addItems(itemList, mediaSource, clear);
-    }, Qt::QueuedConnection);
-}


=====================================
modules/gui/qt/network/networkdevicemodel.hpp
=====================================
@@ -134,7 +134,6 @@ private:
     QString m_sourceName; // '*' -> all sources
     QString m_name; // source long name
 
-    struct ListenerCb;
     Q_DECLARE_PRIVATE(NetworkDeviceModel)
 };
 


=====================================
modules/gui/qt/util/workerthreadset.cpp
=====================================
@@ -0,0 +1,103 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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 "workerthreadset.hpp"
+
+#include <QTimer>
+
+WorkerThreadSet::~WorkerThreadSet()
+{
+    for (auto worker : workers)
+    {
+        if (!worker.thread)
+            continue;
+
+        finish( worker );
+    }
+}
+
+void WorkerThreadSet::assignToWorkerThread(QObject *obj)
+{
+    auto worker = reserve();
+    obj->moveToThread( worker );
+
+    QObject::connect(obj, &QObject::destroyed, worker, [this, worker]()
+    {
+        unreserve( worker );
+    });
+}
+
+void WorkerThreadSet::finish(Worker &worker)
+{
+    worker.thread->quit();
+    worker.thread->wait();
+    delete worker.thread;
+}
+
+QThread *WorkerThreadSet::reserve()
+{
+    auto itr = std::min_element(std::begin(workers)
+                              , std::end(workers)
+                              , [](const Worker &l, const Worker &r)
+    {
+        return l.load < r.load;
+    });
+
+    assert(itr != std::end(workers));
+
+    if (!itr->thread)
+    {
+        itr->thread = new QThread;
+        itr->thread->start();
+    }
+
+    itr->load++;
+    itr->inactiveTime.invalidate();
+    return itr->thread;
+}
+
+void WorkerThreadSet::unreserve(QThread *thread)
+{
+    auto itr = std::find_if(std::begin(workers)
+                            , std::end(workers)
+                            , [thread](const Worker &i) { return i.thread == thread; });
+
+    if (itr == std::end(workers)) return; // impossible?
+
+    const int load = --itr->load;
+    if (load != 0)
+        return;
+
+    itr->inactiveTime.start();
+    QTimer::singleShot(CLEANUP_TIMEOUT, this, &WorkerThreadSet::cleanupInactiveWorker);
+}
+
+void WorkerThreadSet::cleanupInactiveWorker()
+{
+    for (auto &worker : workers)
+    {
+        if ((worker.load == 0)
+                && worker.inactiveTime.hasExpired(MAX_INACTIVE_TIME)
+                && worker.thread)
+        {
+            finish( worker );
+
+            worker.thread = nullptr;
+        }
+    }
+}


=====================================
modules/gui/qt/util/workerthreadset.hpp
=====================================
@@ -0,0 +1,63 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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.
+ *****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QThread>
+
+// maintains a set of reusable worker threads
+// class is not thread safe and must be accessed from Main thread
+class WorkerThreadSet : public QObject
+{
+    Q_OBJECT
+public:
+    using QObject::QObject;
+
+    ~WorkerThreadSet();
+
+    // changes the thread affinity of 'obj' to a worker thread
+    void assignToWorkerThread(QObject *obj);
+
+private:
+    struct Worker
+    {
+        QThread *thread = nullptr;
+        int load = 0;
+        QElapsedTimer inactiveTime;
+    };
+
+    const static int MAX_WORKER = 2;
+    const static int CLEANUP_TIMEOUT = 10000; // 10seconds
+    const static int MAX_INACTIVE_TIME = 6000; // 6seconds
+
+    void finish(Worker &worker);
+
+    // returns a worker thread after increasing it's load
+    QThread *reserve();
+
+    // reduces load of the previously allocated 'thread'
+    // and makes it available for future operations
+    // threads are automatically freed when they remain
+    // inactive for extended amount of time
+    void unreserve(QThread *thread);
+
+    void cleanupInactiveWorker();
+
+    Worker workers[MAX_WORKER] {};
+};



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/fe8ac5c4fa5708e4a0885cc23c545cf3310317f4...4156b1105c98d33b79c7d1155945d29f3bd95aee

-- 
This project does not include diff previews in email notifications.
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/fe8ac5c4fa5708e4a0885cc23c545cf3310317f4...4156b1105c98d33b79c7d1155945d29f3bd95aee
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list