[vlc-commits] [Git][videolan/vlc][master] 20 commits: qt: compare UUID of addons instead of pointer in ServiceDiscoveryModel

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Dec 20 09:11:54 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
49305c06 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: compare UUID of addons instead of pointer in ServiceDiscoveryModel

addonChanged callback will change the pointer, but the item still represent the
same addon

- - - - -
1e3783f7 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: use cache updateItem when addon changes in SDModel

- - - - -
53d6bd8d by Pierre Lamot at 2024-12-20T08:54:36+00:00
addons: mark addons_uuid_to_psz as VLC_MALLOC

- - - - -
98e9d4e4 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: fix memory leak when creating ServiceDiscovery items

- - - - -
65a39a06 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: ensure addon_entry_t are locked before accessing their members in ServiceDiscoveryModel

- - - - -
d7c536d1 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: move private fields from ServiceDiscoveryModel to private class

- - - - -
73bdbffe by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: fix variable typo in ServiceDiscoveryModel

- - - - -
9657bd4d by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: allow filtering by ServiceDiscoveryModel by state or type

- - - - -
67cbed92 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: explicitly gather from VLC or third party repository in ServiceDiscoveryModel

this allows requesting or refreshing the model explicilty

- - - - -
074fd4a1 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: expose addon score without pre-scaling

views can choose their scale, maxScore is exposed to allow the conversion

- - - - -
dfd92283 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: expose more roles in ServiceDiscovery model

SERVICE prefix is removed from the roletype as the model can be used for other
addons types

- - - - -
817f7051 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: allow (un)installing addons using setData in ServiceDiscoveryModel

- - - - -
1169bb3f by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: expose method to get various representation of addon_state_t in ServiceDiscoveryModel

- - - - -
93e2cea5 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: replace code generation with a lambda in plugins.cpp

- - - - -
f6831468 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: don't inherit AddonItemDelegate from ExtensionItemDelegate

The data from their model are unrelated (extension_t and addon_entry_t), so
their model shouldn't inherit one from another.

instead a paint function is provided where the data to render is passed as arguments

- - - - -
dcc290de by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: allow initializing BaseModel from C++

methods are public in QQmlParserStatus

- - - - -
290b9467 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: use service discovery model in Plugin.cpp

Thus avoiding needing two models to represent the same data

- - - - -
9a8eba6f by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: remove obsolete AddonsListModel and AddonsSortFilterProxyModel

- - - - -
7fc14a2f by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: remove obosolete AddonManager

- - - - -
346c18b7 by Pierre Lamot at 2024-12-20T08:54:36+00:00
qt: rename ServiceDiscoveryModel into AddonModel

ServiceDiscoveryModel also handle the other types of addons

- - - - -


14 changed files:

- include/vlc_addons.h
- modules/gui/qt/Makefile.am
- − modules/gui/qt/dialogs/plugins/addons_manager.cpp
- − modules/gui/qt/dialogs/plugins/addons_manager.hpp
- modules/gui/qt/dialogs/plugins/plugins.cpp
- modules/gui/qt/dialogs/plugins/plugins.hpp
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/meson.build
- modules/gui/qt/network/servicesdiscoverymodel.cpp → modules/gui/qt/network/addonsmodel.cpp
- modules/gui/qt/network/servicesdiscoverymodel.hpp → modules/gui/qt/network/addonsmodel.hpp
- modules/gui/qt/network/qml/ServicesManage.qml
- modules/gui/qt/qt.cpp
- modules/gui/qt/util/base_model.hpp
- po/POTFILES.in


Changes:

=====================================
include/vlc_addons.h
=====================================
@@ -200,7 +200,7 @@ static inline bool addons_uuid_read( const char *psz_uuid, addon_uuid_t *p_uuid
     return true;
 }
 
