PCM: package update functionality

Adds package update available state, package update operation and all
the corresponding logic around it to make updating package to another
version easy.
This commit is contained in:
qu1ck 2022-06-30 21:09:25 -07:00 committed by Seth Hillbrand
parent 1e4d0aa1c0
commit 955c5d039e
18 changed files with 701 additions and 237 deletions

View File

@ -22,9 +22,9 @@
// at least on Windows/msys2
#include "kicad_curl/kicad_curl_easy.h"
#include "dialog_pcm.h"
#include "bitmaps.h"
#include "dialog_manage_repositories.h"
#include "dialog_pcm.h"
#include "grid_tricks.h"
#include "ki_exception.h"
#include "kicad_settings.h"
@ -35,11 +35,11 @@
#include "widgets/wx_grid.h"
#include <fstream>
#include <launch_ext.h>
#include <sstream>
#include <vector>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>
#include <launch_ext.h>
#define GRID_CELL_MARGIN 4
@ -71,7 +71,7 @@ DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
for( const std::pair<PCM_PACKAGE_TYPE, wxString>& entry : PACKAGE_TYPE_LIST )
{
PANEL_PACKAGES_VIEW* panel = new PANEL_PACKAGES_VIEW( m_contentNotebook, m_pcm );
wxString label = wxGetTranslation( entry.second );
wxString label = wxGetTranslation( entry.second );
m_contentNotebook->AddPage( panel, wxString::Format( label, 0 ) );
m_repositoryContentPanels.insert( { entry.first, panel } );
}
@ -91,27 +91,31 @@ DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
m_gridPendingActions->SetCellValue( row, PENDING_COL_NAME, aData.package.name );
m_gridPendingActions->SetCellValue( row, PENDING_COL_REPOSITORY, aData.repository_name );
if( aAction == PPA_INSTALL )
switch( aAction )
{
case PPA_INSTALL:
m_gridPendingActions->SetCellValue( row, PENDING_COL_ACTION, _( "Install" ) );
m_gridPendingActions->SetCellValue( row, PENDING_COL_VERSION, aVersion );
m_pendingActions.emplace_back( aAction, aData.repository_id, aData.package, aVersion );
new_state = PPS_PENDING_INSTALL;
}
else
{
break;
case PPA_UPDATE:
m_gridPendingActions->SetCellValue( row, PENDING_COL_ACTION, _( "Update" ) );
m_gridPendingActions->SetCellValue(
row, PENDING_COL_VERSION,
wxString::Format( wxT( "%s \u279C %s" ), aData.current_version, aVersion ) );
new_state = PPS_PENDING_UPDATE;
break;
case PPA_UNINSTALL:
m_gridPendingActions->SetCellValue( row, PENDING_COL_ACTION, _( "Uninstall" ) );
m_gridPendingActions->SetCellValue(
row, PENDING_COL_VERSION,
m_pcm->GetInstalledPackageVersion( aData.package.identifier ) );
m_pendingActions.emplace_back( aAction, aData.repository_id, aData.package, aVersion );
new_state = PPS_PENDING_UNINSTALL;
break;
}
m_pendingActions.emplace_back( aAction, aData.repository_id, aData.package, aVersion );
m_gridPendingActions->Thaw();
updatePendingActionsTab();
@ -127,8 +131,8 @@ DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
m_dialogNotebook->SetSelection( 0 );
SetupStandardButtons( { { wxID_OK, _( "Close" ) },
{ wxID_APPLY, _( "Apply Pending Changes" ) },
SetupStandardButtons( { { wxID_OK, _( "Close" ) },
{ wxID_APPLY, _( "Apply Pending Changes" ) },
{ wxID_CANCEL, _( "Discard Pending Changes" ) } } );
Bind( wxEVT_CLOSE_WINDOW, &DIALOG_PCM::OnCloseWindow, this );
@ -303,15 +307,24 @@ void DIALOG_PCM::setRepositoryData( const wxString& aRepositoryId )
package_data.state = m_pcm->GetPackageState( aRepositoryId, pkg.identifier );
if( package_data.state == PPS_INSTALLED || package_data.state == PPS_UPDATE_AVAILABLE )
package_data.current_version = m_pcm->GetInstalledPackageVersion( pkg.identifier );
if( package_data.state == PPS_UPDATE_AVAILABLE )
package_data.update_version = m_pcm->GetPackageUpdateVersion( pkg );
for( const PENDING_ACTION& action : m_pendingActions )
{
if( action.package.identifier != pkg.identifier )
continue;
if( action.action == PPA_INSTALL )
package_data.state = PPS_PENDING_INSTALL;
else
package_data.state = PPS_PENDING_UNINSTALL;
switch( action.action )
{
case PPA_INSTALL: package_data.state = PPS_PENDING_INSTALL; break;
case PPA_UPDATE: package_data.state = PPS_PENDING_UPDATE; break;
case PPA_UNINSTALL: package_data.state = PPS_PENDING_UNINSTALL; break;
}
break;
}
@ -327,12 +340,12 @@ void DIALOG_PCM::setRepositoryData( const wxString& aRepositoryId )
PCM_PACKAGE_TYPE type = PACKAGE_TYPE_LIST[i].first;
const wxString& label = PACKAGE_TYPE_LIST[i].second;
m_repositoryContentPanels[type]->SetData( data[type], m_callback );
m_contentNotebook->SetPageText( i, wxString::Format( wxGetTranslation( label ),
(int) data[type].size() ) );
m_contentNotebook->SetPageText(
i, wxString::Format( wxGetTranslation( label ), (int) data[type].size() ) );
}
m_dialogNotebook->SetPageText( 0, wxString::Format( _( "Repository (%d)" ),
(int) packages.size() ) );
m_dialogNotebook->SetPageText(
0, wxString::Format( _( "Repository (%d)" ), (int) packages.size() ) );
}
}
@ -381,8 +394,8 @@ void DIALOG_PCM::setInstalledPackages()
m_installedPanel->SetData( package_list, m_callback );
m_dialogNotebook->SetPageText( 1, wxString::Format( _( "Installed (%d)" ),
(int) package_list.size() ) );
m_dialogNotebook->SetPageText(
1, wxString::Format( _( "Installed (%d)" ), (int) package_list.size() ) );
}
@ -400,9 +413,15 @@ void DIALOG_PCM::OnApplyChangesClicked( wxCommandEvent& event )
for( const PENDING_ACTION& action : m_pendingActions )
{
if( action.action == PPA_UNINSTALL )
{
task_manager.Uninstall( action.package );
}
else
task_manager.DownloadAndInstall( action.package, action.version, action.repository_id );
{
bool isUpdate = action.action == PPA_UPDATE;
task_manager.DownloadAndInstall( action.package, action.version, action.repository_id,
isUpdate );
}
}
task_manager.RunQueue( this );
@ -458,8 +477,8 @@ void DIALOG_PCM::discardAction( int aIndex )
PENDING_ACTION action = m_pendingActions[aIndex];
PCM_PACKAGE_STATE state = m_pcm->GetPackageState( action.repository_id,
action.package.identifier );
PCM_PACKAGE_STATE state =
m_pcm->GetPackageState( action.repository_id, action.package.identifier );
m_installedPanel->SetPackageState( action.package.identifier, state );

