Merge branch 'issue-942' into calamares

FIXES #942
main
Adriaan de Groot 4 years ago
commit 9295c91128

@ -21,6 +21,7 @@ This release contains contributions from (alphabetically by first name):
## Modules ##
- A new QML-based *finishedq* module has been added. (Thanks Anke)
- The *users* module now can set a fixed username and prevent editing.
# 3.2.37 (2021-02-23) #

@ -48,9 +48,11 @@ set( libSources
locale/TranslatableString.cpp
# Modules
modulesystem/Config.cpp
modulesystem/Descriptor.cpp
modulesystem/InstanceKey.cpp
modulesystem/Module.cpp
modulesystem/Preset.cpp
modulesystem/RequirementsChecker.cpp
modulesystem/RequirementsModel.cpp

@ -0,0 +1,122 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Config.h"
#include "Preset.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
namespace Calamares
{
namespace ModuleSystem
{
class Config::Private
{
public:
std::unique_ptr< Presets > m_presets;
};
Config::Config( QObject* parent )
: QObject( parent )
, d( std::make_unique< Private >() )
{
}
Config::~Config() {}
bool
Config::isEditable( const QString& fieldName ) const
{
if ( m_unlocked )
{
return true;
}
if ( d && d->m_presets )
{
return d->m_presets->isEditable( fieldName );
}
else
{
cWarning() << "Checking isEditable, but no presets are configured.";
}
return true;
}
Config::ApplyPresets::ApplyPresets( Calamares::ModuleSystem::Config& c, const QVariantMap& configurationMap )
: m_c( c )
, m_bogus( true )
, m_map( CalamaresUtils::getSubMap( configurationMap, "presets", m_bogus ) )
{
c.m_unlocked = true;
if ( !c.d->m_presets )
{
c.d->m_presets = std::make_unique< Presets >();
}
}
Config::ApplyPresets::~ApplyPresets()
{
m_c.m_unlocked = false;
// Check that there's no **settings** (from the configuration map)
// that have not been consumed by apply() -- if they are there,
// that means the configuration map specifies things that the
// Config object does not expect.
bool haveWarned = false;
for ( const auto& k : m_map.keys() )
{
if ( !m_c.d->m_presets->find( k ).isValid() )
{
if ( !haveWarned )
{
cWarning() << "Preset configuration contains unused keys";
haveWarned = true;
}
cDebug() << Logger::SubEntry << "Unused key" << k;
}
}
}
Config::ApplyPresets&
Config::ApplyPresets::apply( const char* fieldName )
{
const auto prop = m_c.property( fieldName );
if ( !prop.isValid() )
{
cWarning() << "Applying invalid property" << fieldName;
}
else
{
const QString key( fieldName );
if ( !key.isEmpty() && m_c.d->m_presets->find( key ).isValid() )
{
cWarning() << "Applying duplicate property" << fieldName;
}
else if ( !key.isEmpty() && m_map.contains( key ) )
{
QVariantMap m = CalamaresUtils::getSubMap( m_map, key, m_bogus );
QVariant value = m[ "value" ];
bool editable = CalamaresUtils::getBool( m, "editable", true );
if ( value.isValid() )
{
m_c.setProperty( fieldName, value );
}
m_c.d->m_presets->append( PresetField { key, value, editable } );
}
}
return *this;
}
} // namespace ModuleSystem
} // namespace Calamares

@ -0,0 +1,147 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef CALAMARES_MODULESYSTEM_CONFIG_H
#define CALAMARES_MODULESYSTEM_CONFIG_H
#include "DllMacro.h"
#include <QObject>
#include <QStringList>
#include <QVariantMap>
#include <memory>
namespace Calamares
{
namespace ModuleSystem
{
/** @brief Base class for Config-objects
*
* This centralizes the things every Config-object should
* do and provides one source of preset-data. A Config-object
* for a module can **optionally** inherit from this class
* to get presets-support.
*
* TODO:3.3 This is not optional
* TODO:3.3 Put consistent i18n for Configurations in here too
*/
class DLLEXPORT Config : public QObject
{
Q_OBJECT
public:
Config( QObject* parent = nullptr );
~Config() override;
/** @brief Set the configuration from the config file
*
* Subclasses must implement this to load configuration data;
* that subclass **should** also call loadPresets() with the
* same map, to pick up the "presets" key consistently.
*/
virtual void setConfigurationMap( const QVariantMap& ) = 0;
public Q_SLOTS:
/** @brief Checks if a @p fieldName is editable according to presets
*
* If the field is named as a preset, **and** the field is set
* to not-editable, returns @c false. Otherwise, return @c true.
* Calling this with an unknown field (one for which no presets
* are accepted) will print a warning and return @c true.
*
* @see CONFIG_PREVENT_EDITING
*
* Most setters will call isEditable() to check if the field should
* be editable. Do not count on the setter not being called: the
* UI might not have set the field to fixed / constant / not-editable
* and then you can have the setter called by changes in the UI.
*
* To prevent the UI from changing **and** to make sure that the UI
* reflects the unchanged value (rather than the changed value it
* sent to the Config object), use CONFIG_PREVENT_EDITING, like so:
*
* CONFIG_PREVENT_EDITING( type, "propertyName" );
*
* The ; is necessary. <type> is the type of the property; for instance
* QString. The name of the property is a (constant) string. The
* macro will return (out of the setter it is used in) if the field
* is not editable, and will send a notification event with the old
* value as soon as the event loop resumes.
*/
bool isEditable( const QString& fieldName ) const;
protected:
friend class ApplyPresets;
/** @brief "Builder" class for presets
*
* Derived classes should instantiate this (with themselves,
* and the whole configuration map that is passed to
* setConfigurationMap()) and then call .apply() to apply
* the presets specified in the configuration to the **named**
* QObject properties.
*/
class ApplyPresets
{
public:
/** @brief Create a preset-applier for this config
*
* The @p configurationMap should be the one passed in to
* setConfigurationMap() . Presets are extracted from the
* standard key *presets* and can be applied to the configuration
* with apply() or operator<<.
*/
ApplyPresets( Config& c, const QVariantMap& configurationMap );
~ApplyPresets();
/** @brief Add a preset for the given @p fieldName
*
* This checks for preset-entries in the configuration map that was
* passed in to the constructor.
*/
ApplyPresets& apply( const char* fieldName );
/** @brief Alternate way of writing apply()
*/
ApplyPresets& operator<<( const char* fieldName ) { return apply( fieldName ); }
private:
Config& m_c;
bool m_bogus = true;
const QVariantMap m_map;
};
private:
class Private;
std::unique_ptr< Private > d;
bool m_unlocked = false;
};
} // namespace ModuleSystem
} // namespace Calamares
/// @see Config::isEditable()
//
// This needs to be a macro, because Q_ARG() is a macro that stringifies
// the type name.
#define CONFIG_PREVENT_EDITING( type, fieldName ) \
do \
{ \
if ( !isEditable( QStringLiteral( fieldName ) ) ) \
{ \
auto prop = property( fieldName ); \
const auto& metaobject = metaObject(); \
auto metaprop = metaobject->property( metaobject->indexOfProperty( fieldName ) ); \
if ( metaprop.hasNotifySignal() ) \
{ \
metaprop.notifySignal().invoke( this, Qt::QueuedConnection, Q_ARG( type, prop.value< type >() ) ); \
} \
return; \
} \
} while ( 0 )
#endif

@ -0,0 +1,82 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Preset.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
static void
loadPresets( Calamares::ModuleSystem::Presets& preset,
const QVariantMap& configurationMap,
std::function< bool( const QString& ) > pred )
{
cDebug() << "Creating presets" << preset.capacity();
for ( auto it = configurationMap.cbegin(); it != configurationMap.cend(); ++it )
{
if ( !it.key().isEmpty() && pred( it.key() ) )
{
QVariantMap m = it.value().toMap();
QString value = CalamaresUtils::getString( m, "value" );
bool editable = CalamaresUtils::getBool( m, "editable", true );
preset.append( Calamares::ModuleSystem::PresetField { it.key(), value, editable } );
cDebug() << Logger::SubEntry << "Preset for" << it.key() << "applied editable?" << editable;
}
}
}
namespace Calamares
{
namespace ModuleSystem
{
Presets::Presets( const QVariantMap& configurationMap )
{
reserve( configurationMap.count() );
loadPresets( *this, configurationMap, []( const QString& ) { return true; } );
}
Presets::Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys )
{
reserve( recognizedKeys.size() );
loadPresets(
*this, configurationMap, [&recognizedKeys]( const QString& s ) { return recognizedKeys.contains( s ); } );
}
bool
Presets::isEditable( const QString& fieldName ) const
{
for ( const auto& p : *this )
{
if ( p.fieldName == fieldName )
{
return p.editable;
}
}
cWarning() << "Checking isEditable for unknown field" << fieldName;
return true;
}
PresetField
Presets::find( const QString& fieldName ) const
{
for ( const auto& p : *this )
{
if ( p.fieldName == fieldName )
{
return p;
}
}
return PresetField();
}
} // namespace ModuleSystem
} // namespace Calamares

