Merge pull request #1879 from dalto8/pkgchooser-netinstall

Allow the packagechooser module to modify the netinstall module
This commit is contained in:
Adriaan de Groot 2022-01-31 12:44:02 +01:00 committed by GitHub
commit 6a2e80a0b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 263 additions and 4 deletions

View file

@ -15,6 +15,7 @@
#include "PackageModel.h"
#include "ui_page_netinst.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "network/Manager.h"
@ -62,4 +63,19 @@ void
NetInstallPage::onActivate()
{
ui->groupswidget->setFocus();
// The NetInstallSelect global storage value can be used to make additional items selected by default
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
const QStringList selectNames = gs->value( "netinstallSelect" ).toStringList();
if ( !selectNames.isEmpty() )
{
m_config->model()->setSelections( selectNames );
}
// If NetInstallAdd is found in global storage, add those items to the tree
const QVariantList groups = gs->value( "netinstallAdd" ).toList();
if ( !groups.isEmpty() )
{
m_config->model()->appendModelData( groups );
}
}

View file

@ -14,6 +14,53 @@
#include "utils/Variant.h"
#include "utils/Yaml.h"
/** @brief Appends groups to the tree
*
* Uses the data from @p groupList to add elements to the
* existing tree that m_rootItem points to. If m_rootItem
* is not valid, it does nothing
*
* Before adding anything to the model, it ensures that there
* is no existing data from the same source. If there is, that
* data is pruned first
*
*/
static void
setSelections2( const QStringList& selectNames, PackageTreeItem* item )
{
for ( int i = 0; i < item->childCount(); i++ )
{
auto* child = item->child( i );
setSelections2( selectNames, child );
}
if ( item->isGroup() && selectNames.contains( item->name() ) )
{
item->setSelected( Qt::CheckState::Checked );
}
}
/** @brief Collects all the "source" values from @p groupList
*
* Iterates over @p groupList and returns all nonempty "source"
* values from the maps.
*
*/
static QStringList
collectSources( const QVariantList& groupList )
{
QStringList sources;
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( !groupMap[ "source" ].toString().isEmpty() )
{
sources.append( groupMap[ "source" ].toString() );
}
}
return sources;
}
PackageModel::PackageModel( QObject* parent )
: QAbstractItemModel( parent )
{
@ -170,6 +217,15 @@ PackageModel::headerData( int section, Qt::Orientation orientation, int role ) c
return QVariant();
}
void
PackageModel::setSelections( const QStringList& selectNames )
{
if ( m_rootItem )
{
setSelections2( selectNames, m_rootItem );
}
}
PackageTreeItem::List
PackageModel::getPackages() const
{
@ -303,9 +359,43 @@ PackageModel::setupModelData( const QVariantList& groupList, PackageTreeItem* pa
void
PackageModel::setupModelData( const QVariantList& l )
{
emit beginResetModel();
Q_EMIT beginResetModel();
delete m_rootItem;
m_rootItem = new PackageTreeItem();
setupModelData( l, m_rootItem );
emit endResetModel();
Q_EMIT endResetModel();
}
void
PackageModel::appendModelData( const QVariantList& groupList )
{
if ( m_rootItem )
{
Q_EMIT beginResetModel();
const QStringList sources = collectSources( groupList );
if ( !sources.isEmpty() )
{
// Prune any existing data from the same source
QList< int > removeList;
for ( int i = 0; i < m_rootItem->childCount(); i++ )
{
PackageTreeItem* child = m_rootItem->child( i );
if ( sources.contains( child->source() ) )
{
removeList.insert( 0, i );
}
}
for ( const int& item : qAsConst( removeList ) )
{
m_rootItem->removeChild( item );
}
}
// Add the new data to the model
setupModelData( groupList, m_rootItem );
Q_EMIT endResetModel();
}
}

View file

@ -54,9 +54,22 @@ public:
int rowCount( const QModelIndex& parent = QModelIndex() ) const override;
int columnCount( const QModelIndex& parent = QModelIndex() ) const override;
/** @brief Sets the checked flag on matching groups in the tree
*
* Recursively traverses the tree pointed to by m_rootItem and
* checks if a group name matches any of the items in @p selectNames.
* If a match is found, set check the box for that group and it's children.
*
* Individual packages will not be matched.
*
*/
void setSelections(const QStringList &selectNames );
PackageTreeItem::List getPackages() const;
PackageTreeItem::List getItemPackages( PackageTreeItem* item ) const;
void appendModelData(const QVariantList& groupList);
private:
friend class ItemTests;

View file

@ -70,6 +70,7 @@ PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, GroupTag&& paren
, m_description( CalamaresUtils::getString( groupData, "description" ) )
, m_preScript( CalamaresUtils::getString( groupData, "pre-install" ) )
, m_postScript( CalamaresUtils::getString( groupData, "post-install" ) )
, m_source( CalamaresUtils::getString( groupData, "source" ) )
, m_isGroup( true )
, m_isCritical( parentCriticality( groupData, parent.parent ) )
, m_isHidden( CalamaresUtils::getBool( groupData, "hidden", false ) )
@ -248,6 +249,19 @@ PackageTreeItem::setChildrenSelected( Qt::CheckState isSelected )
}
}
void
PackageTreeItem::removeChild( int row )
{
if ( row < m_childItems.count() )
{
m_childItems.removeAt( row );
}
else
{
cWarning() << "Attempt to remove invalid child in removeChild() at row " << row;
}
}
int
PackageTreeItem::type() const
{

View file

@ -56,6 +56,7 @@ public:
QString description() const { return m_description; }
QString preScript() const { return m_preScript; }
QString postScript() const { return m_postScript; }
QString source() const { return m_source; }
/** @brief Is this item a group-item?
*
@ -124,6 +125,8 @@ public:
void setSelected( Qt::CheckState isSelected );
void setChildrenSelected( Qt::CheckState isSelected );
void removeChild( int row );
/** @brief Update selectedness based on the children's states
*
* This only makes sense for groups, which might have packages
@ -157,6 +160,7 @@ private:
QString m_description;
QString m_preScript;
QString m_postScript;
QString m_source;
bool m_isGroup = false;
bool m_isCritical = false;
bool m_isHidden = false;

View file

@ -27,6 +27,29 @@
#include "utils/Logger.h"
#include "utils/Variant.h"
/** @brief This removes any values from @p groups that match @p source
*
* This is used to remove duplicates from the netinstallAdd structure
* It iterates over @p groups and for each map in the list, if the
* "source" element matches @p source, it is removed from the returned
* list.
*/
static QVariantList
pruneNetinstallAdd( const QString& source, const QVariant& groups )
{
QVariantList newGroupList;
const QVariantList groupList = groups.toList();
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.value( "source", "" ).toString() != source )
{
newGroupList.append( groupMap );
}
}
return newGroupList;
}
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
@ -55,6 +78,8 @@ PackageChooserMethodNames()
{ "custom", PackageChooserMethod::Legacy },
{ "contextualprocess", PackageChooserMethod::Legacy },
{ "packages", PackageChooserMethod::Packages },
{ "netinstall-add", PackageChooserMethod::NetAdd },
{ "netinstall-select", PackageChooserMethod::NetSelect },
};
return names;
}
@ -121,6 +146,47 @@ Config::updateGlobalStorage( const QStringList& selected ) const
CalamaresUtils::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else if ( m_method == PackageChooserMethod::NetAdd )
{
QVariantList netinstallDataList = m_model->getNetinstallDataForNames( selected );
if ( netinstallDataList.isEmpty() )
{
cWarning() << "No netinstall information found for " << selected;
}
else
{
// If an earlier packagechooser instance added this data to global storage, combine them
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs->contains( "netinstallAdd" ) )
{
netinstallDataList
+= pruneNetinstallAdd( QStringLiteral( "packageChooser" ), gs->value( "netinstallAdd" ) );
}
gs->insert( "netinstallAdd", netinstallDataList );
}
}
else if ( m_method == PackageChooserMethod::NetSelect )
{
cDebug() << m_defaultId << "groups to select in netinstall" << selected;
QStringList newSelected = selected;
auto* gs = Calamares::JobQueue::instance()->globalStorage();
// If an earlier packagechooser instance added this data to global storage, combine them
if ( gs->contains( "netinstallSelect" ) )
{
auto selectedOrig = gs->value( "netinstallSelect" );
if ( selectedOrig.canConvert( QVariant::StringList ) )
{
newSelected += selectedOrig.toStringList();
}
else
{
cWarning() << "Invalid NetinstallSelect data in global storage. Earlier selections purged";
}
gs->remove( "netinstallSelect" );
}
gs->insert( "netinstallSelect", newSelected );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );

