From f4fa3b02c5b98df50a2d6d84a5ee1f0aefd6f985 Mon Sep 17 00:00:00 2001
From: qu1ck <anlutsenko@gmail.com>
Date: Thu, 18 Aug 2022 20:41:43 +0000
Subject: [PATCH] PCM: automatic check for repository updates

---
 common/CMakeLists.txt                         |   2 +
 common/eda_base_frame.cpp                     |   2 +
 {kicad => common/settings}/kicad_settings.cpp |   7 +-
 common/widgets/bitmap_button.cpp              |  27 +-
 {kicad => include/settings}/kicad_settings.h  |  20 ++
 include/widgets/bitmap_button.h               |  16 +
 kicad/CMakeLists.txt                          |   1 -
 kicad/dialogs/panel_kicad_launcher.cpp        |  16 +-
 kicad/kicad.cpp                               |   2 +-
 kicad/kicad_manager_frame.cpp                 |  86 ++++-
 kicad/kicad_manager_frame.h                   |  18 +-
 kicad/pcm/CMakeLists.txt                      |  21 +-
 .../dialogs/dialog_manage_repositories.cpp    |   2 +-
 kicad/pcm/dialogs/dialog_pcm.cpp              |  23 +-
 kicad/pcm/dialogs/dialog_pcm.h                |   2 +-
 kicad/pcm/dialogs/panel_packages_view.cpp     |  48 ++-
 kicad/pcm/dialogs/panel_packages_view.h       |   9 +-
 .../pcm/dialogs/panel_packages_view_base.cpp  |  13 +-
 .../pcm/dialogs/panel_packages_view_base.fbp  | 203 ++++++++----
 kicad/pcm/dialogs/panel_packages_view_base.h  |  10 +-
 kicad/pcm/dialogs/panel_pcm_settings.cpp      |  55 ++++
 kicad/pcm/dialogs/panel_pcm_settings.h        |  39 +++
 kicad/pcm/dialogs/panel_pcm_settings_base.cpp |  28 ++
 kicad/pcm/dialogs/panel_pcm_settings_base.fbp | 127 ++++++++
 kicad/pcm/dialogs/panel_pcm_settings_base.h   |  42 +++
 kicad/pcm/pcm.cpp                             | 295 +++++++++++++++---
 kicad/pcm/pcm.h                               |  66 +++-
 kicad/pcm/pcm_data.h                          |   5 +
 kicad/tools/kicad_manager_control.cpp         |   2 +-
 29 files changed, 1039 insertions(+), 148 deletions(-)
 rename {kicad => common/settings}/kicad_settings.cpp (93%)
 rename {kicad => include/settings}/kicad_settings.h (74%)
 create mode 100644 kicad/pcm/dialogs/panel_pcm_settings.cpp
 create mode 100644 kicad/pcm/dialogs/panel_pcm_settings.h
 create mode 100644 kicad/pcm/dialogs/panel_pcm_settings_base.cpp
 create mode 100644 kicad/pcm/dialogs/panel_pcm_settings_base.fbp
 create mode 100644 kicad/pcm/dialogs/panel_pcm_settings_base.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 48006f851b..08b884e431 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -435,6 +435,7 @@ set( COMMON_SRCS
     settings/nested_settings.cpp
     settings/parameters.cpp
     settings/settings_manager.cpp
+    settings/kicad_settings.cpp
 
     project/board_project_settings.cpp
     project/net_settings.cpp
@@ -460,6 +461,7 @@ target_link_libraries( common
     threadpool
     pybind11::embed
     compoundfilereader
+    pcm_settings
     ${Boost_LIBRARIES}
     ${CURL_LIBRARIES}
     ${wxWidgets_LIBRARIES}
diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp
index 01a4429100..261df2c525 100644
--- a/common/eda_base_frame.cpp
+++ b/common/eda_base_frame.cpp
@@ -38,6 +38,7 @@
 #include <panel_hotkeys_editor.h>
 #include <paths.h>
 #include <confirm.h>
+#include <panel_pcm_settings.h>
 #include <pgm_base.h>
 #include <settings/app_settings.h>
 #include <settings/common_settings.h>
@@ -1074,6 +1075,7 @@ void EDA_BASE_FRAME::OnPreferences( wxCommandEvent& event )
     book->AddSubPage( CREATE_PANEL( PANEL_DS_DISPLAY_OPTIONS ), _( "Display Options" ) );
     book->AddSubPage( CREATE_PANEL( PANEL_DS_COLORS ), _( "Colors" ) );
 
+    book->AddPage( new PANEL_PCM_SETTINGS( book ), _( "Plugin and Content Manager" ) );
 
     // Update all of the action hotkeys. The process of loading the actions through
     // the KiFACE will only get us the default hotkeys
diff --git a/kicad/kicad_settings.cpp b/common/settings/kicad_settings.cpp
similarity index 93%
rename from kicad/kicad_settings.cpp
rename to common/settings/kicad_settings.cpp
index 021f1a4bcc..25abecdd40 100644
--- a/kicad/kicad_settings.cpp
+++ b/common/settings/kicad_settings.cpp
@@ -18,7 +18,7 @@
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "kicad_settings.h"
+#include "settings/kicad_settings.h"
 #include <nlohmann/json.hpp>
 #include <settings/parameters.h>
 
@@ -42,6 +42,8 @@ KICAD_SETTINGS::KICAD_SETTINGS() :
     m_params.emplace_back(
             new PARAM_LIST<wxString>( "system.open_projects", &m_OpenProjects, {} ) );
 
+    m_params.emplace_back( new PARAM<int>( "system.check_for_updates", &m_updateCheck, 0 ) );
+
     m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>(
             "pcm.repositories",
             [&]() -> nlohmann::json
@@ -77,6 +79,9 @@ KICAD_SETTINGS::KICAD_SETTINGS() :
 
     m_params.emplace_back(
             new PARAM<wxString>( "pcm.last_download_dir", &m_PcmLastDownloadDir, "" ) );
+
+    m_params.emplace_back(
+            new PARAM<bool>( "pcm.check_for_updates", &m_PcmUpdateCheck, true ) );
 }
 
 
diff --git a/common/widgets/bitmap_button.cpp b/common/widgets/bitmap_button.cpp
index 26f14c814c..0bc9c0c545 100644
--- a/common/widgets/bitmap_button.cpp
+++ b/common/widgets/bitmap_button.cpp
@@ -35,14 +35,16 @@
 BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
                               const wxSize& aSize, int aStyles ) :
         wxPanel( aParent, aId, aPos, aSize, aStyles ),
-        m_isRadioButton( false ),
-        m_buttonState( 0 ),
-        m_padding( 0 ),
-        m_acceptDraggedInClicks( false )
+        m_isRadioButton( false ), m_buttonState( 0 ), m_padding( 0 ),
+        m_acceptDraggedInClicks( false ), m_showBadge( false ),
+        m_badgeColor( wxColor( 210, 0, 0, 0 ) ), // dark red
+        m_badgeTextColor( wxColor( wxT( "white" ) ) )
 {
     if( aSize == wxDefaultSize )
         SetMinSize( wxButton::GetDefaultSize() + wxSize( m_padding * 2, m_padding * 2) );
 
+    m_badgeFont = GetFont().Smaller().MakeBold();
+
     setupEvents();
 }
 
@@ -254,6 +256,23 @@ void BITMAP_BUTTON::OnPaint( wxPaintEvent& aEvent )
     // Draw the bitmap with the upper-left corner offset by the padding
     if( bmp.IsOk() )
         dc.DrawBitmap( bmp, m_padding, m_padding, true );
+
+    // Draw the badge
+    if( m_showBadge )
+    {
+        dc.SetFont( m_badgeFont );
+
+        wxSize box_size = dc.GetTextExtent( m_badgeText ) + wxSize( 6, 2 );
+        wxSize box_offset = box_size + wxSize( m_padding - 2, m_padding );
+        wxSize text_offset = box_offset - wxSize( 3, 1 );
+
+        dc.SetPen( wxPen( m_badgeColor ) );
+        dc.SetBrush( wxBrush( m_badgeColor ) );
+        dc.DrawRoundedRectangle( rect.GetRightBottom() - box_offset, box_size, -0.25 );
+
+        dc.SetTextForeground( m_badgeTextColor );
+        dc.DrawText( m_badgeText, rect.GetRightBottom() - text_offset );
+    }
 }
 
 
diff --git a/kicad/kicad_settings.h b/include/settings/kicad_settings.h
similarity index 74%
rename from kicad/kicad_settings.h
rename to include/settings/kicad_settings.h
index 82dfa01d51..507f3d7d9e 100644
--- a/kicad/kicad_settings.h
+++ b/include/settings/kicad_settings.h
@@ -36,11 +36,31 @@ public:
 
     int m_LeftWinWidth;
 
+    /**
+     * @brief General setting for various update checks
+     *
+     * A one time popup asks user to allow/disallow update checks on startup.
+     * This is currently used by PCM.
+     *
+     * See enum below for meaning of values.
+     */
+    int m_updateCheck;
+
+    enum UPDATE_CHECK {
+        UNINITIALIZED = 0,
+        NOT_ALLOWED = 1,
+        ALLOWED = 2
+    };
+
     std::vector<wxString> m_OpenProjects;
 
     std::vector<std::pair<wxString, wxString>> m_PcmRepositories;
     wxString                                   m_PcmLastDownloadDir;
 