View File

@ -18,16 +18,15 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/dcclient.h>
#include <math/util.h>
#include <wx/dcclient.h>
#include "panel_package.h"
PANEL_PACKAGE::PANEL_PACKAGE( wxWindow* parent, const ActionCallback& aCallback,
const PACKAGE_VIEW_DATA& aData ) :
PANEL_PACKAGE_BASE( parent ),
m_actionCallback( aCallback ),
m_data( aData )
m_actionCallback( aCallback ), m_data( aData )
{
// Propagate clicks on static elements to the panel handler.
m_name->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( PANEL_PACKAGE::OnClick ), NULL, this );
@ -53,20 +52,26 @@ PANEL_PACKAGE::PANEL_PACKAGE( wxWindow* parent, const ActionCallback& aCallback,
m_desc->SetLabel( m_data.package.description );
descLineHeight = wxSplit( m_desc->GetLabel(), '\n' ).size() * descLineHeight;
int nameLineHeight = m_name->GetTextExtent( "X" ).GetHeight();
int nameLineHeight = m_name->GetTextExtent( "X" ).GetHeight();
wxSize minSize = GetMinSize();
minSize.y = std::max( nameLineHeight + KiROUND( descLineHeight ) + 15, m_minHeight );
SetMinSize( minSize );
wxSizeEvent dummy;
OnSize( dummy );
m_splitButton->SetLabel( _( "Update" ) );
m_splitButton->Bind( wxEVT_BUTTON, &PANEL_PACKAGE::OnButtonClicked, this );
wxMenu* splitMenu = m_splitButton->GetSplitButtonMenu();
wxMenuItem* menuItem = splitMenu->Append( wxID_ANY, _( "Uninstall" ) );
splitMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_PACKAGE::OnUninstallClick, this,
menuItem->GetId() );
SetState( m_data.state );
}
void PANEL_PACKAGE::OnSize( wxSizeEvent& event )
{
{
Layout();
}
@ -78,30 +83,50 @@ void PANEL_PACKAGE::SetState( PCM_PACKAGE_STATE aState )
switch( aState )
{
case PCM_PACKAGE_STATE::PPS_AVAILABLE:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Install" ) );
m_button->Enable();
break;
case PCM_PACKAGE_STATE::PPS_UNAVAILABLE:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Install" ) );
m_button->Disable();
break;
case PCM_PACKAGE_STATE::PPS_INSTALLED:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Uninstall" ) );
m_button->Enable();
break;
case PCM_PACKAGE_STATE::PPS_PENDING_INSTALL:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Install Pending" ) );
m_button->Disable();
break;
case PCM_PACKAGE_STATE::PPS_PENDING_UNINSTALL:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Uninstall Pending" ) );
m_button->Disable();
break;
case PCM_PACKAGE_STATE::PPS_UPDATE_AVAILABLE:
// The only state where the split button is shown instead of the normal one
m_button->Hide();
m_splitButton->Show();
break;
case PCM_PACKAGE_STATE::PPS_PENDING_UPDATE:
m_splitButton->Hide();
m_button->Show();
m_button->SetLabel( _( "Update Pending" ) );
m_button->Disable();
break;
}
// Relayout to change button size to fit the label.
wxSizeEvent dummy;
OnSize( dummy );
Layout();
}
@ -116,6 +141,10 @@ void PANEL_PACKAGE::OnButtonClicked( wxCommandEvent& event )
m_actionCallback( m_data, PPA_INSTALL, version );
}
else if( m_data.state == PPS_UPDATE_AVAILABLE )
{
m_actionCallback( m_data, PPA_UPDATE, m_data.update_version );
}
else
{
m_actionCallback( m_data, PPA_UNINSTALL, m_data.current_version );
@ -123,6 +152,21 @@ void PANEL_PACKAGE::OnButtonClicked( wxCommandEvent& event )
}
void PANEL_PACKAGE::OnUninstallClick( wxCommandEvent& event )
{
if( m_data.state == PPS_UPDATE_AVAILABLE )
{
m_actionCallback( m_data, PPA_UNINSTALL, m_data.current_version );
}
else
{
// Clicking uninstall menu item of the split button should not be possible
// for any state other than UPDATE_AVAILABLE
wxLogError( wxT( "Uninstall clicked in unexpected state" ) );
}
}
void PANEL_PACKAGE::SetSelectCallback( const std::function<void()>& aCallback )
{
m_selectCallback = aCallback;
@ -185,5 +229,3 @@ wxString PANEL_PACKAGE::GetPreferredVersion() const
return ver_it->version;
}

View File

