Merge branch 'calamares' into improve-partition

main
Adriaan de Groot 4 years ago
commit df20aa9ddb

@ -10,8 +10,11 @@ website will have to do for older versions.
# 3.2.40 (unreleased) # # 3.2.40 (unreleased) #
This release contains contributions from (alphabetically by first name): This release contains contributions from (alphabetically by first name):
- Anubhav Choudhary (SoK success!)
- Erik Dubois - Erik Dubois
- Lisa Vitolo - Jerrod Frost (new contributor! welcome!)
- Joe Kamprad
- Lisa Vitolo (blast from the past!)
## Core ## ## Core ##
- The CMake modules for consumption by external modules (e.g. the - The CMake modules for consumption by external modules (e.g. the
@ -22,16 +25,50 @@ This release contains contributions from (alphabetically by first name):
libcalamares to systematically mark filesystem (types) as "in use" libcalamares to systematically mark filesystem (types) as "in use"
or not. This, in turn, means that modules can depend on that information or not. This, in turn, means that modules can depend on that information
for other work (e.g. removing drivers for unused filesystems). #1635 for other work (e.g. removing drivers for unused filesystems). #1635
- The "upload log file" now has a configurable log-file-size. (Thanks Anubhav)
## Modules ## ## Modules ##
- *displaymanager* example configuration has been shuffled around a bit, - *displaymanager* example configuration has been shuffled around a bit,
for better results when the live image is running XFCE. Also lists for better results when the live image is running XFCE. Also lists
more potential display managers. #1205 (Thanks Erik) more potential display managers. #1205 (Thanks Erik)
- The *netinstall* module can now fall back to alternative URLs when
loading groups data. The first URL to yield a non-empty groups
collection is accepted. No changes are needed in the configuration. #1673
- *packagechooser* can now integrate with the *packages* module; that
means you can specify package names to install for a given selection,
and the regular package-installation mechanism will take care of it.
Legacy configurations that use *contextualprocess* are still supported.
See the `packagechooser.conf` file for details. #1550
- A long-neglected pull request from Lisa Vitolo for the *partition* - A long-neglected pull request from Lisa Vitolo for the *partition*
module -- allowing to set filesystem labels during manual partitioning -- module -- allowing to set filesystem labels during manual partitioning --
has been revived and merged. has been revived and merged.
# 3.2.39.3 (2021-04-14) #
A minor bugfix tweak release. Since this contains yet **another**
autologin-related fix, and there is nothing large enough to justify
a 3.2.40 release yet, add it to the growing tail of 3.2.39. (Reported
by Joe Kamprad, #1672). Also fixes a regression from 3.2.28 in
localized packages (e.g. *package-LOCALE* did not work).
# 3.2.39.2 (2021-04-02) #
This is **another** hotfix release for issues around autologin ..
autoLogin, really, since the whole problem is that internal capitalization
changed. An unrelated bug in writing /etc/default/keyboard was
also fixed. (Reported by pcrepix, #1668)
# 3.2.39.1 (2021-03-30) #
This hotfix release corrects a regression in the *displaymanager*
module caused by changes in the *users* module; autologin was
internally renamed and no longer recognized by the *displaymanager*
module. (Reported by Erik Dubois, #1665)
# 3.2.39 (2021-03-19) # # 3.2.39 (2021-03-19) #
This release contains contributions from (alphabetically by first name): This release contains contributions from (alphabetically by first name):

@ -75,6 +75,12 @@ fi
# #
# #
BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.) BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.)
KEY_ID="CFDDC96F12B1915C"
# Try to make gpg cache the signing key, so we can leave the process
# to run and sign.
rm -f CHANGES.gpg
gpg -s -u $KEY_ID CHANGES
### Build with default compiler ### Build with default compiler
# #
@ -124,7 +130,6 @@ test -n "$V" || { echo "Could not obtain version in $BUILDDIR ." ; exit 1 ; }
# #
# This is the signing key ID associated with the GitHub account adriaandegroot, # This is the signing key ID associated with the GitHub account adriaandegroot,
# which is used to create all "verified" tags in the Calamares repo. # which is used to create all "verified" tags in the Calamares repo.
KEY_ID="CFDDC96F12B1915C"
git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; } git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; }
### Create the tarball ### Create the tarball
@ -145,6 +150,7 @@ test -d "$TMPDIR" || { echo "Could not create tarball-build directory." ; exit 1
tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; } tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; }
test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; } test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; }
( cd "$TMPDIR/$TAR_V" && cmake . && make -j4 && make test ) || { echo "Tarball build failed in $TMPDIR ." ; exit 1 ; } ( cd "$TMPDIR/$TAR_V" && cmake . && make -j4 && make test ) || { echo "Tarball build failed in $TMPDIR ." ; exit 1 ; }
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
### Cleanup ### Cleanup
# #
@ -157,7 +163,6 @@ rm -rf "$TMPDIR" # From tarball
cat <<EOF cat <<EOF
# Next steps for this release: # Next steps for this release:
git push origin v$V git push origin v$V
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
# Upload tarball $TAR_FILE and the signature $TAR_FILE.asc # Upload tarball $TAR_FILE and the signature $TAR_FILE.asc
# Announce via https://github.com/calamares/calamares/releases/new # Announce via https://github.com/calamares/calamares/releases/new
# SHA256: $SHA256 # SHA256: $SHA256

@ -5,7 +5,7 @@
# #
# Translators: # Translators:
# Adriaan de Groot <groot@kde.org>, 2020 # Adriaan de Groot <groot@kde.org>, 2020
# Christian Spaan, 2020 # Gustav Gyges, 2020
# Andreas Eitel <github-aneitel@online.de>, 2020 # Andreas Eitel <github-aneitel@online.de>, 2020
# #
#, fuzzy #, fuzzy

@ -227,6 +227,12 @@ slideshowAPI: 2
# - url : Defines the address of pastebin service to be used. # - url : Defines the address of pastebin service to be used.
# Takes string as input. Important bits are the host and port, # Takes string as input. Important bits are the host and port,
# the scheme is not used. # the scheme is not used.
# - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted.
# Takes integer as input. If < 0, no limit will be forced,
# else only last (approximately) 'n' KiB of log file will be pasted.
# Please note that upload size may be slightly over the limit (due
# to last minute logging), so provide a suitable value.
uploadServer : uploadServer :
type : "fiche" type : "fiche"
url : "http://termbin.com:9999" url : "http://termbin.com:9999"
sizeLimit : -1

@ -10,7 +10,7 @@
#include "CalamaresApplication.h" #include "CalamaresApplication.h"
#include "CalamaresConfig.h" #include "CalamaresConfig.h"
#include "CalamaresVersion.h" #include "CalamaresVersionX.h"
#include "CalamaresWindow.h" #include "CalamaresWindow.h"
#include "progresstree/ProgressTreeView.h" #include "progresstree/ProgressTreeView.h"