+    // This controls background update check for PCM.
+    // It is set according to m_updateCheck on first start.
+    bool m_PcmUpdateCheck;
+
 protected:
     virtual std::string getLegacyFrameName() const override { return "KicadFrame"; }
 };
diff --git a/include/widgets/bitmap_button.h b/include/widgets/bitmap_button.h
index e1e824eaf0..498eee2a21 100644
--- a/include/widgets/bitmap_button.h
+++ b/include/widgets/bitmap_button.h
@@ -27,6 +27,7 @@
 
 #include <wx/bitmap.h>
 #include <wx/panel.h>
+#include <wx/colour.h>
 
 
 /**
@@ -104,6 +105,16 @@ public:
      */
     void AcceptDragInAsClick( bool aAcceptDragIn = true );
 
+    void SetShowBadge( bool aShowBadge ) { m_showBadge = aShowBadge; }
+
+    void SetBadgeText( const wxString& aText ) { m_badgeText = aText; }
+
+    void SetBadgeColors( const wxColor& aBadgeColor, const wxColor& aBadgeTextColor )
+    {
+        m_badgeColor = aBadgeColor;
+        m_badgeTextColor = aBadgeTextColor;
+    }
+
 protected:
     void setupEvents();
 
@@ -135,6 +146,11 @@ private:
     wxBitmap  m_disabledBitmap;
 
     bool      m_isRadioButton;
+    bool      m_showBadge;
+    wxString  m_badgeText;
+    wxColor   m_badgeColor;
+    wxColor   m_badgeTextColor;
+    wxFont    m_badgeFont;
     int       m_buttonState;
     int       m_padding;
     wxSize    m_unadjustedMinSize;
diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt
index 193495c34a..5926bb3f52 100644
--- a/kicad/CMakeLists.txt
+++ b/kicad/CMakeLists.txt
@@ -24,7 +24,6 @@ set( KICAD_SRCS
     import_project.cpp
     kicad.cpp
     kicad_manager_frame.cpp
-    kicad_settings.cpp
     menubar.cpp
     project_template.cpp
     project_tree_pane.cpp
diff --git a/kicad/dialogs/panel_kicad_launcher.cpp b/kicad/dialogs/panel_kicad_launcher.cpp
index 8a9dbc4eee..ce63e42f5b 100644
--- a/kicad/dialogs/panel_kicad_launcher.cpp
+++ b/kicad/dialogs/panel_kicad_launcher.cpp
@@ -44,6 +44,8 @@ PANEL_KICAD_LAUNCHER::PANEL_KICAD_LAUNCHER( wxWindow* aParent ) :
 
 void PANEL_KICAD_LAUNCHER::CreateLaunchers()
 {
+    m_frame->SetPcmButton( nullptr );
+
     if( m_toolsSizer->GetRows() > 0 )
     {
         m_toolsSizer->Clear( true );
@@ -119,6 +121,8 @@ void PANEL_KICAD_LAUNCHER::CreateLaunchers()
                 help->Disable();
                 label->Disable();
             }
+
+            return btn;
     };
 
     addLauncher( KICAD_MANAGER_ACTIONS::editSchematic,
@@ -154,10 +158,14 @@ void PANEL_KICAD_LAUNCHER::CreateLaunchers()
                  _( "Edit drawing sheet borders and title blocks for use in schematics and PCB "
                     "designs" ) );
 
-    addLauncher( KICAD_MANAGER_ACTIONS::showPluginManager,
-                    KiScaledBitmap( BITMAPS::icon_pcm, this, 48, true ),
-                    _( "Manage downloadable packages from KiCad and 3rd party repositories" ),
-                    ( KIPLATFORM::POLICY::GetPolicyState( POLICY_KEY_PCM ) != KIPLATFORM::POLICY::STATE::DISABLED ) );
+    BITMAP_BUTTON* bb =
+            addLauncher( KICAD_MANAGER_ACTIONS::showPluginManager,
+                         KiScaledBitmap( BITMAPS::icon_pcm, this, 48, true ),
+                         _( "Manage downloadable packages from KiCad and 3rd party repositories" ),
+                         ( KIPLATFORM::POLICY::GetPolicyState( POLICY_KEY_PCM )
+                           != KIPLATFORM::POLICY::STATE::DISABLED ) );
+
+    m_frame->SetPcmButton( bb );
 
     Layout();
 }
diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp
index 67f4e570bc..9d0fe603fa 100644
--- a/kicad/kicad.cpp
+++ b/kicad/kicad.cpp
@@ -41,6 +41,7 @@
 #include <paths.h>
 #include <richio.h>
 #include <settings/settings_manager.h>
+#include <settings/kicad_settings.h>
 #include <systemdirsappend.h>
 #include <trace_helpers.h>
 #include <wildcards_and_files_ext.h>
@@ -49,7 +50,6 @@
 
 #include "pgm_kicad.h"
 #include "kicad_manager_frame.h"
-#include "kicad_settings.h"
 
 #include <kiplatform/app.h>
 #include <kiplatform/environment.h>
diff --git a/kicad/kicad_manager_frame.cpp b/kicad/kicad_manager_frame.cpp
index e039a143a9..6a5d6a5dbf 100644
--- a/kicad/kicad_manager_frame.cpp
+++ b/kicad/kicad_manager_frame.cpp
@@ -32,7 +32,9 @@
 #include <dialogs/panel_kicad_launcher.h>
 #include <eda_base_frame.h>
 #include <filehistory.h>
+#include <policy_keys.h>
 #include <kiplatform/app.h>
+#include <kiplatform/policy.h>
 #include <kicad_build_version.h>
 #include <kiway.h>
 #include <kiway_express.h>
@@ -65,7 +67,7 @@
 #endif
 
 #include "kicad_manager_frame.h"
-#include "kicad_settings.h"
+#include "settings/kicad_settings.h"
 
 
 #define SEP()   wxFileName::GetPathSeparator()
@@ -124,7 +126,7 @@ KICAD_MANAGER_FRAME::KICAD_MANAGER_FRAME( wxWindow* parent, const wxString& titl
     wxXmlDocument dummy;
 
     // Create the status line (bottom of the frame).  Left half is for project name; right half
-    // is for Reporter (currently used by archiver/unarchiver).
+    // is for Reporter (currently used by archiver/unarchiver and PCM).
     CreateStatusBar( 2 );
     GetStatusBar()->SetFont( KIUI::GetStatusFont( this ) );
 
@@ -153,6 +155,28 @@ KICAD_MANAGER_FRAME::KICAD_MANAGER_FRAME( wxWindow* parent, const wxString& titl
     // Load the settings
     LoadSettings( config() );
 
+    m_pcmButton = nullptr;
+    m_pcmUpdateCount = 0;
+    m_pcm = std::make_shared<PLUGIN_CONTENT_MANAGER>(
+            [this]( int aUpdateCount )
+            {
+                m_pcmUpdateCount = aUpdateCount;
+                CallAfter(
+                        [this]()
+                        {
+                            updatePcmButtonBadge();
+                        } );
+            },
+            [this]( const wxString aText )
+            {
+                CallAfter(
+                        [aText, this]()
+                        {
+                            SetStatusText( aText, 1 );
+                        } );
+            } );
+    m_pcm->SetRepositoryList( kicadSettings()->m_PcmRepositories );
+
     // Left window: is the box which display tree project
     m_leftWin = new PROJECT_TREE_PANE( this );
 
@@ -215,6 +239,8 @@ KICAD_MANAGER_FRAME::~KICAD_MANAGER_FRAME()
     if( m_toolManager )
         m_toolManager->ShutdownAllTools();
 
+    m_pcm->StopBackgroundUpdate();
+
     delete m_actions;
     delete m_toolManager;
     delete m_toolDispatcher;
@@ -656,6 +682,11 @@ void KICAD_MANAGER_FRAME::ShowChangedLanguage()
 void KICAD_MANAGER_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged )
 {
     EDA_BASE_FRAME::CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged );
+
+    if( aEnvVarsChanged )
+    {
+        m_pcm->ReadEnvVar();
+    }
 }
 
 
@@ -792,4 +823,55 @@ void KICAD_MANAGER_FRAME::OnIdle( wxIdleEvent& aEvent )
 
     // clear file states regardless if we opened windows or not due to setting
     Prj().GetLocalSettings().ClearFileState();
+
+    KICAD_SETTINGS* settings = kicadSettings();
+
+    if( settings->m_updateCheck == KICAD_SETTINGS::UPDATE_CHECK::UNINITIALIZED )
+    {
+        if( wxMessageBox( _( "Would you like to automatically check for updates on startup?" ),
+                          _( "Check for updates" ), wxICON_QUESTION | wxYES_NO, this )
+            == wxYES )
+        {
+            settings->m_updateCheck = KICAD_SETTINGS::UPDATE_CHECK::ALLOWED;
+            settings->m_PcmUpdateCheck = true;
+        }
+        else
+        {
+            settings->m_updateCheck = KICAD_SETTINGS::UPDATE_CHECK::NOT_ALLOWED;
+            settings->m_PcmUpdateCheck = false;
+        }
+    }
+
+    if( KIPLATFORM::POLICY::GetPolicyState( POLICY_KEY_PCM ) != KIPLATFORM::POLICY::STATE::DISABLED
+        && settings->m_PcmUpdateCheck )
+    {
+        m_pcm->RunBackgroundUpdate();
+    }
+}
+
+
+void KICAD_MANAGER_FRAME::SetPcmButton( BITMAP_BUTTON* aButton )
+{
+    m_pcmButton = aButton;
+
+    updatePcmButtonBadge();
+}
+
+
+void KICAD_MANAGER_FRAME::updatePcmButtonBadge()
+{
+    if( m_pcmButton )
+    {
+        if( m_pcmUpdateCount > 0 )
+        {
+            m_pcmButton->SetShowBadge( true );
+            m_pcmButton->SetBadgeText( wxString::Format( "%d", m_pcmUpdateCount ) );
+        }
+        else
+        {
+            m_pcmButton->SetShowBadge( false );
+        }
+
+        m_pcmButton->Refresh();
+    }
 }