@ -0,0 +1,91 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef CALAMARES_MODULESYSTEM_PRESET_H
#define CALAMARES_MODULESYSTEM_PRESET_H
#include <QString>
#include <QVariantMap>
#include <QVector>
namespace Calamares
{
namespace ModuleSystem
{
/** @brief The settings for a single field
*
* The settings apply to a single field; **often** this will
* correspond to a single value or property of a Config
* object, but there is no guarantee of a correspondence
* between names here and names in the code.
*
* The value is stored as a string; consumers (e.g. the UI)
* will need to translate the value to whatever is actually
* used (e.g. in the case of an integer field).
*
* By default, presets are still editable. Set that to @c false
* to make the field unchangeable (again, the UI is responsible
* for setting that up).
*/
struct PresetField
{
QString fieldName;
QVariant value;
bool editable = true;
bool isValid() const { return !fieldName.isEmpty(); }
};
/** @brief All the presets for one UI entity
*
* This is a collection of presets read from a module
* configuration file, one setting per field.
*/
class Presets : public QVector< PresetField >
{
public:
/** @brief Reads preset entries from the map
*
* The map's keys are used as field name, and each value entry
* should specify an initial value and whether the entry is editable.
* Fields are editable by default.
*/
explicit Presets( const QVariantMap& configurationMap );
/** @brief Reads preset entries from the @p configurationMap
*
* As above, but only field names that occur in @p recognizedKeys
* are kept; others are discarded.
*/
Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys );
/** @brief Creates an empty presets map
*
* This constructor is primarily intended for use by the ApplyPresets
* helper class, which will reserve suitable space and load
* presets on-demand.
*/
Presets() = default;
/** @brief Is the given @p fieldName editable?
*
* Fields are editable by default, so if there is no explicit setting,
* returns @c true.
*/
bool isEditable( const QString& fieldName ) const;
/** @brief Finds the settings for a field @p fieldName
*
* If there is no such field, returns an invalid PresetField.
*/
PresetField find( const QString& fieldName ) const;
};
} // namespace ModuleSystem
} // namespace Calamares
#endif