@ -215,10 +215,49 @@ calamares_add_test(
${geoip_src} ${geoip_src}
) )
function ( calamares_qrc_translations basename )
set( NAME ${ARGV0} )
set( options "" )
set( oneValueArgs SUBDIRECTORY OUTPUT_VARIABLE )
set( multiValueArgs LANGUAGES )
cmake_parse_arguments( _qrt "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if( NOT _qrt_OUTPUT_VARIABLE )
set( _qrt_OUTPUT_VARIABLE "qrc_translations_${basename}" )
endif()
set( translations_qrc_infile ${CMAKE_CURRENT_BINARY_DIR}/${basename}.qrc )
set( translations_qrc_outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${basename}.cxx )
# Must use this variable name because of the @ substitution
set( calamares_i18n_qrc_content "" )
set( calamares_i18n_ts_filelist "" )
foreach( lang ${_qrt_LANGUAGES} )
string( APPEND calamares_i18n_qrc_content "<file>${basename}_${lang}.qm</file>" )
list( APPEND calamares_i18n_ts_filelist "${CMAKE_CURRENT_SOURCE_DIR}/${_qrt_SUBDIRECTORY}/${basename}_${lang}.ts" )
endforeach()
configure_file( ${CMAKE_SOURCE_DIR}/lang/calamares_i18n.qrc.in ${translations_qrc_infile} @ONLY )
qt5_add_translation(QM_FILES ${calamares_i18n_ts_filelist})
# Run the resource compiler (rcc_options should already be set)
add_custom_command(
OUTPUT ${translations_qrc_outfile}
COMMAND "${Qt5Core_RCC_EXECUTABLE}"
ARGS ${rcc_options} --format-version 1 -name ${basename} -o ${translations_qrc_outfile} ${translations_qrc_infile}
MAIN_DEPENDENCY ${translations_qrc_infile}
DEPENDS ${QM_FILES}
)
set( ${_qrt_OUTPUT_VARIABLE} ${translations_qrc_outfile} PARENT_SCOPE )
endfunction()
calamares_qrc_translations( localetest OUTPUT_VARIABLE localetest_qrc SUBDIRECTORY testdata LANGUAGES nl )
calamares_add_test( calamares_add_test(
libcalamareslocaletest libcalamareslocaletest
SOURCES SOURCES
locale/Tests.cpp locale/Tests.cpp
${localetest_qrc}
) )
calamares_add_test( calamares_add_test(

@ -16,6 +16,7 @@
#include "CalamaresVersion.h" #include "CalamaresVersion.h"
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QtTest/QtTest> #include <QtTest/QtTest>
@ -33,6 +34,7 @@ private Q_SLOTS:
void testTranslatableLanguages(); void testTranslatableLanguages();
void testTranslatableConfig1(); void testTranslatableConfig1();
void testTranslatableConfig2(); void testTranslatableConfig2();
void testTranslatableConfigContext();
void testLanguageScripts(); void testLanguageScripts();
void testEsperanto(); void testEsperanto();
@ -246,6 +248,32 @@ LocaleTests::testTranslatableConfig2()
QCOMPARE( ts3.count(), 1 ); // The empty string QCOMPARE( ts3.count(), 1 ); // The empty string
} }
void
LocaleTests::testTranslatableConfigContext()
{
using TS = CalamaresUtils::Locale::TranslatedString;
const QString original( "Quit" );
TS quitUntranslated( original );
TS quitTranslated( original, metaObject()->className() );
QCOMPARE( quitUntranslated.get(), original );
QCOMPARE( quitTranslated.get(), original );
// Load translation data from QRC
QVERIFY( QFile::exists( ":/lang/localetest_nl.qm" ) );
QTranslator t;
QVERIFY( t.load( QString( ":/lang/localetest_nl" ) ) );
QCoreApplication::installTranslator( &t );
// Translation doesn't affect the one without context
QCOMPARE( quitUntranslated.get(), original );
// But the translation **does** affect this class' context
QCOMPARE( quitTranslated.get(), QStringLiteral( "Ophouden" ) );
QCOMPARE( tr( "Quit" ), QStringLiteral( "Ophouden" ) );
}
void void
LocaleTests::testRegions() LocaleTests::testRegions()
{ {

@ -23,9 +23,15 @@ namespace CalamaresUtils
{ {
namespace Locale namespace Locale
{ {
TranslatedString::TranslatedString( const QString& key, const char* context )
: m_context( context )
{
m_strings[ QString() ] = key;
}
TranslatedString::TranslatedString( const QString& string ) TranslatedString::TranslatedString( const QString& string )
: TranslatedString( string, nullptr )
{ {
m_strings[ QString() ] = string;
} }
TranslatedString::TranslatedString( const QVariantMap& map, const QString& key, const char* context ) TranslatedString::TranslatedString( const QVariantMap& map, const QString& key, const char* context )

@ -50,11 +50,23 @@ public:
* metaObject()->className() as context (from a QObject based class) * metaObject()->className() as context (from a QObject based class)
* to give the TranslatedString the same context as other calls * to give the TranslatedString the same context as other calls
* to tr() within that class. * to tr() within that class.
*
* The @p context, if any, should point to static data; it is
* **not** owned by the TranslatedString.
*/ */
TranslatedString( const QVariantMap& map, const QString& key, const char* context = nullptr ); TranslatedString( const QVariantMap& map, const QString& key, const char* context = nullptr );
/** @brief Not-actually-translated string. /** @brief Not-actually-translated string.
*/ */
TranslatedString( const QString& string ); TranslatedString( const QString& string );
/** @brief Proxy for calling QObject::tr()
*
* This is like the two constructors above, with an empty map an a
* non-null context. It will end up calling tr() with that context.
*
* The @p context, if any, should point to static data; it is
* **not** owned by the TranslatedString.
*/
TranslatedString( const QString& key, const char* context );
/// @brief Empty string /// @brief Empty string
TranslatedString() TranslatedString()
: TranslatedString( QString() ) : TranslatedString( QString() )

@ -12,9 +12,9 @@
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "utils/Logger.h" #include "utils/Logger.h"
bool static bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, additions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module, const QString& key,
const QVariantList& installPackages, const QVariantList& installPackages,
const QVariantList& tryInstallPackages ) const QVariantList& tryInstallPackages )
{ {
@ -25,8 +25,6 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
QVariantList packageOperations = gs->contains( PACKAGEOP ) ? gs->value( PACKAGEOP ).toList() : QVariantList(); QVariantList packageOperations = gs->contains( PACKAGEOP ) ? gs->value( PACKAGEOP ).toList() : QVariantList();
cDebug() << "Existing package operations length" << packageOperations.length(); cDebug() << "Existing package operations length" << packageOperations.length();
const QString key = module.toString();
// Clear out existing operations for this module, going backwards: // Clear out existing operations for this module, going backwards:
// Sometimes we remove an item, and we don't want the index to // Sometimes we remove an item, and we don't want the index to
// fall off the end of the list. // fall off the end of the list.
@ -66,3 +64,25 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
} }
return false; return false;
} }
bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QVariantList& installPackages,
const QVariantList& tryInstallPackages )
{
return additions( gs, module.toString(), installPackages, tryInstallPackages );
}
bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QStringList& installPackages )
{
QVariantList l;
for ( const auto& s : installPackages )
{
l << s;
}
return additions( gs, module.toString(), l, QVariantList() );
}

@ -28,6 +28,14 @@ bool setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module, const Calamares::ModuleSystem::InstanceKey& module,
const QVariantList& installPackages, const QVariantList& installPackages,
const QVariantList& tryInstallPackages ); const QVariantList& tryInstallPackages );
/** @brief Sets the install-packages GS keys for the given module
*
* This replaces previously-set install-packages lists. Use this with
* plain lists of package names. It does not support try-install.
*/
bool setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QStringList& installPackages );
// void setGSPackageRemovals( const Calamares::ModuleSystem::InstanceKey& key, const QVariantList& removePackages ); // void setGSPackageRemovals( const Calamares::ModuleSystem::InstanceKey& key, const QVariantList& removePackages );
} // namespace Packages } // namespace Packages
} // namespace CalamaresUtils } // namespace CalamaresUtils

@ -24,7 +24,15 @@ private Q_SLOTS:
void initTestCase(); void initTestCase();
void testEmpty(); void testEmpty();
void testAdd_data();
/** @brief Test various add calls, for a "clean" GS
*
* Check that adding through the variant- and the stringlist-API
* does the same thing.
*/
void testAdd(); void testAdd();
/// Test replacement and mixing string-list with variant calls
void testAddMixed();
}; };
void void
@ -46,18 +54,114 @@ PackagesTests::testEmpty()
// Adding nothing at all does nothing // Adding nothing at all does nothing
QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList(), QVariantList() ) ); QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList(), QVariantList() ) );
QVERIFY( !gs.contains( topKey ) ); QVERIFY( !gs.contains( topKey ) );
QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QStringList() ) );
QVERIFY( !gs.contains( topKey ) );
}
void
PackagesTests::testAdd_data()
{
QTest::addColumn< QStringList >( "packages" );
QTest::newRow( "one" ) << QStringList { QString( "vim" ) };
QTest::newRow( "two" ) << QStringList { QString( "vim" ), QString( "emacs" ) };
QTest::newRow( "one-again" ) << QStringList { QString( "nano" ) };
QTest::newRow( "six" ) << QStringList { QString( "vim" ), QString( "emacs" ), QString( "nano" ),
QString( "kate" ), QString( "gedit" ), QString( "sublime" ) };
// There is no "de-duplication" so this will insert "cim" twice
QTest::newRow( "dups" ) << QStringList { QString( "cim" ), QString( "vim" ), QString( "cim" ) };
} }
void void
PackagesTests::testAdd() PackagesTests::testAdd()
{ {
Calamares::GlobalStorage gs; Calamares::GlobalStorage gs;
const QString extraEditor( "notepad++" );
const QString topKey( "packageOperations" ); const QString topKey( "packageOperations" );
Calamares::ModuleSystem::InstanceKey k( "this", "that" ); Calamares::ModuleSystem::InstanceKey k( "this", "that" );
Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" );
QFETCH( QStringList, packages );
QVERIFY( !packages.contains( extraEditor ) );
{
QVERIFY( !gs.contains( topKey ) ); QVERIFY( !gs.contains( topKey ) );
QVERIFY( QVERIFY(
CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) ); CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariant( packages ).toList(), QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length() );
for ( const auto& s : qAsConst( packages ) )
{
QVERIFY( op.contains( s ) );
}
cDebug() << op;
}
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 ); // One for each instance key!
auto action = actionList[ 0 ].toMap();
auto secondaction = actionList[ 1 ].toMap();
auto op = action[ "install" ].toList();
auto secondop = secondaction[ "install" ].toList();
QCOMPARE( op, secondop );
}
{
// Replace one and expect differences
packages << extraEditor;
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 ); // One for each instance key!
for ( const auto& actionVariant : qAsConst( actionList ) )
{
auto action = actionVariant.toMap();
QVERIFY( action.contains( "install" ) );
QVERIFY( action.contains( "source" ) );
if ( action[ "source" ].toString() == otherInstance.toString() )
{
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length() ); // changed from original length, though
for ( const auto& s : qAsConst( packages ) )
{
QVERIFY( op.contains( s ) );
}
}
else
{
// This is the "original" instance, so it's missing extraEditor
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length()-1 ); // changed from original length
QVERIFY( !op.contains( extraEditor ) );
}
}
}
}
void
PackagesTests::testAddMixed()
{
Calamares::GlobalStorage gs;
const QString extraEditor( "notepad++" );
const QString topKey( "packageOperations" );
Calamares::ModuleSystem::InstanceKey k( "this", "that" );
Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" );
// Just one
{
QVERIFY( !gs.contains( topKey ) );
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) ); QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList(); auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 ); QCOMPARE( actionList.length(), 1 );
@ -65,21 +169,66 @@ PackagesTests::testAdd()
QVERIFY( action.contains( "install" ) ); QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList(); auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 ); QCOMPARE( op.length(), 1 );
QCOMPARE( op[ 0 ], QString( "vim" ) );
cDebug() << op; cDebug() << op;
}
// Replace with two packages
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) ); &gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) ); QVERIFY( gs.contains( topKey ) );
actionList = gs.value( topKey ).toList(); auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 ); QCOMPARE( actionList.length(), 1 );
action = actionList[ 0 ].toMap(); auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) ); QVERIFY( action.contains( "install" ) );
op = action[ "install" ].toList(); auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 2 ); QCOMPARE( op.length(), 2 );
QCOMPARE( action[ "source" ].toString(), k.toString() ); QCOMPARE( action[ "source" ].toString(), k.toString() );
QVERIFY( op.contains( QString( "vim" ) ) );
QVERIFY( op.contains( QString( "emacs" ) ) );
cDebug() << op;
}
// Replace with one (different) package
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "nano" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 );
QCOMPARE( action[ "source" ].toString(), k.toString() );
QCOMPARE( op[ 0 ], QString( "nano" ) );
cDebug() << op; cDebug() << op;
} }
// Now we have two sources
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, QStringList( extraEditor ) ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 );
for ( const auto& actionVariant : qAsConst( actionList ) )
{
auto action = actionVariant.toMap();
QVERIFY( action.contains( "install" ) );
QVERIFY( action.contains( "source" ) );
if ( action[ "source" ].toString() == otherInstance.toString() )
{
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 );
QVERIFY(
op.contains( action[ "source" ] == otherInstance.toString() ? extraEditor : QString( "nano" ) ) );
}
}
}
}
QTEST_GUILESS_MAIN( PackagesTests ) QTEST_GUILESS_MAIN( PackagesTests )

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- SPDX-FileCopyrightText: no
SPDX-License-Identifier: CC0-1.0
-->
<!DOCTYPE TS>
<TS language="nl" version="2.1">
<context>
<name>LocaleTests</name>
<message>
<location filename="Tests.cpp" line="22"/>
<source>Quit</source>
<translation>Ophouden</translation>
</message>
</context>
</TS>