@ -35,6 +35,7 @@ struct PACKAGE_VIEW_DATA
wxString repository_id;
wxString repository_name;
wxString current_version;
wxString update_version;
PACKAGE_VIEW_DATA( const PCM_PACKAGE aPackage ) :
package( std::move( aPackage ) ), bitmap( nullptr ), state( PPS_INSTALLED ){};
PACKAGE_VIEW_DATA( const PCM_INSTALLATION_ENTRY& aEntry ) :
@ -73,6 +74,8 @@ public:
///< Called when anywhere on the panel is clicked (except install button)
void OnClick( wxMouseEvent& event ) override;
void OnUninstallClick( wxCommandEvent& event );
void OnSize( wxSizeEvent& event ) override;
///< Get preferred version. If criteria are not met, return wxEmptyString

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -67,6 +67,9 @@ PANEL_PACKAGE_BASE::PANEL_PACKAGE_BASE( wxWindow* parent, wxWindowID id, const w
m_button = new wxButton( this, wxID_ANY, _("Install"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer4->Add( m_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
m_splitButton = new SPLIT_BUTTON( this, wxID_ANY, _( "Update" ) );
bSizer4->Add( m_splitButton, 0, wxBOTTOM|wxRIGHT|wxTOP, 5 );
bSizer3->Add( bSizer4, 0, wxEXPAND, 5 );

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<FileVersion major="1" minor="16" />
<object class="Project" expanded="1">
<property name="class_decoration">; </property>
<property name="code_generation">C++</property>
@ -14,6 +14,7 @@
<property name="file">panel_package_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_package_base</property>
@ -25,6 +26,7 @@
<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">
@ -46,6 +48,7 @@
<property name="size">-1,-1</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">wxBORDER_NONE|wxTAB_TRAVERSAL</property>
@ -349,6 +352,7 @@
<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>
@ -409,6 +413,68 @@
<event name="OnButtonClick">OnButtonClicked</event>
</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="CustomControl" 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="class">SPLIT_BUTTON</property>
<property name="close_button">1</property>
<property name="construction">m_splitButton = new SPLIT_BUTTON( this, wxID_ANY, _( &quot;Update&quot; ) );</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="declaration"></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="include">#include &lt;widgets/split_button.h&gt;</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_splitButton</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="settings"></property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
</object>
</object>
</object>
</object>
</object>

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -22,6 +22,7 @@
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <widgets/split_button.h>
#include <wx/panel.h>
///////////////////////////////////////////////////////////////////////////
@ -39,8 +40,9 @@ class PANEL_PACKAGE_BASE : public wxPanel
wxStaticText* m_name;
wxStaticText* m_desc;
wxButton* m_button;
SPLIT_BUTTON* m_splitButton;
// Virtual event handlers, overide them in your derived class
// Virtual event handlers, override them in your derived class
virtual void OnClick( wxMouseEvent& event ) { event.Skip(); }
virtual void OnPaint( wxPaintEvent& event ) { event.Skip(); }
virtual void OnSize( wxSizeEvent& event ) { event.Skip(); }
@ -50,6 +52,7 @@ class PANEL_PACKAGE_BASE : public wxPanel
public:
PANEL_PACKAGE_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxBORDER_NONE|wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
~PANEL_PACKAGE_BASE();
};

View File

@ -20,14 +20,14 @@
#include "panel_packages_view.h"
#include <grid_tricks.h>
#include <html_window.h>
#include <kicad_settings.h>
#include <pgm_base.h>
#include <settings/settings_manager.h>
#include <settings/common_settings.h>
#include <widgets/wx_splitter_window.h>
#include <widgets/wx_panel.h>
#include <settings/settings_manager.h>
#include <string_utils.h>
#include <html_window.h>
#include <widgets/wx_panel.h>
#include <widgets/wx_splitter_window.h>
#include <cmath>
#include <fstream>
@ -82,8 +82,8 @@ PANEL_PACKAGES_VIEW::PANEL_PACKAGES_VIEW( wxWindow*
// Set the minimal width to the column label size.
m_gridVersions->SetColMinimalWidth( col, headingWidth );
m_gridVersions->SetColSize( col, m_gridVersions->GetVisibleWidth( col, true, true,
false ) );
m_gridVersions->SetColSize( col,
m_gridVersions->GetVisibleWidth( col, true, true, false ) );
}
// Most likely should be changed to wxGridSelectNone once WxWidgets>=3.1.5 is mandatory.
@ -163,48 +163,47 @@ void PANEL_PACKAGES_VIEW::setPackageDetails( const PACKAGE_VIEW_DATA& aPackageDa
details << "<h5>" + package.name + "</h5>";
auto format_desc =
[]( const wxString& text ) -> wxString
auto format_desc = []( const wxString& text ) -> wxString
{
wxString result;
bool inURL = false;
wxString url;
for( unsigned i = 0; i < text.length(); ++i )
{
wxUniChar c = text[i];
if( inURL )
{
wxString result;
bool inURL = false;
wxString url;
for( unsigned i = 0; i < text.length(); ++i )
if( c == ' ' )
{
wxUniChar c = text[i];
result += wxString::Format( "<a href='%s'>%s</a>", url, url );
inURL = false;
if( inURL )
{
if( c == ' ' )
{
result += wxString::Format( "<a href='%s'>%s</a>", url, url );
inURL = false;
result += c;
}
else
{
url += c;
}
}
else if( text.Mid( i, 5 ) == "http:" || text.Mid( i, 6 ) == "https:" )
{
url = c;
inURL = true;
}
else if( c == '\n' )
{
result += "</p><p>";
}
else
{
result += c;
}
result += c;
}
else
{
url += c;
}
}
else if( text.Mid( i, 5 ) == "http:" || text.Mid( i, 6 ) == "https:" )
{
url = c;
inURL = true;
}
else if( c == '\n' )
{
result += "</p><p>";
}
else
{
result += c;
}
}
return result;
};
return result;
};
wxString desc = package.description_full;
details << "<p>" + format_desc( desc ) + "</p>";
@ -229,30 +228,28 @@ void PANEL_PACKAGES_VIEW::setPackageDetails( const PACKAGE_VIEW_DATA& aPackageDa
details << "<li>" + _( "Tags: " ) + tags_str + "</li>";
}
auto format_entry =
[]( const std::pair<const std::string, wxString>& entry ) -> wxString
{
wxString name = entry.first;
wxString url = EscapeHTML( entry.second );
auto format_entry = []( const std::pair<const std::string, wxString>& entry ) -> wxString
{
wxString name = entry.first;
wxString url = EscapeHTML( entry.second );
if( name == "email" )
return wxString::Format( "<a href='mailto:%s'>%s</a>", url, url );
else if( url.StartsWith( "http:" ) || url.StartsWith( "https:" ) )
return wxString::Format( "<a href='%s'>%s</a>", url, url );
else
return entry.second;
};
if( name == "email" )
return wxString::Format( "<a href='mailto:%s'>%s</a>", url, url );
else if( url.StartsWith( "http:" ) || url.StartsWith( "https:" ) )
return wxString::Format( "<a href='%s'>%s</a>", url, url );
else
return entry.second;
};
auto write_contact =
[&]( const wxString& type, const PCM_CONTACT& contact )
{
details << "<li>" + type + ": " + contact.name + "<ul>";
auto write_contact = [&]( const wxString& type, const PCM_CONTACT& contact )
{
details << "<li>" + type + ": " + contact.name + "<ul>";
for( const std::pair<const std::string, wxString>& entry : contact.contact )
details << "<li>" + entry.first + ": " + format_entry( entry ) + "</li>";
for( const std::pair<const std::string, wxString>& entry : contact.contact )
details << "<li>" + entry.first + ": " + format_entry( entry ) + "</li>";
details << "</ul>";
};
details << "</ul>";
};
write_contact( _( "Author" ), package.author );
@ -285,7 +282,7 @@ void PANEL_PACKAGES_VIEW::setPackageDetails( const PACKAGE_VIEW_DATA& aPackageDa
int row = 0;
wxString current_version;
if( aPackageData.state == PPS_INSTALLED )
if( aPackageData.state == PPS_INSTALLED || aPackageData.state == PPS_UPDATE_AVAILABLE )
current_version = m_pcm->GetInstalledPackageVersion( package.identifier );
wxFont bold_font = m_gridVersions->GetDefaultCellFont().Bold();
@ -320,13 +317,17 @@ void PANEL_PACKAGES_VIEW::setPackageDetails( const PACKAGE_VIEW_DATA& aPackageDa
for( int col = 0; col < m_gridVersions->GetNumberCols(); col++ )
{
// Set the width to see the full contents
m_gridVersions->SetColSize( col, m_gridVersions->GetVisibleWidth( col, true, true,
false ) );
m_gridVersions->SetColSize( col,
m_gridVersions->GetVisibleWidth( col, true, true, false ) );
}
// Autoselect preferred or installed version
if( m_gridVersions->GetNumberRows() >= 1 )
{
wxString version = m_currentSelected->GetPreferredVersion();
wxString version = m_currentSelected->GetPackageData().current_version;
if( version.IsEmpty() )
version = m_currentSelected->GetPreferredVersion();
if( !version.IsEmpty() )
{
@ -335,6 +336,7 @@ void PANEL_PACKAGES_VIEW::setPackageDetails( const PACKAGE_VIEW_DATA& aPackageDa
if( m_gridVersions->GetCellValue( i, COL_VERSION ) == version )
{
m_gridVersions->SelectRow( i );
m_gridVersions->SetGridCursor( i, COL_VERSION );
break;
}
}
@ -404,15 +406,20 @@ bool PANEL_PACKAGES_VIEW::canDownload() const
}
bool PANEL_PACKAGES_VIEW::canInstall() const
bool PANEL_PACKAGES_VIEW::canRunAction() const
{
if( !m_currentSelected )
return false;
const PACKAGE_VIEW_DATA& packageData = m_currentSelected->GetPackageData();
if( packageData.state != PPS_AVAILABLE && packageData.state != PPS_UNAVAILABLE )
return false;
switch( packageData.state )
{
case PPS_PENDING_INSTALL:
case PPS_PENDING_UNINSTALL:
case PPS_PENDING_UPDATE: return false;
default: break;
}
return m_gridVersions->GetNumberRows() == 1 || m_gridVersions->GetSelectedRows().size() == 1;
}
@ -529,14 +536,23 @@ void PANEL_PACKAGES_VIEW::OnDownloadVersionClicked( wxCommandEvent& event )
}
void PANEL_PACKAGES_VIEW::OnInstallVersionClicked( wxCommandEvent& event )
void PANEL_PACKAGES_VIEW::OnVersionActionClicked( wxCommandEvent& event )
{
if( !canInstall() )
if( !canRunAction() )
{
wxBell();
return;
}
PCM_PACKAGE_ACTION action = getAction();
if( action == PPA_UNINSTALL )
{
m_actionCallback( m_currentSelected->GetPackageData(), PPA_UNINSTALL,
m_currentSelected->GetPackageData().current_version );
return;
}
if( m_gridVersions->GetNumberRows() == 1 )
m_gridVersions->SelectRow( 0 );
@ -562,7 +578,7 @@ void PANEL_PACKAGES_VIEW::OnInstallVersionClicked( wxCommandEvent& event )
return;
}
m_actionCallback( m_currentSelected->GetPackageData(), PPA_INSTALL, version );
m_actionCallback( m_currentSelected->GetPackageData(), action, version );
}
@ -648,7 +664,56 @@ void PANEL_PACKAGES_VIEW::updatePackageList()
void PANEL_PACKAGES_VIEW::updateDetailsButtons()
{
m_buttonDownload->Enable( canDownload() );
m_buttonInstall->Enable( canInstall() );
if( canRunAction() )
{
m_buttonAction->Enable();
PCM_PACKAGE_ACTION action = getAction();
switch( action )
{
case PPA_INSTALL: m_buttonAction->SetLabel( _( "Install" ) ); break;
case PPA_UNINSTALL: m_buttonAction->SetLabel( _( "Uninstall" ) ); break;
case PPA_UPDATE: m_buttonAction->SetLabel( _( "Update" ) ); break;
}
}
else
{
m_buttonAction->Disable();
m_buttonAction->SetLabel( _( "Pending" ) );
}
}
PCM_PACKAGE_ACTION PANEL_PACKAGES_VIEW::getAction() const
{
wxASSERT_MSG( m_gridVersions->GetNumberRows() == 1
|| m_gridVersions->GetSelectedRows().size() == 1,
"getAction() called with ambiguous version selection" );
int selected_row = 0;
if( m_gridVersions->GetSelectedRows().size() == 1 )
selected_row = m_gridVersions->GetSelectedRows()[0];
wxString version = m_gridVersions->GetCellValue( selected_row, COL_VERSION );
const PACKAGE_VIEW_DATA& package = m_currentSelected->GetPackageData();
switch( package.state )
{
case PPS_AVAILABLE:
case PPS_UNAVAILABLE:
return PPA_INSTALL; // Only action for not installed package is to install it
case PPS_INSTALLED:
case PPS_UPDATE_AVAILABLE:
if( version == package.current_version )
return PPA_UNINSTALL;
else
return PPA_UPDATE;
default:
return PPA_INSTALL; // For pending states return value does not matter as button will be disabled
}
}
@ -692,6 +757,6 @@ void PANEL_PACKAGES_VIEW::SetSashOnIdle( wxIdleEvent& aEvent )
m_packageListWindow->FitInside();
m_splitter1->Disconnect( wxEVT_IDLE, wxIdleEventHandler( PANEL_PACKAGES_VIEW::SetSashOnIdle ),
m_splitter1->Disconnect( wxEVT_IDLE, wxIdleEventHandler( PANEL_PACKAGES_VIEW::SetSashOnIdle ),
NULL, this );
}

View File

@ -63,8 +63,8 @@ public:
///< Opens file chooser dialog and downloads selected package version archive
void OnDownloadVersionClicked( wxCommandEvent& event ) override;
///< Schedules installation of selected package version
void OnInstallVersionClicked( wxCommandEvent& event ) override;
///< Schedules relevant action for selected package version
void OnVersionActionClicked( wxCommandEvent& event ) override;
///< Shows all versions including incompatible ones
void OnShowAllVersionsClicked( wxCommandEvent& event ) override;
@ -102,8 +102,11 @@ private:
///< Returns true if it the download operation can be performed
bool canDownload() const;
///< Returns true if the install operation can be performed
bool canInstall() const;
///< Returns true if the package action can be performed
bool canRunAction() const;
///< Returns implied action for the action button
PCM_PACKAGE_ACTION getAction() const;
private:
ActionCallback m_actionCallback;

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -78,12 +78,12 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
m_gridVersions->AutoSizeColumns();
m_gridVersions->EnableDragColMove( false );
m_gridVersions->EnableDragColSize( true );
m_gridVersions->SetColLabelSize( 22 );
m_gridVersions->SetColLabelValue( 0, _("Version") );
m_gridVersions->SetColLabelValue( 1, _("Download Size") );
m_gridVersions->SetColLabelValue( 2, _("Install Size") );
m_gridVersions->SetColLabelValue( 3, _("Compatible") );
m_gridVersions->SetColLabelValue( 4, _("Status") );
m_gridVersions->SetColLabelSize( 22 );
m_gridVersions->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
// Rows
@ -109,8 +109,8 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
m_buttonDownload = new wxButton( m_infoScrollWindow, wxID_ANY, _("Download"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerVersionButtons->Add( m_buttonDownload, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_buttonInstall = new wxButton( m_infoScrollWindow, wxID_ANY, _("Install"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerVersionButtons->Add( m_buttonInstall, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
m_buttonAction = new wxButton( m_infoScrollWindow, wxID_ANY, _("Install"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerVersionButtons->Add( m_buttonAction, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
m_sizerVersions->Add( bSizerVersionButtons, 0, wxEXPAND|wxRIGHT, 5 );
@ -143,7 +143,7 @@ PANEL_PACKAGES_VIEW_BASE::PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID
m_gridVersions->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( PANEL_PACKAGES_VIEW_BASE::OnVersionsCellClicked ), NULL, this );
m_showAllVersions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnShowAllVersionsClicked ), NULL, this );
m_buttonDownload->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnDownloadVersionClicked ), NULL, this );
m_buttonInstall->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnInstallVersionClicked ), NULL, this );
m_buttonAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnVersionActionClicked ), NULL, this );
}
PANEL_PACKAGES_VIEW_BASE::~PANEL_PACKAGES_VIEW_BASE()
@ -155,6 +155,6 @@ PANEL_PACKAGES_VIEW_BASE::~PANEL_PACKAGES_VIEW_BASE()
m_gridVersions->Disconnect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( PANEL_PACKAGES_VIEW_BASE::OnVersionsCellClicked ), NULL, this );
m_showAllVersions->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnShowAllVersionsClicked ), NULL, this );
m_buttonDownload->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnDownloadVersionClicked ), NULL, this );
m_buttonInstall->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnInstallVersionClicked ), NULL, this );
m_buttonAction->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PACKAGES_VIEW_BASE::OnVersionActionClicked ), NULL, this );
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<FileVersion major="1" minor="16" />
<object class="Project" expanded="1">
<property name="class_decoration">; </property>
<property name="code_generation">C++</property>
@ -14,6 +14,7 @@
<property name="file">panel_packages_view_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_packages_view_base</property>
@ -25,6 +26,7 @@
<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">
@ -46,6 +48,7 @@
<property name="size">-1,-1</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>
@ -680,6 +683,7 @@
<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>
@ -753,6 +757,7 @@
<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>
@ -787,7 +792,7 @@
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_buttonInstall</property>
<property name="name">m_buttonAction</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
@ -810,7 +815,7 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">OnInstallVersionClicked</event>
<event name="OnButtonClick">OnVersionActionClicked</event>
</object>
</object>
</object>

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -27,10 +27,10 @@ class WX_PANEL;
#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/button.h>
#include <wx/splitter.h>
///////////////////////////////////////////////////////////////////////////
@ -55,21 +55,22 @@ class PANEL_PACKAGES_VIEW_BASE : public wxPanel
WX_GRID* m_gridVersions;
wxCheckBox* m_showAllVersions;
wxButton* m_buttonDownload;
wxButton* m_buttonInstall;
wxButton* m_buttonAction;
// Virtual event handlers, overide them in your derived class
// Virtual event handlers, override them in your derived class
virtual void OnSizeInfoBox( wxSizeEvent& event ) { event.Skip(); }
virtual void OnURLClicked( wxHtmlLinkEvent& event ) { event.Skip(); }
virtual void OnInfoMouseWheel( wxMouseEvent& event ) { event.Skip(); }
virtual void OnVersionsCellClicked( wxGridEvent& event ) { event.Skip(); }
virtual void OnShowAllVersionsClicked( wxCommandEvent& event ) { event.Skip(); }
virtual void OnDownloadVersionClicked( wxCommandEvent& event ) { event.Skip(); }
virtual void OnInstallVersionClicked( wxCommandEvent& event ) { event.Skip(); }
virtual void OnVersionActionClicked( wxCommandEvent& event ) { event.Skip(); }
public:
PANEL_PACKAGES_VIEW_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
~PANEL_PACKAGES_VIEW_BASE();
void m_splitter1OnIdle( wxIdleEvent& )