diff --git a/kicad/kicad_manager_frame.h b/kicad/kicad_manager_frame.h
index 9eb29dde11..143267bd6f 100644
--- a/kicad/kicad_manager_frame.h
+++ b/kicad/kicad_manager_frame.h
@@ -29,6 +29,8 @@
 #include <wx/process.h>
 #include <kiway_player.h>
 #include <wx/dnd.h>
+#include "pcm.h"
+#include "widgets/bitmap_button.h"
 
 class PROJECT_TREE;
 class PROJECT_TREE_PANE;
@@ -151,6 +153,10 @@ public:
 
     wxWindow* GetToolCanvas() const override;
 
+    std::shared_ptr<PLUGIN_CONTENT_MANAGER> GetPcm() { return m_pcm; };
+
+    void SetPcmButton( BITMAP_BUTTON* aButton );
+
     DECLARE_EVENT_TABLE()
 
 protected:
@@ -176,15 +182,19 @@ private:
 
     void language_change( wxCommandEvent& event );
 
-    bool m_openSavedWindows;
+    void updatePcmButtonBadge();
+
+    bool m_openSavedWindows;
+    int  m_leftWinWidth;
+    bool m_active_project;
 
-private:
     PROJECT_TREE_PANE*    m_leftWin;
     PANEL_KICAD_LAUNCHER* m_launcher;
     ACTION_TOOLBAR*       m_mainToolBar;
 
-    int                 m_leftWinWidth;
-    bool                m_active_project;
+    std::shared_ptr<PLUGIN_CONTENT_MANAGER> m_pcm;
+    BITMAP_BUTTON*                          m_pcmButton;
+    int                                     m_pcmUpdateCount;
 };
 
 
diff --git a/kicad/pcm/CMakeLists.txt b/kicad/pcm/CMakeLists.txt
index 75853384d3..78caa856c3 100644
--- a/kicad/pcm/CMakeLists.txt
+++ b/kicad/pcm/CMakeLists.txt
@@ -8,9 +8,9 @@ include_directories( BEFORE ${INC_BEFORE} )
 
 add_definitions( -DPCM )
 
-include_directories(
-    ${CMAKE_SOURCE_DIR}/common
-    ${CMAKE_SOURCE_DIR}/kicad
+set ( PCM_SETTINGS_SRCS
+    dialogs/panel_pcm_settings_base.cpp
+    dialogs/panel_pcm_settings.cpp
 )
 
 set ( PCM_DLG_SRCS
@@ -48,6 +48,21 @@ target_link_libraries( pcm
     nlohmann_json_schema_validator
 )
 
+add_library( pcm_settings STATIC
+    ${PCM_SETTINGS_SRCS}
+)
+
+# This is a circular dependency but it's not a problem for static libs.
+# Refactoring this would need separating kicad_settings, settings manager
+# and pgm_base out of common.
+target_link_libraries( pcm_settings common )
+
+target_include_directories(
+    pcm_settings
+    PUBLIC dialogs
+    PRIVATE $<TARGET_PROPERTY:common,INTERFACE_INCLUDE_DIRECTORIES>
+)
+
 INSTALL( DIRECTORY
     schemas
     DESTINATION ${KICAD_DATA}
diff --git a/kicad/pcm/dialogs/dialog_manage_repositories.cpp b/kicad/pcm/dialogs/dialog_manage_repositories.cpp
index 29d39b848e..0004075c9e 100644
--- a/kicad/pcm/dialogs/dialog_manage_repositories.cpp
+++ b/kicad/pcm/dialogs/dialog_manage_repositories.cpp
@@ -22,7 +22,7 @@
 #include "bitmaps/bitmap_types.h"
 #include "bitmaps/bitmaps_list.h"
 #include "grid_tricks.h"
-#include "kicad_settings.h"
+#include "settings/kicad_settings.h"
 #include "widgets/wx_grid.h"
 
 
diff --git a/kicad/pcm/dialogs/dialog_pcm.cpp b/kicad/pcm/dialogs/dialog_pcm.cpp
index eb0662d8b1..9e8497432e 100644
--- a/kicad/pcm/dialogs/dialog_pcm.cpp
+++ b/kicad/pcm/dialogs/dialog_pcm.cpp
@@ -27,9 +27,9 @@
 #include "dialog_pcm.h"
 #include "grid_tricks.h"
 #include "ki_exception.h"
-#include "kicad_settings.h"
 #include "pcm_task_manager.h"
 #include "pgm_base.h"
+#include "settings/kicad_settings.h"
 #include "settings/settings_manager.h"
 #include "thread"
 #include "widgets/wx_grid.h"
@@ -53,11 +53,13 @@ static std::vector<std::pair<PCM_PACKAGE_TYPE, wxString>> PACKAGE_TYPE_LIST = {
 };
 
 
-DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
+DIALOG_PCM::DIALOG_PCM( wxWindow* parent, std::shared_ptr<PLUGIN_CONTENT_MANAGER> pcm ) :
+        DIALOG_PCM_BASE( parent ), m_pcm( pcm )
 {
     m_defaultBitmap = KiBitmap( BITMAPS::icon_pcm );
 
-    m_pcm = std::make_shared<PLUGIN_CONTENT_MANAGER>( this );
+    m_pcm->SetDialogWindow( this );
+    m_pcm->StopBackgroundUpdate();
 
     m_gridPendingActions->PushEventHandler( new GRID_TRICKS( m_gridPendingActions ) );
 
@@ -142,11 +144,6 @@ DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
     m_sdbSizer1Cancel->Bind( wxEVT_UPDATE_UI, &DIALOG_PCM::OnUpdateEventButtons, this );
     m_sdbSizer1Apply->Bind( wxEVT_UPDATE_UI, &DIALOG_PCM::OnUpdateEventButtons, this );
 
-    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
-    KICAD_SETTINGS*   app_settings = mgr.GetAppSettings<KICAD_SETTINGS>();
-
-    m_pcm->SetRepositoryList( app_settings->m_PcmRepositories );
-
     setRepositoryListFromPcm();
 
     for( int col = 0; col < m_gridPendingActions->GetNumberCols(); col++ )
@@ -165,6 +162,10 @@ DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
 
 DIALOG_PCM::~DIALOG_PCM()
 {
+    m_pcm->SaveInstalledPackages();
+    m_pcm->SetDialogWindow( nullptr );
+    m_pcm->RunBackgroundUpdate();
+
     m_gridPendingActions->PopEventHandler( true );
 }
 
@@ -392,6 +393,12 @@ void DIALOG_PCM::setInstalledPackages()
         else
             package_data.bitmap = &m_defaultBitmap;
 
+        package_data.state =
+                m_pcm->GetPackageState( entry.repository_id, entry.package.identifier );
+
+        if( package_data.state == PPS_UPDATE_AVAILABLE )
+            package_data.update_version = m_pcm->GetPackageUpdateVersion( entry.package );
+
         package_list.emplace_back( package_data );
     }
 
diff --git a/kicad/pcm/dialogs/dialog_pcm.h b/kicad/pcm/dialogs/dialog_pcm.h
index 09b7be08f7..f652e04778 100644
--- a/kicad/pcm/dialogs/dialog_pcm.h
+++ b/kicad/pcm/dialogs/dialog_pcm.h
@@ -36,7 +36,7 @@ class DIALOG_PCM : public DIALOG_PCM_BASE
 {
 public:
     /** Constructor */
-    DIALOG_PCM( wxWindow* parent );
+    DIALOG_PCM( wxWindow* parent, std::shared_ptr<PLUGIN_CONTENT_MANAGER> pcm );
     ~DIALOG_PCM();
 
     ///< Closes the window, asks user confirmation if there are pending actions
diff --git a/kicad/pcm/dialogs/panel_packages_view.cpp b/kicad/pcm/dialogs/panel_packages_view.cpp
index 79185696d3..5679eba2c0 100644
--- a/kicad/pcm/dialogs/panel_packages_view.cpp
+++ b/kicad/pcm/dialogs/panel_packages_view.cpp
@@ -21,10 +21,10 @@
 #include "panel_packages_view.h"
 #include <grid_tricks.h>
 #include <html_window.h>
-#include <kicad_settings.h>
 #include <pgm_base.h>
 #include <settings/common_settings.h>
 #include <settings/settings_manager.h>
+#include <settings/kicad_settings.h>
 #include <string_utils.h>
 #include <widgets/wx_panel.h>
 #include <widgets/wx_splitter_window.h>
@@ -113,6 +113,7 @@ void PANEL_PACKAGES_VIEW::ClearData()
     unsetPackageDetails();
 
     m_currentSelected = nullptr;
+    m_updateablePackages.clear();
     m_packagePanels.clear();
     m_packageInitialOrder.clear();
     m_packageListWindow->GetSizer()->Clear( true ); // Delete panels
@@ -148,9 +149,13 @@ void PANEL_PACKAGES_VIEW::SetData( const std::vector<PACKAGE_VIEW_DATA>& aPackag
 
         m_packagePanels.insert( { data.package.identifier, package_panel } );
         m_packageInitialOrder.push_back( data.package.identifier );
+
+        if( data.state == PPS_UPDATE_AVAILABLE )
+            m_updateablePackages.insert( data.package.identifier );
     }
 
     updatePackageList();
+    updateCommonState();
 }
 
 
@@ -426,7 +431,7 @@ bool PANEL_PACKAGES_VIEW::canRunAction() const
 
 
 void PANEL_PACKAGES_VIEW::SetPackageState( const wxString&         aPackageId,
-                                           const PCM_PACKAGE_STATE aState ) const
+                                           const PCM_PACKAGE_STATE aState )
 {
     auto it = m_packagePanels.find( aPackageId );
 
@@ -439,6 +444,17 @@ void PANEL_PACKAGES_VIEW::SetPackageState( const wxString&         aPackageId,
             wxMouseEvent dummy;
             m_currentSelected->OnClick( dummy );
         }
+
+        if( aState == PPS_UPDATE_AVAILABLE )
+        {
+            m_updateablePackages.insert( aPackageId );
+        }
+        else
+        {
+            m_updateablePackages.erase( aPackageId );
+        }
+
+        updateCommonState();
     }
 }
 
@@ -760,3 +776,31 @@ void PANEL_PACKAGES_VIEW::SetSashOnIdle( wxIdleEvent& aEvent )
     m_splitter1->Disconnect( wxEVT_IDLE, wxIdleEventHandler( PANEL_PACKAGES_VIEW::SetSashOnIdle ),
                              NULL, this );
 }