@ -290,6 +290,17 @@ operator<<( QDebug& s, const Pointer& p )
return s; return s;
} }
/** @brief Convenience object for supplying SubEntry to a debug stream
*
* In a function with convoluted control paths, it may be unclear
* when to supply SubEntry to a debug stream -- it is convenient
* for the **first** debug statement from a given function to print
* the function header, and all subsequent onces to get SubEntry.
*
* Create an object of type Once and send it (first) to all CDebug
* objects; this will print the function header only once within the
* lifetime of that Once object.
*/
class Once class Once
{ {
public: public:

@ -174,6 +174,22 @@ struct NamedEnumTable
return table.begin()->second; return table.begin()->second;
} }
/** @brief Find a name @p s in the table.
*
* Searches case-insensitively.
*
* If the name @p s is not found, the value @p d is returned as
* a default. Otherwise the value corresponding to @p s is returned.
* This is a shortcut over find() using a bool to distinguish
* successful and unsuccesful lookups.
*/
enum_t find( const string_t& s, enum_t d ) const
{
bool ok = false;
enum_t e = find( s, ok );
return ok ? e : d;
}
/** @brief Find a value @p s in the table and return its name. /** @brief Find a value @p s in the table and return its name.
* *
* If @p s is an enum value in the table, return the corresponding * If @p s is an enum value in the table, return the corresponding

@ -113,7 +113,7 @@ BrandingLoader::tryLoad( QTranslator* translator )
} }
else else
{ {
cDebug() << Logger::SubEntry << "Branding using default, system locale not found:" << m_localeName; cDebug() << Logger::SubEntry << "Branding no translation for" << m_localeName << "using default (en)";
// TODO: this loads something completely different // TODO: this loads something completely different
return translator->load( m_prefix + "en" ); return translator->load( m_prefix + "en" );
} }

@ -18,6 +18,7 @@
#include "utils/ImageRegistry.h" #include "utils/ImageRegistry.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/NamedEnum.h" #include "utils/NamedEnum.h"
#include "utils/Units.h"
#include "utils/Yaml.h" #include "utils/Yaml.h"
#include <QDir> #include <QDir>
@ -153,15 +154,18 @@ uploadServerFromMap( const QVariantMap& map )
QString typestring = map[ "type" ].toString(); QString typestring = map[ "type" ].toString();
QString urlstring = map[ "url" ].toString(); QString urlstring = map[ "url" ].toString();
qint64 sizeLimitKiB = map[ "sizeLimit" ].toLongLong();
if ( typestring.isEmpty() || urlstring.isEmpty() ) if ( typestring.isEmpty() || urlstring.isEmpty() )
{ {
return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl() ); return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl(), 0 );
} }
bool bogus = false; // we don't care about type-name lookup success here bool bogus = false; // we don't care about type-name lookup success here
return Branding::UploadServerInfo( names.find( typestring, bogus ), return Branding::UploadServerInfo(
QUrl( urlstring, QUrl::ParsingMode::StrictMode ) ); names.find( typestring, bogus ),
QUrl( urlstring, QUrl::ParsingMode::StrictMode ),
sizeLimitKiB >= 0 ? CalamaresUtils::KiBtoBytes( static_cast< unsigned long long >( sizeLimitKiB ) ) : -1 );
} }
/** @brief Load the @p map with strings from @p config /** @brief Load the @p map with strings from @p config

@ -223,10 +223,11 @@ public:
/** @brief Upload server configuration /** @brief Upload server configuration
* *
* This is both the type (which may be none, in which case the URL * This object has 3 items : the type (which may be none, in which case the URL
* is irrelevant and usually empty) and the URL for the upload. * is irrelevant and usually empty), the URL for the upload and the size limit of upload
* in bytes (for configuration value < 0, it serves -1, which stands for having no limit).
*/ */
using UploadServerInfo = QPair< UploadServerType, QUrl >; using UploadServerInfo = std::tuple< UploadServerType, QUrl, qint64 >;
UploadServerInfo uploadServer() const { return m_uploadServer; } UploadServerInfo uploadServer() const { return m_uploadServer; }
/** /**

@ -143,8 +143,9 @@ ViewManager::insertViewStep( int before, ViewStep* step )
void void
ViewManager::onInstallationFailed( const QString& message, const QString& details ) ViewManager::onInstallationFailed( const QString& message, const QString& details )
{ {
bool shouldOfferWebPaste bool shouldOfferWebPaste = std::get< 0 >( Calamares::Branding::instance()->uploadServer() )
= Calamares::Branding::instance()->uploadServer().first != Calamares::Branding::UploadServerType::None; != Calamares::Branding::UploadServerType::None
and std::get< 2 >( Calamares::Branding::instance()->uploadServer() ) != 0;
cError() << "Installation failed:" << message; cError() << "Installation failed:" << message;
cDebug() << Logger::SubEntry << "- message:" << message; cDebug() << Logger::SubEntry << "- message:" << message;

@ -30,8 +30,12 @@ using namespace CalamaresUtils::Units;
* Returns an empty QByteArray() on any kind of error. * Returns an empty QByteArray() on any kind of error.
*/ */
STATICTEST QByteArray STATICTEST QByteArray
logFileContents() logFileContents( const qint64 sizeLimitBytes )
{ {
if ( sizeLimitBytes != -1 )
{
cDebug() << "Log upload size limit was limited to" << sizeLimitBytes << "bytes";
}
const QString name = Logger::logFile(); const QString name = Logger::logFile();
QFile pasteSourceFile( name ); QFile pasteSourceFile( name );
if ( !pasteSourceFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) if ( !pasteSourceFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
@ -39,12 +43,18 @@ logFileContents()
cWarning() << "Could not open log file" << name; cWarning() << "Could not open log file" << name;
return QByteArray(); return QByteArray();
} }
if ( sizeLimitBytes == -1 )
{
return pasteSourceFile.readAll();
}
QFileInfo fi( pasteSourceFile ); QFileInfo fi( pasteSourceFile );
if ( fi.size() > 16_KiB ) if ( fi.size() > sizeLimitBytes )
{ {
pasteSourceFile.seek( fi.size() - 16_KiB ); cDebug() << "Only last" << sizeLimitBytes << "bytes of log file (sized" << fi.size() << "bytes) uploaded";
fi.refresh();
pasteSourceFile.seek( fi.size() - sizeLimitBytes );
} }
return pasteSourceFile.read( 16_KiB ); return pasteSourceFile.read( sizeLimitBytes );
} }
@ -101,7 +111,7 @@ ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* par
QString QString
CalamaresUtils::Paste::doLogUpload( QObject* parent ) CalamaresUtils::Paste::doLogUpload( QObject* parent )
{ {
auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer(); auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer();
if ( !serverUrl.isValid() ) if ( !serverUrl.isValid() )
{ {
cWarning() << "Upload configure with invalid URL"; cWarning() << "Upload configure with invalid URL";
@ -113,7 +123,7 @@ CalamaresUtils::Paste::doLogUpload( QObject* parent )
return QString(); return QString();
} }
QByteArray pasteData = logFileContents(); QByteArray pasteData = logFileContents( sizeLimitBytes );
if ( pasteData.isEmpty() ) if ( pasteData.isEmpty() )
{ {
// An error has already been logged // An error has already been logged
@ -165,6 +175,6 @@ CalamaresUtils::Paste::doLogUploadUI( QWidget* parent )
bool bool
CalamaresUtils::Paste::isEnabled() CalamaresUtils::Paste::isEnabled()
{ {
auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer(); auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer();
return type != Calamares::Branding::UploadServerType::None; return type != Calamares::Branding::UploadServerType::None;
} }

@ -10,13 +10,14 @@
*/ */
#include "Paste.h" #include "Paste.h"
#include "network/Manager.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include <QDateTime> #include <QDateTime>
#include <QtTest/QtTest> #include <QtTest/QtTest>
extern QByteArray logFileContents(); extern QByteArray logFileContents( qint64 sizeLimitBytes );
extern QString ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent ); extern QString ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent );
class TestPaste : public QObject class TestPaste : public QObject
@ -30,6 +31,7 @@ public:
private Q_SLOTS: private Q_SLOTS:
void testGetLogFile(); void testGetLogFile();
void testFichePaste(); void testFichePaste();
void testUploadSize();
}; };
void void
@ -37,14 +39,18 @@ TestPaste::testGetLogFile()
{ {
QFile::remove( Logger::logFile() ); QFile::remove( Logger::logFile() );
// This test assumes nothing **else** has set up logging yet // This test assumes nothing **else** has set up logging yet
QByteArray contentsOfLogfileBefore = logFileContents(); QByteArray logLimitedBefore = logFileContents( 16 );
QVERIFY( contentsOfLogfileBefore.isEmpty() ); QVERIFY( logLimitedBefore.isEmpty() );
QByteArray logUnlimitedBefore = logFileContents( -1 );
QVERIFY( logUnlimitedBefore.isEmpty() );
Logger::setupLogLevel( Logger::LOGDEBUG ); Logger::setupLogLevel( Logger::LOGDEBUG );
Logger::setupLogfile(); Logger::setupLogfile();
QByteArray contentsOfLogfileAfterSetup = logFileContents(); QByteArray logLimitedAfter = logFileContents( 16 );
QVERIFY( !contentsOfLogfileAfterSetup.isEmpty() ); QVERIFY( !logLimitedAfter.isEmpty() );
QByteArray logUnlimitedAfter = logFileContents( -1 );
QVERIFY( !logUnlimitedAfter.isEmpty() );
} }
void void
@ -60,7 +66,19 @@ TestPaste::testFichePaste()
QVERIFY( !s.isEmpty() ); QVERIFY( !s.isEmpty() );
} }
void
TestPaste::testUploadSize()
{
QByteArray logContent = logFileContents( 100 );
QString s = ficheLogUpload( logContent, QUrl( "http://termbin.com:9999" ), nullptr );
QVERIFY( !s.isEmpty() );
QUrl url( s );
QByteArray returnedData = CalamaresUtils::Network::Manager::instance().synchronousGet( url );
QCOMPARE( returnedData.size(), 100 );
}
QTEST_GUILESS_MAIN( TestPaste ) QTEST_GUILESS_MAIN( TestPaste )
#include "utils/moc-warnings.h" #include "utils/moc-warnings.h"