@ -22,7 +22,9 @@
#include <QCoreApplication>
#include <QFile>
#include <QMetaProperty>
#include <QRegExp>
#include <QTimer>
#ifdef HAVE_ICU
#include <unicode/translit.h>
@ -91,7 +93,7 @@ hostNameActionNames()
}
Config::Config( QObject* parent )
: QObject( parent )
: Calamares::ModuleSystem::Config( parent )
{
emit readyChanged( m_isReady ); // false
@ -105,7 +107,7 @@ Config::Config( QObject* parent )
connect( this, &Config::requireStrongPasswordsChanged, this, &Config::checkReady );
}
Config::~Config() { }
Config::~Config() {}
void
Config::setUserShell( const QString& shell )
@ -183,6 +185,7 @@ Config::setSudoersGroup( const QString& group )
void
Config::setLoginName( const QString& login )
{
CONFIG_PREVENT_EDITING( QString, "loginName" );
if ( login != m_loginName )
{
m_customLoginName = !login.isEmpty();
@ -393,6 +396,8 @@ makeHostnameSuggestion( const QStringList& parts )
void
Config::setFullName( const QString& name )
{
CONFIG_PREVENT_EDITING( QString, "fullName" );
if ( name.isEmpty() && !m_fullName.isEmpty() )
{
if ( !m_customHostName )
@ -836,6 +841,9 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
updateGSAutoLogin( doAutoLogin(), loginName() );
checkReady();
ApplyPresets( *this, configurationMap ) << "fullName"
<< "loginName";
}
void

@ -13,6 +13,7 @@
#include "CheckPWQuality.h"
#include "Job.h"
#include "modulesystem/Config.h"
#include "utils/NamedEnum.h"
#include <QList>
@ -85,7 +86,7 @@ private:
};
class PLUGINDLLEXPORT Config : public QObject
class PLUGINDLLEXPORT Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
@ -161,7 +162,7 @@ public:
Config( QObject* parent = nullptr );
~Config() override;
void setConfigurationMap( const QVariantMap& );
void setConfigurationMap( const QVariantMap& ) override;
/** @brief Fill Global Storage with some settings
*

@ -101,6 +101,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent )
connect( config, &Config::rootPasswordSecondaryChanged, ui->textBoxVerifiedRootPassword, &QLineEdit::setText );
connect( config, &Config::rootPasswordStatusChanged, this, &UsersPage::reportRootPasswordStatus );
ui->textBoxFullName->setText( config->fullName() );
connect( ui->textBoxFullName, &QLineEdit::textEdited, config, &Config::setFullName );
connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited );
@ -108,6 +109,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent )
connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText );
connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus );
ui->textBoxLoginName->setText( config->loginName() );
connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName );
connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText );
connect( config, &Config::loginNameStatusChanged, this, &UsersPage::reportLoginNameStatus );
@ -140,6 +142,11 @@ UsersPage::UsersPage( Config* config, QWidget* parent )
CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate )
onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() );
onFullNameTextEdited( m_config->fullName() );
reportLoginNameStatus( m_config->loginNameStatus() );
ui->textBoxLoginName->setEnabled( m_config->isEditable( "loginName" ) );
ui->textBoxFullName->setEnabled( m_config->isEditable( "fullName" ) );
}
UsersPage::~UsersPage()

@ -159,3 +159,11 @@ setHostname: EtcFile
# (also adds localhost and some ipv6 standard entries).
# Defaults to *true*.
writeHostsFile: true
presets:
fullName:
value: "OEM User"
editable: false
loginName:
value: "oem"
editable: false

@ -43,6 +43,18 @@ properties:
setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] }
writeHostsFile: { type: boolean, default: true }
# Presets
#
# TODO: lift up somewhere, since this will return in many modules;
# the type for each field (fullname, loginname) is a
# preset-description (value, editable).
presets:
type: object
additionalProperties: false
properties:
fullname: { type: object }
loginname: { type: object }
required:
- defaultGroups
- autologinGroup

Loading…
Cancel
Save