View file

@ -33,6 +33,8 @@ enum class PackageChooserMethod
{
Legacy, // use contextualprocess or other custom
Packages, // use the packages module
NetAdd, // adds packages to the netinstall module
NetSelect, // makes selections in the netinstall module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();

View file

@ -15,6 +15,16 @@
#include <QFileInfo>
/** @brief A wrapper for CalamaresUtils::getSubMap that excludes the success param
*/
static QVariantMap
getSubMap( const QVariantMap& map, const QString& key )
{
bool success;
return CalamaresUtils::getSubMap( map, key, success );
}
static QPixmap
loadScreenshot( const QString& path )
{
@ -51,12 +61,13 @@ PackageItem::PackageItem( const QString& a_id,
{
}
PackageItem::PackageItem::PackageItem( const QVariantMap& item_map )
PackageItem::PackageItem( const QVariantMap& item_map )
: id( CalamaresUtils::getString( item_map, "id" ) )
, name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) )
, description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) )
, screenshot( loadScreenshot( CalamaresUtils::getString( item_map, "screenshot" ) ) )
, packageNames( CalamaresUtils::getStringList( item_map, "packages" ) )
, netinstallData( getSubMap( item_map, "netinstall" ) )
{
if ( name.isEmpty() && id.isEmpty() )
{
@ -125,6 +136,25 @@ PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const
return l;
}
QVariantList
PackageListModel::getNetinstallDataForNames( const QStringList& ids ) const
{
QVariantList l;
for ( auto& p : m_packages )
{
if ( ids.contains( p.id ) )
{
if ( !p.netinstallData.isEmpty() )
{
QVariantMap newData = p.netinstallData;
newData[ "source" ] = QStringLiteral( "packageChooser" );
l.append( newData );
}
}
}
return l;
}
int
PackageListModel::rowCount( const QModelIndex& index ) const
{

View file

@ -26,6 +26,7 @@ struct PackageItem
CalamaresUtils::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
QVariantMap netinstallData;
/// @brief Create blank PackageItem
PackageItem();
@ -111,6 +112,14 @@ public:
*/
QStringList getInstallPackagesForNames( const QStringList& ids ) const;
/** @brief Does a name lookup (based on id) and returns the netinstall data
*
* If there is a package with an id in @p ids, returns their netinstall data
*
* returns a list of netinstall data or an emply list if none is found
*/
QVariantList getNetinstallDataForNames( const QStringList& ids ) const;
enum Roles : int
{
NameRole = Qt::DisplayRole,

View file

@ -33,6 +33,15 @@ mode: required
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
#
# - "netinstall-select"
# When this is set, the id(s) selected are passed to the netinstall module.
# Any id that matches a group name in that module is set to checked
#
# - "netinstall-add"
# With this method, the packagechooser module is used to add groups to the
# netinstall module. For this to hav=e any effect. You must set netinstall,
# which is described below.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
@ -101,13 +110,19 @@ labels:
# an additional attempt is made to load the image from the **branding**
# directory.
#
# The following field is **optional** for an item:
# The following fields are **optional** for an item:
#
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# - *netinstall* :
# The data in this field should follow the format of a group
# from the netinstall module documented in
# src/modules/netinstall/netinstall.conf. This is only used
# when method is set to "netinstall-add"
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*