+
+
+void PANEL_PACKAGES_VIEW::updateCommonState()
+{
+    m_buttonUpdateAll->Enable( m_updateablePackages.size() > 0 );
+}
+
+
+void PANEL_PACKAGES_VIEW::OnUpdateAllClicked( wxCommandEvent& event )
+{
+    // The map will be modified by the callback so we copy the list here
+    std::vector<wxString> packages;
+
+    std::copy( m_updateablePackages.begin(), m_updateablePackages.end(),
+               std::back_inserter( packages ) );
+
+    for( const wxString& pkg_id : packages )
+    {
+        auto it = m_packagePanels.find( pkg_id );
+
+        if( it != m_packagePanels.end() )
+        {
+            const PACKAGE_VIEW_DATA& data = it->second->GetPackageData();
+
+            m_actionCallback( data, PPA_UPDATE, data.update_version );
+        }
+    }
+}
diff --git a/kicad/pcm/dialogs/panel_packages_view.h b/kicad/pcm/dialogs/panel_packages_view.h
index dcbea15f75..d70723f330 100644
--- a/kicad/pcm/dialogs/panel_packages_view.h
+++ b/kicad/pcm/dialogs/panel_packages_view.h
@@ -52,7 +52,7 @@ public:
      * @param aPackageId id of the package
      * @param aState new state
      */
-    void SetPackageState( const wxString& aPackageId, const PCM_PACKAGE_STATE aState ) const;
+    void SetPackageState( const wxString& aPackageId, const PCM_PACKAGE_STATE aState );
 
     ///< Destroys package panels
     void ClearData();
@@ -83,6 +83,9 @@ public:
     ///< Replacement of wxFormBuilder's ill-advised m_splitter1OnIdle
     void SetSashOnIdle( wxIdleEvent& );
 
+    ///< Enqueues all available package updates
+    void OnUpdateAllClicked( wxCommandEvent& event ) override;
+
 private:
     ///< Updates package listing according to search term
     void updatePackageList();
@@ -90,6 +93,9 @@ private:
     ///< Updates buttons below the package details: Download and Install
     void updateDetailsButtons();
 
+    ///< Called when package state changes, currently used to calculate Update All button state
+    void updateCommonState();
+
     ///< Updates details panel
     void setPackageDetails( const PACKAGE_VIEW_DATA& aPackageData );
 
@@ -113,6 +119,7 @@ private:
     std::unordered_map<wxString, PANEL_PACKAGE*> m_packagePanels;
     std::vector<wxString>                        m_packageInitialOrder;
     PANEL_PACKAGE*                               m_currentSelected;
+    std::unordered_set<wxString>                 m_updateablePackages;
     std::shared_ptr<PLUGIN_CONTENT_MANAGER>      m_pcm;
 
     enum PACKAGE_VERSIONS_GRID_COLUMNS
diff --git a/kicad/pcm/dialogs/panel_packages_view_base.cpp b/kicad/pcm/dialogs/panel_packages_view_base.cpp
index 8572edc168..74570e032f 100644
--- a/kicad/pcm/dialogs/panel_packages_view_base.cpp
+++ b/kicad/pcm/dialogs/panel_packages_view_base.cpp
@@ -25,12 +25,21 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
 	wxBoxSizer* bPanelListSizer;
 	bPanelListSizer = new wxBoxSizer( wxVERTICAL );
 
+	wxBoxSizer* bSizer8;
+	bSizer8 = new wxBoxSizer( wxHORIZONTAL );
+
 	m_searchCtrl = new wxSearchCtrl( m_panelList, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
 	#ifndef __WXMAC__
 	m_searchCtrl->ShowSearchButton( true );
 	#endif
 	m_searchCtrl->ShowCancelButton( false );
-	bPanelListSizer->Add( m_searchCtrl, 0, wxEXPAND|wxALL, 5 );
+	bSizer8->Add( m_searchCtrl, 1, wxEXPAND|wxALL, 5 );
+
+	m_buttonUpdateAll = new wxButton( m_panelList, wxID_ANY, _("Update All"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer8->Add( m_buttonUpdateAll, 0, wxBOTTOM|wxRIGHT|wxTOP, 5 );
+
+
+	bPanelListSizer->Add( bSizer8, 0, wxEXPAND, 5 );
 
 	m_packageListWindow = new wxScrolledWindow( m_panelList, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE|wxFULL_REPAINT_ON_RESIZE|wxVSCROLL );
 	m_packageListWindow->SetScrollRate( 5, 5 );
@@ -137,6 +146,7 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
 	bSizer1->Fit( this );
 
 	// Connect Events
+	m_buttonUpdateAll->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnUpdateAllClicked ), NULL, this );
 	m_infoScrollWindow->Connect( wxEVT_SIZE, wxSizeEventHandler( PANEL_PACKAGES_VIEW_BASE::OnSizeInfoBox ), NULL, this );
 	m_infoText->Connect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( PANEL_PACKAGES_VIEW_BASE::OnURLClicked ), NULL, this );
 	m_infoText->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PACKAGES_VIEW_BASE::OnInfoMouseWheel ), NULL, this );
@@ -149,6 +159,7 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
 PANEL_PACKAGES_VIEW_BASE::~PANEL_PACKAGES_VIEW_BASE()
 {
 	// Disconnect Events
+	m_buttonUpdateAll->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnUpdateAllClicked ), NULL, this );
 	m_infoScrollWindow->Disconnect( wxEVT_SIZE, wxSizeEventHandler( PANEL_PACKAGES_VIEW_BASE::OnSizeInfoBox ), NULL, this );
 	m_infoText->Disconnect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( PANEL_PACKAGES_VIEW_BASE::OnURLClicked ), NULL, this );
 	m_infoText->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PACKAGES_VIEW_BASE::OnInfoMouseWheel ), NULL, this );
diff --git a/kicad/pcm/dialogs/panel_packages_view_base.fbp b/kicad/pcm/dialogs/panel_packages_view_base.fbp
index 07c1a5d4fc..189b92148b 100644
--- a/kicad/pcm/dialogs/panel_packages_view_base.fbp
+++ b/kicad/pcm/dialogs/panel_packages_view_base.fbp
@@ -177,67 +177,152 @@
                                     <property name="permission">none</property>
                                     <object class="sizeritem" expanded="1">
                                         <property name="border">5</property>
-                                        <property name="flag">wxEXPAND|wxALL</property>
+                                        <property name="flag">wxEXPAND</property>
                                         <property name="proportion">0</property>
-                                        <object class="wxSearchCtrl" expanded="1">
-                                            <property name="BottomDockable">1</property>
-                                            <property name="LeftDockable">1</property>
-                                            <property name="RightDockable">1</property>
-                                            <property name="TopDockable">1</property>
-                                            <property name="aui_layer"></property>
-                                            <property name="aui_name"></property>
-                                            <property name="aui_position"></property>
-                                            <property name="aui_row"></property>
-                                            <property name="best_size"></property>
-                                            <property name="bg"></property>
-                                            <property name="cancel_button">0</property>
-                                            <property name="caption"></property>
-                                            <property name="caption_visible">1</property>
-                                            <property name="center_pane">0</property>
-                                            <property name="close_button">1</property>
-                                            <property name="context_help"></property>
-                                            <property name="context_menu">1</property>
-                                            <property name="default_pane">0</property>
-                                            <property name="dock">Dock</property>
-                                            <property name="dock_fixed">0</property>
-                                            <property name="docking">Left</property>
-                                            <property name="enabled">1</property>
-                                            <property name="fg"></property>
-                                            <property name="floatable">1</property>
-                                            <property name="font"></property>
-                                            <property name="gripper">0</property>
-                                            <property name="hidden">0</property>
-                                            <property name="id">wxID_ANY</property>
-                                            <property name="max_size"></property>
-                                            <property name="maximize_button">0</property>
-                                            <property name="maximum_size"></property>
-                                            <property name="min_size"></property>
-                                            <property name="minimize_button">0</property>
+                                        <object class="wxBoxSizer" expanded="1">
                                             <property name="minimum_size"></property>