@ -195,6 +195,8 @@ desktop_environments = [
DesktopEnvironment('/usr/bin/dwm', 'dwm'), DesktopEnvironment('/usr/bin/dwm', 'dwm'),
DesktopEnvironment('/usr/bin/jwm', 'jwm'), DesktopEnvironment('/usr/bin/jwm', 'jwm'),
DesktopEnvironment('/usr/bin/icewm-session', 'icewm-session'), DesktopEnvironment('/usr/bin/icewm-session', 'icewm-session'),
DesktopEnvironment('/usr/bin/fvwm3', 'fvwm3'),
DesktopEnvironment('/usr/bin/sway', 'sway'),
] ]

@ -92,6 +92,7 @@ def write_mkinitcpio_lines(hooks, modules, files, root_mount_point):
with open(path, "w") as mkinitcpio_file: with open(path, "w") as mkinitcpio_file:
mkinitcpio_file.write("\n".join(mklins) + "\n") mkinitcpio_file.write("\n".join(mklins) + "\n")
def detect_plymouth(): def detect_plymouth():
""" """
Checks existence (runnability) of plymouth in the target system. Checks existence (runnability) of plymouth in the target system.
@ -99,10 +100,8 @@ def detect_plymouth():
@return True if plymouth exists in the target, False otherwise @return True if plymouth exists in the target, False otherwise
""" """
# Used to only check existence of path /usr/bin/plymouth in target # Used to only check existence of path /usr/bin/plymouth in target
isPlymouth = target_env_call(["sh", "-c", "which plymouth"]) return target_env_call(["sh", "-c", "which plymouth"]) == 0
debug("which plymouth exit code: {!s}".format(isPlymouth))
return isPlymouth == 0
def modify_mkinitcpio_conf(partitions, root_mount_point): def modify_mkinitcpio_conf(partitions, root_mount_point):
""" """

@ -210,6 +210,10 @@ Config::Config( QObject* parent )
m_setxkbmapTimer.start( QApplication::keyboardInputInterval() ); m_setxkbmapTimer.start( QApplication::keyboardInputInterval() );
emit prettyStatusChanged(); emit prettyStatusChanged();
} ); } );
m_selectedModel = m_keyboardModelsModel->key( m_keyboardModelsModel->currentIndex() );
m_selectedLayout = m_keyboardLayoutsModel->item( m_keyboardLayoutsModel->currentIndex() ).first;
m_selectedVariant = m_keyboardVariantsModel->key( m_keyboardVariantsModel->currentIndex() );
} }
KeyboardModelsModel* KeyboardModelsModel*
@ -528,38 +532,15 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
{ {
using namespace CalamaresUtils; using namespace CalamaresUtils;
if ( configurationMap.contains( "xOrgConfFileName" ) const auto xorgConfDefault = QStringLiteral( "00-keyboard.conf" );
&& configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName", xorgConfDefault );
&& !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) if ( m_xOrgConfFileName.isEmpty() )
{
m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" );
}
else
{ {
m_xOrgConfFileName = "00-keyboard.conf"; m_xOrgConfFileName = xorgConfDefault;
} }
if ( configurationMap.contains( "convertedKeymapPath" )
&& configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String
&& !getString( configurationMap, "convertedKeymapPath" ).isEmpty() )
{
m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" );
}
else
{
m_convertedKeymapPath = QString();
}
if ( configurationMap.contains( "writeEtcDefaultKeyboard" )
&& configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool )
{
m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true );
} }
else
{
m_writeEtcDefaultKeyboard = true;
}
}
void void
Config::retranslate() Config::retranslate()

@ -259,8 +259,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location
auto newLocale = automaticLocaleConfiguration(); auto newLocale = automaticLocaleConfiguration();
if ( !m_selectedLocaleConfiguration.explicit_lang ) if ( !m_selectedLocaleConfiguration.explicit_lang )
{ {
m_selectedLocaleConfiguration.setLanguage( newLocale.language() ); setLanguage( newLocale.language() );
emit currentLanguageStatusChanged( currentLanguageStatus() );
} }
if ( !m_selectedLocaleConfiguration.explicit_lc ) if ( !m_selectedLocaleConfiguration.explicit_lc )
{ {
@ -302,12 +301,21 @@ Config::localeConfiguration() const
void void
Config::setLanguageExplicitly( const QString& language ) Config::setLanguageExplicitly( const QString& language )
{ {
m_selectedLocaleConfiguration.setLanguage( language );
m_selectedLocaleConfiguration.explicit_lang = true; m_selectedLocaleConfiguration.explicit_lang = true;
setLanguage( language );
}
void
Config::setLanguage( const QString& language )
{
if ( language != m_selectedLocaleConfiguration.language() )
{
m_selectedLocaleConfiguration.setLanguage( language );
emit currentLanguageStatusChanged( currentLanguageStatus() ); emit currentLanguageStatusChanged( currentLanguageStatus() );
emit currentLanguageCodeChanged( currentLanguageCode() ); emit currentLanguageCodeChanged( currentLanguageCode() );
} }
}
void void
Config::setLCLocaleExplicitly( const QString& locale ) Config::setLCLocaleExplicitly( const QString& locale )

@ -95,6 +95,8 @@ private:
} }
public Q_SLOTS: public Q_SLOTS:
/// Set the language, but do not mark it as user-choice
void setLanguage( const QString& language );
/// Set a language by user-choice, overriding future location changes /// Set a language by user-choice, overriding future location changes
void setLanguageExplicitly( const QString& language ); void setLanguageExplicitly( const QString& language );
/// Set LC (formats) by user-choice, overriding future location changes /// Set LC (formats) by user-choice, overriding future location changes

@ -26,6 +26,8 @@ calamares_add_test(
netinstalltest netinstalltest
SOURCES SOURCES
Tests.cpp Tests.cpp
Config.cpp
LoaderQueue.cpp
PackageTreeItem.cpp PackageTreeItem.cpp
PackageModel.cpp PackageModel.cpp
LIBRARIES LIBRARIES

@ -97,7 +97,6 @@ Config::loadGroupList( const QVariantList& groupData )
{ {
setStatus( Status::Ok ); setStatus( Status::Ok );
} }
emit statusReady();
} }
void void
@ -108,6 +107,7 @@ Config::loadingDone()
m_queue->deleteLater(); m_queue->deleteLater();
m_queue = nullptr; m_queue = nullptr;
} }
emit statusReady();
} }
@ -136,26 +136,24 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
// Lastly, load the groups data // Lastly, load the groups data
const QString key = QStringLiteral( "groupsUrl" ); const QString key = QStringLiteral( "groupsUrl" );
const auto& groupsUrlVariant = configurationMap.value( key ); const auto& groupsUrlVariant = configurationMap.value( key );
m_queue = new LoaderQueue( this );
if ( groupsUrlVariant.type() == QVariant::String ) if ( groupsUrlVariant.type() == QVariant::String )
{ {
m_queue = new LoaderQueue( this );
m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) ); m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) );
} }
else if ( groupsUrlVariant.type() == QVariant::List ) else if ( groupsUrlVariant.type() == QVariant::List )
{ {
m_queue = new LoaderQueue( this );
for ( const auto& s : groupsUrlVariant.toStringList() ) for ( const auto& s : groupsUrlVariant.toStringList() )
{ {
m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) ); m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) );
} }
} }
if ( m_queue && m_queue->count() > 0 )
{ setStatus( required() ? Status::FailedNoData : Status::Ok );
cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources."; cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources.";
connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone ); connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone );
m_queue->load(); m_queue->load();
} }
}
void void
Config::finalizeGlobalStorage( const Calamares::ModuleSystem::InstanceKey& key ) Config::finalizeGlobalStorage( const Calamares::ModuleSystem::InstanceKey& key )

@ -49,10 +49,12 @@ public:
FailedNetworkError, FailedNetworkError,
FailedBadData, FailedBadData,
FailedNoData FailedNoData
}; };
/// Human-readable, translated representation of the status
QString status() const; QString status() const;
/// Internal code for the status
Status statusCode() const { return m_status; }
void setStatus( Status s ); void setStatus( Status s );
bool required() const { return m_required; } bool required() const { return m_required; }