View File

@ -616,6 +616,9 @@ void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepository
void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
const wxString& aRepositoryId )
{
// In case of package update remove old data
MarkUninstalled( aPackage );
PCM_INSTALLATION_ENTRY entry;
entry.package = aPackage;
entry.current_version = aVersion;
@ -641,11 +644,10 @@ void PLUGIN_CONTENT_MANAGER::MarkUninstalled( const PCM_PACKAGE& aPackage )
PCM_PACKAGE_STATE PLUGIN_CONTENT_MANAGER::GetPackageState( const wxString& aRepositoryId,
const wxString& aPackageId )
{
if( m_installed.find( aPackageId ) != m_installed.end() )
return PPS_INSTALLED;
bool installed = m_installed.find( aPackageId ) != m_installed.end();
if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
return PPS_UNAVAILABLE;
return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
const PCM_REPOSITORY& repo = getCachedRepository( aRepositoryId );
@ -656,23 +658,59 @@ PCM_PACKAGE_STATE PLUGIN_CONTENT_MANAGER::GetPackageState( const wxString& aRepo
} );
if( pkg_it == repo.package_list.end() )
return PPS_UNAVAILABLE;
return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
const PCM_PACKAGE& pkg = *pkg_it;
auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
[]( const PACKAGE_VERSION& ver )
{
return ver.compatible;
} );
if( installed )
{
// Package is installed, check for available updates at the same or
// higher (numerically lower) version stability level
wxString update_version = GetPackageUpdateVersion( pkg );
if( ver_it == pkg.versions.end() )
return PPS_UNAVAILABLE;
return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
}
else
return PPS_AVAILABLE;
{
// Find any compatible version
auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
[]( const PACKAGE_VERSION& ver )
{
return ver.compatible;
} );
return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
}
}
const wxString PLUGIN_CONTENT_MANAGER::GetPackageUpdateVersion( const PCM_PACKAGE& aPackage )
{
wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
"GetPackageUpdateVersion called on a not installed package" );
const PCM_INSTALLATION_ENTRY& installation_entry = m_installed.at( aPackage.identifier );
auto installed_ver_it = std::find_if(
installation_entry.package.versions.begin(), installation_entry.package.versions.end(),
[&]( const PACKAGE_VERSION& ver )
{
return ver.version == installation_entry.current_version;
} );
wxASSERT_MSG( installed_ver_it != installation_entry.package.versions.end(),
"Installed package version not found" );
auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
[&]( const PACKAGE_VERSION& ver )
{
return ver.compatible && installed_ver_it->status >= ver.status
&& installed_ver_it->parsed_version < ver.parsed_version;
} );
return ver_it == aPackage.versions.end() ? wxT( "" ) : ver_it->version;
}
time_t PLUGIN_CONTENT_MANAGER::getCurrentTimestamp() const
{
return std::chrono::duration_cast<std::chrono::seconds>(

View File

@ -55,7 +55,9 @@ enum PCM_PACKAGE_STATE
PPS_UNAVAILABLE = 1,
PPS_INSTALLED = 2,
PPS_PENDING_INSTALL = 3,
PPS_PENDING_UNINSTALL = 4
PPS_PENDING_UNINSTALL = 4,
PPS_UPDATE_AVAILABLE = 5,
PPS_PENDING_UPDATE = 6,
};
@ -63,7 +65,8 @@ enum PCM_PACKAGE_STATE
enum PCM_PACKAGE_ACTION
{
PPA_INSTALL = 0,
PPA_UNINSTALL = 1
PPA_UNINSTALL = 1,
PPA_UPDATE = 2,
};
@ -224,6 +227,18 @@ public:
*/
PCM_PACKAGE_STATE GetPackageState( const wxString& aRepositoryId, const wxString& aPackageId );
/**
* @brief Get the preferred package update version or empty string if there is none
*
* Works only for installed packages and returns highest compatible version greater
* than currently installed that is at the same or higher (numerically lower)
* version stability level.
*
* @param aPackage package
* @return package version string
*/
const wxString GetPackageUpdateVersion( const PCM_PACKAGE& aPackage );
/**
* @brief Downloads url to an output stream
*

View File

@ -59,6 +59,9 @@ void to_json( json& j, const PACKAGE_VERSION& v )
if( v.kicad_version_max )
j["kicad_version_max"] = v.kicad_version_max.get();
if( v.keep_on_update.size() > 0 )
nlohmann::to_json( j["keep_on_update"], v.keep_on_update );
}
@ -77,6 +80,9 @@ void from_json( const json& j, PACKAGE_VERSION& v )
if( j.contains( "platforms" ) )
j.at( "platforms" ).get_to( v.platforms );
if( j.contains( "keep_on_update" ) )
j.at( "keep_on_update" ).get_to( v.keep_on_update );
}
@ -97,6 +103,9 @@ void to_json( json& j, const PCM_PACKAGE& p )
if( p.tags.size() > 0 )
j["tags"] = p.tags;
if( p.keep_on_update.size() > 0 )
j["keep_on_update"] = p.keep_on_update;
}
@ -116,6 +125,9 @@ void from_json( const json& j, PCM_PACKAGE& p )
if( j.contains( "tags" ) )
j.at( "tags" ).get_to( p.tags );
if( j.contains( "keep_on_update" ) )
j.at( "keep_on_update" ).get_to( p.keep_on_update );
}
@ -133,7 +145,7 @@ void from_json( const json& j, PCM_RESOURCE_REFERENCE& r )
j.at( "url" ).get_to( r.url );
j.at( "update_timestamp" ).get_to( r.update_timestamp );
to_optional(j, "sha256", r.sha256 );
to_optional( j, "sha256", r.sha256 );
}
@ -157,7 +169,7 @@ void from_json( const json& j, PCM_REPOSITORY& r )
j.at( "name" ).get_to( r.name );
j.at( "packages" ).get_to( r.packages );
to_optional(j, "resources", r.resources );
to_optional(j, "manifests", r.manifests );
to_optional(j, "maintainer", r.maintainer );
to_optional( j, "resources", r.resources );
to_optional( j, "manifests", r.manifests );
to_optional( j, "maintainer", r.maintainer );
}

View File

@ -82,6 +82,7 @@ struct PACKAGE_VERSION
std::vector<std::string> platforms;
wxString kicad_version;
boost::optional<wxString> kicad_version_max;
std::vector<std::string> keep_on_update;
// Not serialized fields
std::tuple<int, int, int, int> parsed_version; // Full version tuple for sorting
@ -102,6 +103,7 @@ struct PCM_PACKAGE
wxString license;
STRING_MAP resources;
std::vector<std::string> tags;
std::vector<std::string> keep_on_update;
std::vector<PACKAGE_VERSION> versions;
};

View File

@ -19,7 +19,7 @@
*/
// kicad_curl_easy.h **must be** included before any wxWidgets header to avoid conflicts
// at least on Windows/msys2
#include <kicad_curl/kicad_curl.h>
#include "kicad_curl/kicad_curl.h"
#include "kicad_curl/kicad_curl_easy.h"
#include "pcm_task_manager.h"
@ -37,92 +37,62 @@
#include <wx/zipstrm.h>
void PCM_TASK_MANAGER::DownloadAndInstall( const PCM_PACKAGE& aPackage, const wxString& aVersion,
const wxString& aRepositoryId )
void compile_keep_on_update_regex( const PCM_PACKAGE& pkg, const PACKAGE_VERSION& ver,
std::forward_list<wxRegEx>& aKeepOnUpdate )
{
PCM_TASK download_task = [aPackage, aVersion, aRepositoryId, this]()
auto compile_regex = [&]( const wxString& regex )
{
aKeepOnUpdate.emplace_front( regex, wxRE_DEFAULT );
if( !aKeepOnUpdate.front().IsValid() )
aKeepOnUpdate.pop_front();
};
std::for_each( pkg.keep_on_update.begin(), pkg.keep_on_update.end(), compile_regex );
std::for_each( ver.keep_on_update.begin(), ver.keep_on_update.end(), compile_regex );
}
void PCM_TASK_MANAGER::DownloadAndInstall( const PCM_PACKAGE& aPackage, const wxString& aVersion,
const wxString& aRepositoryId, const bool isUpdate )
{
PCM_TASK download_task = [aPackage, aVersion, aRepositoryId, isUpdate, this]()
{
wxFileName file_path( m_pcm->Get3rdPartyPath(), "" );
file_path.AppendDir( "cache" );
file_path.SetFullName( wxString::Format( "%s_v%s.zip", aPackage.identifier, aVersion ) );
auto find_pkgver = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
[&aVersion]( const PACKAGE_VERSION& pv )
{
return pv.version == aVersion;
} );
[&aVersion]( const PACKAGE_VERSION& pv )
{
return pv.version == aVersion;
} );
wxASSERT_MSG( find_pkgver != aPackage.versions.end(), "Package version not found" );
if( !wxDirExists( file_path.GetPath() )
&& !wxFileName::Mkdir( file_path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
{
m_reporter->PCMReport( _( "Unable to create download directory!" ), RPT_SEVERITY_ERROR );
m_reporter->PCMReport( _( "Unable to create download directory!" ),
RPT_SEVERITY_ERROR );
return;
}
int code = downloadFile( file_path.GetFullPath(), find_pkgver->download_url.get() );
if( code == CURLE_OK )
if( code != CURLE_OK )
{
PCM_TASK install_task = [aPackage, aVersion, aRepositoryId, file_path, this]()
{
auto get_pkgver = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
[&aVersion]( const PACKAGE_VERSION& pv )
{
return pv.version == aVersion;
} );
const boost::optional<wxString>& hash = get_pkgver->download_sha256;
bool hash_match = true;
if( hash )
{
std::ifstream stream( file_path.GetFullPath().ToUTF8(), std::ios::binary );
hash_match = m_pcm->VerifyHash( stream, hash.get() );
}
if( !hash_match )
{
m_reporter->PCMReport( wxString::Format( _( "Downloaded archive hash for package "
"%s does not match repository entry. "
"This may indicate a problem with the "
"package, if the issue persists "
"report this to repository maintainers." ),
aPackage.identifier ),
RPT_SEVERITY_ERROR );
}
else
{
m_reporter->PCMReport( wxString::Format( _( "Extracting package '%s'." ),
aPackage.identifier ),
RPT_SEVERITY_INFO );
if( extract( file_path.GetFullPath(), aPackage.identifier, true ) )
{
m_pcm->MarkInstalled( aPackage, get_pkgver->version, aRepositoryId );
// TODO register libraries.
}
else
{
// Cleanup possibly partially extracted package
deletePackageDirectories( aPackage.identifier );
}
}
m_reporter->PCMReport( wxString::Format( _( "Removing downloaded archive '%s'." ),
file_path.GetFullName() ),
RPT_SEVERITY_INFO );
wxRemoveFile( file_path.GetFullPath() );
};
m_install_queue.push( install_task );
}
else
{
// Cleanup after ourselves.
// Cleanup after ourselves and exit
wxRemoveFile( file_path.GetFullPath() );
return;
}
PCM_TASK install_task = [aPackage, aVersion, aRepositoryId, file_path, isUpdate, this]()
{
installDownloadedPackage( aPackage, aVersion, aRepositoryId, file_path, isUpdate );
};
m_install_queue.push( install_task );
};
m_download_queue.push( download_task );
@ -150,7 +120,7 @@ int PCM_TASK_MANAGER::downloadFile( const wxString& aFilePath, const wxString& u
curl.SetTransferCallback( callback, 250000L );
m_reporter->PCMReport( wxString::Format( _( "Downloading package url: '%s'" ), url ),
RPT_SEVERITY_INFO );
RPT_SEVERITY_INFO );
int code = curl.Perform();
@ -164,14 +134,88 @@ int PCM_TASK_MANAGER::downloadFile( const wxString& aFilePath, const wxString& u
if( code != CURLE_OK && code != CURLE_ABORTED_BY_CALLBACK )
{
m_reporter->PCMReport( wxString::Format( _( "Failed to download url %s\n%s" ), url,
curl.GetErrorText( code ) ),
RPT_SEVERITY_ERROR );
curl.GetErrorText( code ) ),
RPT_SEVERITY_ERROR );
}
return code;
}
void PCM_TASK_MANAGER::installDownloadedPackage( const PCM_PACKAGE& aPackage,
const wxString& aVersion,
const wxString& aRepositoryId,
const wxFileName& aFilePath, const bool isUpdate )
{
auto pkgver = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
[&aVersion]( const PACKAGE_VERSION& pv )
{
return pv.version == aVersion;
} );
wxASSERT_MSG( pkgver != aPackage.versions.end(), "Package version not found" );
// wxRegEx is not CopyConstructible hence the weird choice of forward_list
std::forward_list<wxRegEx> keep_on_update;
if( isUpdate )
compile_keep_on_update_regex( aPackage, *pkgver, keep_on_update );
const boost::optional<wxString>& hash = pkgver->download_sha256;
bool hash_match = true;
if( hash )
{
std::ifstream stream( aFilePath.GetFullPath().ToUTF8(), std::ios::binary );
hash_match = m_pcm->VerifyHash( stream, hash.get() );
}
if( !hash_match )
{
m_reporter->PCMReport( wxString::Format( _( "Downloaded archive hash for package "
"%s does not match repository entry. "
"This may indicate a problem with the "
"package, if the issue persists "
"report this to repository maintainers." ),
aPackage.identifier ),
RPT_SEVERITY_ERROR );
}
else
{
if( isUpdate )
{
m_reporter->PCMReport(
wxString::Format( _( "Removing previous version of package '%s'." ),
aPackage.identifier ),
RPT_SEVERITY_INFO );
deletePackageDirectories( aPackage.identifier, keep_on_update );
}
m_reporter->PCMReport(
wxString::Format( _( "Extracting package '%s'." ), aPackage.identifier ),
RPT_SEVERITY_INFO );
if( extract( aFilePath.GetFullPath(), aPackage.identifier, true ) )
{
m_pcm->MarkInstalled( aPackage, pkgver->version, aRepositoryId );
// TODO register libraries.
}
else
{
// Cleanup possibly partially extracted package
deletePackageDirectories( aPackage.identifier );
}
}
m_reporter->PCMReport(
wxString::Format( _( "Removing downloaded archive '%s'." ), aFilePath.GetFullName() ),
RPT_SEVERITY_INFO );
wxRemoveFile( aFilePath.GetFullPath() );
}
bool PCM_TASK_MANAGER::extract( const wxString& aFilePath, const wxString& aPackageId,
bool isMultiThreaded )
{
@ -319,7 +363,11 @@ void PCM_TASK_MANAGER::InstallFromFile( wxWindow* aParent, const wxString& aFile
return;
}
const auto installed_packages = m_pcm->GetInstalledPackages();
bool isUpdate = false;
// wxRegEx is not CopyConstructible hence the weird choice of forward_list
std::forward_list<wxRegEx> keep_on_update;
const std::vector<PCM_INSTALLATION_ENTRY> installed_packages = m_pcm->GetInstalledPackages();
if( std::find_if( installed_packages.begin(), installed_packages.end(),
[&]( const PCM_INSTALLATION_ENTRY& entry )
{
@ -327,15 +375,32 @@ void PCM_TASK_MANAGER::InstallFromFile( wxWindow* aParent, const wxString& aFile
} )
!= installed_packages.end() )
{
wxLogError( wxString::Format( _( "Package with identifier %s is already installed, you "
"must first uninstall this package." ),
package.identifier ) );
return;
if( wxMessageBox(
wxString::Format(
_( "Package with identifier %s is already installed. "
"Would you like to update it to the version from selected file?" ),
package.identifier ),
_( "Update package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
== wxNO )
return;
isUpdate = true;
compile_keep_on_update_regex( package, package.versions[0], keep_on_update );
}
m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent, false );
m_reporter->Show();
if( isUpdate )
{
m_reporter->PCMReport( wxString::Format( _( "Removing previous version of package '%s'." ),
package.identifier ),
RPT_SEVERITY_INFO );
deletePackageDirectories( package.identifier, keep_on_update );
}
if( extract( aFilePath, package.identifier, false ) )
m_pcm->MarkInstalled( package, package.versions[0].version, "" );
@ -346,23 +411,68 @@ void PCM_TASK_MANAGER::InstallFromFile( wxWindow* aParent, const wxString& aFile
}
void PCM_TASK_MANAGER::deletePackageDirectories( const wxString& aPackageId )
class PATH_COLLECTOR : public wxDirTraverser
{
private:
std::vector<wxString>& m_files;
std::vector<wxString>& m_dirs;
public:
explicit PATH_COLLECTOR( std::vector<wxString>& aFiles, std::vector<wxString>& aDirs ) :
m_files( aFiles ), m_dirs( aDirs )
{
}
wxDirTraverseResult OnFile( const wxString& aFilePath ) override
{
m_files.push_back( aFilePath );
return wxDIR_CONTINUE;
}
wxDirTraverseResult OnDir( const wxString& dirPath ) override
{
m_dirs.push_back( dirPath );
return wxDIR_CONTINUE;
}
};
void PCM_TASK_MANAGER::deletePackageDirectories( const wxString& aPackageId,
const std::forward_list<wxRegEx>& aKeep )
{
// Namespace delimiter changed on disk to allow flat loading of Python modules
wxString clean_package_id = aPackageId;
clean_package_id.Replace( '.', '_' );
int path_prefix_len = m_pcm->Get3rdPartyPath().Length();
auto sort_func = []( const wxString& a, const wxString& b )
{
if( a.length() > b.length() )
return true;
if( a.length() < b.length() )
return false;
if( a != b )
return a < b;
return false;
};
for( const wxString& dir : PCM_PACKAGE_DIRECTORIES )
{
wxFileName d( m_pcm->Get3rdPartyPath(), "" );
d.AppendDir( dir );
d.AppendDir( clean_package_id );
if( d.DirExists() )
{
m_reporter->PCMReport( wxString::Format( _( "Removing directory %s" ), d.GetPath() ),
RPT_SEVERITY_INFO );
if( !d.DirExists() )
continue;
m_reporter->PCMReport( wxString::Format( _( "Removing directory %s" ), d.GetPath() ),
RPT_SEVERITY_INFO );
if( aKeep.empty() )
{
if( !d.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
{
m_reporter->PCMReport(
@ -370,6 +480,50 @@ void PCM_TASK_MANAGER::deletePackageDirectories( const wxString& aPackageId )
RPT_SEVERITY_ERROR );
}
}
else
{
std::vector<wxString> files;
std::vector<wxString> dirs;
PATH_COLLECTOR collector( files, dirs );
wxDir( d.GetFullPath() )
.Traverse( collector, wxEmptyString, wxDIR_DEFAULT | wxDIR_NO_FOLLOW );
// Do a poor mans post order traversal by sorting paths in reverse length order
std::sort( files.begin(), files.end(), sort_func );
std::sort( dirs.begin(), dirs.end(), sort_func );
// Delete files that don't match any of the aKeep regexs
for( const wxString& file : files )
{
bool del = true;
for( const wxRegEx& re : aKeep )
{
wxString tmp = file.Mid( path_prefix_len );
tmp.Replace( "\\", "/" );
if( re.Matches( tmp ) )
{
// m_reporter->PCMReport( wxString::Format( _( "Keeping file '%s'." ), tmp ),
// RPT_SEVERITY_INFO );
del = false;
break;
}
}
if( del )
wxRemoveFile( file );
}
// Delete any empty dirs
for( const wxString& dir : dirs )
{
wxFileName d( dir, "" );
d.Rmdir(); // not passing any flags here will only remove empty directories
}
}
}
}
@ -382,8 +536,9 @@ void PCM_TASK_MANAGER::Uninstall( const PCM_PACKAGE& aPackage )
m_pcm->MarkUninstalled( aPackage );
m_reporter->PCMReport( wxString::Format( _( "Package %s uninstalled" ), aPackage.identifier ),
RPT_SEVERITY_INFO );
m_reporter->PCMReport(
wxString::Format( _( "Package %s uninstalled" ), aPackage.identifier ),
RPT_SEVERITY_INFO );
};
m_install_queue.push( task );

View File

@ -32,6 +32,7 @@
#include <mutex>
#include <nlohmann/json-schema.hpp>
#include <widgets/wx_progress_reporters.h>
#include <wx/regex.h>
#include <wx/string.h>
@ -67,7 +68,7 @@ public:
* @param aRepositoryId id of the source repository
*/
void DownloadAndInstall( const PCM_PACKAGE& aPackage, const wxString& aVersion,
const wxString& aRepositoryId );
const wxString& aRepositoryId, const bool isUpdate );
/**
* @brief Enqueue package uninstallation
@ -113,6 +114,19 @@ private:
*/
int downloadFile( const wxString& aFilePath, const wxString& aUrl );
/**
* @brief Installs downloaded package archive
*
* @param aPackage package metadata
* @param aVersion version to be installed
* @param aRepositoryId id of the source repository
* @param aFilePath path to the archive
* @param isUpdate true if this is an update operation
*/
void installDownloadedPackage( const PCM_PACKAGE& aPackage, const wxString& aVersion,
const wxString& aRepositoryId, const wxFileName& aFilePath,
const bool isUpdate );
/**
* @brief Extract package archive
*
@ -127,8 +141,10 @@ private:
* @brief Delete all package files
*
* @param aPackageId id of the package
* @param aKeep list of regex indicating which files should not be deleted
*/
void deletePackageDirectories( const wxString& aPackageId );
void deletePackageDirectories( const wxString& aPackageId,
const std::forward_list<wxRegEx>& aKeep = {} );
std::unique_ptr<DIALOG_PCM_PROGRESS> m_reporter;
SYNC_QUEUE<PCM_TASK> m_download_queue;

View File

@ -70,6 +70,14 @@
"minItems": 1,
"uniqueItems": true
},
"keep_on_update": {
"type": "array",
"description": "List of regular expressions describing which files should be kept on package update",
"items": {
"type": "string"
},
"uniqueItems": true
},
"versions": {
"type": "array",
"description": "Package versions",
@ -183,6 +191,14 @@
"type": "string",
"description": "Maximum supported KiCad version",
"pattern": "^\\d{1,2}(\\.\\d{1,2}(\\.\\d{1,2})?)?$"
},
"keep_on_update": {
"type": "array",
"description": "List of regular expressions describing which files should be kept on package update",
"items": {
"type": "string"
},
"uniqueItems": true
}
},
"required": [