-static inline char * addons_uuid_to_psz( const addon_uuid_t * p_uuid )
+VLC_MALLOC static inline char * addons_uuid_to_psz( const addon_uuid_t * p_uuid )
 {
     char *psz = (char*) calloc( ADDON_UUID_PSZ_SIZE + 1 , sizeof(char) );
     if ( psz )


=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -128,7 +128,6 @@ libqt_plugin_la_SOURCES = \
 	dialogs/open/open.cpp dialogs/open/open.hpp \
 	dialogs/open/open_panels.cpp dialogs/open/open_panels.hpp \
 	dialogs/open/openurl.cpp dialogs/open/openurl.hpp \
-	dialogs/plugins/addons_manager.cpp dialogs/plugins/addons_manager.hpp \
 	dialogs/plugins/plugins.cpp dialogs/plugins/plugins.hpp \
 	dialogs/podcast/podcast_configuration.cpp \
 	dialogs/podcast/podcast_configuration.hpp \
@@ -251,6 +250,8 @@ libqt_plugin_la_SOURCES = \
 	menus/qml_menu_wrapper.cpp \
 	menus/qml_menu_wrapper.hpp \
 	menus/menus.cpp menus/menus.hpp \
+	network/addonsmodel.cpp \
+	network/addonsmodel.hpp \
 	network/mediatreelistener.cpp \
 	network/mediatreelistener.hpp \
 	network/devicesourceprovider.cpp \
@@ -263,8 +264,6 @@ libqt_plugin_la_SOURCES = \
 	network/networksourcesmodel.hpp \
 	network/networkmediamodel.cpp \
 	network/networkmediamodel.hpp \
-	network/servicesdiscoverymodel.cpp \
-	network/servicesdiscoverymodel.hpp \
 	network/standardpathmodel.cpp \
 	network/standardpathmodel.hpp \
 	network/vlcmediasourcewrapper.hpp \
@@ -402,7 +401,6 @@ nodist_libqt_plugin_la_SOURCES = \
 	dialogs/open/open.moc.cpp \
 	dialogs/open/open_panels.moc.cpp \
 	dialogs/open/openurl.moc.cpp \
-	dialogs/plugins/addons_manager.moc.cpp \
 	dialogs/plugins/plugins.moc.cpp \
 	dialogs/podcast/podcast_configuration.moc.cpp \
 	dialogs/preferences/complete_preferences.moc.cpp \
@@ -454,12 +452,12 @@ nodist_libqt_plugin_la_SOURCES = \
 	menus/custom_menus.moc.cpp \
 	menus/qml_menu_wrapper.moc.cpp \
 	menus/menus.moc.cpp \
+	network/addonsmodel.moc.cpp \
 	network/devicesourceprovider.moc.cpp \
 	network/networkdevicemodel.moc.cpp \
 	network/networkbasemodel.moc.cpp \
 	network/networksourcesmodel.moc.cpp \
 	network/networkmediamodel.moc.cpp \
-	network/servicesdiscoverymodel.moc.cpp \
 	network/standardpathmodel.moc.cpp \
 	style/colorcontext.moc.cpp \
 	style/systempalette.moc.cpp \


=====================================
modules/gui/qt/dialogs/plugins/addons_manager.cpp deleted
=====================================
@@ -1,143 +0,0 @@
-/*****************************************************************************
- * addons_manager.cpp: Addons manager for Qt
- ****************************************************************************
- * Copyright (C) 2013 VideoLAN and authors
- *
- * 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 "addons_manager.hpp"
-#include <QApplication>
-
-const QEvent::Type AddonManagerEvent::AddedEvent =
-        (QEvent::Type)QEvent::registerEventType();
-const QEvent::Type AddonManagerEvent::ChangedEvent =
-        (QEvent::Type)QEvent::registerEventType();
-const QEvent::Type AddonManagerEvent::DiscoveryEndedEvent =
-        (QEvent::Type)QEvent::registerEventType();
-
-AddonsManager::AddonsManager( qt_intf_t *p_intf )
-{
-    struct addons_manager_owner owner =
-    {
-        this,
-        addonFoundCallback,
-        addonsDiscoveryEndedCallback,
-        addonChangedCallback,
-    };
-
-    p_manager = addons_manager_New( VLC_OBJECT(p_intf), &owner );
-}
-
-AddonsManager::~AddonsManager()
-{
-    if ( p_manager )
-        addons_manager_Delete( p_manager );
-}
-
-void AddonsManager::findNewAddons()
-{
-    addons_manager_Gather( p_manager, "repo://" );
-}
-
-void AddonsManager::findDesignatedAddon( QString uri )
-{
-    addons_manager_Gather( p_manager, qtu(uri) );
-}
-
-void AddonsManager::findInstalled()
-{
-    addons_manager_LoadCatalog( p_manager );
-}
-
-void AddonsManager::install( QByteArray id )
-{
-    Q_ASSERT( id.size() == sizeof(addon_uuid_t) );
-    addon_uuid_t addonid;
-    memcpy( &addonid, id.constData(), sizeof(addon_uuid_t) );
-    addons_manager_Install( p_manager, addonid );
-}
-
-void AddonsManager::remove( QByteArray id )
-{
-    Q_ASSERT( id.size() == sizeof(addon_uuid_t) );
-    addon_uuid_t addonid;
-    memcpy( &addonid, id.constData(), sizeof(addon_uuid_t) );
-    addons_manager_Remove( p_manager, addonid );
-}
-
-QString AddonsManager::getAddonType( int i_type )
-{
-    switch ( i_type )
-    {
-    case ADDON_SKIN2:
-        return qtr( "Skins" );
-    case ADDON_PLAYLIST_PARSER:
-        return qtr("Playlist parsers");
-    case ADDON_SERVICE_DISCOVERY:
-        return qtr("Service Discovery");
-    case ADDON_INTERFACE:
-        return qtr("Interfaces");
-    case ADDON_META:
-        return qtr("Art and meta fetchers");
-    case ADDON_EXTENSION:
-        return qtr("Extensions");
-    default:
-        return qtr("Unknown");
-    }
-}
-
-void AddonsManager::addonFoundCallback( addons_manager_t *manager,
-                                        addon_entry_t *entry )
-{
-    AddonsManager *me = (AddonsManager *) manager->owner.sys;
-    QEvent *ev = new AddonManagerEvent( AddonManagerEvent::AddedEvent,
-                                        entry );
-    QApplication::postEvent( me, ev );
-}
-
-void AddonsManager::addonsDiscoveryEndedCallback( addons_manager_t *manager )
-{
-    AddonsManager *me = (AddonsManager *) manager->owner.sys;
-    QEvent *ev = new QEvent( AddonManagerEvent::DiscoveryEndedEvent );
-    QApplication::postEvent( me, ev );
-}
-
-void AddonsManager::addonChangedCallback( addons_manager_t *manager,
-                                          addon_entry_t *entry )
-{
-    AddonsManager *me = (AddonsManager *) manager->owner.sys;
-    QEvent *ev = new AddonManagerEvent( AddonManagerEvent::ChangedEvent,
-                                        entry );
-    QApplication::postEvent( me, ev );
-}
-
-void AddonsManager::customEvent( QEvent *event )
-{
-    if ( event->type() == AddonManagerEvent::AddedEvent )
-    {
-        AddonManagerEvent *ev = static_cast<AddonManagerEvent *>(event);
-        emit addonAdded( ev->entry() );
-    }
-    else if ( event->type() == AddonManagerEvent::ChangedEvent )
-    {
-        AddonManagerEvent *ev = static_cast<AddonManagerEvent *>(event);
-        emit addonChanged( ev->entry() );
-    }
-    else if ( event->type() == AddonManagerEvent::DiscoveryEndedEvent )
-    {
-        emit discoveryEnded();
-    }
-}


=====================================
modules/gui/qt/dialogs/plugins/addons_manager.hpp deleted
=====================================
@@ -1,90 +0,0 @@
-/*****************************************************************************
- * addons_manager.hpp: Addons manager for Qt
- ****************************************************************************
- * Copyright (C) 2013 VideoLAN and authors
- *
- * 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 ADDONS_MANAGER_HPP
-#define ADDONS_MANAGER_HPP
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "qt.hpp"
-#include "util/singleton.hpp"
-
-#include <vlc_addons.h>
-
-#include <QObject>
-#include <QEvent>
-
-
-class AddonManagerEvent : public QEvent
-{
-public:
-    static const QEvent::Type AddedEvent;
-    static const QEvent::Type ChangedEvent;
-    static const QEvent::Type DiscoveryEndedEvent;
-
-    AddonManagerEvent( QEvent::Type type, addon_entry_t *_p_entry )
-        : QEvent( type ), p_entry( _p_entry )
-    {
-        addon_entry_Hold( p_entry );
-    }
-    virtual ~AddonManagerEvent()
-    {
-        addon_entry_Release( p_entry );
-    }
-
-    addon_entry_t *entry() const { return p_entry; }
-
-private:
-    addon_entry_t *p_entry;
-};
-
-class AddonsManager : public QObject, public Singleton<AddonsManager>
-{
-    Q_OBJECT
-    friend class Singleton<AddonsManager>;
-
-public:
-    AddonsManager( qt_intf_t * );
-    virtual ~AddonsManager();
-    static void addonFoundCallback( addons_manager_t *, addon_entry_t * );
-    static void addonsDiscoveryEndedCallback( addons_manager_t * );
-    static void addonChangedCallback( addons_manager_t *, addon_entry_t * );
-    void customEvent( QEvent * );
-    void install( QByteArray id );
-    void remove( QByteArray id );
-    static QString getAddonType( int );
-
-signals:
-    void addonAdded( addon_entry_t * );
-    void addonChanged( const addon_entry_t * );
-    void discoveryEnded();
-
-public slots:
-    void findNewAddons();
-    void findDesignatedAddon( QString uri );
-    void findInstalled();
-
-private:
-    addons_manager_t* p_manager;
-};
-
-#endif // ADDONS_MANAGER_HPP


=====================================
modules/gui/qt/dialogs/plugins/plugins.cpp
=====================================
@@ -29,7 +29,7 @@
 
 #include "widgets/native/searchlineedit.hpp"
 #include "dialogs/extensions/extensions_manager.hpp"
-#include "addons_manager.hpp"
+#include "network/addonsmodel.hpp"
 #include "widgets/native/animators.hpp"
 #include "util/imagehelper.hpp"
 
@@ -66,6 +66,7 @@
 #include <QStackedWidget>
 #include <QPainterPath>
 #include <QSignalMapper>
+#include <QtQml/QQmlFile>
 
 //match the image source (width/height)
 #define SCORE_ICON_WIDTH_SCALE 4
@@ -302,28 +303,6 @@ void ExtensionTab::moreInformation()
     dlg.exec();
 }
 
-static QIcon iconFromCategory( int type )
-{
-    switch( type )
-    {
-        case ADDON_EXTENSION:
-            return QIcon( ":/addons/addon_yellow.svg" );
-        case ADDON_PLAYLIST_PARSER:
-            return QIcon( ":/addons/addon_green.svg" );
-        case ADDON_SERVICE_DISCOVERY:
-            return QIcon( ":/addons/addon_red.svg" );
-        case ADDON_SKIN2:
-            return QIcon( ":/addons/addon_cyan.svg" );
-        case ADDON_INTERFACE:
-            return QIcon( ":/addons/addon_blue.svg" );
-        case ADDON_META:
-            return QIcon( ":/addons/addon_magenta.svg" );
-        default:
-            return QIcon( ":/addons/default.svg" );
-    }
-    vlc_assert_unreachable();
-}
-
 /* Add-ons tab */
 AddonsTab::AddonsTab( qt_intf_t *p_intf_ ) : QVLCFrame( p_intf_ )
 {
@@ -354,49 +333,53 @@ AddonsTab::AddonsTab( qt_intf_t *p_intf_ ) : QVLCFrame( p_intf_ )
     leftPane->layout()->addWidget( searchInput );
     leftPane->layout()->addItem( new QSpacerItem( 0, 10 ) );
 
-    QToolButton * button;
     signalMapper = new QSignalMapper();
-#define ADD_CATEGORY( label, ltooltip, numb ) \
-    button = new QToolButton( this );\
-    button->setIcon( iconFromCategory( numb ) ); \
-    button->setText( label );\
-    button->setToolTip( ltooltip );\
-    button->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );\
-    button->setIconSize( QSize( 32, 32 ) );\
-    button->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum) ;\
-    button->setMinimumSize( 32, 32 );\
-    button->setAutoRaise( true );\
-    button->setCheckable( true );\
-    if ( numb == -1 ) button->setChecked( true );\
-    button->setAutoExclusive( true );\
-    connect( button, &QToolButton::clicked, signalMapper, QOverload<>::of(&QSignalMapper::map) );\
-    signalMapper->setMapping( button, numb );\
-    leftPane->layout()->addWidget( button );
-
-    ADD_CATEGORY( qtr("All"), qtr("Interface Settings"),
-                  -1 );
-    ADD_CATEGORY( qtr("Skins"),
+
+    auto addCategory = [this, leftPane]( const QString& label, const QString& ltooltip,  AddonsModel::Type type) {
+        auto button = new QToolButton( this );
+
+        QString iconpath = QQmlFile::urlToLocalFileOrQrc(AddonsModel::getIconForType( type ));
+        button->setIcon( QIcon{ iconpath } );
+        button->setText( label );
+        button->setToolTip( ltooltip );
+        button->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+        button->setIconSize( QSize( 32, 32 ) );
+        button->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum) ;
+        button->setMinimumSize( 32, 32 );
+        button->setAutoRaise( true );
+        button->setCheckable( true );
+        if ( type == AddonsModel::Type::TYPE_NONE )
+            button->setChecked( true );
+        button->setAutoExclusive( true );
+        connect( button, &QToolButton::clicked, signalMapper, QOverload<>::of(&QSignalMapper::map) );
+        signalMapper->setMapping( button, static_cast<int>(type) );
+        leftPane->layout()->addWidget( button );
+    };
+
+    addCategory( qtr("All"), qtr("Interface Settings"),
+                  AddonsModel::Type::TYPE_NONE );
+    addCategory( qtr("Skins"),
                   qtr( "Skins customize player's appearance."
                        " You can activate them through preferences." ),
-                  ADDON_SKIN2 );
-    ADD_CATEGORY( qtr("Playlist parsers"),
+                  AddonsModel::Type::TYPE_SKIN2 );
+    addCategory( qtr("Playlist parsers"),
                   qtr( "Playlist parsers add new capabilities to read"
                        " internet streams or extract meta data." ),
-                  ADDON_PLAYLIST_PARSER );
-    ADD_CATEGORY( qtr("Service Discovery"),
+                  AddonsModel::Type::TYPE_PLAYLIST_PARSER );
+    addCategory( qtr("Service Discovery"),
                   qtr( "Service discoveries adds new sources to your playlist"
                        " such as web radios, video websites, ..." ),
-                  ADDON_SERVICE_DISCOVERY );
-    ADD_CATEGORY( qtr("Interfaces"),
+                  AddonsModel::Type::TYPE_SERVICE_DISCOVERY );
+    addCategory( qtr("Interfaces"),
                   "",
-                  ADDON_INTERFACE );
-    ADD_CATEGORY( qtr("Art and meta fetchers"),
+                  AddonsModel::Type::TYPE_INTERFACE );
+    addCategory( qtr("Art and meta fetchers"),
                   qtr( "Retrieves extra info and art for playlist items" ),
-                  ADDON_META );
-    ADD_CATEGORY( qtr("Extensions"),
+                  AddonsModel::Type::TYPE_META );
+    addCategory( qtr("Extensions"),
                   qtr( "Extensions brings various enhancements."
                        " Check descriptions for more details" ),
-                  ADDON_EXTENSION );
+                  AddonsModel::Type::TYPE_EXTENSION );
 
     // Right Pane
     rightPane->layout()->setContentsMargins(0, 0, 0, 0);
@@ -420,20 +403,44 @@ AddonsTab::AddonsTab( qt_intf_t *p_intf_ ) : QVLCFrame( p_intf_ )
     switchStack->insertWidget( WITHONLINEADDONS, installedOnlyBox );
     connect( installedOnlyBox, &QCheckBox::stateChanged, this, &AddonsTab::installChecked );
 
+    // Model
+    m_model = std::make_unique<AddonsModel>( );
+    //model expect QMLlike behavior
+    m_model->classBegin();
+    m_model->setCtx(p_intf->p_mi);
+    connect( signalMapper, &QSignalMapper::mappedInt,
+            m_model.get(), [model = m_model.get()](int mapped){
+                model->setTypeFilter(static_cast<AddonsModel::Type>(mapped));
+            });
+
+    connect( searchInput, &SearchLineEdit::textChanged,
+            m_model.get(), &AddonsModel::setSearchPattern );
+
+
+    // Update button
     QPushButton *reposyncButton = new QPushButton( QIcon( ":/menu/update.svg" ),
                                               qtr("Find more addons online") );
     reposyncButton->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
     switchStack->insertWidget( ONLYLOCALADDONS, reposyncButton );
     switchStack->setCurrentIndex( ONLYLOCALADDONS );
     connect( reposyncButton, &QPushButton::clicked, this, &AddonsTab::reposync );
+    connect(
+        m_model.get(), &AddonsModel::loadingChanged,
+        this, [this]() {
+            if ( !m_model->loading()) {
+                spinnerAnimation->stop();
+                addonsView->viewport()->update();
+            }
+        });
 
     leftPane->layout()->addItem( new QSpacerItem( 0, 0, QSizePolicy::Maximum, QSizePolicy::Expanding ) );
 
     // Main View
-    AddonsManager *AM = AddonsManager::getInstance( p_intf );
 
     // ListView
     addonsView = new QListView( this );
+    addonsView->setModel(m_model.get());
+
     connect( addonsView, &QListView::activated, this, &AddonsTab::moreInformation );
     layout->addWidget( addonsView );
 
@@ -453,30 +460,9 @@ AddonsTab::AddonsTab( qt_intf_t *p_intf_ ) : QVLCFrame( p_intf_ )
     addonsView->setDropIndicatorShown( true );
     addonsView->setDragDropMode( QAbstractItemView::DropOnly );
 
-    // Model
-    AddonsListModel *model = new AddonsListModel( AM, addonsView );
-    addonsModel = new AddonsSortFilterProxyModel( addonsView );
-    addonsModel->setDynamicSortFilter( true );
-    addonsModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
-    addonsModel->setSortRole( Qt::DisplayRole );
-    addonsModel->sort( 0, Qt::AscendingOrder );
-    addonsModel->setSourceModel( model );
-    addonsModel->setFilterRole( Qt::DisplayRole );
-    addonsView->setModel( addonsModel );
-
-    connect( signalMapper, &QSignalMapper::mappedInt,
-             addonsModel, &AddonsSortFilterProxyModel::setTypeFilter );
-
-    connect( searchInput, &SearchLineEdit::textChanged,
-             addonsModel, &AddonsSortFilterProxyModel::setFilterFixedString );
-
     connect( addonsView->selectionModel(), &QItemSelectionModel::currentChanged,
              addonsView, QOverload<const QModelIndex&>::of(&QListView::edit) );
 