@ -25,6 +25,10 @@
* On destruction, a new call to fetchNext() is queued, so that * On destruction, a new call to fetchNext() is queued, so that
* the queue continues loading. Calling release() before the * the queue continues loading. Calling release() before the
* destructor skips the fetchNext(), ending the queue-loading. * destructor skips the fetchNext(), ending the queue-loading.
*
* Calling done(b) is a conditional release: if @p b is @c true,
* queues a call to done() on the queue and releases it; otherwise,
* does nothing.
*/ */
class FetchNextUnless class FetchNextUnless
{ {
@ -41,6 +45,17 @@ public:
} }
} }
void release() { m_q = nullptr; } void release() { m_q = nullptr; }
void done( bool b )
{
if ( b )
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "done", Qt::QueuedConnection );
}
release();
}
}
private: private:
LoaderQueue* m_q = nullptr; LoaderQueue* m_q = nullptr;
@ -83,7 +98,6 @@ LoaderQueue::fetchNext()
{ {
if ( m_queue.isEmpty() ) if ( m_queue.isEmpty() )
{ {
m_config->setStatus( Config::Status::FailedBadData );
emit done(); emit done();
return; return;
} }
@ -138,7 +152,7 @@ LoaderQueue::fetch( const QUrl& url )
void void
LoaderQueue::dataArrived() LoaderQueue::dataArrived()
{ {
FetchNextUnless finished( this ); FetchNextUnless next( this );
if ( !m_reply || !m_reply->isFinished() ) if ( !m_reply || !m_reply->isFinished() )
{ {
@ -170,16 +184,14 @@ LoaderQueue::dataArrived()
if ( groups.IsSequence() ) if ( groups.IsSequence() )
{ {
finished.release();
m_config->loadGroupList( CalamaresUtils::yamlSequenceToVariant( groups ) ); m_config->loadGroupList( CalamaresUtils::yamlSequenceToVariant( groups ) );
emit done(); next.done( m_config->statusCode() == Config::Status::Ok );
} }
else if ( groups.IsMap() ) else if ( groups.IsMap() )
{ {
finished.release();
auto map = CalamaresUtils::yamlMapToVariant( groups ); auto map = CalamaresUtils::yamlMapToVariant( groups );
m_config->loadGroupList( map.value( "groups" ).toList() ); m_config->loadGroupList( map.value( "groups" ).toList() );
emit done(); next.done( m_config->statusCode() == Config::Status::Ok );
} }
else else
{ {

@ -7,13 +7,17 @@
* *
*/ */
#include "Config.h"
#include "PackageModel.h" #include "PackageModel.h"
#include "PackageTreeItem.h" #include "PackageTreeItem.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Variant.h" #include "utils/Variant.h"
#include "utils/Yaml.h" #include "utils/Yaml.h"
#include <KMacroExpander>
#include <QtTest/QtTest> #include <QtTest/QtTest>
class ItemTests : public QObject class ItemTests : public QObject
@ -40,6 +44,9 @@ private Q_SLOTS:
void testCompare(); void testCompare();
void testModel(); void testModel();
void testExampleFiles(); void testExampleFiles();
void testUrlFallback_data();
void testUrlFallback();
}; };
ItemTests::ItemTests() {} ItemTests::ItemTests() {}
@ -326,6 +333,93 @@ ItemTests::testExampleFiles()
} }
} }
void
ItemTests::testUrlFallback_data()
{
QTest::addColumn< QString >( "filename" );
QTest::addColumn< int >( "status" );
QTest::addColumn< int >( "count" );
using S = Config::Status;
QTest::newRow( "bad" ) << "1a-single-bad.conf" << smash( S::FailedBadConfiguration ) << 0;
QTest::newRow( "empty" ) << "1a-single-empty.conf" << smash( S::FailedNoData ) << 0;
QTest::newRow( "error" ) << "1a-single-error.conf" << smash( S::FailedBadData ) << 0;
QTest::newRow( "two" ) << "1b-single-small.conf" << smash( S::Ok ) << 2;
QTest::newRow( "five" ) << "1b-single-large.conf" << smash( S::Ok ) << 5;
QTest::newRow( "none" ) << "1c-none.conf" << smash( S::FailedNoData ) << 0;
QTest::newRow( "unset" ) << "1c-unset.conf" << smash( S::FailedNoData ) << 0;
// Finds small, then stops
QTest::newRow( "fallback-small" ) << "1d-fallback-small.conf" << smash( S::Ok ) << 2;
// Finds large, then stops
QTest::newRow( "fallback-large" ) << "1d-fallback-large.conf" << smash( S::Ok ) << 5;
// Finds empty, finds small
QTest::newRow( "fallback-mixed" ) << "1d-fallback-mixed.conf" << smash( S::Ok ) << 2;
// Finds empty, then bad
QTest::newRow( "fallback-bad" ) << "1d-fallback-bad.conf" << smash( S::FailedBadConfiguration ) << 0;
}
void
ItemTests::testUrlFallback()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
QFETCH( QString, filename );
QFETCH( int, status );
QFETCH( int, count );
cDebug() << "Loading" << filename;
// BUILD_AS_TEST is the source-directory path
QString testdir = QString( "%1/tests" ).arg( BUILD_AS_TEST );
QFile fi( QString( "%1/%2" ).arg( testdir, filename ) );
QVERIFY( fi.exists() );
Config c;
QFile yamlFile( fi.fileName() );
if ( yamlFile.exists() && yamlFile.open( QFile::ReadOnly | QFile::Text ) )
{
QString ba( yamlFile.readAll() );
QVERIFY( ba.length() > 0 );
QHash< QString, QString > replace;
replace.insert( "TESTDIR", testdir );
QString correctedDocument = KMacroExpander::expandMacros( ba, replace, '$' );
try
{
YAML::Node yamldoc = YAML::Load( correctedDocument.toUtf8() );
auto map = CalamaresUtils::yamlToVariant( yamldoc ).toMap();
QVERIFY( map.count() > 0 );
c.setConfigurationMap( map );
}
catch ( YAML::Exception& )
{
bool badYaml = true;
QVERIFY( !badYaml );
}
}
else
{
QCOMPARE( QStringLiteral( "not found" ), fi.fileName() );
}
// Each of the configs sets required to **true**, which is not the default
QVERIFY( c.required() );
// Now give the loader time to complete
QEventLoop loop;
connect( &c, &Config::statusReady, &loop, &QEventLoop::quit );
QSignalSpy spy( &c, &Config::statusReady );
QTimer::singleShot( std::chrono::seconds(1), &loop, &QEventLoop::quit );
loop.exec();
// Check it didn't time out
QCOMPARE( spy.count(), 1 );
// Check YAML-loading results
QCOMPARE( smash( c.statusCode() ), status );
QCOMPARE( c.model()->rowCount(), count );
}
QTEST_GUILESS_MAIN( ItemTests ) QTEST_GUILESS_MAIN( ItemTests )

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/bad.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-empty.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-error.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-large.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-small.yaml

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl: []

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-bad.yaml

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-large.yaml
- file://$TESTDIR/data-small.yaml

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-small.yaml
- file://$TESTDIR/data-large.yaml
- file://$TESTDIR/data-bad.yaml

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-small.yaml
- file://$TESTDIR/data-large.yaml

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
bogus: true

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
derp
derp
herpa-derp: no
--
# This file is not valid YAML

@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
- name: "Default"
description: "Default group"
hidden: false
selected: true
critical: false
packages:
- base
- name: "Two"
description: "group 2"
hidden: false
selected: true
critical: false
packages:
- chakra-live-two
- name: "Three"
description: "group 3"
hidden: false
selected: true
critical: false
packages:
- chakra-live-three
- name: "Four"
description: "group 4"
hidden: false
selected: true
critical: false
packages:
- chakra-live-four
- name: "Five"
description: "group 5"
hidden: false
selected: true
critical: false
packages:
- chakra-live-five

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
- name: "Default"
description: "Default group"
hidden: false
selected: true
critical: false
packages:
- base
- name: "Second"
description: "Second group"
hidden: false
selected: true
critical: false
packages:
- chakra-live-skel

@ -45,6 +45,7 @@ calamares_add_plugin( packagechooser
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES SOURCES
Config.cpp
PackageChooserPage.cpp PackageChooserPage.cpp
PackageChooserViewStep.cpp PackageChooserViewStep.cpp
PackageModel.cpp PackageModel.cpp

@ -0,0 +1,224 @@
/* === 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"
#ifdef HAVE_XML
#include "ItemAppData.h"
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "packages/Globals.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
const NamedEnumTable< PackageChooserMethod >&
PackageChooserMethodNames()
{
static const NamedEnumTable< PackageChooserMethod > names {
{ "legacy", PackageChooserMethod::Legacy },
{ "custom", PackageChooserMethod::Legacy },
{ "contextualprocess", PackageChooserMethod::Legacy },
{ "packages", PackageChooserMethod::Packages },
};
return names;
}
Config::Config( QObject* parent )
: Calamares::ModuleSystem::Config( parent )
, m_model( new PackageListModel( this ) )
, m_mode( PackageChooserMode::Required )
{
}
Config::~Config() {}
const PackageItem&
Config::introductionPackage() const
{
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.isNonePackage() )
{
return package;
}
}
static PackageItem* defaultIntroduction = nullptr;
if ( !defaultIntroduction )
{
const auto name = QT_TR_NOOP( "Package Selection" );
const auto description
= QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." );
defaultIntroduction = new PackageItem( QString(), name, description );
defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
defaultIntroduction->name = CalamaresUtils::Locale::TranslatedString( name, metaObject()->className() );
defaultIntroduction->description
= CalamaresUtils::Locale::TranslatedString( description, metaObject()->className() );
}
return *defaultIntroduction;
}
void
Config::updateGlobalStorage( const QStringList& selected ) const
{
if ( m_method == PackageChooserMethod::Legacy )
{
QString value = selected.join( ',' );
Calamares::JobQueue::instance()->globalStorage()->insert( m_id, value );
cDebug() << m_id<< "selected" << value;
}
else if ( m_method == PackageChooserMethod::Packages )
{
QStringList packageNames = m_model->getInstallPackagesForNames( selected );
cDebug() << m_defaultId << "packages to install" << packageNames;
CalamaresUtils::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
static void
fillModel( PackageListModel* model, const QVariantList& items )
{
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
model->addPackage( PackageItem( item_map ) );
}
}
cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries.";
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
m_mode = packageChooserModeNames().find( CalamaresUtils::getString( configurationMap, "mode" ),
PackageChooserMode::Required );
m_method = PackageChooserMethodNames().find( CalamaresUtils::getString( configurationMap, "method" ),
PackageChooserMethod::Legacy );
if ( m_method == PackageChooserMethod::Legacy )
{
const QString configId = CalamaresUtils::getString( configurationMap, "id" );
const QString base = QStringLiteral( "packagechooser_" );
if ( configId.isEmpty() )
{
if ( m_defaultId.id().isEmpty() )
{
// We got nothing to work with
m_id = base;
}
else
{
m_id = base + m_defaultId.id();
}
cDebug() << "Using default ID" << m_id << "from" << m_defaultId.toString();
}
else
{
m_id = base + configId;
cDebug() << "Using configured ID" << m_id;
}
}
if ( configurationMap.contains( "items" ) )
{
fillModel( m_model, configurationMap.value( "items" ).toList() );
}
QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
if ( !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultModelIndex = item_idx;
break;
}
}
}
}

@ -0,0 +1,92 @@
/* === 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 PACKAGECHOOSER_CONFIG_H
#define PACKAGECHOOSER_CONFIG_H
#include "PackageModel.h"
#include "modulesystem/Config.h"
#include "modulesystem/InstanceKey.h"
#include <memory>
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& packageChooserModeNames();
enum class PackageChooserMethod
{
Legacy, // use contextualprocess or other custom
Packages, // use the packages module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();
class Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
public:
Config( QObject* parent = nullptr );
~Config() override;
/** @brief Sets the default Id for this Config
*
* The default Id is the (owning) module identifier for the config,
* and it is used when the Id read from the config file is empty.
* The **usual** configuration when using method *packages* is
* to rely on the default Id.
*/
void setDefaultId( const Calamares::ModuleSystem::InstanceKey& defaultId ) { m_defaultId = defaultId; }
void setConfigurationMap( const QVariantMap& ) override;
PackageChooserMode mode() const { return m_mode; }
PackageListModel* model() const { return m_model; }
QModelIndex defaultSelectionIndex() const { return m_defaultModelIndex; }
/** @brief Returns an "introductory package" which describes packagechooser
*
* If the model contains a "none" package, returns that one on
* the assumption that it is one to describe the whole; otherwise
* returns a totally generic description.
*/
const PackageItem& introductionPackage() const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking all
* (and only) the packages in @p selected as selected.
*/
void updateGlobalStorage( const QStringList& selected ) const;
/// As updateGlobalStorage() with an empty selection list
void updateGlobalStorage() const { updateGlobalStorage( QStringList() ); }
private:
PackageListModel* m_model = nullptr;
QModelIndex m_defaultModelIndex;
/// Selection mode for this module
PackageChooserMode m_mode = PackageChooserMode::Optional;
/// How this module stores to GS
PackageChooserMethod m_method = PackageChooserMethod::Legacy;
/// Id (used to identify settings from this module in GS)
QString m_id;
/// Value to use for id if none is set in the config file
Calamares::ModuleSystem::InstanceKey m_defaultId;
};
#endif

@ -10,6 +10,7 @@
#ifndef PACKAGECHOOSERPAGE_H #ifndef PACKAGECHOOSERPAGE_H
#define PACKAGECHOOSERPAGE_H #define PACKAGECHOOSERPAGE_H
#include "Config.h"
#include "PackageModel.h" #include "PackageModel.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>

@ -9,20 +9,22 @@
#include "PackageChooserViewStep.h" #include "PackageChooserViewStep.h"
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#ifdef HAVE_XML #ifdef HAVE_XML
#include "ItemAppData.h" #include "ItemAppData.h"
#endif #endif
#ifdef HAVE_APPSTREAM #ifdef HAVE_APPSTREAM
#include "ItemAppStream.h" #include "ItemAppStream.h"
#include <AppStreamQt/pool.h> #include <AppStreamQt/pool.h>
#include <memory> #include <memory>
#endif #endif
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "JobQueue.h" #include "JobQueue.h"
#include "locale/TranslatableConfiguration.h" #include "locale/TranslatableConfiguration.h"
#include "utils/CalamaresUtilsSystem.h" #include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h" #include "utils/Logger.h"
@ -35,9 +37,8 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlug
PackageChooserViewStep::PackageChooserViewStep( QObject* parent ) PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent ) : Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr ) , m_widget( nullptr )
, m_model( nullptr )
, m_mode( PackageChooserMode::Required )
, m_stepName( nullptr ) , m_stepName( nullptr )
{ {
emit nextStatusChanged( false ); emit nextStatusChanged( false );
@ -50,7 +51,6 @@ PackageChooserViewStep::~PackageChooserViewStep()
{ {
m_widget->deleteLater(); m_widget->deleteLater();
} }
delete m_model;
delete m_stepName; delete m_stepName;
} }
@ -67,20 +67,12 @@ PackageChooserViewStep::widget()
{ {
if ( !m_widget ) if ( !m_widget )
{ {
m_widget = new PackageChooserPage( m_mode, nullptr ); m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget, &PackageChooserPage::selectionChanged, [=]() { connect( m_widget, &PackageChooserPage::selectionChanged, [=]() {
emit nextStatusChanged( this->isNextEnabled() ); emit nextStatusChanged( this->isNextEnabled() );
} ); } );
if ( m_model )
{
hookupModel(); hookupModel();
} }
else
{
cWarning() << "PackageChooser Widget created before model.";
}
}
return m_widget; return m_widget;
} }
@ -88,18 +80,13 @@ PackageChooserViewStep::widget()
bool bool
PackageChooserViewStep::isNextEnabled() const PackageChooserViewStep::isNextEnabled() const
{ {
if ( !m_model )
{
return false;
}
if ( !m_widget ) if ( !m_widget )
{ {
// No way to have changed anything // No way to have changed anything
return true; return true;
} }
switch ( m_mode ) switch ( m_config->mode() )
{ {
case PackageChooserMode::Optional: case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple: case PackageChooserMode::OptionalMultiple:
@ -139,22 +126,14 @@ PackageChooserViewStep::onActivate()
{ {
if ( !m_widget->hasSelection() ) if ( !m_widget->hasSelection() )
{ {
m_widget->setSelection( m_defaultIdx ); m_widget->setSelection( m_config->defaultSelectionIndex() );
} }
} }
void void
PackageChooserViewStep::onLeave() PackageChooserViewStep::onLeave()
{ {
QString key = QStringLiteral( "packagechooser_%1" ).arg( m_id ); m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
QString value;
if ( m_widget->hasSelection() )
{
value = m_widget->selectedPackageIds().join( ',' );
}
Calamares::JobQueue::instance()->globalStorage()->insert( key, value );
cDebug() << "PackageChooser" << key << "selected" << value;
} }
Calamares::JobList Calamares::JobList
@ -167,23 +146,8 @@ PackageChooserViewStep::jobs() const
void void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap ) PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{ {
QString mode = CalamaresUtils::getString( configurationMap, "mode" ); m_config->setDefaultId( moduleInstanceKey() );
bool mode_ok = false; m_config->setConfigurationMap( configurationMap );
if ( !mode.isEmpty() )
{
m_mode = roleNames().find( mode, mode_ok );
}
if ( !mode_ok )
{
m_mode = PackageChooserMode::Required;
}
m_id = CalamaresUtils::getString( configurationMap, "id" );
if ( m_id.isEmpty() )
{
// Not set, so use the instance id
m_id = moduleInstanceKey().id();
}
bool labels_ok = false; bool labels_ok = false;
auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok ); auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
@ -195,117 +159,22 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
} }
} }
QString default_item_id = CalamaresUtils::getString( configurationMap, "default" ); if ( m_widget )
m_defaultIdx = QModelIndex();
bool first_time = !m_model;
if ( configurationMap.contains( "items" ) )
{
fillModel( configurationMap.value( "items" ).toList() );
}
if ( first_time && m_widget && m_model )
{ {
hookupModel(); hookupModel();
} }
// find default item
if ( first_time && m_model && !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultIdx = item_idx;
break;
}
}
}
} }
void
PackageChooserViewStep::fillModel( const QVariantList& items )
{
if ( !m_model )
{
m_model = new PackageListModel( nullptr );
}
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
m_model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
m_model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
m_model->addPackage( PackageItem( item_map ) );
}
}
}
void void
PackageChooserViewStep::hookupModel() PackageChooserViewStep::hookupModel()
{ {
if ( !m_model || !m_widget ) if ( !m_config->model() || !m_widget )
{ {
cError() << "Can't hook up model until widget and model both exist."; cError() << "Can't hook up model until widget and model both exist.";
return; return;
} }
m_widget->setModel( m_model ); m_widget->setModel( m_config->model() );
for ( int i = 0; i < m_model->packageCount(); ++i ) m_widget->setIntroduction( m_config->introductionPackage() );
{
const auto& package = m_model->packageData( i );
if ( package.id.isEmpty() )
{
m_widget->setIntroduction( package );
break;
}
}
} }

