[merge] upstream

This commit is contained in:
Philip Müller 2021-04-30 15:27:56 +02:00
commit 90ee4cdb4c
33 changed files with 915 additions and 308 deletions

13
CHANGES
View File

@ -10,9 +10,11 @@ website will have to do for older versions.
# 3.2.40 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Anubhav Choudhary (SoK success!)
- Erik Dubois
- Jerrod Frost (new contributor! welcome!)
- Joe Kamprad
- Lisa Vitolo
- Lisa Vitolo (blast from the past!)
## Core ##
- The CMake modules for consumption by external modules (e.g. the
@ -23,11 +25,20 @@ This release contains contributions from (alphabetically by first name):
libcalamares to systematically mark filesystem (types) as "in use"
or not. This, in turn, means that modules can depend on that information
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 ##
- *displaymanager* example configuration has been shuffled around a bit,
for better results when the live image is running XFCE. Also lists
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*
module -- allowing to set filesystem labels during manual partitioning --
has been revived and merged.

View File

@ -220,13 +220,19 @@ slideshowAPI: 2
# These options are to customize online uploading of logs to pastebins:
# - type : Defines the kind of pastebin service to be used. Currently
# it accepts two values:
# - none : disables the pastebin functionality
# - fiche : use fiche pastebin server
# - url : Defines the address of pastebin service to be used.
# Takes string as input. Important bits are the host and port,
# the scheme is not used.
# - type : Defines the kind of pastebin service to be used. Currently
# it accepts two values:
# - none : disables the pastebin functionality
# - fiche : use fiche pastebin server
# - url : Defines the address of pastebin service to be used.
# Takes string as input. Important bits are the host and port,
# 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 :
type : "fiche"
url : "http://termbin.com:9999"
sizeLimit : -1

View File

@ -215,10 +215,49 @@ calamares_add_test(
${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(
libcalamareslocaletest
SOURCES
locale/Tests.cpp
${localetest_qrc}
)
calamares_add_test(

View File

@ -16,6 +16,7 @@
#include "CalamaresVersion.h"
#include "GlobalStorage.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QtTest/QtTest>
@ -33,6 +34,7 @@ private Q_SLOTS:
void testTranslatableLanguages();
void testTranslatableConfig1();
void testTranslatableConfig2();
void testTranslatableConfigContext();
void testLanguageScripts();
void testEsperanto();
@ -246,6 +248,32 @@ LocaleTests::testTranslatableConfig2()
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
LocaleTests::testRegions()
{

View File

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

View File

@ -50,11 +50,23 @@ public:
* metaObject()->className() as context (from a QObject based class)
* to give the TranslatedString the same context as other calls
* 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 );
/** @brief Not-actually-translated 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
TranslatedString()
: TranslatedString( QString() )

View File

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

View File

@ -28,6 +28,14 @@ bool setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QVariantList& installPackages,
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 );
} // namespace Packages
} // namespace CalamaresUtils

View File

@ -24,7 +24,15 @@ private Q_SLOTS:
void initTestCase();
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();
/// Test replacement and mixing string-list with variant calls
void testAddMixed();
};
void
@ -46,38 +54,179 @@ PackagesTests::testEmpty()
// Adding nothing at all does nothing
QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList(), QVariantList() ) );
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
PackagesTests::testAdd()
{
Calamares::GlobalStorage gs;
const QString extraEditor( "notepad++" );
const QString topKey( "packageOperations" );
Calamares::ModuleSystem::InstanceKey k( "this", "that" );
Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" );
QVERIFY( !gs.contains( topKey ) );
QVERIFY(
CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList { QString( "vim" ) }, 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 );
cDebug() << op;
QFETCH( QStringList, packages );
QVERIFY( !packages.contains( extraEditor ) );
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
op = action[ "install" ].toList();
QCOMPARE( op.length(), 2 );
QCOMPARE( action[ "source" ].toString(), k.toString() );
cDebug() << op;
{
QVERIFY( !gs.contains( topKey ) );
QVERIFY(
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 ) );
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( op[ 0 ], QString( "vim" ) );
cDebug() << op;
}
// Replace with two packages
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, 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(), 2 );
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;
}
// 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" ) ) );
}
}
}
}

View File

@ -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>

View File

@ -174,6 +174,22 @@ struct NamedEnumTable
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.
*
* If @p s is an enum value in the table, return the corresponding

View File

@ -113,7 +113,7 @@ BrandingLoader::tryLoad( QTranslator* translator )
}
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
return translator->load( m_prefix + "en" );
}

View File

@ -18,6 +18,7 @@
#include "utils/ImageRegistry.h"
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Units.h"
#include "utils/Yaml.h"
#include <QDir>
@ -153,15 +154,18 @@ uploadServerFromMap( const QVariantMap& map )
QString typestring = map[ "type" ].toString();
QString urlstring = map[ "url" ].toString();
qint64 sizeLimitKiB = map[ "sizeLimit" ].toLongLong();
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
return Branding::UploadServerInfo( names.find( typestring, bogus ),
QUrl( urlstring, QUrl::ParsingMode::StrictMode ) );
return Branding::UploadServerInfo(
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

View File

@ -223,10 +223,11 @@ public:
/** @brief Upload server configuration
*
* This is both the type (which may be none, in which case the URL
* is irrelevant and usually empty) and the URL for the upload.
* This object has 3 items : the type (which may be none, in which case the URL
* 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; }
/**

View File

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

View File

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

View File

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

View File

@ -392,7 +392,7 @@ ItemTests::testUrlFallback()
QVERIFY( map.count() > 0 );
c.setConfigurationMap( map );
}
catch ( YAML::Exception& e )
catch ( YAML::Exception& )
{
bool badYaml = true;
QVERIFY( !badYaml );

View File

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
derp
derp
herpa-derp: no

View File

@ -15,7 +15,7 @@ option( WITH_APPDATA "Support appdata: items in PackageChooser (requires QtXml)"
if ( WITH_APPDATA )
find_package(Qt5 COMPONENTS Xml)
if ( Qt5Xml_FOUND )
add_definitions( -DHAVE_XML )
add_definitions( -DHAVE_APPDATA )
list( APPEND _extra_libraries Qt5::Xml )
list( APPEND _extra_src ItemAppData.cpp )
endif()
@ -45,6 +45,7 @@ calamares_add_plugin( packagechooser
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
PackageChooserPage.cpp
PackageChooserViewStep.cpp
PackageModel.cpp

View File

@ -0,0 +1,231 @@
/* === 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_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM
#include "ItemAppStream.h"
#include <AppStreamQt/pool.h>
#include <memory>
#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;
}
}
}
}

View File

@ -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

View File

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

View File

@ -9,20 +9,12 @@
#include "PackageChooserViewStep.h"
#ifdef HAVE_XML
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM
#include "ItemAppStream.h"
#include <AppStreamQt/pool.h>
#include <memory>
#endif
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
@ -35,9 +27,8 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlug
PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
, m_model( nullptr )
, m_mode( PackageChooserMode::Required )
, m_stepName( nullptr )
{
emit nextStatusChanged( false );
@ -50,7 +41,6 @@ PackageChooserViewStep::~PackageChooserViewStep()
{
m_widget->deleteLater();
}
delete m_model;
delete m_stepName;
}
@ -67,19 +57,11 @@ PackageChooserViewStep::widget()
{
if ( !m_widget )
{
m_widget = new PackageChooserPage( m_mode, nullptr );
m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget, &PackageChooserPage::selectionChanged, [=]() {
emit nextStatusChanged( this->isNextEnabled() );
} );
if ( m_model )
{
hookupModel();
}
else
{
cWarning() << "PackageChooser Widget created before model.";
}
hookupModel();
}
return m_widget;
}
@ -88,18 +70,13 @@ PackageChooserViewStep::widget()
bool
PackageChooserViewStep::isNextEnabled() const
{
if ( !m_model )
{
return false;
}
if ( !m_widget )
{
// No way to have changed anything
return true;
}
switch ( m_mode )
switch ( m_config->mode() )
{
case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple:
@ -139,22 +116,14 @@ PackageChooserViewStep::onActivate()
{
if ( !m_widget->hasSelection() )
{
m_widget->setSelection( m_defaultIdx );
m_widget->setSelection( m_config->defaultSelectionIndex() );
}
}
void
PackageChooserViewStep::onLeave()
{
QString key = QStringLiteral( "packagechooser_%1" ).arg( m_id );
QString value;
if ( m_widget->hasSelection() )
{
value = m_widget->selectedPackageIds().join( ',' );
}
Calamares::JobQueue::instance()->globalStorage()->insert( key, value );
cDebug() << "PackageChooser" << key << "selected" << value;
m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
}
Calamares::JobList
@ -167,23 +136,8 @@ PackageChooserViewStep::jobs() const
void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
QString mode = CalamaresUtils::getString( configurationMap, "mode" );
bool mode_ok = false;
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();
}
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
bool labels_ok = false;
auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
@ -195,117 +149,22 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
}
}
QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
m_defaultIdx = QModelIndex();
bool first_time = !m_model;
if ( configurationMap.contains( "items" ) )
{
fillModel( configurationMap.value( "items" ).toList() );
}
if ( first_time && m_widget && m_model )
if ( m_widget )
{
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
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.";
return;
}
m_widget->setModel( m_model );
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.id.isEmpty() )
{
m_widget->setIntroduction( package );
break;
}
}
m_widget->setModel( m_config->model() );
m_widget->setIntroduction( m_config->introductionPackage() );
}

View File

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

View File

@ -12,46 +12,20 @@
#include "utils/Logger.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( const QString& a_id,
const QString& a_package,
const QString& a_name,
const QString& a_description )
PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
: id( a_id )
, package( a_package )
, name( a_name )
, description( a_description )
{
}
PackageItem::PackageItem( const QString& a_id,
const QString& a_package,
const QString& a_name,
const QString& a_description,
const QString& screenshotPath )
: id( a_id )
, package( a_package )
, name( a_name )
, description( a_description )
, screenshot( screenshotPath )
@ -60,10 +34,10 @@ PackageItem::PackageItem( const QString& a_id,
PackageItem::PackageItem::PackageItem( const QVariantMap& item_map )
: id( CalamaresUtils::getString( item_map, "id" ) )
, package( CalamaresUtils::getString( item_map, "package" ) )
, name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) )
, description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) )
, screenshot( CalamaresUtils::getString( item_map, "screenshot" ) )
, packageNames( CalamaresUtils::getStringList( item_map, "packages" ) )
{
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
PackageListModel::rowCount( const QModelIndex& index ) const
{

View File

@ -18,24 +18,14 @@
#include <QPixmap>
#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
{
QString id;
// FIXME: unused
QString package;
CalamaresUtils::Locale::TranslatedString name;
CalamaresUtils::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
/// @brief Create blank PackageItem
PackageItem();
@ -44,7 +34,7 @@ struct PackageItem
* This constructor sets all the text members,
* 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.
*
@ -52,17 +42,21 @@ struct PackageItem
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands.
*/
PackageItem( const QString& id,
const QString& package,
const QString& name,
const QString& description,
const QString& screenshotPath );
PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath );
/** @brief Creates a PackageItem from a QVariantMap
*
* This is intended for use when loading PackageItems from a
* configuration map. It will look up the various keys in the map
* 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 );
@ -104,6 +98,19 @@ public:
/// @brief Direct (non-abstract) count of package data
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
{
NameRole = Qt::DisplayRole,

View File

@ -9,7 +9,7 @@
#include "Tests.h"
#ifdef HAVE_XML
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM

View File

@ -3,26 +3,47 @@
#
# Configuration for the low-density software chooser
---
# The packagechooser writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. If *id* is set here,
# it is substituted into the key name. If it is not set, the module's
# instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooser module, and no *id* is set,
# resulting GS key is probably *packagechooser_packagechooser*.
#
# The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected.
#
# 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)
# 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`.
# If there is just one packagechooser module, and no special instance is set,
# 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
# 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: ""
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
# - *step*, used in the overall progress view (left-hand pane)
@ -49,27 +70,45 @@ labels:
# as a source for the data.
#
# 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
# are mandatory:
# setting the value of *packagechooser_<module-id>*. The following field
# is mandatory:
#
# - *id* : ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows
# selecting none.
# - *package* : Package name for the product. While mandatory, this is
# not actually used anywhere.
# - *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.
# - *description* : Human-readable description. These can be translated
# as well.
# - *screenshot* : Path to a single screenshot of the product. May be
# a filesystem path or a QRC path,
# e.g. ":/images/no-selection.png".
# - *id*
# ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows
# selecting none. The name and description given for the "no package
# selected" item are displayed when the module starts.
#
# Use the empty string "" as ID / key for the "no selection" item if
# you want to customize the display of that item as well.
# Each item must adhere to one of three "styles" of item. Which styles
# are supported depends on compile-time dependencies of Calamares.
# Both AppData and AppStream may **optionally** be available.
#
# # 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.
# - *description*
# Human-readable description. These can be translated as well.
# - *screenshot*
# Path to a single screenshot of the product. May be a filesystem
# path or a QRC path, e.g. ":/images/no-selection.png".
#
# The following field is **optional** for an item:
#
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*
# 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
# the settings from AppData.
#
# # AppStream Items #
#
# For data provided by AppStream cache: the item has an *appstream*
# key which matches the AppStream identifier in the cache (e.g.
# *org.kde.kwrite.desktop*). Data is retrieved from the AppStream

View File

@ -256,6 +256,23 @@ class PMEntropy(PackageManager):
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):
backend = "packagekit"

View File

@ -1,13 +1,30 @@
# SPDX-FileCopyrightText: no
# 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:
# - apk - Alpine Linux package manager
# - apt - APT frontend for DEB and RPM
# - 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
# - pacman - Pacman
# - pamac - Manjaro package manager

View File

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

View File

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