-    connect( AM, SIGNAL(addonAdded( addon_entry_t * )),
-             model, SLOT(addonAdded( addon_entry_t * )) );
-    connect( AM, SIGNAL(addonChanged( const addon_entry_t * )),
-             model, SLOT(addonChanged( const addon_entry_t * )) );
 
     QList<QString> frames;
     frames << ":/misc/wait1.svg";
@@ -487,6 +473,9 @@ AddonsTab::AddonsTab( qt_intf_t *p_intf_ ) : QVLCFrame( p_intf_ )
     connect( spinnerAnimation, &PixmapAnimator::pixmapReady,
              addonsView->viewport(), QOverload<>::of(&QWidget::update) );
     addonsView->viewport()->installEventFilter( this );
+
+    //model expect QMLlike behavior
+    m_model->componentComplete();
 }
 
 AddonsTab::~AddonsTab()
@@ -528,7 +517,7 @@ bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
             point -= QPoint( textsize.width() / 2, -spinner.height() );
             painter.drawText( point, text );
         }
-        else if ( addonsModel->rowCount() == 0 )
+        else if ( m_model->rowCount() == 0 )
         {
             QWidget *viewport = qobject_cast<QWidget *>( obj );
             if ( !viewport ) break;
@@ -540,14 +529,6 @@ bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
             painter.drawText( point, text );
         }
         break;
-    case QEvent::Show:
-        if ( !b_localdone && addonsView->model()->rowCount() < 1 )
-        {
-            b_localdone = true;
-            AddonsManager *AM = AddonsManager::getInstance( p_intf );
-            AM->findInstalled();
-        }
-        break;
     case QEvent::DragEnter:
     {
         QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent *>(event);
@@ -578,8 +559,7 @@ bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
             return false;
         if ( dropEvent->mimeData()->urls().count() )
         {
-            AddonsManager *AM = AddonsManager::getInstance( p_intf );
-            AM->findDesignatedAddon( dropEvent->mimeData()->urls().first().toString() );
+            m_model->loadFromExternalRepository(dropEvent->mimeData()->urls().first());
             dropEvent->acceptProposedAction();
         }
         return true;
@@ -603,9 +583,9 @@ void AddonsTab::moreInformation()
 void AddonsTab::installChecked( int i )
 {
     if ( i == Qt::Checked )
-        addonsModel->setStatusFilter( ADDON_INSTALLED );
+        m_model->setStateFilter( AddonsModel::State::STATE_INSTALLED );
     else
-        addonsModel->setStatusFilter( 0 );
+        m_model->setStateFilter( AddonsModel::State::STATE_NONE );
 }
 
 void AddonsTab::reposync()
@@ -614,11 +594,10 @@ void AddonsTab::reposync()
     if ( tab )
     {
         tab->setCurrentIndex( WITHONLINEADDONS );
-        AddonsManager *AM = AddonsManager::getInstance( p_intf );
-        connect( AM, &AddonsManager::discoveryEnded, spinnerAnimation, &PixmapAnimator::stop );
-        connect( AM, &AddonsManager::discoveryEnded, addonsView->viewport(), QOverload<>::of(&QWidget::update) );
+
         spinnerAnimation->start();
-        AM->findNewAddons();
+
+        m_model->loadFromDefaultRepository();
     }
 }
 
@@ -758,300 +737,22 @@ QModelIndex ExtensionListModel::index( int row, int column,
     return createIndex( row, 0, extensions.at( row ) );
 }
 