@ -15,12 +15,9 @@
#include "utils/PluginFactory.h" #include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h" #include "viewpages/ViewStep.h"
#include "PackageModel.h"
#include <QObject>
#include <QUrl>
#include <QVariantMap> #include <QVariantMap>
class Config;
class PackageChooserPage; class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
@ -49,17 +46,11 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override; void setConfigurationMap( const QVariantMap& configurationMap ) override;
private: private:
void fillModel( const QVariantList& items );
void hookupModel(); void hookupModel();
Config* m_config;
PackageChooserPage* m_widget; PackageChooserPage* m_widget;
PackageListModel* m_model;
// Configuration
PackageChooserMode m_mode;
QString m_id;
CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
QModelIndex m_defaultIdx;
}; };
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory ) CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )

@ -12,46 +12,20 @@
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/Variant.h" #include "utils/Variant.h"
const NamedEnumTable< PackageChooserMode >&
roleNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
PackageItem::PackageItem() {} PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id, PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
const QString& a_package,
const QString& a_name,
const QString& a_description )
: id( a_id ) : id( a_id )
, package( a_package )
, name( a_name ) , name( a_name )
, description( a_description ) , description( a_description )
{ {
} }
PackageItem::PackageItem( const QString& a_id, PackageItem::PackageItem( const QString& a_id,
const QString& a_package,
const QString& a_name, const QString& a_name,
const QString& a_description, const QString& a_description,
const QString& screenshotPath ) const QString& screenshotPath )
: id( a_id ) : id( a_id )
, package( a_package )
, name( a_name ) , name( a_name )
, description( a_description ) , description( a_description )
, screenshot( screenshotPath ) , screenshot( screenshotPath )
@ -60,10 +34,10 @@ PackageItem::PackageItem( const QString& a_id,
PackageItem::PackageItem::PackageItem( const QVariantMap& item_map ) PackageItem::PackageItem::PackageItem( const QVariantMap& item_map )
: id( CalamaresUtils::getString( item_map, "id" ) ) : id( CalamaresUtils::getString( item_map, "id" ) )
, package( CalamaresUtils::getString( item_map, "package" ) )
, name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) ) , name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) )
, description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) ) , description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) )
, screenshot( CalamaresUtils::getString( item_map, "screenshot" ) ) , screenshot( CalamaresUtils::getString( item_map, "screenshot" ) )
, packageNames( CalamaresUtils::getStringList( item_map, "packages" ) )
{ {
if ( name.isEmpty() && id.isEmpty() ) if ( name.isEmpty() && id.isEmpty() )
{ {
@ -105,6 +79,33 @@ PackageListModel::addPackage( PackageItem&& p )
} }
} }
QStringList
PackageListModel::getInstallPackagesForName( const QString& id ) const
{
for ( const auto& p : qAsConst( m_packages ) )
{
if ( p.id == id )
{
return p.packageNames;
}
}
return QStringList();
}
QStringList
PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const
{
QStringList l;
for ( const auto& p : qAsConst( m_packages ) )
{
if ( ids.contains( p.id ) )
{
l.append( p.packageNames );
}
}
return l;
}
int int
PackageListModel::rowCount( const QModelIndex& index ) const PackageListModel::rowCount( const QModelIndex& index ) const
{ {

@ -18,24 +18,14 @@
#include <QPixmap> #include <QPixmap>
#include <QVector> #include <QVector>
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& roleNames();
struct PackageItem struct PackageItem
{ {
QString id; QString id;
// FIXME: unused
QString package;
CalamaresUtils::Locale::TranslatedString name; CalamaresUtils::Locale::TranslatedString name;
CalamaresUtils::Locale::TranslatedString description; CalamaresUtils::Locale::TranslatedString description;
QPixmap screenshot; QPixmap screenshot;
QStringList packageNames;
/// @brief Create blank PackageItem /// @brief Create blank PackageItem
PackageItem(); PackageItem();
@ -44,7 +34,7 @@ struct PackageItem
* This constructor sets all the text members, * This constructor sets all the text members,
* but leaves the screenshot blank. Set that separately. * but leaves the screenshot blank. Set that separately.
*/ */
PackageItem( const QString& id, const QString& package, const QString& name, const QString& description ); PackageItem( const QString& id, const QString& name, const QString& description );
/** @brief Creates a PackageItem from given strings. /** @brief Creates a PackageItem from given strings.
* *
@ -52,17 +42,21 @@ struct PackageItem
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or * @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands. * a filesystem path, whatever QPixmap understands.
*/ */
PackageItem( const QString& id, PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath );
const QString& package,
const QString& name,
const QString& description,
const QString& screenshotPath );
/** @brief Creates a PackageItem from a QVariantMap /** @brief Creates a PackageItem from a QVariantMap
* *
* This is intended for use when loading PackageItems from a * This is intended for use when loading PackageItems from a
* configuration map. It will look up the various keys in the map * configuration map. It will look up the various keys in the map
* and handle translation strings as well. * and handle translation strings as well.
*
* The following keys are used:
* - *id*: the identifier for this item; if it is the empty string
* then this is the special "no-package".
* - *name* (and *name[lang]*): for the name and its translations
* - *description* (and *description[lang]*)
* - *screenshot*: a path to a screenshot for this package
* - *packages*: a list of package names
*/ */
PackageItem( const QVariantMap& map ); PackageItem( const QVariantMap& map );
@ -104,6 +98,19 @@ public:
/// @brief Direct (non-abstract) count of package data /// @brief Direct (non-abstract) count of package data
int packageCount() const { return m_packages.count(); } int packageCount() const { return m_packages.count(); }
/** @brief Does a name lookup (based on id) and returns the packages member
*
* If there is a package with the given @p id, returns its packages
* (e.g. the names of underlying packages to install for it); returns
* an empty list if the id is not found.
*/
QStringList getInstallPackagesForName( const QString& id ) const;
/** @brief Name-lookup all the @p ids and returns the packages members
*
* Concatenates installPackagesForName() for each id in @p ids.
*/
QStringList getInstallPackagesForNames( const QStringList& ids ) const;
enum Roles : int enum Roles : int
{ {
NameRole = Qt::DisplayRole, NameRole = Qt::DisplayRole,

@ -3,25 +3,46 @@
# #
# Configuration for the low-density software chooser # Configuration for the low-density software chooser
--- ---
# The packagechooser writes a GlobalStorage value for the choice that # Software selection mode, to set whether the software packages
# has been made. The key is *packagechooser_<id>*. If *id* is set here, # can be chosen singly, or multiply.
# it is substituted into the key name. If it is not set, the module's #
# Possible modes are "optional", "required" (for zero-or-one or exactly-one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Software installation method:
#
# - "legacy" or "custom" or "contextualprocess"
# When set to "legacy", writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. Normally, the module's
# instance name is used; see the *instances* section of `settings.conf`. # instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooser module, and no *id* is set, # If there is just one packagechooser module, and no special instance is set,
# resulting GS key is probably *packagechooser_packagechooser*. # resulting GS key is probably *packagechooser@packagechooser*.
# You can set *id* to change that, but it is not recommended.
# #
# The GS value is a comma-separated list of the IDs of the selected # The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected. # packages, or an empty string if none is selected.
# #
# With "legacy" installation, you should have a contextualprocess or similar
# module somewhere in the `exec` phase to process the GlobalStorage key
# and actually **do** something for the packages.
#
# - "packages"
# When set to "packages", writes GlobalStorage values suitable for
# consumption by the *packages* module (which should appear later
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
# The *id* key is not used.
#
# 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
# module, in the `exec` section to do the actual work.
method: legacy
# The *id* key is used only in "legacy" mode
# id: "" # id: ""
# Software selection mode, to set whether the software packages
# can be chosen singly, or multiply.
#
# Possible modes are "optional", "required" (for zero or one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Human-visible strings in this module. These are all optional. # Human-visible strings in this module. These are all optional.
# The following translated keys are used: # The following translated keys are used:
@ -49,27 +70,45 @@ labels:
# as a source for the data. # as a source for the data.
# #
# For data provided by the list: the item has an id, which is used in # For data provided by the list: the item has an id, which is used in
# setting the value of *packagechooser_<module-id>*. The following fields # setting the value of *packagechooser_<module-id>*. The following field
# are mandatory: # is mandatory:
# #
# - *id* : ID for the product. The ID "" is special, and is used for # - *id*
# ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows # "no package selected". Only include this if the mode allows
# selecting none. # selecting none. The name and description given for the "no package
# - *package* : Package name for the product. While mandatory, this is # selected" item are displayed when the module starts.
# not actually used anywhere. #
# - *name* : Human-readable name of the product. To provide translations, # Each item must adhere to one of three "styles" of item. Which styles
# add a *[lang]* decoration as part of the key name, # are supported depends on compile-time dependencies of Calamares.
# e.g. `name[nl]` for Dutch. # Both AppData and AppStream may **optionally** be available.
# The list of usable languages can be found in #
# # Generic Items #
#
# These items are always supported. They require the most configuration
# **in this file** and duplicate information that may be available elsewhere
# (e.g. in AppData or AppStream), but do not require any additional
# dependencies. These items have the following **mandatory** fields:
#
# - *name*
# Human-readable name of the product. To provide translations,
# add a *[lang]* decoration as part of the key name, e.g. `name[nl]`
# for Dutch. The list of usable languages can be found in
# `CMakeLists.txt` or as part of the debug output of Calamares. # `CMakeLists.txt` or as part of the debug output of Calamares.
# - *description* : Human-readable description. These can be translated # - *description*
# as well. # Human-readable description. These can be translated as well.
# - *screenshot* : Path to a single screenshot of the product. May be # - *screenshot*
# a filesystem path or a QRC path, # Path to a single screenshot of the product. May be a filesystem
# e.g. ":/images/no-selection.png". # path or a QRC path, e.g. ":/images/no-selection.png".
#
# The following field is **optional** for an item:
# #
# Use the empty string "" as ID / key for the "no selection" item if # - *packages* :
# you want to customize the display of that item as well. # 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).
#
# # AppData Items #
# #
# For data provided by AppData XML: the item has an *appdata* # For data provided by AppData XML: the item has an *appdata*
# key which points to an AppData XML file in the local filesystem. # key which points to an AppData XML file in the local filesystem.
@ -84,6 +123,8 @@ labels:
# **may** specify an ID or screenshot path, as above. This will override # **may** specify an ID or screenshot path, as above. This will override
# the settings from AppData. # the settings from AppData.
# #
# # AppStream Items #
#
# For data provided by AppStream cache: the item has an *appstream* # For data provided by AppStream cache: the item has an *appstream*
# key which matches the AppStream identifier in the cache (e.g. # key which matches the AppStream identifier in the cache (e.g.
# *org.kde.kwrite.desktop*). Data is retrieved from the AppStream # *org.kde.kwrite.desktop*). Data is retrieved from the AppStream
@ -93,19 +134,19 @@ labels:
# key which will override the data from AppStream. # key which will override the data from AppStream.
items: items:
- id: "" - id: ""
package: "" # packages: [] # This item installs no packages
name: "No Desktop" name: "No Desktop"
name[nl]: "Geen desktop" name[nl]: "Geen desktop"
description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later." description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later."
description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren." description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren."
screenshot: ":/images/no-selection.png" screenshot: ":/images/no-selection.png"
- id: kde - id: kde
package: kde packages: [ kde-frameworks, kde-plasma, kde-gear ]
name: Plasma Desktop name: Plasma Desktop
description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks." description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks."
screenshot: ":/images/kde.png" screenshot: ":/images/kde.png"
- id: gnome - id: gnome
package: gnome packages: [ gnome-all ]
name: GNOME name: GNOME
description: GNU Networked Object Modeling Environment Desktop description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png" screenshot: ":/images/gnome.png"

@ -256,6 +256,23 @@ class PMEntropy(PackageManager):
pass pass
class PMLuet(PackageManager):
backend = "luet"
def install(self, pkgs, from_local=False):
check_target_env_call(["luet", "install", "-y"] + pkgs)
def remove(self, pkgs):
check_target_env_call(["luet", "uninstall", "-y"] + pkgs)
def update_db(self):
# Luet checks for DB update everytime its ran.
pass
def update_system(self):
check_target_env_call(["luet", "upgrade", "-y"])
class PMPackageKit(PackageManager): class PMPackageKit(PackageManager):
backend = "packagekit" backend = "packagekit"

@ -1,13 +1,30 @@
# SPDX-FileCopyrightText: no # SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
# #
# The configuration for the package manager starts with the
# *backend* key, which picks one of the backends to use.
# In `main.py` there is a base class `PackageManager`.
# Implementations must subclass that and set a (class-level)
# property *backend* to the name of the backend (e.g. "dummy").
# That property is used to match against the *backend* key here.
#
# You will have to add such a class for your package manager.
# It is fairly simple Python code. The API is described in the
# abstract methods in class `PackageManager`. Mostly, the only
# trick is to figure out the correct commands to use, and in particular,
# whether additional switches are required or not. Some package managers
# have more installer-friendly defaults than others, e.g., DNF requires
# passing --disablerepo=* -C to allow removing packages without Internet
# connectivity, and it also returns an error exit code if the package did
# not exist to begin with.
--- ---
# #
# Which package manager to use, options are: # Which package manager to use, options are:
# - apk - Alpine Linux package manager # - apk - Alpine Linux package manager
# - apt - APT frontend for DEB and RPM # - apt - APT frontend for DEB and RPM
# - dnf - DNF, the new RPM frontend # - dnf - DNF, the new RPM frontend
# - entropy - Sabayon package manager # - entropy - Sabayon package manager (is being deprecated)
# - luet - Sabayon package manager (next-gen)
# - packagekit - PackageKit CLI tool # - packagekit - PackageKit CLI tool
# - pacman - Pacman # - pacman - Pacman
# - pamac - Manjaro package manager # - pamac - Manjaro package manager

@ -13,6 +13,7 @@ properties:
- apt - apt
- dnf - dnf
- entropy - entropy
- luet
- packagekit - packagekit
- pacman - pacman
- pamac - pamac

@ -264,7 +264,8 @@ FillGlobalStorageJob::prettyDescription() const
"<strong>%2</strong>%4." ) "<strong>%2</strong>%4." )
.arg( path ) .arg( path )
.arg( mountPoint ) .arg( mountPoint )
.arg( fsType ) ); .arg( fsType )
.arg( QString() ) );
} }
} }
} }

