From f4fa3b02c5b98df50a2d6d84a5ee1f0aefd6f985 Mon Sep 17 00:00:00 2001 From: qu1ck 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 #include #include +#include #include #include #include @@ -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 . */ -#include "kicad_settings.h" +#include "settings/kicad_settings.h" #include #include @@ -42,6 +42,8 @@ KICAD_SETTINGS::KICAD_SETTINGS() : m_params.emplace_back( new PARAM_LIST( "system.open_projects", &m_OpenProjects, {} ) ); + m_params.emplace_back( new PARAM( "system.check_for_updates", &m_updateCheck, 0 ) ); + m_params.emplace_back( new PARAM_LAMBDA( "pcm.repositories", [&]() -> nlohmann::json @@ -77,6 +79,9 @@ KICAD_SETTINGS::KICAD_SETTINGS() : m_params.emplace_back( new PARAM( "pcm.last_download_dir", &m_PcmLastDownloadDir, "" ) ); + + m_params.emplace_back( + new PARAM( "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 m_OpenProjects; std::vector> 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 #include +#include /** @@ -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 #include #include +#include #include #include #include @@ -49,7 +50,6 @@ #include "pgm_kicad.h" #include "kicad_manager_frame.h" -#include "kicad_settings.h" #include #include 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 #include #include +#include #include +#include #include #include #include @@ -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( + [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 #include #include +#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 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 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 $ +) + 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> PACKAGE_TYPE_LIST = { }; -DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent ) +DIALOG_PCM::DIALOG_PCM( wxWindow* parent, std::shared_ptr pcm ) : + DIALOG_PCM_BASE( parent ), m_pcm( pcm ) { m_defaultBitmap = KiBitmap( BITMAPS::icon_pcm ); - m_pcm = std::make_shared( 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(); - - 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 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 #include -#include #include #include #include +#include #include #include #include @@ -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& 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 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 m_packagePanels; std::vector m_packageInitialOrder; PANEL_PACKAGE* m_currentSelected; + std::unordered_set m_updateablePackages; std::shared_ptr 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 @@ none 5 - wxEXPAND|wxALL + wxEXPAND 0 - - 1 - 1 - 1 - 1 - - - - - - - 0 - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 + - 1 - m_searchCtrl - 1 - - - protected - 1 - - Resizable - 1 - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - + bSizer8 + wxHORIZONTAL + none + + 5 + wxEXPAND|wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + 0 + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_searchCtrl + 1 + + + protected + 1 + + Resizable + 1 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 1 + + 1 + + + 0 + 0 + wxID_ANY + Update All + + 0 + + 0 + + + 0 + + 1 + m_buttonUpdateAll + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + OnUpdateAllClicked + + 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 #include #include +#include +#include +#include +#include #include #include #include #include #include #include -#include -#include -#include -#include #include /////////////////////////////////////////////////////////////////////////// @@ -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 +#include +#include + +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(); + + m_updateCheck->SetValue( settings->m_PcmUpdateCheck ); + + return true; +} + + +bool PANEL_PCM_SETTINGS::TransferDataFromWindow() +{ + SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager(); + KICAD_SETTINGS* settings = mgr.GetAppSettings(); + + 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 @@ + + + + + ; + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + panel_pcm_settings_base + 1000 + none + + + 1 + panel_pcm_settings_base + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + + 1 + 1 + impl_virtual + + + 0 + wxID_ANY + + + PANEL_PCM_SETTINGS_BASE + + 500,300 + ; ; forward_declare + + 0 + + + wxTAB_TRAVERSAL + + + bSizer1 + wxVERTICAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Check for package updates on startup + + 0 + + + 0 + + 1 + m_updateCheck + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// 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 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 m_statusCallback; + + wxString m_title; + wxString m_report; +}; + + +PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( + std::function aAvailableUpdateCallback, + std::function 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& aHash, std::vector& 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 reporter = - std::make_unique( m_dialog, wxT( "" ), 1 ); + std::shared_ptr reporter; + + if( m_dialog ) + reporter = std::make_shared( 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::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 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 PLUGIN_CONTENT_MANAGER::GetInstalledPacka return bitmaps; } + + +void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate() +{ + m_statusReporter = std::make_shared( m_statusCallback ); + + m_updateThread = std::thread( + [this]() + { + if( m_installed.size() == 0 ) + return; + + // Only fetch repositories that have installed packages + std::unordered_set 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 #include #include #include +#include #include #include #include @@ -74,6 +76,9 @@ typedef std::vector> STRING_PAIR_LIST; typedef std::vector> STRING_TUPLE_LIST; +class STATUS_TEXT_REPORTER; + + /** * @brief Main class of Plugin and Content Manager subsystem * @@ -96,9 +101,17 @@ typedef std::vector> STRING_TUPLE_LIST; class PLUGIN_CONTENT_MANAGER { public: - PLUGIN_CONTENT_MANAGER( wxWindow* aParent ); + PLUGIN_CONTENT_MANAGER( std::function aAvailableUpdateCallback, + std::function aStatusCallback ); ~PLUGIN_CONTENT_MANAGER(); + /** + * @brief Saves metadata of installed packages to disk + * + * Path is /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 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& aHash, - std::vector& aPackages, WX_PROGRESS_REPORTER* aReporter ); + std::vector& 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 m_installed; const static std::tuple m_kicad_version; + std::function m_availableUpdateCallback; + std::function m_statusCallback; + std::thread m_updateThread; + + std::shared_ptr 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 package_list; + // pkg id to index of package from package_list for quick lookup + std::unordered_map 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;