-AddonsListModel::Addon::Addon( addon_entry_t *p_entry_ )
-{
-    p_entry = p_entry_;
-    addon_entry_Hold( p_entry );
-}
-
-AddonsListModel::Addon::~Addon()
-{
-    addon_entry_Release( p_entry );
-}
-
-bool AddonsListModel::Addon::operator==( const Addon & other ) const
-{
-    //return data( IDRole ) == other.data( IDRole );
-    return p_entry == other.p_entry;
-}
-
-bool AddonsListModel::Addon::operator==( const addon_entry_t * p_other ) const
-{
-    return p_entry == p_other;
-}
-
-QVariant AddonsListModel::Addon::data( int role ) const
-{
-    QVariant returnval;
-
-    vlc_mutex_lock( &p_entry->lock );
-    switch( role )
-    {
-    case Qt::DisplayRole:
-    {
-        returnval = qfu( p_entry->psz_name );
-        break;
-    }
-    case Qt::DecorationRole:
-        if ( p_entry->psz_image_data )
-        {
-            QPixmap pixmap;
-            pixmap.loadFromData( QByteArray::fromBase64( QByteArray( p_entry->psz_image_data ) ),
-                0,
-                Qt::AutoColor
-            );
-            returnval = pixmap;
-        }
-        else if ( p_entry->e_flags & ADDON_BROKEN )
-            returnval = QPixmap( ":/addons/addon_broken.svg" );
-        else
-            returnval = QPixmap( ":/addons/addon_default.svg" );
-        break;
-    case Qt::ToolTipRole:
-    {
-        if ( !( p_entry->e_flags & ADDON_MANAGEABLE ) )
-        {
-            returnval = qtr("This addon has been installed manually. VLC can't manage it by itself.");
-        }
-        break;
-    }
-    case SummaryRole:
-        returnval = qfu( p_entry->psz_summary );
-        break;
-    case DescriptionRole:
-        returnval = qfu( p_entry->psz_description );
-        break;
-    case TypeRole:
-        returnval = QVariant( (int) p_entry->e_type );
-        break;
-    case UUIDRole:
-        returnval = QByteArray( (const char *) p_entry->uuid, (int) sizeof( addon_uuid_t ) );
-        break;
-    case FlagsRole:
-        returnval = QVariant( (int) p_entry->e_flags );
-        break;
-    case StateRole:
-        returnval = QVariant( (int) p_entry->e_state );
-        break;
-    case DownloadsCountRole:
-        returnval = QVariant( (double) p_entry->i_downloads );
-        break;
-    case ScoreRole:
-        returnval = QVariant( (int) p_entry->i_score );
-        break;
-    case VersionRole:
-        returnval = QVariant( qfu(p_entry->psz_version) );
-        break;
-    case AuthorRole:
-        returnval = qfu( p_entry->psz_author );
-        break;
-    case LinkRole:
-        returnval = qfu( p_entry->psz_source_uri );
-        break;
-    case FilenameRole:
-    {
-        QList<QString> list;
-        addon_file_t *p_file;
-        ARRAY_FOREACH( p_file, p_entry->files )
-            list << qfu( p_file->psz_filename );
-        returnval = QVariant( list );
-        break;
-    }
-    default:
-        break;
-    }
-    vlc_mutex_unlock( &p_entry->lock );
-
-    return returnval;
-}
-
-AddonsListModel::AddonsListModel( AddonsManager *AM_, QObject *parent )
-    :ExtensionListModel( parent ), AM( AM_ )
-{
-
-}
-
-AddonsListModel::~AddonsListModel()
-{
-    qDeleteAll( addons );
-}
-
-void AddonsListModel::addonAdded(  addon_entry_t *p_entry )
-{
-    beginInsertRows( QModelIndex(), addons.count(), addons.count() );
-    addons << new Addon( p_entry );
-    insertRow( addons.count() - 1 );
-    endInsertRows();
-}
-
-void AddonsListModel::addonChanged( const addon_entry_t *p_entry )
-{
-    int row = 0;
-    foreach ( const Addon *addon, addons )
-    {
-        if ( *addon == p_entry )
-        {
-            emit dataChanged( index( row, 0 ), index( row, 0 ) );
-            break;
-        }
-        row++;
-    }
-}
-
-int AddonsListModel::rowCount( const QModelIndex & ) const
-{
-    return addons.count();
-}
-
-Qt::ItemFlags AddonsListModel::flags( const QModelIndex &index ) const
-{
-    Qt::ItemFlags i_flags = ExtensionListModel::flags( index );
-    int i_state = data( index, StateRole ).toInt();
-
-    if ( i_state == ADDON_UNINSTALLING || i_state == ADDON_INSTALLING )
-        i_flags &= ~Qt::ItemIsEnabled;
-
-    i_flags |= Qt::ItemIsEditable;
-
-    return i_flags;
-}
-
-bool AddonsListModel::setData( const QModelIndex &index, const QVariant &value, int role )
-{
-    /* We NEVER set values directly */
-    if ( role == StateRole )
-    {
-        int i_value = value.toInt();
-        if ( i_value == ADDON_INSTALLING )
-        {
-            AM->install( data( index, UUIDRole ).toByteArray() );
-        }
-        else if ( i_value == ADDON_UNINSTALLING )
-        {
-            AM->remove( data( index, UUIDRole ).toByteArray() );
-        }
-    }
-    else if ( role == StateRole + 1 )
-    {
-        emit dataChanged( index, index );
-    }
-    return true;
-}
-
-QColor AddonsListModel::getColorByAddonType( int i_type )
-{
-    QColor color;
-    switch( i_type )
-    {
-    case ADDON_EXTENSION:
-        color = QColor(0xDB, 0xC5, 0x40);
-        break;
-    case ADDON_PLAYLIST_PARSER:
-        color = QColor(0x36, 0xBB, 0x59);
-        break;
-    case ADDON_SERVICE_DISCOVERY:
-        color = QColor(0xDB, 0x52, 0x40);
-        break;
-    case ADDON_SKIN2:
-        color = QColor(0x8B, 0xD6, 0xFC);
-        break;
-    case ADDON_INTERFACE:
-        color = QColor(0x00, 0x13, 0x85);
-        break;
-    case ADDON_META:
-        color = QColor(0xCD, 0x23, 0xBF);
-        break;
-    case ADDON_PLUGIN:
-    case ADDON_UNKNOWN:
-    case ADDON_OTHER:
-    default:
-        break;
-    }
-    return color;
-}
-
-QVariant AddonsListModel::data( const QModelIndex& index, int role ) const
-{
-    if( !index.isValid() )
-        return QVariant();
-
-    return ((Addon *)index.internalPointer())->data( role );
-}
-
-QModelIndex AddonsListModel::index( int row, int column,
-                                       const QModelIndex& ) const
-{
-    if( column != 0 )
-        return QModelIndex();
-    if( row < 0 || row >= addons.count() )
-        return QModelIndex();
-
-    return createIndex( row, 0, addons.at( row ) );
-}
-
-/* Sort Filter */
-AddonsSortFilterProxyModel::AddonsSortFilterProxyModel( QObject *parent )
-    : QSortFilterProxyModel( parent )
-{
-    i_type_filter = -1;
-    i_status_filter = 0;
-}
-
-void AddonsSortFilterProxyModel::setTypeFilter( int type )
-{
-    i_type_filter = type;
-    invalidateFilter();
-}
-
-void AddonsSortFilterProxyModel::setStatusFilter( int flags )
-{
-    i_status_filter = flags;
-    invalidateFilter();
-}
-
-bool AddonsSortFilterProxyModel::filterAcceptsRow( int source_row,
-                                       const QModelIndex &source_parent ) const
-{
-    if ( !QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
-        return false;
-
-    QModelIndex item = sourceModel()->index( source_row, 0, source_parent );
-
-    if ( i_type_filter > -1 &&
-         item.data( AddonsListModel::TypeRole ).toInt() != i_type_filter )
-        return false;
-
-    if ( i_status_filter > 0 &&
-        ( item.data( AddonsListModel::StateRole ).toInt() & i_status_filter ) != i_status_filter )
-        return false;
-
-    return true;
-}
-
-/* Extension List Widget Item */
-ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
-        : QStyledItemDelegate( parent )
-{
-    margins = QMargins( 4, 4, 4, 4 );
-}
-
-ExtensionItemDelegate::~ExtensionItemDelegate()
-{
-}
-
-void ExtensionItemDelegate::paint( QPainter *painter,
-                                   const QStyleOptionViewItem &option,
-                                   const QModelIndex &index ) const
+static void commonPaint(
+    QPainter *painter,
+    const QStyleOptionViewItem &option,
+    const QMargins& margins,
+    const QPixmap& icon,
+    const QString& name,
+    const QString& description
+    )
 {
     QStyleOptionViewItem opt = option;
-    initStyleOption( &opt, index );
 
     // Draw background
     if ( opt.state & QStyle::State_Selected )
         painter->fillRect( opt.rect, opt.palette.highlight() );
 
     // Icon
-    QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
     if( !icon.isNull() )
     {
         painter->drawPixmap( opt.rect.left() + margins.left(),
@@ -1077,18 +778,50 @@ void ExtensionItemDelegate::paint( QPainter *painter,
                      - margins.right(),
                      - margins.bottom() - opt.fontMetrics.height() );
 
-    painter->drawText( textrect, Qt::AlignLeft,
-                       index.data( Qt::DisplayRole ).toString() );
+    painter->drawText( textrect, Qt::AlignLeft, name);
 
     font.setBold( false );
     painter->setFont( font );
     painter->drawText( textrect.translated( 0, option.fontMetrics.height() ),
                        Qt::AlignLeft,
-                       index.data( ExtensionListModel::SummaryRole ).toString() );
+                       description );
 
     painter->restore();
 }
 
+/* Extension List Widget Item */
+ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
+        : QStyledItemDelegate( parent )
+{
+    margins = QMargins( 4, 4, 4, 4 );
+}
+
+ExtensionItemDelegate::~ExtensionItemDelegate()
+{
+}
+
+void ExtensionItemDelegate::paint( QPainter *painter,
+                                   const QStyleOptionViewItem &option,
+                                   const QModelIndex &index ) const
+{
+    QStyleOptionViewItem opt = option;
+    initStyleOption( &opt, index );
+
+    /* Draw common base  */
+    QString name = index.data( Qt::DisplayRole ).toString();
+    QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
+    QString description = index.data( ExtensionListModel::SummaryRole ).toString();
+
+    commonPaint(
+        painter,
+        option,
+        margins,
+        icon,
+        name,
+        description
+        );
+}
+
 QSize ExtensionItemDelegate::sizeHint( const QStyleOptionViewItem &option,
                                        const QModelIndex &index ) const
 {
@@ -1111,9 +844,8 @@ void ExtensionItemDelegate::initStyleOption( QStyleOptionViewItem *option,
 }
 
 AddonItemDelegate::AddonItemDelegate( QObject *parent )
-    : ExtensionItemDelegate( parent )
-    , animator( NULL )
-    , progressbar( NULL )
+    : QStyledItemDelegate( parent )
+    , margins( 4,4,4,4 )
 { }
 
 AddonItemDelegate::~AddonItemDelegate()
@@ -1126,11 +858,11 @@ void AddonItemDelegate::paint( QPainter *painter,
                                const QModelIndex &index ) const
 {
     QStyleOptionViewItem newopt = option;
-    int i_state = index.data( AddonsListModel::StateRole ).toInt();
-    int i_type = index.data( AddonsListModel::TypeRole ).toInt();
+    auto i_state = index.data( AddonsModel::Role::STATE ).value<AddonsModel::State>();
+    auto i_type = index.data( AddonsModel::Role::TYPE ).value<AddonsModel::Type>();
 
     /* Draw Background gradient by addon type */
-    QColor backgroundColor = AddonsListModel::getColorByAddonType( i_type );
+    QColor backgroundColor = AddonsModel::getColorForType( i_type );
 
     if ( backgroundColor.isValid() )
     {
@@ -1146,15 +878,27 @@ void AddonItemDelegate::paint( QPainter *painter,
     }
 
     /* Draw base info from parent */
-    ExtensionItemDelegate::paint( painter, newopt, index );
-
     initStyleOption( &newopt, index );
 
+    /* Draw common base  */
+    QString name = index.data( Qt::DisplayRole ).toString();
+    QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
+    QString description = index.data( AddonsModel::Role::SUMMARY ).toString();
+
+    commonPaint(
+        painter,
+        newopt,
+        margins,
+        icon,
+        name,
+        description
+        );
+
     painter->save();
     painter->setRenderHint( QPainter::TextAntialiasing );
 
     /* Addon status */
-    if ( i_state == ADDON_INSTALLED )
+    if ( i_state == AddonsModel::State::STATE_INSTALLED )
     {
         painter->save();
         painter->setRenderHint( QPainter::Antialiasing );
@@ -1192,14 +936,14 @@ void AddonItemDelegate::paint( QPainter *painter,
     textrect.translate( 0, newopt.fontMetrics.height() * 2 );
 
     /* Version */
-    QString version = index.data( AddonsListModel::VersionRole ).toString();
+    QString version = index.data( AddonsModel::Role::ADDON_VERSION).toString();
     if ( !version.isEmpty() )
         painter->drawText( textrect, Qt::AlignLeft, qtr("Version %1").arg( version ) );
 
     textrect.translate( 0, newopt.fontMetrics.height() );
 
     /* Score */
-    int i_score = index.data( AddonsListModel::ScoreRole ).toInt();
+    int i_score = index.data( AddonsModel::Role::SCORE).toInt();
     QPixmap scoreicon;
     if ( i_score )
     {
@@ -1207,7 +951,7 @@ void AddonItemDelegate::paint( QPainter *painter,
         int i_scoreicon_width = i_scoreicon_height * SCORE_ICON_WIDTH_SCALE;
         scoreicon = ImageHelper::loadSvgToPixmap( ":/addons/addon_score.svg",
                     i_scoreicon_width, i_scoreicon_height );
-        int i_width = ( (float) i_score / ADDON_MAX_SCORE ) * i_scoreicon_width;
+        int i_width = ( (float) i_score / AddonsModel::getMaxScore() ) * i_scoreicon_width;
         /* Erase the end (value) of our pixmap with a shadow */
         QPainter erasepainter( &scoreicon );
         erasepainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
@@ -1219,7 +963,7 @@ void AddonItemDelegate::paint( QPainter *painter,
     }
 
     /* Downloads # */
-    int i_downloads = index.data( AddonsListModel::DownloadsCountRole ).toInt();
+    int i_downloads = index.data( AddonsModel::DOWNLOAD_COUNT).toInt();
     if ( i_downloads )
         painter->drawText( textrect.translated( scoreicon.width() + margins.left(), 0 ),
                            Qt::AlignLeft, qtr("%1 downloads").arg( i_downloads ) );
@@ -1230,12 +974,14 @@ void AddonItemDelegate::paint( QPainter *painter,
     {
         if ( animator->isRunning() && animator->getIndex() == index )
         {
-            if ( i_state != ADDON_INSTALLING && i_state != ADDON_UNINSTALLING )
+            if ( i_state != AddonsModel::State::STATE_INSTALLING
+                && i_state != AddonsModel::State::STATE_UNINSTALLING )
                 animator->run( false );
         }
         /* Create our installation progress overlay */
 
-        if ( i_state == ADDON_INSTALLING || i_state == ADDON_UNINSTALLING )
+        if ( i_state == AddonsModel::State::STATE_INSTALLING
+            || i_state == AddonsModel::State::STATE_UNINSTALLING )
         {
             painter->save();
             painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
@@ -1269,6 +1015,15 @@ QSize AddonItemDelegate::sizeHint( const QStyleOptionViewItem &option,
         return QSize();
 }
 
+void AddonItemDelegate::initStyleOption( QStyleOptionViewItem *option,
+                                            const QModelIndex &index ) const
+{
+    QStyledItemDelegate::initStyleOption( option, index );
+    option->decorationSize = QSize( option->rect.height(), option->rect.height() );
+    option->decorationSize -= QSize( margins.left() + margins.right(),
+                                    margins.top() + margins.bottom() );
+}
+
 QWidget *AddonItemDelegate::createEditor( QWidget *parent,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &index) const
@@ -1286,10 +1041,10 @@ QWidget *AddonItemDelegate::createEditor( QWidget *parent,
     connect( infoButton, &QPushButton::clicked, this, &AddonItemDelegate::showInfo );
     editorWidget->layout()->addWidget( infoButton );
 
-    if ( ADDON_MANAGEABLE &
-         index.data( AddonsListModel::FlagsRole ).toInt() )
+    if ( index.data( AddonsModel::Role::MANAGEABLE).toBool() )
     {
-        if ( index.data( AddonsListModel::StateRole ).toInt() == ADDON_INSTALLED )
+        if ( index.data( AddonsModel::Role::STATE ).value<AddonsModel::State>()
+            == AddonsModel::State::STATE_INSTALLED )
             installButton = new QPushButton( QIcon( ":/menu/remove.svg" ),
                                              qtr("&Uninstall"), parent );
         else
@@ -1319,12 +1074,12 @@ void AddonItemDelegate::updateEditorGeometry( QWidget *editor,
 void AddonItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
                                       const QModelIndex &index ) const
 {
-    model->setData( index, editor->property("Addon::state"), AddonsListModel::StateRole );
+    model->setData( index, editor->property("Addon::state"), AddonsModel::Role::STATE);
 }
 
 void AddonItemDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
 {
-    editor->setProperty("Addon::state", index.data( AddonsListModel::StateRole ) );
+    editor->setProperty("Addon::state", index.data( AddonsModel::Role::STATE ) );
 }
 
 void AddonItemDelegate::setAnimator( DelegateAnimationHelper *animator_ )
@@ -1344,13 +1099,13 @@ void AddonItemDelegate::editButtonClicked()
 {
     QWidget *editor = qobject_cast<QWidget *>(sender()->parent());
     if ( !editor ) return;
-    int value = editor->property("Addon::state").toInt();
-    if ( value == ADDON_INSTALLED )
+    auto value = editor->property("Addon::state").value<AddonsModel::State>();
+    if ( value == AddonsModel::State::STATE_INSTALLED )
         /* uninstall */
-        editor->setProperty("Addon::state", ADDON_UNINSTALLING );
+        editor->setProperty("Addon::state", QVariant::fromValue(AddonsModel::State::STATE_UNINSTALLING) );
     else
         /* install */
-        editor->setProperty("Addon::state", ADDON_INSTALLING );
+        editor->setProperty("Addon::state", QVariant::fromValue(AddonsModel::State::STATE_INSTALLING) );
     emit commitData( editor );
     emit closeEditor( editor );
 }
@@ -1449,7 +1204,7 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     setWindowModality( Qt::WindowModal );
 
     // Window title
-    setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
+    setWindowTitle( qtr( "About" ) + " " + index.data(AddonsModel::Role::NAME).toString() );
 
     // Layout
     QGridLayout *layout = new QGridLayout( this );
@@ -1465,7 +1220,7 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     layout->addWidget( iconLabel, 1, 0, 2, 1 );
 
     // Title
-    label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
+    label = new QLabel( index.data(AddonsModel::Role::NAME).toString(), this );
     QFont font = label->font();
     font.setBold( true );
     font.setPointSizeF( font.pointSizeF() * 1.3f );
@@ -1483,12 +1238,12 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     layout->addWidget( textContent, 1, 1, 4, -1 );
 
     // Type
-    QString type = AddonsManager::getAddonType( index.data(AddonsListModel::TypeRole).toInt() );
+    QString type = AddonsModel::getLabelForType( index.data(AddonsModel::Role::TYPE).value<AddonsModel::Type>() );
     textContent->append( QString("<b>%1:</b> %2<br/>")
                          .arg( qtr("Type") ).arg( type ) );
 
     // Version
-    QString version = index.data(ExtensionListModel::VersionRole).toString();
+    QString version = index.data(AddonsModel::Role::ADDON_VERSION).toString();
     if ( !version.isEmpty() )
     {
         textContent->append( QString("<b>%1:</b> %2<br/>")
@@ -1496,7 +1251,7 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     }
 
     // Author
-    QString author = index.data(ExtensionListModel::AuthorRole).toString();
+    QString author = index.data(AddonsModel::Role::AUTHOR).toString();
     if ( !author.isEmpty() )
     {
         textContent->append( QString("<b>%1:</b> %2<br/>")
@@ -1505,10 +1260,10 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
 
     // Summary
     textContent->append( QString("%1<br/>\n")
-                .arg( index.data(AddonsListModel::SummaryRole).toString() ) );
+                .arg( index.data(AddonsModel::Role::SUMMARY).toString() ) );
 
     // Description
-    QString description = index.data(AddonsListModel::DescriptionRole).toString();
+    QString description = index.data(AddonsModel::Role::DESCRIPTION).toString();
     if ( !description.isEmpty() )
     {
         textContent->append( QString("<hr/>\n%1")
@@ -1516,7 +1271,7 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     }
 
     // URL
-    QString sourceUrl = index.data(ExtensionListModel::LinkRole).toString();
+    QString sourceUrl = index.data(AddonsModel::Role::LINK).toString();
     if ( !sourceUrl.isEmpty() )
     {
         label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
@@ -1528,7 +1283,7 @@ AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
     }
 
     // Script files
-    QList<QVariant> list = index.data(ExtensionListModel::FilenameRole).toList();
+    QList<QVariant> list = index.data(AddonsModel::Role::FILENAME).toList();
     if ( ! list.empty() )
     {
         label = new QLabel( "<b>" + qtr( "Files" ) + ":</b>", this );


=====================================
modules/gui/qt/dialogs/plugins/plugins.hpp
=====================================
@@ -50,10 +50,9 @@ class ExtensionListItem;
 class SearchLineEdit;
 class ExtensionCopy;
 class ExtensionsManager;
-class AddonsManager;
 class PixmapAnimator;
 class DelegateAnimationHelper;
-class AddonsSortFilterProxyModel;
+class AddonsModel;
 
 extern "C" {
     typedef struct extension_t extension_t;
@@ -148,11 +147,11 @@ private:
         WITHONLINEADDONS
     };
     QListView *addonsView;
-    AddonsSortFilterProxyModel *addonsModel;
     /* Wait spinner */
     PixmapAnimator *spinnerAnimation;
     bool b_localdone;
     QSignalMapper *signalMapper;
+    std::unique_ptr<AddonsModel> m_model;
 };
 
 class PluginTreeItem : public QTreeWidgetItem
@@ -211,74 +210,6 @@ private:
     QList<ExtensionCopy*> extensions;
 };
 
-class AddonsListModel: public ExtensionListModel
-{
-    Q_OBJECT
-
-public:
-    AddonsListModel( AddonsManager *AM, QObject *parent = 0 );
-    virtual ~AddonsListModel();
-    QVariant data( const QModelIndex& index, int role ) const override;
-    QModelIndex index( int row, int column = 0,
-                       const QModelIndex& = QModelIndex() ) const override;
-    int rowCount( const QModelIndex& = QModelIndex() ) const override;
-    Qt::ItemFlags flags( const QModelIndex &index ) const override;
-    bool setData( const QModelIndex &index, const QVariant &value, int role ) override;
-
-    enum
-    {
-        TypeRole = FilenameRole + 1,
-        DescriptionRole,
-        UUIDRole,
-        FlagsRole,
-        StateRole,
-        DownloadsCountRole,
-        ScoreRole
-    };
-
-    static QColor getColorByAddonType( int );
-
-protected slots:
-    void addonAdded( addon_entry_t * );
-    void addonChanged( const addon_entry_t * );
-
-protected:
-
-    class Addon
-    {
-    public:
-        Addon( addon_entry_t * );
-        ~Addon();
-        bool operator==( const Addon & other ) const;
-        bool operator==( const addon_entry_t * p_other ) const;
-        QVariant data( int ) const;
-    private:
-        addon_entry_t * p_entry;
-    };
-
-    QList<Addon*> addons;
-    AddonsManager *AM;
-};
-
-class AddonsSortFilterProxyModel : public QSortFilterProxyModel
-{
-    Q_OBJECT
-
-public:
-    AddonsSortFilterProxyModel( QObject *parent = 0 );
-
-public slots:
-    virtual void setTypeFilter( int );
-    virtual void setStatusFilter( int );
-
-protected:
-    bool filterAcceptsRow( int, const QModelIndex & ) const override;
-
-private:
-    int i_type_filter;
-    int i_status_filter;
-};
-
 class ExtensionItemDelegate : public QStyledItemDelegate
 {
     Q_OBJECT
@@ -300,7 +231,7 @@ protected:
 };
 
 
-class AddonItemDelegate : public ExtensionItemDelegate
+class AddonItemDelegate : public QStyledItemDelegate
 {
     Q_OBJECT
 
@@ -313,6 +244,8 @@ public:
                 const QModelIndex &index ) const override;
     QSize sizeHint( const QStyleOptionViewItem &option,
                     const QModelIndex &index ) const override;
+    void initStyleOption( QStyleOptionViewItem *option,
+                         const QModelIndex &index ) const override;
     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
     void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
     void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
@@ -327,8 +260,9 @@ signals:
     void showInfo();
 
 protected:
-    DelegateAnimationHelper *animator;
-    QWidget *progressbar;
+    QMargins margins;
+    DelegateAnimationHelper *animator = nullptr;
+    QWidget *progressbar = nullptr;
 };
 
 class ExtensionInfoDialog : public QVLCDialog
@@ -346,4 +280,3 @@ public:
 };
 
 #endif
-


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -54,7 +54,7 @@
 #include "network/networkmediamodel.hpp"
 #include "network/networkdevicemodel.hpp"
 #include "network/networksourcesmodel.hpp"
-#include "network/servicesdiscoverymodel.hpp"
+#include "network/addonsmodel.hpp"
 #include "network/standardpathmodel.hpp"
 
 #include "menus/qml_menu_wrapper.hpp"
@@ -289,7 +289,7 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<NetworkMediaModel>( uri, versionMajor, versionMinor, "NetworkMediaModel");
         qmlRegisterType<NetworkDeviceModel>( uri, versionMajor, versionMinor, "NetworkDeviceModel");
         qmlRegisterType<NetworkSourcesModel>( uri, versionMajor, versionMinor, "NetworkSourcesModel");
-        qmlRegisterType<ServicesDiscoveryModel>( uri, versionMajor, versionMinor, "ServicesDiscoveryModel");
+        qmlRegisterType<AddonsModel>( uri, versionMajor, versionMinor, "ServicesDiscoveryModel");
         qmlRegisterType<StandardPathModel>( uri, versionMajor, versionMinor, "StandardPathModel");
         qmlRegisterType<MLFoldersModel>( uri, versionMajor, versionMinor, "MLFolderModel");
 
@@ -380,7 +380,7 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<NetworkMediaModel>( uri, versionMajor, versionMinor, "NetworkMediaModel");
         qmlRegisterType<NetworkDeviceModel>( uri, versionMajor, versionMinor, "NetworkDeviceModel");
         qmlRegisterType<NetworkSourcesModel>( uri, versionMajor, versionMinor, "NetworkSourcesModel");
-        qmlRegisterType<ServicesDiscoveryModel>( uri, versionMajor, versionMinor, "ServicesDiscoveryModel");
+        qmlRegisterType<AddonsModel>( uri, versionMajor, versionMinor, "ServicesDiscoveryModel");
         qmlRegisterType<MLFoldersModel>( uri, versionMajor, versionMinor, "MLFolderModel");
         qmlRegisterType<MLRecentsModel>( uri, versionMajor, versionMinor, "MLRecentModel" );
 


=====================================
modules/gui/qt/meson.build
=====================================
@@ -49,7 +49,6 @@ moc_headers = files(
     'dialogs/open/open.hpp',
     'dialogs/open/open_panels.hpp',
     'dialogs/open/openurl.hpp',
-    'dialogs/plugins/addons_manager.hpp',
     'dialogs/plugins/plugins.hpp',
     'dialogs/podcast/podcast_configuration.hpp',
     'dialogs/preferences/complete_preferences.hpp',
@@ -102,12 +101,12 @@ moc_headers = files(
     'menus/custom_menus.hpp',
     'menus/qml_menu_wrapper.hpp',
     'menus/menus.hpp',
+    'network/addonsmodel.hpp',
     'network/devicesourceprovider.hpp',
     'network/networkbasemodel.hpp',
     'network/networkdevicemodel.hpp',
     'network/networksourcesmodel.hpp',
     'network/networkmediamodel.hpp',
-    'network/servicesdiscoverymodel.hpp',
     'network/standardpathmodel.hpp',
     'style/systempalette.hpp',
     'style/colorcontext.hpp',
@@ -234,8 +233,6 @@ some_sources = files(
     'dialogs/open/open_panels.hpp',
     'dialogs/open/openurl.cpp',
     'dialogs/open/openurl.hpp',
-    'dialogs/plugins/addons_manager.cpp',
-    'dialogs/plugins/addons_manager.hpp',
     'dialogs/plugins/plugins.cpp',
     'dialogs/plugins/plugins.hpp',
     'dialogs/podcast/podcast_configuration.cpp',
@@ -363,6 +360,8 @@ some_sources = files(
     'menus/qml_menu_wrapper.hpp',
     'menus/menus.cpp',
     'menus/menus.hpp',
+    'network/addonsmodel.cpp',
+    'network/addonsmodel.hpp',
     'network/mediatreelistener.cpp',
     'network/mediatreelistener.hpp',
     'network/devicesourceprovider.cpp',
@@ -375,8 +374,6 @@ some_sources = files(
     'network/networksourcesmodel.hpp',
     'network/networkmediamodel.cpp',
     'network/networkmediamodel.hpp',
-    'network/servicesdiscoverymodel.cpp',
-    'network/servicesdiscoverymodel.hpp',
     'network/standardpathmodel.cpp',
     'network/standardpathmodel.hpp',
     'network/vlcmediasourcewrapper.hpp',


=====================================
modules/gui/qt/network/servicesdiscoverymodel.cpp → modules/gui/qt/network/addonsmodel.cpp
=====================================
@@ -16,7 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
-#include "servicesdiscoverymodel.hpp"
+#include "addonsmodel.hpp"
 
 #include "util/locallistbasemodel.hpp"
 
@@ -27,27 +27,54 @@
 
 #include <memory>
 #include <QPixmap>
+#include <QQmlFile>
 
 #include <vlc_media_source.h>
 #include <vlc_addons.h>
 #include <vlc_cxx_helpers.hpp>
 #include <vlc_addons.h>
 
+#define CHECK_ADDON_TYPE_MATCH(type) \
+    static_assert(static_cast<addon_type_t>(AddonsModel::Type::TYPE_ ## type) == ADDON_##type)
+
+CHECK_ADDON_TYPE_MATCH(UNKNOWN);
+CHECK_ADDON_TYPE_MATCH(PLAYLIST_PARSER);
+CHECK_ADDON_TYPE_MATCH(SERVICE_DISCOVERY);
+CHECK_ADDON_TYPE_MATCH(SKIN2);
+CHECK_ADDON_TYPE_MATCH(PLUGIN);
+CHECK_ADDON_TYPE_MATCH(INTERFACE);
+CHECK_ADDON_TYPE_MATCH(META);
+CHECK_ADDON_TYPE_MATCH(OTHER);
+
+#undef CHECK_ADDON_TYPE_MATCH
+
+#define CHECK_ADDON_STATE_MATCH(type) \
+    static_assert(static_cast<addon_state_t>(AddonsModel::State::STATE_ ## type) == ADDON_##type)
+
+CHECK_ADDON_STATE_MATCH(INSTALLING);
+CHECK_ADDON_STATE_MATCH(INSTALLED);
+CHECK_ADDON_STATE_MATCH(UNINSTALLING);
+
+#undef CHECK_ADDON_STATE_MATCH
+
 namespace {
 
 using AddonPtr = vlc_shared_data_ptr_type(addon_entry_t,
                                           addon_entry_Hold, addon_entry_Release);
 
-class SDItem {
+class AddonItem {
 public:
-    SDItem( AddonPtr addon )
+    //
+    AddonItem( AddonPtr addon )
     {
+        vlc_mutex_locker locker{&addon->lock};
         name = qfu( addon->psz_name );
-        summery = qfu( addon->psz_summary ).trimmed();
+        summary = qfu( addon->psz_summary ).trimmed();
         description = qfu( addon->psz_description ).trimmed();
         author = qfu( addon->psz_author );
         sourceUrl = QUrl( addon->psz_source_uri );
         entry = addon;
+        uuid = QByteArray( reinterpret_cast<const char*>(addon->uuid), sizeof( addon_uuid_t ));
 
         if ( addon->psz_image_data ) {
             char *cDir = config_GetUserDir( VLC_CACHE_DIR );
@@ -59,9 +86,9 @@ public:
                 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);
+                char* uuidStr = addons_uuid_to_psz( &addon->uuid );
+                QString filename = QString("addon_thumbnail_%1.png").arg(uuidStr);
+                free(uuidStr);
                 QString absoluteFilePath =  dir.absoluteFilePath(filename);
 
                 if ( !QFileInfo::exists( absoluteFilePath )) {
@@ -82,8 +109,9 @@ public:
     }
 
 public:
+    QByteArray  uuid;
     QString name;
-    QString summery;
+    QString summary;
     QString description;
     QString author;
     QUrl sourceUrl;
@@ -93,59 +121,70 @@ public:
 
 } //namespace
 
-using SDItemPtr = std::shared_ptr<SDItem> ;
-using SDItemList = std::vector<SDItemPtr> ;
+using AddonItemPtr = std::shared_ptr<AddonItem> ;
+using AddonItemList = std::vector<AddonItemPtr> ;
 
 // ListCache specialisation
 
 template<>
-bool ListCache<SDItemPtr>::compareItems(const SDItemPtr& a, const SDItemPtr& b)
+bool ListCache<AddonItemPtr>::compareItems(const AddonItemPtr& a, const AddonItemPtr& b)
 {
     //just compare the pointers here
-    return a == b;
+    return a->uuid == b->uuid;
 }
 
-// ServicesDiscoveryModelPrivate
+// AddonsModelPrivate
 
-class ServicesDiscoveryModelPrivate
-    : public LocalListBaseModelPrivate<SDItemPtr>
+class AddonsModelPrivate
+    : public LocalListBaseModelPrivate<AddonItemPtr>
 {
 
 public:
-    Q_DECLARE_PUBLIC(ServicesDiscoveryModel)
+    Q_DECLARE_PUBLIC(AddonsModel)
 
 public: //ctor/dtor
-    ServicesDiscoveryModelPrivate(ServicesDiscoveryModel* pub)
-        : LocalListBaseModelPrivate<SDItemPtr>(pub)
+    AddonsModelPrivate(AddonsModel* pub)
+        : LocalListBaseModelPrivate<AddonItemPtr>(pub)
     {
     }
 
-    ~ServicesDiscoveryModelPrivate()
+    ~AddonsModelPrivate()
     {
         if ( m_manager )
             addons_manager_Delete( m_manager );
     }
 
 public:
-    const SDItem* getItemForRow(int row) const
+    const AddonItem* getItemForRow(int row) const
     {
-        const SDItemPtr* ref = item(row);
+        const AddonItemPtr* ref = item(row);
         if (ref)
             return ref->get();
         return nullptr;
     }
 
+    bool gatherRepository(const char* uri)
+    {
+        Q_Q(AddonsModel);
+        if (!m_manager)
+            return false;
+        m_loading = true;
+        emit q->loadingChanged();
+        addons_manager_Gather( m_manager, uri);
+        return true;
+    }
+
 public: //BaseModelPrivateT implementation
     bool initializeModel() override;
 
-    LocalListCacheLoader<SDItemPtr>::ItemCompare getSortFunction() const override
+    LocalListCacheLoader<AddonItemPtr>::ItemCompare getSortFunction() const override
     {
         if (m_sortOrder == Qt::SortOrder::DescendingOrder)
-            return [](const SDItemPtr& a, const SDItemPtr& b){
+            return [](const AddonItemPtr& a, const AddonItemPtr& b){
                 return QString::compare(a->name, b->name) > 0;
             };
         else
-            return [](const SDItemPtr& a, const SDItemPtr& b) {
+            return [](const AddonItemPtr& a, const AddonItemPtr& b) {
                 return QString::compare(a->name, b->name) < 0;
             };
     }
@@ -159,123 +198,387 @@ public: //discovery callbacks
 
 public: //LocalListCacheLoader implementation
     //return the data matching the pattern
-    SDItemList getModelData(const QString& pattern) const override
+    AddonItemList getModelData(const QString& pattern) const override
     {
-        if (pattern.isEmpty())
+        if (pattern.isEmpty()
+            && m_typeFilter == AddonsModel::Type::TYPE_NONE
+            && m_stateFilter == AddonsModel::State::STATE_NONE)
             return m_items;
 
-        SDItemList items;
+        AddonItemList items;
         std::copy_if(
             m_items.cbegin(), m_items.cend(),
             std::back_inserter(items),
-            [&pattern](const SDItemPtr& item) {
+            [&pattern, filter = m_typeFilter, state = m_stateFilter](const AddonItemPtr& item) {
+                if (state != AddonsModel::State::STATE_NONE
+                    && static_cast<AddonsModel::State>(item->entry->e_state) != state)
+                    return false;
+                if (filter != AddonsModel::Type::TYPE_NONE
+                    && static_cast<AddonsModel::Type>(item->entry->e_type) != filter)
+                        return false;
                 return item->name.contains(pattern, Qt::CaseInsensitive);
             });
         return items;
     }
 
 public: // data
+    MainCtx* m_ctx = nullptr;
     addons_manager_t* m_manager = nullptr;
-    SDItemList m_items;
+    AddonItemList m_items;
+    AddonsModel::Type m_typeFilter = AddonsModel::Type::TYPE_NONE;
+    AddonsModel::State m_stateFilter = AddonsModel::State::STATE_NONE;
 };
 
-ServicesDiscoveryModel::ServicesDiscoveryModel( QObject* parent )
-    : BaseModel( new ServicesDiscoveryModelPrivate(this), parent )
+AddonsModel::AddonsModel( QObject* parent )
+    : BaseModel( new AddonsModelPrivate(this), parent )
 {
 }
 
-QVariant ServicesDiscoveryModel::data( const QModelIndex& index, int role ) const
+QVariant AddonsModel::data( const QModelIndex& index, int role ) const
 {
-    Q_D(const ServicesDiscoveryModel);
-    if (!m_ctx)
+    Q_D(const AddonsModel);
+    if (!d->m_ctx)
         return {};
 
-    const SDItem* item = d->getItemForRow(index.row());
+    const AddonItem* item = d->getItemForRow(index.row());
     if (!item)
         return {};
 
     switch ( role )
     {
-        case Role::SERVICE_NAME:
+        case Qt::DisplayRole:
+        case Role::NAME:
             return item->name;
-        case Role::SERVICE_AUTHOR:
+        case Role::AUTHOR:
             return item->author;
-        case Role::SERVICE_SUMMARY:
-            return item->summery;
-        case Role::SERVICE_DESCRIPTION:
+        case Role::SUMMARY:
+            return item->summary;
+        case Role::DESCRIPTION:
             return item->description;
-        case Role::SERVICE_DOWNLOADS:
+        case Role::DOWNLOADS:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
             return QVariant::fromValue( item->entry->i_downloads );
-        case Role::SERVICE_SCORE:
-            return item->entry->i_score / 100;
-        case Role::SERVICE_STATE:
+        }
+        case Role::SCORE:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return item->entry->i_score;
+        }
+        case Role::STATE:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
             return item->entry->e_state;
-        case Role::SERVICE_ARTWORK:
+        }
+        case Role::ARTWORK:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
             return item->artworkUrl;
+        }
+        case Role::TYPE:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return QVariant::fromValue(item->entry->e_type);
+        }
+        case Role::LINK:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return QString{ item->entry->psz_source_uri };
+        }
+        case Role::ADDON_VERSION:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return qfu(item->entry->psz_version);
+        }
+        case Role::UUID:
+            return item->uuid;
+        case Role::DOWNLOAD_COUNT:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return QVariant::fromValue(item->entry->i_downloads);
+        }
+        case Role::FILENAME:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            QList<QString> list;
+            addon_file_t *p_file;
+            ARRAY_FOREACH( p_file, item->entry->files )
+            list << qfu( p_file->psz_filename );
+            return QVariant{ list };
+        }
+        case  Role::BROKEN:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return item->entry->e_flags & ADDON_BROKEN;
+        }
+        case  Role::MANAGEABLE:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return item->entry->e_flags & ADDON_MANAGEABLE;
+        }
+        case  Role::UPDATABLE:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            return item->entry->e_flags & ADDON_UPDATABLE;
+        }
+
+        case Qt::ToolTipRole:
+        {
+            vlc_mutex_locker locker{&item->entry->lock};
+            if ( !( item->entry->e_flags & ADDON_MANAGEABLE ) )
+            {
+                return qtr("This addon has been installed manually. VLC can't manage it by itself.");
+            }
+            return QVariant{};
+        }
+        case Qt::DecorationRole:
+            return QPixmap{QQmlFile::urlToLocalFileOrQrc(item->artworkUrl)};
         default:
             return {};
     }
 }
 
-QHash<int, QByteArray> ServicesDiscoveryModel::roleNames() const
+bool AddonsModel::setData(const QModelIndex& index, const QVariant &value, int role)
+{
+    if ( role == Role::STATE )
+    {
+        auto i_value = value.value<AddonsModel::State>();
+        if ( i_value == AddonsModel::State::STATE_INSTALLING )
+        {
+            installService(index.row());
+        }
+        else if ( i_value == AddonsModel::State::STATE_UNINSTALLING )
+        {
+            removeService(index.row());
+        }
+    }
+    return true;
+}
+
+Qt::ItemFlags AddonsModel::flags( const QModelIndex &index ) const
+{
+    Q_D(const AddonsModel);
+    Qt::ItemFlags qtFlags = BaseModel::flags(index);
+
+    const AddonItem* item = d->getItemForRow(index.row());
+    if (!item)
+        return qtFlags;
+
+    {
+        vlc_mutex_locker locker{&item->entry->lock};
+        addon_state_t addonState = item->entry->e_state;
+        if (addonState == ADDON_INSTALLING || addonState == ADDON_UNINSTALLING)
+            qtFlags &= ~Qt::ItemIsEnabled;
+
+    }
+    qtFlags |= Qt::ItemIsEditable;
+
+    return qtFlags;
+}
+
+QHash<int, QByteArray> AddonsModel::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" }
+        { Role::NAME, "name" },
+        { Role::AUTHOR, "author"},
+        { Role::SUMMARY, "summary" },
+        { Role::DESCRIPTION, "description" },
+        { Role::DOWNLOADS, "downloads" },
+        { Role::SCORE, "score" },
+        { Role::STATE, "state" },
+        { Role::ARTWORK, "artwork" },
+        { Role::TYPE, "type" },
+        { Role::LINK, "link" },
+        { Role::FILENAME, "filename" },
+        { Role::ADDON_VERSION, "version" },
+        { Role::UUID, "uuid" },
+        { Role::DOWNLOAD_COUNT, "downloadCount" },
+        { Role::BROKEN, "broken" },
+        { Role::MANAGEABLE, "manageable" },
+        { Role::UPDATABLE, "updatable" },
     };
 }
 
-void ServicesDiscoveryModel::installService(int idx)
+void AddonsModel::installService(int idx)
 {
-    Q_D(ServicesDiscoveryModel);
+    Q_D(AddonsModel);
 
-    const SDItem* item = d->getItemForRow(idx);
+    const AddonItem* item = d->getItemForRow(idx);
     if (!item)
         return;
 
     addon_uuid_t uuid;
-    memcpy( uuid, item->entry->uuid, sizeof( uuid ) );
+    assert(sizeof(uuid) == item->uuid.size());
+    memcpy( uuid, item->uuid.constData(), sizeof( uuid ) );
     addons_manager_Install( d->m_manager, uuid );
 }
 
-void ServicesDiscoveryModel::removeService(int idx)
+void AddonsModel::removeService(int idx)
 {
-    Q_D(ServicesDiscoveryModel);
+    Q_D(AddonsModel);
 
-    const SDItem* item = d->getItemForRow(idx);
+    const AddonItem* item = d->getItemForRow(idx);
     if (!item)
         return;
 
     addon_uuid_t uuid;
-    memcpy( uuid, item->entry->uuid, sizeof( uuid ) );
+    assert(sizeof(uuid) == item->uuid.size());
+    memcpy( uuid, item->uuid.constData(), sizeof( uuid ) );
     addons_manager_Remove( d->m_manager, uuid );
 }
 
+void AddonsModel::loadFromDefaultRepository()
+{
+    Q_D(AddonsModel);
+    d->gatherRepository("repo://");
+}
 
-void ServicesDiscoveryModel::setCtx(MainCtx* ctx)
+void AddonsModel::loadFromExternalRepository(QUrl uri)
 {
-    Q_D(ServicesDiscoveryModel);
+    Q_D(AddonsModel);
+    d->gatherRepository(uri.toEncoded().constData());
+}
 
-    if (ctx == m_ctx)
+void AddonsModel::setCtx(MainCtx* ctx)
+{
+    Q_D(AddonsModel);
+
+    if (ctx == d->m_ctx)
         return;
 
     assert(ctx);
-    m_ctx = ctx;
+    d->m_ctx = ctx;
     d->initializeModel();
     emit ctxChanged();
 }
 
-static void addonFoundCallback( addons_manager_t *manager, addon_entry_t *entry )
+MainCtx* AddonsModel::getCtx() const
+{
+    Q_D(const AddonsModel);
+    return d->m_ctx;
+}
+
+AddonsModel::Type AddonsModel::getTypeFilter() const
+{
+    Q_D(const AddonsModel);
+    return d->m_typeFilter;
+}
+
+void AddonsModel::setTypeFilter(AddonsModel::Type type)
 {
-    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
+    Q_D(AddonsModel);
+    if (type == d->m_typeFilter)
         return;
-    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    d->m_typeFilter = type;
+
+    d->m_revision++;
+    invalidateCache();
+
+    emit typeFilterChanged();
+}
+
+AddonsModel::State AddonsModel::getStateFilter() const
+{
+    Q_D(const AddonsModel);
+    return d->m_stateFilter;
+}
+
+void AddonsModel::setStateFilter(AddonsModel::State state)
+{
+    Q_D(AddonsModel);
+    if (state == d->m_stateFilter)
+        return;
+    d->m_stateFilter = state;
+
+    d->m_revision++;
+    invalidateCache();
+
+    emit stateFilterChanged();
+}
+
+QString AddonsModel::getLabelForType(AddonsModel::Type type)
+{
+    switch ( type )
+    {
+    case Type::TYPE_SKIN2 :
+        return qtr( "Skins" );
+    case Type::TYPE_PLAYLIST_PARSER:
+        return qtr("Playlist parsers");
+    case Type::TYPE_SERVICE_DISCOVERY:
+        return qtr("Service Discovery");
+    case Type::TYPE_INTERFACE:
+        return qtr("Interfaces");
+    case Type::TYPE_META:
+        return qtr("Art and meta fetchers");
+    case Type::TYPE_EXTENSION:
+        return qtr("Extensions");
+    default:
+        return qtr("Unknown");
+    }
+}
+
+QColor AddonsModel::getColorForType(AddonsModel::Type type)
+{
+    QColor color;
+    switch( type )
+    {
+    case Type::TYPE_EXTENSION:
+        color = QColor(0xDB, 0xC5, 0x40);
+        break;
+    case Type::TYPE_PLAYLIST_PARSER:
+        color = QColor(0x36, 0xBB, 0x59);
+        break;
+    case Type::TYPE_SERVICE_DISCOVERY:
+        color = QColor(0xDB, 0x52, 0x40);
+        break;
+    case Type::TYPE_SKIN2:
+        color = QColor(0x8B, 0xD6, 0xFC);
+        break;
+    case Type::TYPE_INTERFACE:
+        color = QColor(0x00, 0x13, 0x85);
+        break;
+    case Type::TYPE_META:
+        color = QColor(0xCD, 0x23, 0xBF);
+        break;
+    case Type::TYPE_PLUGIN:
+    case Type::TYPE_UNKNOWN:
+    case Type::TYPE_OTHER:
+    default:
+        break;
+    }
+    return color;
+}
+
+QString AddonsModel::getIconForType(AddonsModel::Type type)
+{
+    switch( type )
+    {
+    case AddonsModel::Type::TYPE_EXTENSION:
+        return QStringLiteral("qrc:///addons/addon_yellow.svg");
+    case AddonsModel::Type::TYPE_PLAYLIST_PARSER:
+        return QStringLiteral("qrc:///addons/addon_green.svg");
+    case AddonsModel::Type::TYPE_SERVICE_DISCOVERY:
+        return QStringLiteral("qrc:///addons/addon_red.svg");
+    case AddonsModel::Type::TYPE_SKIN2:
+        return QStringLiteral("qrc:///addons/addon_cyan.svg");
+    case AddonsModel::Type::TYPE_INTERFACE:
+        return QStringLiteral("qrc:///addons/addon_blue.svg");
+    case AddonsModel::Type::TYPE_META:
+        return QStringLiteral("qrc:///addons/addon_magenta.svg");
+    default:
+        return QStringLiteral("qrc:///addons/addon_default.svg");
+    }
+    vlc_assert_unreachable();
+}
+
+int AddonsModel::getMaxScore()
+{
+    return ADDON_MAX_SCORE;
+}
+
+static void addonFoundCallback( addons_manager_t *manager, addon_entry_t *entry )
+{
+    AddonsModelPrivate* d = (AddonsModelPrivate*) manager->owner.sys;
     QMetaObject::invokeMethod( d->q_func(), [d, entryPtr = AddonPtr(entry)]()
         {
             d->addonFound( std::move( entryPtr ) );
@@ -284,7 +587,7 @@ static void addonFoundCallback( addons_manager_t *manager, addon_entry_t *entry
 
 static void addonsDiscoveryEndedCallback( addons_manager_t *manager )
 {
-    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    AddonsModelPrivate* d = (AddonsModelPrivate*) manager->owner.sys;
     QMetaObject::invokeMethod( d->q_func(), [d]()
         {
             d->discoveryEnded();
@@ -293,19 +596,17 @@ static void addonsDiscoveryEndedCallback( addons_manager_t *manager )
 
 static void addonChangedCallback( addons_manager_t *manager, addon_entry_t *entry )
 {
-    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
-        return;
-    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    AddonsModelPrivate* d = (AddonsModelPrivate*) manager->owner.sys;
     QMetaObject::invokeMethod( d->q_func(), [d, entryPtr = AddonPtr(entry)]()
         {
             d->addonChanged( std::move( entryPtr ) );
         }, Qt::QueuedConnection);
 }
 
-bool ServicesDiscoveryModelPrivate::initializeModel()
+bool AddonsModelPrivate::initializeModel()
 {
-    Q_Q(ServicesDiscoveryModel);
-    if (m_qmlInitializing || !q->m_ctx)
+    Q_Q(AddonsModel);
+    if (m_qmlInitializing || !m_ctx)
         return false;
 
     if ( m_manager )
@@ -319,41 +620,39 @@ bool ServicesDiscoveryModelPrivate::initializeModel()
         addonChangedCallback,
     };
 
-    m_manager = addons_manager_New( VLC_OBJECT( q->m_ctx->getIntf() ), &owner );
+    m_manager = addons_manager_New( VLC_OBJECT( m_ctx->getIntf() ), &owner );
     assert( m_manager );
 
     emit q->loadingChanged();
     addons_manager_LoadCatalog( m_manager );
-    addons_manager_Gather( m_manager, "repo://" );
+
+    m_revision++;
+    m_loading = false;
+    emit q->loadingChanged();
     return true;
 }
 
 
-void ServicesDiscoveryModelPrivate::addonFound( AddonPtr addon )
+void AddonsModelPrivate::addonFound( AddonPtr addon )
 {
-    m_items.emplace_back(std::make_shared<SDItem>(addon));
-    m_revision++;
-    invalidateCache();
+    m_items.emplace_back(std::make_shared<AddonItem>(addon));
 }
 
-void ServicesDiscoveryModelPrivate::addonChanged( AddonPtr addon )
+void AddonsModelPrivate::addonChanged( AddonPtr addon )
 {
-    for ( size_t r = 0; r < m_items.size(); ++r )
-    {
-        if ( memcmp( m_items[r]->entry->uuid, addon->uuid, sizeof( addon->uuid ) ) )
-            continue;
+    AddonItemPtr sdItem = std::make_shared<AddonItem>(addon);
+    m_cache->updateItem(std::move(sdItem));
 
-        m_items[r] = std::make_shared<SDItem>(addon);
-        break;
-    }
     m_revision++;
     invalidateCache();
 }
 
-void ServicesDiscoveryModelPrivate::discoveryEnded()
+void AddonsModelPrivate::discoveryEnded()
 {
-    Q_Q(ServicesDiscoveryModel);
+    Q_Q(AddonsModel);
     assert( m_loading );
     m_loading = false;
     emit q->loadingChanged();
+    m_revision++;
+    invalidateCache();
 }


=====================================
modules/gui/qt/network/servicesdiscoverymodel.hpp → modules/gui/qt/network/addonsmodel.hpp
=====================================
@@ -16,67 +16,111 @@
  * 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
+#ifndef ADDONS_MODEL_HPP
+#define ADDONS_MODEL_HPP
 
 #include "util/base_model.hpp"
 #include "maininterface/mainctx.hpp"
 
-class ServicesDiscoveryModelPrivate;
-class ServicesDiscoveryModel : public BaseModel
+class AddonsModelPrivate;
+class AddonsModel : public BaseModel
 {
     Q_OBJECT
 
 public:
 
     Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
+    //filter list by to match addonTypeFilter, set to NONE to disable filtering
+    Q_PROPERTY(Type typeFilter READ getTypeFilter WRITE setTypeFilter NOTIFY typeFilterChanged FINAL)
+    Q_PROPERTY(State stateFilter READ getStateFilter WRITE setStateFilter NOTIFY stateFilterChanged FINAL)
+
+    Q_PROPERTY(int maxScore READ getMaxScore CONSTANT FINAL)
 
-    enum State // equivalent to addon_state_t
+    enum class State // equivalent to addon_state_t
     {
-        NOTINSTALLED = 0,
-        INSTALLING,
-        INSTALLED,
-        UNINSTALLING
+        STATE_NOTINSTALLED = 0,
+        STATE_INSTALLING,
+        STATE_INSTALLED,
+        STATE_UNINSTALLING,
+        STATE_NONE //not defined in addon_state_t
     };
     Q_ENUM(State)
 
-    enum Role {
-        SERVICE_NAME = Qt::UserRole + 1,
-        SERVICE_AUTHOR,
-        SERVICE_SUMMARY,
-        SERVICE_DESCRIPTION,
-        SERVICE_DOWNLOADS,
-        SERVICE_SCORE,
-        SERVICE_STATE,
-        SERVICE_ARTWORK
+    enum Role : int {
+        NAME = Qt::UserRole + 1,
+        AUTHOR,
+        SUMMARY,
+        DESCRIPTION,
+        DOWNLOADS,
+        SCORE,
+        STATE,
+        TYPE,
+        ARTWORK,
+        LINK,
+        FILENAME,
+        ADDON_VERSION, //VERSION conflicts with config.h define
+        UUID,
+        DOWNLOAD_COUNT,
+        BROKEN,
+        MANAGEABLE,
+        UPDATABLE,
     };
     Q_ENUM(Role)
 
-    explicit ServicesDiscoveryModel(QObject* parent = nullptr);
+    enum class Type // equivalent as addon_type_t
+    {
+        TYPE_UNKNOWN = 0,
+        TYPE_EXTENSION,
+        TYPE_PLAYLIST_PARSER,
+        TYPE_SERVICE_DISCOVERY,
+        TYPE_SKIN2,
+        TYPE_PLUGIN,
+        TYPE_INTERFACE,
+        TYPE_META,
+        TYPE_OTHER,
+        TYPE_NONE //not defined in addon_type_t
+    };
+    Q_ENUM(Type)
+
+
+    explicit AddonsModel(QObject* parent = nullptr);
 
 public: //QAbstractListModel override
     QVariant data(const QModelIndex& index, int role) const override;
+    bool setData(const QModelIndex& index, const QVariant &value, int role) override;
+    Qt::ItemFlags flags( const QModelIndex &index ) const override;
     QHash<int, QByteArray> roleNames() const override;
 
 public: //invokable functions
     Q_INVOKABLE void installService(int idx);
     Q_INVOKABLE void removeService(int idx);
 
+    Q_INVOKABLE void loadFromDefaultRepository();
+    Q_INVOKABLE void loadFromExternalRepository(QUrl uri);
+
+    Q_INVOKABLE static QString getLabelForType(Type type);
+    Q_INVOKABLE static QColor getColorForType(Type type);
+    Q_INVOKABLE static QString getIconForType(Type type);
+
+    static int getMaxScore();
+
 public: // properties
     void setCtx(MainCtx* ctx);
-    inline MainCtx* getCtx() const { return m_ctx; }
+    MainCtx* getCtx() const;
+
+    State getStateFilter() const;
+    void setStateFilter(State state);
+
+    Type getTypeFilter() const;
+    void setTypeFilter(Type type);
 
 signals:
     void ctxChanged();
+    void stateFilterChanged();
+    void typeFilterChanged();
 
 private:
-    MainCtx* m_ctx = nullptr;
-
-    Q_DECLARE_PRIVATE(ServicesDiscoveryModel);
+    Q_DECLARE_PRIVATE(AddonsModel);
 };
 
-#endif // MLServicesDiscoveryModel_HPP
+#endif // ADDONS_MODEL_HPP


=====================================
modules/gui/qt/network/qml/ServicesManage.qml
=====================================
@@ -38,9 +38,14 @@ Widgets.ListViewExt {
 
         ctx: MainCtx
 
+        typeFilter: ServicesDiscoveryModel.TYPE_SERVICE_DISCOVERY
         searchPattern: MainCtx.search.pattern
         sortOrder: MainCtx.sort.order
         sortCriteria: MainCtx.sort.criteria
+
+        Component.onCompleted: {
+            discoveryModel.loadFromDefaultRepository()
+        }
     }
 
     topMargin: VLCStyle.margin_large
@@ -111,25 +116,25 @@ Widgets.ListViewExt {
                         id: action_btn
 
                         focus: true
-                        iconTxt: model.state === ServicesDiscoveryModel.INSTALLED ? VLCIcons.del : VLCIcons.add
-                        busy: model.state === ServicesDiscoveryModel.INSTALLING || model.state === ServicesDiscoveryModel.UNINSTALLING
+                        iconTxt: model.state === ServicesDiscoveryModel.STATE_INSTALLED ? VLCIcons.del : VLCIcons.add
+                        busy: model.state === ServicesDiscoveryModel.STATE_INSTALLING || model.state === ServicesDiscoveryModel.STATE_UNINSTALLING
                         text: {
                             switch(model.state) {
-                            case ServicesDiscoveryModel.INSTALLED:
+                            case ServicesDiscoveryModel.STATE_INSTALLED:
                                 return qsTr("Remove")
-                            case ServicesDiscoveryModel.NOTINSTALLED:
+                            case ServicesDiscoveryModel.STATE_NOTINSTALLED:
                                 return qsTr("Install")
-                            case ServicesDiscoveryModel.INSTALLING:
+                            case ServicesDiscoveryModel.STATE_INSTALLING:
                                 return qsTr("Installing")
-                            case ServicesDiscoveryModel.UNINSTALLING:
+                            case ServicesDiscoveryModel.STATE_UNINSTALLING:
                                 return qsTr("Uninstalling")
                             }
                         }
 
                         onClicked: {
-                            if (model.state === ServicesDiscoveryModel.NOTINSTALLED)
+                            if (model.state === ServicesDiscoveryModel.STATE_NOTINSTALLED)
                                 discoveryModel.installService(index)
-                            else if (model.state === ServicesDiscoveryModel.INSTALLED)
+                            else if (model.state === ServicesDiscoveryModel.STATE_INSTALLED)
                                 discoveryModel.removeService(index)
                         }
                     }
@@ -146,7 +151,9 @@ Widgets.ListViewExt {
                 }
 
                 Widgets.CaptionLabel {
-                    text: qsTr("Score: %1/5  Downloads: %2").arg(model.score).arg(model.downloads)
+                    text: qsTr("Score: %1/5  Downloads: %2")
+                        .arg( (5 * model.score / discoveryModel.maxScore).toFixed(1) )
+                        .arg(model.downloads)
                     topPadding: VLCStyle.margin_xsmall
                     color: servicesView.colorContext.fg.secondary
                     Layout.fillWidth: true


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -78,7 +78,6 @@ extern "C" char **environ;
 #endif
 #include "style/defaultthemeproviders.hpp"
 #include "dialogs/extensions/extensions_manager.hpp" /* Extensions manager */
-#include "dialogs/plugins/addons_manager.hpp" /* Addons manager */
 #include "dialogs/help/help.hpp"     /* Launch Update */
 #include "util/dismiss_popup_event_filter.hpp"
 #include "maininterface/compositor.hpp"
@@ -1124,7 +1123,6 @@ static void *ThreadCleanup( qt_intf_t *p_intf, CleanupReason cleanupReason )
 
     /* */
     ExtensionsManager::killInstance();
-    AddonsManager::killInstance();
 
     /* Destroy all remaining windows,
        because some are connected to some slots


=====================================
modules/gui/qt/util/base_model.hpp
=====================================
@@ -97,7 +97,7 @@ signals:
 public:
     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
 
-protected:
+public:
     void classBegin() override;
     void componentComplete() override;
 


=====================================
po/POTFILES.in
=====================================
@@ -709,8 +709,6 @@ modules/gui/qt/dialogs/open/openurl.cpp
 modules/gui/qt/dialogs/open/openurl.hpp
 modules/gui/qt/dialogs/playlists/playlists.cpp
 modules/gui/qt/dialogs/playlists/playlists.hpp
-modules/gui/qt/dialogs/plugins/addons_manager.cpp
-modules/gui/qt/dialogs/plugins/addons_manager.hpp
 modules/gui/qt/dialogs/plugins/plugins.cpp
 modules/gui/qt/dialogs/plugins/plugins.hpp
 modules/gui/qt/dialogs/podcast/podcast_configuration.cpp
@@ -809,6 +807,7 @@ modules/gui/qt/menus/menus.hpp
 modules/gui/qt/menus/qml/Menubar.qml
 modules/gui/qt/menus/qml_menu_wrapper.cpp
 modules/gui/qt/menus/qml_menu_wrapper.hpp
+modules/gui/qt/network/addonsmodel.cpp
 modules/gui/qt/network/standardpathmodel.cpp
 modules/gui/qt/network/standardpathmodel.hpp
 modules/gui/qt/network/qml/BrowseDeviceView.qml



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cac68167eb562b4e74777bcedc686cd797f6325c...346c18b719ae5775e7e41a6084c07c65a89bd79b

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cac68167eb562b4e74777bcedc686cd797f6325c...346c18b719ae5775e7e41a6084c07c65a89bd79b
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