@ -27,6 +27,16 @@ def pretty_name():
return _("Configure Plymouth theme") return _("Configure Plymouth theme")
def detect_plymouth():
"""
Checks existence (runnability) of plymouth in the target system.
@return True if plymouth exists in the target, False otherwise
"""
# Used to only check existence of path /usr/bin/plymouth in target
return target_env_call(["sh", "-c", "which plymouth"]) == 0
class PlymouthController: class PlymouthController:
def __init__(self): def __init__(self):
@ -42,14 +52,8 @@ class PlymouthController:
plymouth_theme + '|', "-i", plymouth_theme + '|', "-i",
"/etc/plymouth/plymouthd.conf"]) "/etc/plymouth/plymouthd.conf"])
def detect(self):
isPlymouth = target_env_call(["sh", "-c", "which plymouth"])
debug("which plymouth exit code: {!s}".format(isPlymouth))
return isPlymouth
def run(self): def run(self):
if self.detect() == 0: if detect_plymouth():
if (("plymouth_theme" in libcalamares.job.configuration) and if (("plymouth_theme" in libcalamares.job.configuration) and
(libcalamares.job.configuration["plymouth_theme"] is not None)): (libcalamares.job.configuration["plymouth_theme"] is not None)):
self.setTheme() self.setTheme()