-                                            <property name="moveable">1</property>
-                                            <property name="name">m_searchCtrl</property>
-                                            <property name="pane_border">1</property>
-                                            <property name="pane_position"></property>
-                                            <property name="pane_size"></property>
-                                            <property name="permission">protected</property>
-                                            <property name="pin_button">1</property>
-                                            <property name="pos"></property>
-                                            <property name="resize">Resizable</property>
-                                            <property name="search_button">1</property>
-                                            <property name="show">1</property>
-                                            <property name="size"></property>
-                                            <property name="style"></property>
-                                            <property name="subclass">; ; forward_declare</property>
-                                            <property name="toolbar_pane">0</property>
-                                            <property name="tooltip"></property>
-                                            <property name="validator_data_type"></property>
-                                            <property name="validator_style">wxFILTER_NONE</property>
-                                            <property name="validator_type">wxDefaultValidator</property>
-                                            <property name="validator_variable"></property>
-                                            <property name="value"></property>
-                                            <property name="window_extra_style"></property>
-                                            <property name="window_name"></property>
-                                            <property name="window_style"></property>
+                                            <property name="name">bSizer8</property>
+                                            <property name="orient">wxHORIZONTAL</property>
+                                            <property name="permission">none</property>
+                                            <object class="sizeritem" expanded="1">
+                                                <property name="border">5</property>
+                                                <property name="flag">wxEXPAND|wxALL</property>
+                                                <property name="proportion">1</property>
+                                                <object class="wxSearchCtrl" expanded="1">
+                                                    <property name="BottomDockable">1</property>
+                                                    <property name="LeftDockable">1</property>
+                                                    <property name="RightDockable">1</property>
+                                                    <property name="TopDockable">1</property>
+                                                    <property name="aui_layer"></property>
+                                                    <property name="aui_name"></property>
+                                                    <property name="aui_position"></property>
+                                                    <property name="aui_row"></property>
+                                                    <property name="best_size"></property>
+                                                    <property name="bg"></property>
+                                                    <property name="cancel_button">0</property>
+                                                    <property name="caption"></property>
+                                                    <property name="caption_visible">1</property>
+                                                    <property name="center_pane">0</property>
+                                                    <property name="close_button">1</property>
+                                                    <property name="context_help"></property>
+                                                    <property name="context_menu">1</property>
+                                                    <property name="default_pane">0</property>
+                                                    <property name="dock">Dock</property>
+                                                    <property name="dock_fixed">0</property>
+                                                    <property name="docking">Left</property>
+                                                    <property name="enabled">1</property>
+                                                    <property name="fg"></property>
+                                                    <property name="floatable">1</property>
+                                                    <property name="font"></property>
+                                                    <property name="gripper">0</property>
+                                                    <property name="hidden">0</property>
+                                                    <property name="id">wxID_ANY</property>
+                                                    <property name="max_size"></property>
+                                                    <property name="maximize_button">0</property>
+                                                    <property name="maximum_size"></property>
+                                                    <property name="min_size"></property>
+                                                    <property name="minimize_button">0</property>
+                                                    <property name="minimum_size"></property>
+                                                    <property name="moveable">1</property>
+                                                    <property name="name">m_searchCtrl</property>
+                                                    <property name="pane_border">1</property>
+                                                    <property name="pane_position"></property>
+                                                    <property name="pane_size"></property>
+                                                    <property name="permission">protected</property>
+                                                    <property name="pin_button">1</property>
+                                                    <property name="pos"></property>
+                                                    <property name="resize">Resizable</property>
+                                                    <property name="search_button">1</property>
+                                                    <property name="show">1</property>
+                                                    <property name="size"></property>
+                                                    <property name="style"></property>
+                                                    <property name="subclass">; ; forward_declare</property>
+                                                    <property name="toolbar_pane">0</property>
+                                                    <property name="tooltip"></property>
+                                                    <property name="validator_data_type"></property>
+                                                    <property name="validator_style">wxFILTER_NONE</property>
+                                                    <property name="validator_type">wxDefaultValidator</property>
+                                                    <property name="validator_variable"></property>
+                                                    <property name="value"></property>
+                                                    <property name="window_extra_style"></property>
+                                                    <property name="window_name"></property>
+                                                    <property name="window_style"></property>
+                                                </object>
+                                            </object>
+                                            <object class="sizeritem" expanded="1">
+                                                <property name="border">5</property>
+                                                <property name="flag">wxBOTTOM|wxRIGHT|wxTOP</property>
+                                                <property name="proportion">0</property>
+                                                <object class="wxButton" expanded="1">
+                                                    <property name="BottomDockable">1</property>
+                                                    <property name="LeftDockable">1</property>
+                                                    <property name="RightDockable">1</property>
+                                                    <property name="TopDockable">1</property>
+                                                    <property name="aui_layer"></property>
+                                                    <property name="aui_name"></property>
+                                                    <property name="aui_position"></property>
+                                                    <property name="aui_row"></property>
+                                                    <property name="auth_needed">0</property>
+                                                    <property name="best_size"></property>
+                                                    <property name="bg"></property>
+                                                    <property name="bitmap"></property>
+                                                    <property name="caption"></property>
+                                                    <property name="caption_visible">1</property>
+                                                    <property name="center_pane">0</property>
+                                                    <property name="close_button">1</property>
+                                                    <property name="context_help"></property>
+                                                    <property name="context_menu">1</property>
+                                                    <property name="current"></property>
+                                                    <property name="default">0</property>
+                                                    <property name="default_pane">0</property>
+                                                    <property name="disabled"></property>
+                                                    <property name="dock">Dock</property>
+                                                    <property name="dock_fixed">0</property>
+                                                    <property name="docking">Left</property>
+                                                    <property name="enabled">1</property>
+                                                    <property name="fg"></property>
+                                                    <property name="floatable">1</property>
+                                                    <property name="focus"></property>
+                                                    <property name="font"></property>
+                                                    <property name="gripper">0</property>
+                                                    <property name="hidden">0</property>
+                                                    <property name="id">wxID_ANY</property>
+                                                    <property name="label">Update All</property>
+                                                    <property name="margins"></property>
+                                                    <property name="markup">0</property>
+                                                    <property name="max_size"></property>
+                                                    <property name="maximize_button">0</property>
+                                                    <property name="maximum_size"></property>
+                                                    <property name="min_size"></property>
+                                                    <property name="minimize_button">0</property>
+                                                    <property name="minimum_size"></property>
+                                                    <property name="moveable">1</property>
+                                                    <property name="name">m_buttonUpdateAll</property>
+                                                    <property name="pane_border">1</property>
+                                                    <property name="pane_position"></property>
+                                                    <property name="pane_size"></property>
+                                                    <property name="permission">protected</property>
+                                                    <property name="pin_button">1</property>
+                                                    <property name="pos"></property>
+                                                    <property name="position"></property>
+                                                    <property name="pressed"></property>
+                                                    <property name="resize">Resizable</property>
+                                                    <property name="show">1</property>
+                                                    <property name="size"></property>
+                                                    <property name="style"></property>
+                                                    <property name="subclass">; ; forward_declare</property>
+                                                    <property name="toolbar_pane">0</property>
+                                                    <property name="tooltip"></property>
+                                                    <property name="validator_data_type"></property>
+                                                    <property name="validator_style">wxFILTER_NONE</property>
+                                                    <property name="validator_type">wxDefaultValidator</property>
+                                                    <property name="validator_variable"></property>
+                                                    <property name="window_extra_style"></property>
+                                                    <property name="window_name"></property>
+                                                    <property name="window_style"></property>
+                                                    <event name="OnButtonClick">OnUpdateAllClicked</event>
+                                                </object>
+                                            </object>
                                         </object>
                                     </object>
                                     <object class="sizeritem" expanded="1">
diff --git a/kicad/pcm/dialogs/panel_packages_view_base.h b/kicad/pcm/dialogs/panel_packages_view_base.h
index 84e0e35485..645507d8fe 100644
--- a/kicad/pcm/dialogs/panel_packages_view_base.h
+++ b/kicad/pcm/dialogs/panel_packages_view_base.h
@@ -21,16 +21,16 @@ class WX_PANEL;
 #include <wx/font.h>
 #include <wx/colour.h>
 #include <wx/settings.h>
+#include <wx/button.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
 #include <wx/sizer.h>
 #include <wx/scrolwin.h>
 #include <wx/panel.h>
 #include <wx/html/htmlwin.h>
 #include <wx/grid.h>
 #include <wx/checkbox.h>
-#include <wx/button.h>
-#include <wx/bitmap.h>
-#include <wx/image.h>
-#include <wx/icon.h>
 #include <wx/splitter.h>
 
 ///////////////////////////////////////////////////////////////////////////
@@ -47,6 +47,7 @@ class PANEL_PACKAGES_VIEW_BASE : public wxPanel
 		WX_SPLITTER_WINDOW* m_splitter1;
 		WX_PANEL* m_panelList;
 		wxSearchCtrl* m_searchCtrl;