@ -803,6 +803,29 @@ addPasswordCheck( const QString& key, const QVariant& value, PasswordCheckList&
return true; return true;
} }
/** @brief Returns a value of either key from the map
*
* Takes a function (e.g. getBool, or getString) and two keys,
* returning the value in the map of the one that is there (or @p defaultArg)
*/
template < typename T, typename U >
T
either( T ( *f )( const QVariantMap&, const QString&, U ),
const QVariantMap& configurationMap,
const QString& oldKey,
const QString& newKey,
U defaultArg )
{
if ( configurationMap.contains( oldKey ) )
{
return f( configurationMap, oldKey, defaultArg );
}
else
{
return f( configurationMap, newKey, defaultArg );
}
}
void void
Config::setConfigurationMap( const QVariantMap& configurationMap ) Config::setConfigurationMap( const QVariantMap& configurationMap )
{ {
@ -814,13 +837,21 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
// Now it might be explicitly set to empty, which is ok // Now it might be explicitly set to empty, which is ok
setUserShell( shell ); setUserShell( shell );
setAutoLoginGroup( CalamaresUtils::getString( configurationMap, "autoLoginGroup" ) ); setAutoLoginGroup( either< QString, const QString& >(
CalamaresUtils::getString, configurationMap, "autologinGroup", "autoLoginGroup", QString() ) );
setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) ); setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) );
m_hostNameActions = getHostNameActions( configurationMap ); m_hostNameActions = getHostNameActions( configurationMap );
setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); setConfigurationDefaultGroups( configurationMap, m_defaultGroups );
m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutoLogin", false );
// Renaming of Autologin -> AutoLogin in 4ffa79d4cf also affected
// configuration keys, which was not intended. Accept both.
m_doAutoLogin = either( CalamaresUtils::getBool,
configurationMap,
QStringLiteral( "doAutologin" ),
QStringLiteral( "doAutoLogin" ),
false );
m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true ); m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true );
Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword ); Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword );

@ -44,6 +44,9 @@ private Q_SLOTS:
void testHostActions(); void testHostActions();
void testPasswordChecks(); void testPasswordChecks();
void testUserPassword(); void testUserPassword();
void testAutoLogin_data();
void testAutoLogin();
}; };
UserTests::UserTests() {} UserTests::UserTests() {}
@ -339,6 +342,43 @@ UserTests::testUserPassword()
} }
} }
void
UserTests::testAutoLogin_data()
{
QTest::addColumn< QString >( "filename" );
QTest::addColumn< bool >( "autoLoginIsSet" );
QTest::addColumn< QString >( "autoLoginGroupName" );
QTest::newRow( "old, old" ) << "tests/6a-issue-1672.conf" << true << "derp";
QTest::newRow( "old, new" ) << "tests/6b-issue-1672.conf" << true << "derp";
QTest::newRow( "new, old" ) << "tests/6c-issue-1672.conf" << true << "derp";
QTest::newRow( "new, new" ) << "tests/6d-issue-1672.conf" << true << "derp";
QTest::newRow( "default" ) << "tests/6e-issue-1672.conf" << false << QString();
}
void
UserTests::testAutoLogin()
{
QFETCH( QString, filename );
QFETCH( bool, autoLoginIsSet );
QFETCH( QString, autoLoginGroupName );
// BUILD_AS_TEST is the source-directory path
QFile fi( QString( "%1/%2" ).arg( BUILD_AS_TEST, filename ) );
QVERIFY( fi.exists() );
bool ok = false;
const auto map = CalamaresUtils::loadYaml( fi, &ok );
QVERIFY( ok );
QVERIFY( map.count() > 0 );
Config c;
c.setConfigurationMap( map );
QCOMPARE( c.doAutoLogin(), autoLoginIsSet );
QCOMPARE( c.autoLoginGroup(), autoLoginGroupName );
}
QTEST_GUILESS_MAIN( UserTests ) QTEST_GUILESS_MAIN( UserTests )

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autologinGroup: derp
doAutologin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autologinGroup: derp
doAutoLogin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autoLoginGroup: derp
doAutologin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autoLoginGroup: derp
doAutoLogin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
doautologin: true
autologingroup: wheel
Loading…
Cancel
Save