+		wxButton* m_buttonUpdateAll;
 		wxScrolledWindow* m_packageListWindow;
 		wxPanel* m_panelDetails;
 		wxScrolledWindow* m_infoScrollWindow;
@@ -58,6 +59,7 @@ class PANEL_PACKAGES_VIEW_BASE : public wxPanel
 		wxButton* m_buttonAction;
 
 		// Virtual event handlers, override them in your derived class
+		virtual void OnUpdateAllClicked( wxCommandEvent& event ) { event.Skip(); }
 		virtual void OnSizeInfoBox( wxSizeEvent& event ) { event.Skip(); }
 		virtual void OnURLClicked( wxHtmlLinkEvent& event ) { event.Skip(); }
 		virtual void OnInfoMouseWheel( wxMouseEvent& event ) { event.Skip(); }
diff --git a/kicad/pcm/dialogs/panel_pcm_settings.cpp b/kicad/pcm/dialogs/panel_pcm_settings.cpp
new file mode 100644
index 0000000000..bc835de229
--- /dev/null
+++ b/kicad/pcm/dialogs/panel_pcm_settings.cpp
@@ -0,0 +1,55 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Andrew Lutsenko, anlutsenko at gmail dot com
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "panel_pcm_settings.h"
+
+#include <pgm_base.h>
+#include <settings/kicad_settings.h>
+#include <settings/settings_manager.h>
+
+PANEL_PCM_SETTINGS::PANEL_PCM_SETTINGS( wxWindow* parent ) : PANEL_PCM_SETTINGS_BASE( parent )
+{
+}
+
+
+bool PANEL_PCM_SETTINGS::TransferDataToWindow()
+{
+    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
+    KICAD_SETTINGS*   settings = mgr.GetAppSettings<KICAD_SETTINGS>();
+
+    m_updateCheck->SetValue( settings->m_PcmUpdateCheck );
+
+    return true;
+}
+
+
+bool PANEL_PCM_SETTINGS::TransferDataFromWindow()
+{
+    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
+    KICAD_SETTINGS*   settings = mgr.GetAppSettings<KICAD_SETTINGS>();
+
+    settings->m_PcmUpdateCheck = m_updateCheck->GetValue();
+
+    return true;
+}
diff --git a/kicad/pcm/dialogs/panel_pcm_settings.h b/kicad/pcm/dialogs/panel_pcm_settings.h
new file mode 100644
index 0000000000..7dcc50e093
--- /dev/null
+++ b/kicad/pcm/dialogs/panel_pcm_settings.h
@@ -0,0 +1,39 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Andrew Lutsenko, anlutsenko at gmail dot com
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef PANEL_PCM_SETTINGS_H_
+#define PANEL_PCM_SETTINGS_H_
+
+#include "panel_pcm_settings_base.h"
+
+class PANEL_PCM_SETTINGS : public PANEL_PCM_SETTINGS_BASE
+{
+public:
+    PANEL_PCM_SETTINGS( wxWindow* parent );
+
+    bool TransferDataToWindow() override;
+    bool TransferDataFromWindow() override;
+};
+
+#endif // PANEL_PCM_SETTINGS_H_
diff --git a/kicad/pcm/dialogs/panel_pcm_settings_base.cpp b/kicad/pcm/dialogs/panel_pcm_settings_base.cpp
new file mode 100644
index 0000000000..e79e2b2b7b
--- /dev/null
+++ b/kicad/pcm/dialogs/panel_pcm_settings_base.cpp
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "panel_pcm_settings_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+PANEL_PCM_SETTINGS_BASE::PANEL_PCM_SETTINGS_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name )
+{
+	wxBoxSizer* bSizer1;
+	bSizer1 = new wxBoxSizer( wxVERTICAL );
+
+	m_updateCheck = new wxCheckBox( this, wxID_ANY, _("Check for package updates on startup"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_updateCheck->SetValue(true);
+	bSizer1->Add( m_updateCheck, 0, wxALL, 5 );
+
+
+	this->SetSizer( bSizer1 );
+	this->Layout();
+}
+
+PANEL_PCM_SETTINGS_BASE::~PANEL_PCM_SETTINGS_BASE()
+{
+}
diff --git a/kicad/pcm/dialogs/panel_pcm_settings_base.fbp b/kicad/pcm/dialogs/panel_pcm_settings_base.fbp
new file mode 100644
index 0000000000..92477d5a6c
--- /dev/null
+++ b/kicad/pcm/dialogs/panel_pcm_settings_base.fbp
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="16" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration">; </property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">panel_pcm_settings_base</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="image_path_wrapper_function_name"></property>
+        <property name="indent_with_spaces"></property>
+        <property name="internationalize">1</property>
+        <property name="name">panel_pcm_settings_base</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_array_enum">0</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Panel" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">PANEL_PCM_SETTINGS_BASE</property>
+            <property name="pos"></property>
+            <property name="size">500,300</property>
+            <property name="subclass">; ; forward_declare</property>
+            <property name="tooltip"></property>
+            <property name="two_step_creation">0</property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style">wxTAB_TRAVERSAL</property>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bSizer1</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxALL</property>
+                    <property name="proportion">0</property>
+                    <object class="wxCheckBox" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="checked">1</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label">Check for package updates on startup</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_updateCheck</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="validator_data_type"></property>
+                        <property name="validator_style">wxFILTER_NONE</property>
+                        <property name="validator_type">wxDefaultValidator</property>
+                        <property name="validator_variable"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/kicad/pcm/dialogs/panel_pcm_settings_base.h b/kicad/pcm/dialogs/panel_pcm_settings_base.h
new file mode 100644
index 0000000000..8499823700
--- /dev/null
+++ b/kicad/pcm/dialogs/panel_pcm_settings_base.h
@@ -0,0 +1,42 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/intl.h>
+#include <wx/string.h>
+#include <wx/checkbox.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/panel.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class PANEL_PCM_SETTINGS_BASE
+///////////////////////////////////////////////////////////////////////////////
+class PANEL_PCM_SETTINGS_BASE : public wxPanel
+{
+	private:
+
+	protected:
+		wxCheckBox* m_updateCheck;
+
+	public:
+
+		PANEL_PCM_SETTINGS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 500,300 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
+
+		~PANEL_PCM_SETTINGS_BASE();
+
+};
+
diff --git a/kicad/pcm/pcm.cpp b/kicad/pcm/pcm.cpp
index e5365eda1b..b35b92f8ab 100644
--- a/kicad/pcm/pcm.cpp
+++ b/kicad/pcm/pcm.cpp
@@ -61,16 +61,48 @@ class THROWING_ERROR_HANDLER : public nlohmann::json_schema::error_handler
 };
 
 
-PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( wxWindow* aParent ) : m_dialog( aParent )
+class STATUS_TEXT_REPORTER : public PROGRESS_REPORTER_BASE
 {
-    // Get 3rd party path
-    const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
-    auto               it = env.find( "KICAD6_3RD_PARTY" );
+public:
+    STATUS_TEXT_REPORTER( std::function<void( const wxString )> aStatusCallback ) :
+            PROGRESS_REPORTER_BASE( 1 ), m_statusCallback( aStatusCallback )
+    {
+    }
 
-    if( it != env.end() && !it->second.GetValue().IsEmpty() )
-        m_3rdparty_path = it->second.GetValue();
-    else
-        m_3rdparty_path = PATHS::GetDefault3rdPartyPath();
+    void SetTitle( const wxString& aTitle ) override
+    {
+        m_title = aTitle;
+        m_report = wxT( "" );
+    }
+
+    void Report( const wxString& aMessage ) override
+    {
+        m_report = wxString::Format( ": %s", aMessage );
+    }
+
+    void Cancel() { m_cancelled.store( true ); }
+
+private:
+    bool updateUI() override
+    {
+        m_statusCallback( wxString::Format( "%s%s", m_title, m_report ) );
+        return true;
+    }
+
+    const std::function<void( const wxString )> m_statusCallback;
+
+    wxString m_title;
+    wxString m_report;
+};
+
+
+PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER(
+        std::function<void( int )>            aAvailableUpdateCallback,
+        std::function<void( const wxString )> aStatusCallback ) :
+        m_dialog( nullptr ),
+        m_availableUpdateCallback( aAvailableUpdateCallback ), m_statusCallback( aStatusCallback )
+{
+    ReadEnvVar();
 
     // Read and store pcm schema
     wxFileName schema_file( PATHS::GetStockDataPath( true ), "pcm.v1.schema.json" );
@@ -185,9 +217,22 @@ PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( wxWindow* aParent ) : m_dialog(
 }
 
 
+void PLUGIN_CONTENT_MANAGER::ReadEnvVar()
+{
+    // Get 3rd party path
+    const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
+    auto               it = env.find( "KICAD6_3RD_PARTY" );
+
+    if( it != env.end() && !it->second.GetValue().IsEmpty() )
+        m_3rdparty_path = it->second.GetValue();
+    else
+        m_3rdparty_path = PATHS::GetDefault3rdPartyPath();
+}
+
+
 bool PLUGIN_CONTENT_MANAGER::DownloadToStream( const wxString& aUrl, std::ostream* aOutput,
-                                               WX_PROGRESS_REPORTER* aReporter,
-                                               const size_t          aSizeLimit )
+                                               PROGRESS_REPORTER* aReporter,
+                                               const size_t       aSizeLimit )
 {
     bool size_exceeded = false;
 
@@ -228,10 +273,13 @@ bool PLUGIN_CONTENT_MANAGER::DownloadToStream( const wxString& aUrl, std::ostrea
 
     if( code != CURLE_OK )
     {
-        if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
-            wxMessageBox( _( "Download is too large." ) );
-        else if( code != CURLE_ABORTED_BY_CALLBACK )
-            wxLogError( wxString( curl.GetErrorText( code ) ) );
+        if( m_dialog )
+        {
+            if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
+                wxMessageBox( _( "Download is too large." ) );
+            else if( code != CURLE_ABORTED_BY_CALLBACK )
+                wxLogError( wxString( curl.GetErrorText( code ) ) );
+        }
 
         return false;
     }
@@ -241,7 +289,7 @@ bool PLUGIN_CONTENT_MANAGER::DownloadToStream( const wxString& aUrl, std::ostrea
 
 
 bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository,
-                                              WX_PROGRESS_REPORTER* aReporter )
+                                              PROGRESS_REPORTER* aReporter )
 {
     std::stringstream repository_stream;
 
@@ -249,7 +297,9 @@ bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITO
 
     if( !DownloadToStream( aUrl, &repository_stream, aReporter, 20480 ) )
     {
-        wxLogError( _( "Unable to load repository url" ) );
+        if( m_dialog )
+            wxLogError( _( "Unable to load repository url" ) );
+
         return false;
     }
 
@@ -265,7 +315,9 @@ bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITO
     }
     catch( const std::exception& e )
     {
-        wxLogError( wxString::Format( _( "Unable to parse repository:\n\n%s" ), e.what() ) );
+        if( m_dialog )
+            wxLogError( wxString::Format( _( "Unable to parse repository:\n\n%s" ), e.what() ) );
+
         return false;
     }
 
@@ -284,7 +336,7 @@ void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json&     aJson,
 bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString&                  aUrl,
                                             const boost::optional<wxString>& aHash,
                                             std::vector<PCM_PACKAGE>&        aPackages,
-                                            WX_PROGRESS_REPORTER*            aReporter )
+                                            PROGRESS_REPORTER*               aReporter )
 {
     std::stringstream packages_stream;
 
@@ -292,7 +344,9 @@ bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString&                  aUr
 
     if( !DownloadToStream( aUrl, &packages_stream, aReporter ) )
     {
-        wxLogError( _( "Unable to load repository packages url." ) );
+        if( m_dialog )
+            wxLogError( _( "Unable to load repository packages url." ) );
+
         return false;
     }
 
@@ -300,7 +354,9 @@ bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString&                  aUr
 
     if( aHash && !VerifyHash( isstream, aHash.get() ) )
     {
-        wxLogError( _( "Packages hash doesn't match. Repository may be corrupted." ) );
+        if( m_dialog )
+            wxLogError( _( "Packages hash doesn't match. Repository may be corrupted." ) );
+
         return false;
     }
 
@@ -313,7 +369,10 @@ bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString&                  aUr
     }
     catch( std::exception& e )
     {
-        wxLogError( wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ), e.what() ) );
+        if( m_dialog )
+            wxLogError(
+                    wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ), e.what() ) );
+
         return false;
     }
 
@@ -363,8 +422,12 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
     nlohmann::json js;
     PCM_REPOSITORY current_repo;
 
-    std::unique_ptr<WX_PROGRESS_REPORTER> reporter =
-            std::make_unique<WX_PROGRESS_REPORTER>( m_dialog, wxT( "" ), 1 );
+    std::shared_ptr<PROGRESS_REPORTER> reporter;
+
+    if( m_dialog )
+        reporter = std::make_shared<WX_PROGRESS_REPORTER>( m_dialog, wxT( "" ), 1 );
+    else
+        reporter = m_statusReporter;
 
     if( !FetchRepository( url, current_repo, reporter.get() ) )
         return false;
@@ -394,8 +457,11 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
                 packages_cache_stream >> js;
                 saved_repo.package_list = js["packages"].get<std::vector<PCM_PACKAGE>>();
 
-                std::for_each( saved_repo.package_list.begin(), saved_repo.package_list.end(),
-                               &preparePackage );
+                for( size_t i = 0; i < saved_repo.package_list.size(); i++ )
+                {
+                    preparePackage( saved_repo.package_list[i] );
+                    saved_repo.package_map[saved_repo.package_list[i].identifier] = i;
+                }
 
                 m_repository_cache[aRepositoryId] = std::move( saved_repo );
 
@@ -403,8 +469,9 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
             }
             catch( ... )
             {
-                wxLogError( _( "Packages cache for current repository is "
-                               "corrupted, it will be redownloaded." ) );
+                if( m_dialog )
+                    wxLogError( _( "Packages cache for current repository is "
+                                   "corrupted, it will be redownloaded." ) );
             }
         }
     }
@@ -418,8 +485,11 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
             return false;
         }
 
-        std::for_each( current_repo.package_list.begin(), current_repo.package_list.end(),
-                       &preparePackage );
+        for( size_t i = 0; i < current_repo.package_list.size(); i++ )
+        {
+            preparePackage( current_repo.package_list[i] );
+            current_repo.package_map[current_repo.package_list[i].identifier] = i;
+        }
 
         repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
 
@@ -468,9 +538,11 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
                 if( resources.sha256 && !VerifyHash( read_stream, resources.sha256.get() ) )
                 {
                     read_stream.close();
-                    wxLogError(
-                            _( "Resources file hash doesn't match and will not be used. Repository "
-                               "may be corrupted." ) );
+
+                    if( m_dialog )
+                        wxLogError( _( "Resources file hash doesn't match and will not be used. "
+                                       "Repository may be corrupted." ) );
+
                     wxRemoveFile( resource_file.GetFullPath() );
                 }
             }
@@ -482,10 +554,70 @@ const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryI
         }
     }
 
+    updateInstalledPackagesMetadata( aRepositoryId );
+
     return true;
 }
 
 
+void PLUGIN_CONTENT_MANAGER::updateInstalledPackagesMetadata( const wxString& aRepositoryId )
+{
+    const PCM_REPOSITORY& repository = getCachedRepository( aRepositoryId );
+
+    for( auto& entry : m_installed )
+    {
+        PCM_INSTALLATION_ENTRY& installation_entry = entry.second;
+
+        // If current package is not from this repository, skip it
+        if( installation_entry.repository_id != aRepositoryId )
+            continue;
+
+        // If current package is no longer in this repository, keep it as is
+        if( repository.package_map.count( installation_entry.package.identifier ) == 0 )
+            continue;
+
+        boost::optional<PACKAGE_VERSION> current_version;
+
+        auto current_version_it =
+                std::find_if( installation_entry.package.versions.begin(),
+                              installation_entry.package.versions.end(),
+                              [&]( const PACKAGE_VERSION& version )
+                              {
+                                  return version.version == installation_entry.current_version;
+                              } );
+
+        if( current_version_it != installation_entry.package.versions.end() )
+            current_version = *current_version_it; // copy
+
+        // Copy repository metadata into installation entry
+        installation_entry.package = repository.package_list[repository.package_map.at(
+                installation_entry.package.identifier )];
+
+        // Insert current version if it's missing from repository metadata
+        current_version_it =
+                std::find_if( installation_entry.package.versions.begin(),
+                              installation_entry.package.versions.end(),
+                              [&]( const PACKAGE_VERSION& version )
+                              {
+                                  return version.version == installation_entry.current_version;
+                              } );
+
+        if( current_version_it == installation_entry.package.versions.end() )
+        {
+            installation_entry.package.versions.emplace_back( current_version.get() );
+
+            // Re-sort the versions by descending version
+            std::sort( installation_entry.package.versions.begin(),
+                       installation_entry.package.versions.end(),
+                       []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
+                       {
+                           return a.parsed_version > b.parsed_version;
+                       } );
+        }
+    }
+}
+
+
 void PLUGIN_CONTENT_MANAGER::preparePackage( PCM_PACKAGE& aPackage )
 {
     // Parse package version strings
@@ -605,8 +737,8 @@ void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepository
     if( m_repository_cache.count( aRepositoryId ) > 0 )
         m_repository_cache.erase( aRepositoryId );
 
-    wxFileName repo_cache( m_3rdparty_path, "" );
-    repo_cache.AppendDir( "cache" );
+    wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), "" );
+    repo_cache.AppendDir( "pcm" );
     repo_cache.AppendDir( aRepositoryId );
 
     if( repo_cache.DirExists() )
@@ -652,16 +784,10 @@ PCM_PACKAGE_STATE PLUGIN_CONTENT_MANAGER::GetPackageState( const wxString& aRepo
 
     const PCM_REPOSITORY& repo = getCachedRepository( aRepositoryId );
 
-    auto pkg_it = std::find_if( repo.package_list.begin(), repo.package_list.end(),
-                                [&aPackageId]( const PCM_PACKAGE& pkg )
-                                {
-                                    return pkg.identifier == aPackageId;
-                                } );
-
-    if( pkg_it == repo.package_list.end() )
+    if( repo.package_map.count( aPackageId ) == 0 )
         return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
 
-    const PCM_PACKAGE& pkg = *pkg_it;
+    const PCM_PACKAGE& pkg = repo.package_list[repo.package_map.at( aPackageId )];
 
     if( installed )
     {
@@ -720,10 +846,8 @@ time_t PLUGIN_CONTENT_MANAGER::getCurrentTimestamp() const
 }
 
 
-PLUGIN_CONTENT_MANAGER::~PLUGIN_CONTENT_MANAGER()
+void PLUGIN_CONTENT_MANAGER::SaveInstalledPackages()
 {
-    // Save current installed packages list.
-
     try
     {
         nlohmann::json js;
@@ -920,3 +1044,84 @@ std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPacka
 
     return bitmaps;
 }
+
+
+void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate()
+{
+    m_statusReporter = std::make_shared<STATUS_TEXT_REPORTER>( m_statusCallback );
+
+    m_updateThread = std::thread(
+            [this]()
+            {
+                if( m_installed.size() == 0 )
+                    return;
+
+                // Only fetch repositories that have installed packages
+                std::unordered_set<wxString> repo_ids;
+
+                for( auto& entry : m_installed )
+                    repo_ids.insert( entry.second.repository_id );
+
+                for( const auto& entry : m_repository_list )
+                {
+                    const wxString& repository_id = std::get<0>( entry );
+
+                    if( repo_ids.count( repository_id ) == 0 )
+                        continue;
+
+                    CacheRepository( repository_id );
+
+                    if( m_statusReporter->IsCancelled() )
+                        break;
+                }
+
+                if( m_statusReporter->IsCancelled() )
+                    return;
+
+                // Count packages with updates
+                int availableUpdateCount = 0;
+
+                for( auto& entry : m_installed )
+                {
+                    PCM_INSTALLATION_ENTRY& installed_package = entry.second;
+
+                    if( m_repository_cache.find( installed_package.repository_id )
+                        != m_repository_cache.end() )
+                    {
+                        PCM_PACKAGE_STATE state =
+                                GetPackageState( installed_package.repository_id,
+                                                 installed_package.package.identifier );
+
+                        if( state == PPS_UPDATE_AVAILABLE )
+                            availableUpdateCount++;
+                    }
+
+                    if( m_statusReporter->IsCancelled() )
+                        return;
+                }
+
+                // Update the badge on PCM button
+                m_availableUpdateCallback( availableUpdateCount );
+
+                m_statusCallback( availableUpdateCount > 0 ? _( "Package updates are available" )
+                                                           : _( "No package updates available" ) );
+            } );
+}
+
+
+void PLUGIN_CONTENT_MANAGER::StopBackgroundUpdate()
+{
+    if( m_updateThread.joinable() )
+    {
+        m_statusReporter->Cancel();
+        m_updateThread.join();
+    }
+}
+
+
+PLUGIN_CONTENT_MANAGER::~PLUGIN_CONTENT_MANAGER()
+{
+    // By the time object is being destroyed the thread should be
+    // stopped already but just in case do it here too.
+    StopBackgroundUpdate();
+}
diff --git a/kicad/pcm/pcm.h b/kicad/pcm/pcm.h
index 3827610da4..e7c6dc4261 100644
--- a/kicad/pcm/pcm.h
+++ b/kicad/pcm/pcm.h
@@ -24,9 +24,11 @@
 #include "core/wx_stl_compat.h"
 #include "pcm_data.h"
 #include "widgets/wx_progress_reporters.h"
+#include <functional>
 #include <iostream>
 #include <map>
 #include <nlohmann/json-schema.hpp>
+#include <thread>
 #include <tuple>
 #include <unordered_map>
 #include <unordered_set>
@@ -74,6 +76,9 @@ typedef std::vector<std::pair<wxString, wxString>>            STRING_PAIR_LIST;
 typedef std::vector<std::tuple<wxString, wxString, wxString>> STRING_TUPLE_LIST;
 
 
+class STATUS_TEXT_REPORTER;
+
+
 /**
  * @brief Main class of Plugin and Content Manager subsystem
  *
@@ -96,9 +101,17 @@ typedef std::vector<std::tuple<wxString, wxString, wxString>> STRING_TUPLE_LIST;
 class PLUGIN_CONTENT_MANAGER
 {
 public:
-    PLUGIN_CONTENT_MANAGER( wxWindow* aParent );
+    PLUGIN_CONTENT_MANAGER( std::function<void( int )>            aAvailableUpdateCallback,
+                            std::function<void( const wxString )> aStatusCallback );
     ~PLUGIN_CONTENT_MANAGER();
 
+    /**
+     * @brief Saves metadata of installed packages to disk
+     *
+     * Path is <user settings>/installed_packages.json
+     */
+    void SaveInstalledPackages();
+
     /**
      * @brief Fetches repository metadata from given url
      *
@@ -109,7 +122,7 @@ public:
      * @return false if URL could not be downloaded or result could not be parsed
      */
     bool FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository,
-                          WX_PROGRESS_REPORTER* aReporter );
+                          PROGRESS_REPORTER* aReporter );
 
     /**
      * @brief Validates json against a specific definition in the PCM schema
@@ -250,8 +263,8 @@ public:
      * @return false if download failed or was too large
      */
     bool DownloadToStream( const wxString& aUrl, std::ostream* aOutput,
-                           WX_PROGRESS_REPORTER* aReporter,
-                           const size_t          aSizeLimit = DEFAULT_DOWNLOAD_MEM_LIMIT );
+                           PROGRESS_REPORTER* aReporter,
+                           const size_t       aSizeLimit = DEFAULT_DOWNLOAD_MEM_LIMIT );
 
     /**
      * @brief Get the approximate measure of how much given package matches the search term
@@ -283,6 +296,32 @@ public:
      */
     std::unordered_map<wxString, wxBitmap> GetInstalledPackageBitmaps();
 
+    /**
+     * @brief Set the Dialog Window
+     *
+     * PCM can effectively run in "silent" mode with a background thread that
+     * reports to kicad manager window status bar. Setting valid window pointer here
+     * will switch it to GUI mode with WX_PROGRESS_DIALOG popup for downloads.
+     *
+     * @param aDialog parent dialog for progress window
+     */
+    void SetDialogWindow( wxWindow* aDialog ) { m_dialog = aDialog; };
+
+    /**
+     * @brief Runs a background update thread that checks for new package versions
+     */
+    void RunBackgroundUpdate();
+
+    /**
+     * @brief Interrupts and joins() the update thread
+     */
+    void StopBackgroundUpdate();
+
+    /**
+     * @brief Stores 3rdparty path from environment variables
+     */
+    void ReadEnvVar();
+
 private:
     ///< Default download limit of 10 Mb to not use too much memory
     static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT = 10 * 1024 * 1024;
@@ -297,7 +336,7 @@ private:
      * @return true if packages were successfully downloaded, verified and parsed
      */
     bool fetchPackages( const wxString& aUrl, const boost::optional<wxString>& aHash,
-                        std::vector<PCM_PACKAGE>& aPackages, WX_PROGRESS_REPORTER* aReporter );
+                        std::vector<PCM_PACKAGE>& aPackages, PROGRESS_REPORTER* aReporter );
 
     /**
      * @brief Get the cached repository metadata
@@ -307,6 +346,18 @@ private:
      */
     const PCM_REPOSITORY& getCachedRepository( const wxString& aRepositoryId ) const;
 
+    /**
+     * @brief Updates metadata of installed packages from freshly fetched repo
+     *
+     * This completely replaces all fields including description.
+     * Only exception is versions field, if currently installed version is missing
+     * from the repo metadata it is manually added back in to correctly display in the
+     * installed packages.
+     *
+     * @param aRepositoryId
+     */
+    void updateInstalledPackagesMetadata( const wxString& aRepositoryId );
+
     /**
      * @brief Parses version strings and calculates compatibility
      *
@@ -329,6 +380,11 @@ private:
     // Using sorted map to keep order of entries in installed list stable
     std::map<wxString, PCM_INSTALLATION_ENTRY> m_installed;
     const static std::tuple<int, int, int>     m_kicad_version;
+    std::function<void( int )>                 m_availableUpdateCallback;
+    std::function<void( const wxString )>      m_statusCallback;
+    std::thread                                m_updateThread;
+
+    std::shared_ptr<STATUS_TEXT_REPORTER> m_statusReporter;
 };
 
 #endif // PCM_H_
diff --git a/kicad/pcm/pcm_data.h b/kicad/pcm/pcm_data.h
index 8fa151c73c..c03c41ef53 100644
--- a/kicad/pcm/pcm_data.h
+++ b/kicad/pcm/pcm_data.h
@@ -128,6 +128,8 @@ struct PCM_REPOSITORY
 
     // Not serialized fields
     std::vector<PCM_PACKAGE> package_list;
+    // pkg id to index of package from package_list for quick lookup
+    std::unordered_map<wxString, size_t> package_map;
 };
 
 
@@ -139,6 +141,9 @@ struct PCM_INSTALLATION_ENTRY
     wxString    repository_id;
     wxString    repository_name;
     uint64_t    install_timestamp;
+
+    // Not serialized fields
+    bool update_available;
 };
 
 
diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp
index 6da1974cf5..e4809a8bb1 100644
--- a/kicad/tools/kicad_manager_control.cpp
+++ b/kicad/tools/kicad_manager_control.cpp
@@ -806,7 +806,7 @@ int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent )
     m_frame->SetFocus();
     wxSafeYield();
 
-    DIALOG_PCM pcm( m_frame );
+    DIALOG_PCM pcm( m_frame, m_frame->GetPcm() );
     pcm.ShowModal();
 
     return 0;