59 Commits

Author SHA1 Message Date
Adriaan de Groot
81b4f01fe8 Changes: wrangle release
Pretend there was never a 3.11 (there was no tag, anyway) and
go straight to 3.12, while repairing the CMake code that
imports CalamaresConfig.
2024-11-21 22:58:51 +01:00
Adriaan de Groot
cfdf15f9bd CI: use same build-dir pattern as Calamares 2024-11-21 22:58:34 +01:00
Adriaan de Groot
36f99b1291 unpackfsc: module has moved to main Calamares repository 2024-11-21 22:56:30 +01:00
Adriaan de Groot
e4f38b235e Changes: pre-release housekeeping 2024-11-05 15:45:19 +01:00
Adriaan de Groot
a50195e372 Merge pull request #28 from nintyfan/FlatpakPuller
Flatpak puller
2024-11-05 15:42:54 +01:00
Adriaan de Groot
86e24fad9a CMake: bump version and required release 2024-11-05 15:18:24 +01:00
Adriaan de Groot
0186985726 [unpackfsc] Copy 'condition' idea from unpackfs 2024-11-05 15:16:04 +01:00
Adriaan de Groot
7bec3f6107 CI: update formatting to match Calamares 2024-11-05 15:12:47 +01:00
nintyfan
657207374e Merge branch 'calamares:calamares' into FlatpakPuller 2024-11-01 17:55:37 +01:00
Sławomir Lach
9661b70c57 (Allow to work with newer version) 2024-06-14 19:18:48 +02:00
Sławomir Lach
aa382db619 Make filenames more consistend 2024-06-05 19:03:10 +02:00
Sławomir Lach
7ab294176a Repair coding-style (apply clang-format) 2024-06-05 19:02:06 +02:00
Sławomir Lach
730b9fa983 Made possible to select flatpaks to install (fill list of netinstall) 2024-06-05 19:00:59 +02:00
Adriaan de Groot
2abe92a0f4 CMake: bump version and requirements
Builds cleanly with Calamares 3.3.1
2024-06-05 19:00:59 +02:00
Adriaan de Groot
0fe1341c25 CMake: find Calamares first, then other bits 2024-06-05 19:00:57 +02:00
Adriaan de Groot
c51d68828a CHANGES: post-release housekeeping 2024-06-05 18:59:56 +02:00
Adriaan de Groot
a61c0c6890 CMake: be more verbose when looking for Calamares 2024-06-05 18:59:56 +02:00
Adriaan de Groot
47cda51f8f CHANGES: post-release housekeeping 2024-01-15 23:45:28 +01:00
Adriaan de Groot
18e68245c1 CMake: be more verbose when looking for Calamares 2024-01-15 21:39:01 +01:00
Adriaan de Groot
15a2369a98 CMake: bump version and requirements
Builds cleanly with Calamares 3.3.1
2024-01-13 15:46:55 +01:00
Adriaan de Groot
0b6b923480 CMake: find Calamares first, then other bits 2024-01-13 15:46:55 +01:00
undef
1b594b7125 Port modules to Calamares 3.3.0
Calamares 3.3.0 changed the import name and namespace of
CalamaresUtilsSystem and CalamaresUtilsGui.
2024-01-13 15:46:55 +01:00
undef
f1ce9235ac CMakeLists: Copy KF CoreAddons and FeatureSummary imports from Calamares
Without these the package no-longer compiles with recent versions of
cmake.
2024-01-13 15:46:55 +01:00
Adriaan de Groot
51e89e58dd CMake: bump version and requirements
Builds cleanly with Calamares 3.3.1
2024-01-12 23:15:55 +01:00
Adriaan de Groot
d8d5a1fb26 CMake: find Calamares first, then other bits 2024-01-09 23:10:38 +01:00
Adriaan de Groot
85f05e8121 Merge pull request #31 from Undef-a/wip/undef/fix-ftbfs
Fix failure to configure/build with newer CMake and Calamares 3.3.0
2024-01-09 23:06:45 +01:00
undef
c28c9b8114 Port modules to Calamares 3.3.0
Calamares 3.3.0 changed the import name and namespace of
CalamaresUtilsSystem and CalamaresUtilsGui.
2023-12-17 03:40:44 +00:00
undef
935f21ace5 CMakeLists: Copy KF CoreAddons and FeatureSummary imports from Calamares
Without these the package no-longer compiles with recent versions of
cmake.
2023-12-17 03:40:40 +00:00
nintyfan
14301dbc0b Update ItemFlatpak.cpp 2023-12-09 19:59:34 +01:00
nintyfan
a26a7b2e3a Merge branch 'calamares:calamares' into FlatpakPuller 2023-11-05 08:50:27 +01:00
Sławomir Lach
d594db2308 Remove global variable packages 2023-10-29 09:28:00 +01:00
Sławomir Lach
f29e3aaf4f Remove global variable installed list 2023-10-24 16:14:58 +02:00
Sławomir Lach
aeec465e63 Initialize installed list on startup of module 2023-10-22 16:56:40 +02:00
Sławomir Lach
a7dcf46f95 Repair data members' names 2023-10-22 16:49:10 +02:00
Sławomir Lach
575c000d5d Make code more functional by eliminating loop 2023-10-11 14:56:06 +02:00
Sławomir Lach
7a6e6c63b7 Change implementation of preparing installed package list routine 2023-10-11 14:55:39 +02:00
Sławomir Lach
03e0d3e4df Avoid conversion to std::string 2023-10-11 14:03:36 +02:00
Sławomir Lach
f06744bacf Use Camel-Case 2023-10-11 14:00:15 +02:00
Sławomir Lach
36f2589067 Allow to better optimise getters 2023-10-11 13:57:29 +02:00
Sławomir Lach
6f6bc4de5e Remove unnecessary void 2023-10-11 13:48:13 +02:00
Sławomir Lach
86db905e1e Remove redundant this 2023-10-11 13:46:47 +02:00
Sławomir Lach
ebb7511f5e Cleanup includes in ItemFlatpak 2023-10-11 13:39:44 +02:00
Sławomir Lach
0dc15edb8f Make filenames more consistend 2023-10-11 13:35:05 +02:00
Sławomir Lach
bc0742be43 Add properl licensing info 2023-10-11 13:27:53 +02:00
Sławomir Lach
4412694cac Repair code style 2023-09-11 20:06:18 +02:00
Sławomir Lach
8f3f6090c3 Repair coding-style (apply clang-format) 2023-09-08 17:35:38 +02:00
Sławomir Lach
3d05a0d66e Repair code style 2023-09-07 17:24:55 +02:00
Sławomir Lach
7c6e73cedb Remove unused variables 2023-09-07 17:24:33 +02:00
Sławomir Lach
ea01b275d3 Remove unnecessary check 2023-09-07 17:22:54 +02:00
Sławomir Lach
ab5623018a User do not see duplicate names of packages 2023-09-07 17:21:43 +02:00
Sławomir Lach
ecee3d746c User see if flatpak was installed (and cannot be removed) 2023-09-06 19:04:28 +02:00
Sławomir Lach
c7d989dd36 Use CalamaresUtils to create process 2023-09-06 19:04:07 +02:00
Sławomir Lach
fe9c71d4fc Disallow to uncheck installed flatpaks 2023-09-06 17:12:15 +02:00
Sławomir Lach
59a4ea58e9 Correctly set items as markable in netinstall 2023-09-06 17:11:19 +02:00
Sławomir Lach
9b12dd711d Flatpaks will be correctly marked as installed or not 2023-09-06 17:10:59 +02:00
Sławomir Lach
6e4c2e728c Skipping wrong package names (blank lines) 2023-09-05 16:32:37 +02:00
Sławomir Lach
98b72d79ea Speedup operations 2023-09-05 16:29:52 +02:00
Sławomir Lach
c5377b75ac - User must select flatpak to install it 2023-09-04 21:04:29 +02:00
Sławomir Lach
c5bae4b0cc Made possible to select flatpaks to install (fill list of netinstall) 2023-09-04 21:02:42 +02:00
57 changed files with 414 additions and 4343 deletions

View File

@@ -22,14 +22,16 @@ Cpp11BracedListStyle: "false"
FixNamespaceComments: "true"
IncludeBlocks: Preserve
IndentWidth: "4"
InsertBraces: "true"
MaxEmptyLinesToKeep: "2"
NamespaceIndentation: None
PointerAlignment: Left
ReflowComments: "false"
SortIncludes: "true"
SpaceAfterCStyleCast: "false"
SpaceInEmptyBlock: "false"
SpacesBeforeTrailingComments: "2"
SpacesInAngles: "true"
SpacesInParentheses: "true"
SpacesInSquareBrackets: "true"
Standard: Cpp11
Standard: c++17

24
CHANGES
View File

@@ -6,6 +6,30 @@ This is the changelog for Calamares-Extensions. For each release, the major
changes and contributors are listed. Note that Calamares-Extensions does not
have a historical changelog -- this log starts with version 1.0.0.
# 3.3.12 (2024-11-21)
This is a release to match Calamares 3.3.12. The *unpackfsc* module has
moved to Calamares proper. There is a new module *flatpakinfo* that
tries to populate a netinstall key with packages available as flatpak.
This release contains contributions from (alphabetically by first name):
- Adriaan de Groot
- Sławomir Lach
# 3.3.1 (2024-01-15)
This is the first *calamares-extensions* release to go with a Calamares 3.3
release. The extensions now require Calamares 3.3. There is a branch for
3.2 legacy support but no releases are planned for it. The main reason
for this release is to have a 3.3-compatible release of *-extensions* at all.
This release contains contributions from (alphabetically by first name):
- Adriaan de Groot
- Anke Boersma
- undef
# 1.3.2 (2023-08-28)
We skipped a couple of releases in the release-notes, then tagged

View File

@@ -32,6 +32,9 @@
# In this repository, there is just one "group" to which USE_* applies:
# USE_os : operating-system-specific modules.
#
# There is a knob WITH_QT6 which can be used to build against Qt6 rather
# than Qt5. This must match what Calamares itself is built with.
#
### NOTES
#
# Call this CMake file in script mode, e.g. `cmake -P CMakeLists.txt`
@@ -40,7 +43,7 @@
#
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set( CALAMARES_EXTENSIONS_VERSION 1.4.0 )
set( CALAMARES_EXTENSIONS_VERSION 3.3.12 )
include( ${CMAKE_CURRENT_LIST_DIR}/CMakeModules/ExtendedVersion.cmake )
if ( CMAKE_SCRIPT_MODE_FILE )
@@ -60,16 +63,44 @@ set( CMAKE_CXX_STANDARD_REQUIRED ON )
# consumers by loading the developer's config from a build
# directory (which doesn't have the rest of the config
# installed inside it).
set( CALAMARES_VERSION_REQUIRED 3.3.0 )
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} NO_CMAKE_PACKAGE_REGISTRY)
set( CALAMARES_VERSION_REQUIRED 3.3.12 )
message(STATUS "Looking for Calamares system-wide")
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} CONFIG NO_CMAKE_PACKAGE_REGISTRY)
if (NOT TARGET Calamares::calamares OR NOT TARGET Calamares::calamaresui)
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} REQUIRED)
message(STATUS "Looking for Calamares in the package registry")
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} CONFIG REQUIRED)
endif()
message(STATUS "Found Calamares version ${Calamares_VERSION}")
message(STATUS " libraries ${Calamares_LIB_DIRS}")
message(STATUS "")
### EXTRACTING DEPENDENCIES AND CONFIGURATION FROM CALAMARES
#
#
if(Calamares_WITH_QT6)
set(kfname "KF6")
set(KF_VERSION 5.240) # KDE Neon weirdness
else()
message(STATUS "Building Calamares-extensions with Qt5")
set(kfname "KF5")
set(KF_VERSION 5.78)
# API that was deprecated before Qt 5.15 causes a compile error
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050f00)
endif()
include( FeatureSummary )
find_package(${kfname}CoreAddons ${KF_VERSION} QUIET)
set_package_properties(
${kfname}CoreAddons
PROPERTIES
TYPE REQUIRED
DESCRIPTION "KDE Framework CoreAddons"
URL "https://api.kde.org/frameworks/"
PURPOSE "Essential Framework for AboutData and Macros"
)
### CMAKE SETUP
#
# Enable IN_LIST
@@ -121,7 +152,9 @@ calamares_add_module_subdirectory( modules/os-freebsd LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/os-nixos LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/refind LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/slowpython LIST_SKIPPED_MODULES ) # Python job
calamares_add_module_subdirectory( modules/unpackfsc LIST_SKIPPED_MODULES )
# The unpackfsc module moved into Calamares proper in 3.3.11
#
# calamares_add_module_subdirectory( modules/unpackfsc LIST_SKIPPED_MODULES )
message(STATUS "Calamares extensions ${CALAMARES_EXTENSIONS_VERSION} for Calamares version ${Calamares_VERSION}")

View File

@@ -164,10 +164,6 @@ phase).
### Example Modules
- [filekeeper](modules/filekeeper/CMakeLists.txt) is a C++ **job** module
to copy files from the host (live) system to the target system at
the end of installation, like logfiles. (This module is made obsolete
by the *preservefiles* module included with Calamares proper)
- [freebsddisk](modules/freebsddisk/CMakeLists.txt) is a C++ **view**
module with a QML-based UI. It has no actual functionality, and serves
as a test that view modules can be built out-of-tree.

View File

@@ -93,7 +93,7 @@ fi
### Setup
#
#
BUILDDIR=$(mktemp -d -p . -t build.XXXXX)
BUILDDIR=$(mktemp -d -p . -t cala-tmp-XXXXX)
### Build with default compiler
#

View File

@@ -4,12 +4,14 @@
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
# Calls astyle with settings matching Calamares coding style
# Requires astyle >= 2.04 and clang-format-8 or later
# Apply Calamares-style formatting to sources. Requires clang-format-15.
#
# You can pass in directory names, in which case the files
# in that directory (NOT below it) are processed.
#
# If the environment variable CLANG_FORMAT is set to a (full path) and
# that path is executable, it will be used if possible.
#
LANG=C
LC_ALL=C
LC_NUMERIC=C
@@ -19,12 +21,19 @@ BASEDIR=$(dirname $0)
TOPDIR=$( cd $BASEDIR/.. && pwd -P )
test -d "$BASEDIR" || { echo "! Could not determine base for $0" ; exit 1 ; }
test -d "$TOPDIR" || { echo "! Cound not determine top-level source dir" ; exit 1 ; }
test -f "$TOPDIR/.clang-format.base" || { echo "! No .clang-format support files in $TOPDIR" ; exit 1 ; }
test -f "$TOPDIR/.clang-format" || { echo "! No .clang-format support files in $TOPDIR" ; exit 1 ; }
AS=$( which astyle )
# Allow specifying CF_VERSIONS outside already
CF_VERSIONS="$CF_VERSIONS clang-format-8 clang-format80 clang-format90 clang-format-9.0.1 clang-format"
# Start with CLANG_FORMAT, if it is specified
CF_VERSIONS=""
if test -n "$CLANG_FORMAT" && test -x "$CLANG_FORMAT" ; then
CF_VERSIONS="$CLANG_FORMAT"
fi
# And a bunch of other potential known versions of clang-format, newest first
CF_VERSIONS="$CF_VERSIONS clang-format-17"
CF_VERSIONS="$CF_VERSIONS clang-format-16 clang-format-16.0.6 "
CF_VERSIONS="$CF_VERSIONS clang-format15 clang-format-15 "
# Generic name of clang-format
CF_VERSIONS="$CF_VERSIONS clang-format"
for _cf in $CF_VERSIONS
do
# Not an error if this particular clang-format isn't found
@@ -32,39 +41,33 @@ do
test -n "$CF" && break
done
test -n "$AS" || { echo "! No astyle found in PATH"; exit 1 ; }
test -n "$CF" || { echo "! No clang-format ($CF_VERSIONS) found in PATH"; exit 1 ; }
test -x "$AS" || { echo "! $AS is not executable."; exit 1 ; }
test -x "$CF" || { echo "! $CF is not executable."; exit 1 ; }
### CLANG-FORMAT-WRANGLING
#
# Version 7 and earlier doesn't understand all the options we would like
# Version 8 is ok
# Version 9 is ok
# Later versions change some defaults so need extra wrangling.
# .. there are extra files that are appended to the settings, per
# .. clang-format version.
# Version 7 and earlier doesn't understand all the options we would like.
# Version 12 handled lambdas nicely and was the norm for Calamares 3.2.
# Version 13 was also ok.
# Version 14 behaves differently with short-functions-in-class,
# spreading functions out that 13 keeps on one line. To avoid
# ping-pong commits, forbid 14.
# Version 15 is available on recent-ish Ubuntus and FreeBSD, pick it.
# It also supports inserting braces, which is the one thing we kept
# astyle around for.
# Version 16 is available on openSUSE and is ok as well.
# Version 17 is available on FreeBSD and KaOS and is ok as well.
format_version=`"$CF" --version | tr -dc '[^.0-9]' | cut -d . -f 1`
case "$format_version" in
[0-7] )
echo "! Clang-format version 8+ required"
exit 1
;;
[89] )
15|16|17 )
:
;;
* )
echo "! Clang-format version '$format_version' unsupported."
echo "! Clang-format version '$format_version' unsupported, versions 15-17 are ok."
exit 1
;;
esac
_fmt="$TOPDIR/.clang-format"
cp "$_fmt.base" "$_fmt"
for f in "$extra_settings" ; do
test -f "$_fmt.$f" && cat "$_fmt.$f" >> "$_fmt"
done
### FILE PROCESSING
@@ -81,7 +84,6 @@ done
style_some()
{
if test -n "$*" ; then
$AS --options=$BASEDIR/astylerc --quiet "$@"
$CF -i -style=file "$@"
fi
}
@@ -98,8 +100,3 @@ if test "x$any_dirs" = "xyes" ; then
else
style_some "$@"
fi
### CLANG-FORMAT-WRANGLING
#
# Restore the original .clang-format
cp "$_fmt.base" "$_fmt"

View File

@@ -0,0 +1,17 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
calamares_add_plugin(flatpakInfo
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
FlatpakInfoJob.h
ItemFlatpak.h
PackagePool.h
FlatpakInfoJob.cpp
ItemFlatpak.cpp
PackagePool.cpp
SHARED_LIB
)

View File

@@ -0,0 +1,63 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "FlatpakInfoJob.h"
#include "utils/Runner.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include <QProcess>
#include <unistd.h>
#include "ItemFlatpak.h"
#include "PackagePool.h"
FlatpakInfoJob::FlatpakInfoJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
FlatpakInfoJob::~FlatpakInfoJob()
{
ItemFlatpak_freeMem();
}
QString
FlatpakInfoJob::prettyName() const
{
return tr( "Fill netinstall with flatpak packages" );
}
Calamares::JobResult
FlatpakInfoJob::exec()
{
QVariantList partitions;
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
downloadPackagesInfo();
serializePackagesInfo();
return Calamares::JobResult::ok();
}
void
FlatpakInfoJob::setConfigurationMap( const QVariantMap& map )
{
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( FlatpakInfoJobFactory, registerPlugin< FlatpakInfoJob >(); )

View File

@@ -0,0 +1,43 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef FLATPAKINFOJOB_H
#define FLATPAKINFOJOB_H
#include <QObject>
#include <QStringList>
#include <QVariantMap>
#include "CppJob.h"
#include "utils/PluginFactory.h"
#include "DllMacro.h"
/** @brief Create zpools and zfs datasets
*
*/
class PLUGINDLLEXPORT FlatpakInfoJob : public Calamares::CppJob
{
Q_OBJECT
public:
explicit FlatpakInfoJob( QObject* parent = nullptr );
~FlatpakInfoJob() override;
QString prettyName() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( FlatpakInfoJobFactory )
#endif // ZFSJOB_H

View File

@@ -0,0 +1,66 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/* Qt */
#include <QVariantMap>
/* CPP */
#include <fstream>
#include <iostream>
/* Calamares */
#include "utils/Runner.h"
/* Module */
#include "ItemFlatpak.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
PackageItem
fromFlatpak( const QVariantMap& itemMap, InstalledList &installed )
{
// check if it is installed
PackageItem item( Calamares::getString( itemMap, "appstream" ) );
item.setInstalled( false );
item.setInstalled( installed.contains( Calamares::getString( itemMap, "appstream" ) ) );
return item;
}
InstalledList::InstalledList()
{
long long int prev_pos;
long long int pos = 0;
QString line;
auto process = Calamares::System::instance()->targetEnvCommand(
QStringList { QString::fromLatin1( "flatpak" ),
QString::fromLatin1( "list" ),
QString::fromLatin1( "--app" ),
QString::fromLatin1( "--columns=application" ) } );
auto outputStr = process.second;
do {
prev_pos = pos;
pos = outputStr.indexOf('\n', prev_pos);
QString line = outputStr.mid(prev_pos, pos);
installed.append(line);
/* Increase by 1 to not stuck on newline */
++pos;
/* QString::indexOf returns -1 since no occurences. 0 = -1 + 1.*/
} while (0 != pos);
}
InstalledList::~InstalledList()
{
installed.clear();
}

View File

View File

@@ -0,0 +1,110 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*/
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <QString>
#include <QDesktopServices>
#include <QVariantMap>
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "ItemFlatpak.h"
#include "PackagePool.h"
#include "utils/System.h"
void PackagePool::downloadPackagesInfo(InstalledList &list)
{
QHash<QString,bool> addedPackages;
QString line;
auto process = Calamares::System::instance()->targetEnvCommand( QStringList { QString::fromStdString( "flatpak" ), QString::fromStdString( "remotes" ), QString::fromStdString( "--columns=name" ) });
auto outputStr = process.second;
QTextStream output(&outputStr);
while (output.readLineInto(&line))
{
QString line2;
auto process2 = Calamares::System::instance()->targetEnvCommand(
QStringList { QString::fromStdString( "flatpak" ),
QString::fromStdString( "remote-ls" ),
QString::fromStdString( "--app" ),
QString::fromStdString( "--columns=application" ),
line } );
auto output2Str = process2.second;
QTextStream output2( &output2Str );
while ( output2.readLineInto( &line2 ) )
{
if ( line2 == "" )
{
continue;
}
QVariantMap itemMap;
if ( addedPackages.contains( line2 ) )
{
continue;
}
addedPackages.insert( line2, true );
itemMap.insert( "appstream", QVariant( line2 ) );
itemMap.insert( "id", QVariant( line2 ) );
PackageItem item = fromFlatpak( itemMap, list );
packages.append( item );
}
}
serializePackagesInfo();
}
void PackagePool::serializePackagesInfo()
{
QList<QVariant> changedValue;
auto* gs = Calamares::JobQueue::instance()->globalStorage();
// If an earlier packagechooser instance added this data to global storage, combine them
if ( gs->contains( "netinstallAdd" ) )
{
auto selectedOrig = gs->value( "netinstallAdd" );
changedValue = selectedOrig.toList();
for (auto current: packages)
{
QStringList selfInstall;
QVariantMap newValue;
newValue.insert("name", current.getAppStreamId());
if (current.getInstalled())
{
newValue.insert("selected", true);
newValue.insert("immutable", true);
newValue.insert("description", "[Already installed; cannot be uninstalled]");
}
else
{
newValue.insert("selected", false);
}
selfInstall.append(current.getAppStreamId());
newValue.insert("packages", selfInstall);
changedValue.append(newValue);
}
gs->remove( "netinstallAdd" );
}
gs->insert( "netinstallAdd", changedValue );
}

View File

View File

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The flatpakinfo module will collect package list from configured flatpak repositories
#
#
#
---

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "flatpakinfo"
interface: "qtplugin"
load: "libcalamares_job_flatpakInfo.so"

View File

@@ -18,7 +18,7 @@ Config::Config( QObject* parent )
void
Config::setConfigurationMap( const QVariantMap& cfgMap )
{
using namespace CalamaresUtils;
using namespace Calamares;
if ( getBool( cfgMap, "bogus", false ) )
{

View File

@@ -5,7 +5,7 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/System.h"
#include "utils/Logger.h"
#include <QDir>
@@ -75,7 +75,7 @@ Calamares::JobResult
PartitionJob::exec()
{
using namespace Calamares;
using namespace CalamaresUtils;
using namespace Calamares;
using namespace std;
const QString pathMount = "/mnt/install";

View File

@@ -5,7 +5,7 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/System.h"
#include "utils/Logger.h"
#include <QDir>
@@ -47,7 +47,7 @@ Calamares::JobResult
UsersJob::exec()
{
using namespace Calamares;
using namespace CalamaresUtils;
using namespace Calamares;
using namespace std;
QList< QPair< QStringList, QString > > commands = {

View File

@@ -1,58 +0,0 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
find_package(${qtname} COMPONENTS Core Gui Widgets REQUIRED)
set(_extra_libraries "")
set(_extra_src "")
### OPTIONAL AppData XML support in PackageModel
#
#
option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" OFF)
if(BUILD_APPDATA)
find_package(${qtname} REQUIRED COMPONENTS Xml)
if(TARGET ${qtname}::Xml)
add_definitions(-DHAVE_APPDATA)
list(APPEND _extra_libraries ${qtname}::Xml)
list(APPEND _extra_src ItemAppData.cpp)
endif()
endif()
### OPTIONAL AppStream support in PackageModel
#
#
# include(AppStreamHelper)
#
calamares_add_plugin(packagechooser
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
PackageChooserPage.cpp
PackageChooserViewStep.cpp
PackageModel.cpp
LoaderQueue.cpp
PackageTreeItem.cpp
${_extra_src}
RESOURCES
packagechooser.qrc
UI
page_package.ui
LINK_PRIVATE_LIBRARIES
${_extra_libraries}
SHARED_LIB
)
# if(AppStreamQt_FOUND)
# target_link_libraries(calamares_viewmodule_packagechooser PRIVATE calamares::appstreamqt)
# target_sources(calamares_viewmodule_packagechooser PRIVATE ItemAppStream.cpp)
# endif()
# calamares_add_test(
# packagechoosertest
# GUI
# SOURCES Tests.cpp
# LIBRARIES calamares_viewmodule_packagechooser ${_extra_libraries}
# )

View File

@@ -1,444 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Config.h"
#include "LoaderQueue.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#include <memory>
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "compat/Variant.h"
#include "packages/Globals.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "network/Manager.h"
#include <QNetworkReply>
/** @brief This removes any values from @p groups that match @p source
*
* This is used to remove duplicates from the netinstallAdd structure
* It iterates over @p groups and for each map in the list, if the
* "source" element matches @p source, it is removed from the returned
* list.
*/
static QVariantList
pruneNetinstallAdd( const QString& source, const QVariant& groups )
{
QVariantList newGroupList;
const QVariantList groupList = groups.toList();
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.value( "source", "" ).toString() != source )
{
newGroupList.append( groupMap );
}
}
return newGroupList;
}
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
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 },
{ "netinstall-add", PackageChooserMethod::NetAdd },
{ "netinstall-select", PackageChooserMethod::NetSelect },
};
return names;
}
Config::Config( QObject* parent )
: Calamares::ModuleSystem::Config( parent )
, m_model( new PackageListModel( this ) )
, m_netmodel( new PackageNetModel( this ) )
, m_mode( PackageChooserMode::Required )
{
}
Config::~Config() {}
QString
Config::status() const
{
switch ( m_status )
{
case Status::Ok:
return QString();
case Status::FailedBadConfiguration:
return tr( "Network Installation. (Disabled: Incorrect configuration)" );
case Status::FailedBadData:
return tr( "Network Installation. (Disabled: Received invalid groups data)" );
case Status::FailedInternalError:
return tr( "Network Installation. (Disabled: Internal error)" );
case Status::FailedNetworkError:
return tr( "Network Installation. (Disabled: Unable to fetch package lists, check your network connection)" );
case Status::FailedNoData:
return tr( "Network Installation. (Disabled: No package list)" );
}
__builtin_unreachable();
}
void
Config::setStatus( Status s )
{
m_status = s;
emit statusChanged( status() );
}
void
Config::loadGroupList( const QVariantList& groupData )
{
m_netmodel->setupModelData( groupData );
if ( m_netmodel->rowCount() < 1 )
{
cWarning() << "NetInstall groups data was empty.";
setStatus( Status::FailedNoData );
}
else
{
setStatus( Status::Ok );
}
}
void
Config::loadingDone()
{
if ( m_queue )
{
m_queue->deleteLater();
m_queue = nullptr;
}
emit statusReady();
}
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 = Calamares::Locale::TranslatedString( name, metaObject()->className() );
defaultIntroduction->description
= Calamares::Locale::TranslatedString( description, metaObject()->className() );
}
return *defaultIntroduction;
}
static inline QString
make_gs_key( const Calamares::ModuleSystem::InstanceKey& key )
{
return QStringLiteral( "packagechooser_" ) + key.id();
}
void
Config::updateGlobalStorage( const QStringList& selected ) const
{
if ( m_packageChoice.has_value() )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
QString value = selected.join( ',' );
Calamares::JobQueue::instance()->globalStorage()->insert( make_gs_key( m_defaultId ), value );
cDebug() << m_defaultId << "selected" << value;
}
else if ( m_method == PackageChooserMethod::Packages )
{
QStringList packageNames = m_model->getInstallPackagesForNames( selected );
cDebug() << m_defaultId << "packages to install" << packageNames;
Calamares::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else if ( m_method == PackageChooserMethod::NetAdd )
{
QVariantList netinstallDataList = m_model->getNetinstallDataForNames( selected );
if ( netinstallDataList.isEmpty() )
{
cWarning() << "No netinstall information found for " << selected;
}
else
{
// If an earlier packagechooser instance added this data to global storage, combine them
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs->contains( "netinstallAdd" ) )
{
netinstallDataList
+= pruneNetinstallAdd( QStringLiteral( "packageChooser" ), gs->value( "netinstallAdd" ) );
}
gs->insert( "netinstallAdd", netinstallDataList );
}
}
else if ( m_method == PackageChooserMethod::NetSelect )
{
cDebug() << m_defaultId << "groups to select in netinstall" << selected;
QStringList newSelected = selected;
auto* gs = Calamares::JobQueue::instance()->globalStorage();
// If an earlier packagechooser instance added this data to global storage, combine them
if ( gs->contains( "netinstallSelect" ) )
{
auto selectedOrig = gs->value( "netinstallSelect" );
if ( selectedOrig.canConvert< QStringList >() )
{
newSelected += selectedOrig.toStringList();
}
else
{
cWarning() << "Invalid NetinstallSelect data in global storage. Earlier selections purged";
}
gs->remove( "netinstallSelect" );
}
gs->insert( "netinstallSelect", newSelected );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::updateGlobalStorage() const
{
if ( m_model->packageCount() > 0 )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( m_packageChoice.has_value() )
{
gs->insert( make_gs_key( m_defaultId ), m_packageChoice.value() );
}
else
{
gs->remove( make_gs_key( m_defaultId ) );
}
}
else if ( m_method == PackageChooserMethod::Packages )
{
cWarning() << "Unsupported single-selection packagechooser method 'Packages'";
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::setPackageChoice( const QString& packageChoice )
{
if ( packageChoice.isEmpty() )
{
m_packageChoice.reset();
}
else
{
m_packageChoice = packageChoice;
}
emit packageChoiceChanged( m_packageChoice.value_or( QString() ) );
}
QString
Config::prettyName() const
{
return m_stepName ? m_stepName->get() : tr( "Packages" );
}
QString
Config::prettyStatus() const
{
return tr( "Install option: <strong>%1</strong>" ).arg( m_packageChoice.value_or( tr( "None" ) ) );
}
static void
fillModel( PackageListModel* model, const QVariantList& items )
{
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM_VERSION
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_VERSION
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( Calamares::getString( configurationMap, "mode" ),
PackageChooserMode::Required );
m_method = PackageChooserMethodNames().find( Calamares::getString( configurationMap, "method" ),
PackageChooserMethod::Legacy );
if ( m_method == PackageChooserMethod::Legacy )
{
cDebug() << "Using module ID" << m_defaultId;
}
if ( configurationMap.contains( "items" ) )
{
fillModel( m_model, configurationMap.value( "items" ).toList() );
// Lastly, load the groups data
// const QString key = QStringLiteral( "groupsUrl" );
//
//
// const auto& groupsUrlVariant = configurationMap.value( key );
// m_queue = new LoaderQueue( this );
// if ( Calamares::typeOf( groupsUrlVariant ) == Calamares::StringVariantType )
// {
// m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) );
// }
// else if ( Calamares::typeOf( groupsUrlVariant ) == Calamares::ListVariantType )
// {
// for ( const auto& s : groupsUrlVariant.toStringList() )
// {
// m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) );
// }
// }
//
// setStatus( required() ? Status::FailedNoData : Status::Ok );
// cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources.";
// connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone );
// m_queue->load();
QString default_item_id = Calamares::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;
}
}
}
}
else
{
setPackageChoice( Calamares::getString( configurationMap, "packageChoice" ) );
if ( m_method != PackageChooserMethod::Legacy )
{
cWarning() << "Single-selection QML module must use 'Legacy' method.";
}
}
bool labels_ok = false;
auto labels = Calamares::getSubMap( configurationMap, "labels", labels_ok );
if ( labels_ok )
{
if ( labels.contains( "step" ) )
{
m_stepName = new Calamares::Locale::TranslatedString( labels, "step" );
}
}
}

View File

@@ -1,171 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* 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 <QObject>
#include <QVariantMap>
#include <memory>
#include <optional>
class LoaderQueue;
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
NetAdd, // adds packages to the netinstall module
NetSelect, // makes selections in the netinstall module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();
class Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
/** @brief This is the single-select package-choice
*
* For (QML) modules that support only a single selection and
* just want to do things in a straightforward pick-this-one
* way, the packageChoice property is a (the) way to go.
*
* Writing to this property means that any other form of package-
* choice or selection is ignored.
*/
Q_PROPERTY( QString packageChoice READ packageChoice WRITE setPackageChoice NOTIFY packageChoiceChanged )
Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL )
Q_PROPERTY( PackageNetModel* PackageNetModel MEMBER m_netmodel FINAL )
Q_PROPERTY( QString status READ status NOTIFY statusChanged FINAL )
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; }
PackageNetModel* netmodel() const { return m_netmodel; }
void loadGroupList( const QVariantList& groupData );
/** @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;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking **only**
* the package choice as selected. This assumes that the single-
* selection QML code is in use.
*/
void updateGlobalStorage() const;
QString packageChoice() const { return m_packageChoice.value_or( QString() ); }
void setPackageChoice( const QString& packageChoice );
QString prettyName() const;
QString prettyStatus() const;
enum class Status
{
Ok,
FailedBadConfiguration,
FailedInternalError,
FailedNetworkError,
FailedBadData,
FailedNoData
};
/// Human-readable, translated representation of the status
QString status() const;
/// Internal code for the status
Status statusCode() const { return m_status; }
void setStatus( Status s );
signals:
void packageChoiceChanged( QString packageChoice );
void prettyStatusChanged();
Q_SIGNALS:
void statusChanged( QString status ); ///< Something changed
void sidebarLabelChanged( QString label );
void titleLabelChanged( QString label );
void statusReady(); ///< Loading groups is complete
private Q_SLOTS:
void loadingDone();
private:
PackageListModel* m_model = nullptr;
QModelIndex m_defaultModelIndex;
PackageNetModel* m_netmodel = nullptr;
LoaderQueue* m_queue = nullptr;
Status m_status = Status::Ok;
/// Selection mode for this module
PackageChooserMode m_mode = PackageChooserMode::Optional;
/// How this module stores to GS
PackageChooserMethod m_method = PackageChooserMethod::Legacy;
/// Value to use for id if none is set in the config file
Calamares::ModuleSystem::InstanceKey m_defaultId;
/** @brief QML selection (for single-selection approaches)
*
* If there is no value, then there has been no selection.
* Reading the property will return an empty QString.
*/
std::optional< QString > m_packageChoice;
Calamares::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
};
#endif

View File

@@ -1,225 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppData XML files.
*
* Only used if QtXML is found, implements PackageItem::fromAppData().
*/
#include "PackageModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QDomDocument>
#include <QDomNodeList>
#include <QFile>
/** @brief try to load the given @p fileName XML document
*
* Returns a QDomDocument, which will be valid iff the file can
* be read and contains valid XML data.
*/
static inline QDomDocument
loadAppData( const QString& fileName )
{
QFile file( fileName );
if ( !file.open( QIODevice::ReadOnly ) )
{
return QDomDocument();
}
QDomDocument doc( "AppData" );
if ( !doc.setContent( &file ) )
{
file.close();
return QDomDocument();
}
file.close();
return doc;
}
/** @brief gets the text of child element @p tagName
*/
static inline QString
getChildText( const QDomNode& n, const QString& tagName )
{
QDomElement e = n.firstChildElement( tagName );
return e.isNull() ? QString() : e.text();
}
/** @brief Gets a suitable screenshot path
*
* The <screenshots> element contains zero or more <screenshot>
* elements, which can have a *type* associated with them.
* Scan the screenshot elements, return the <image> path
* for the one labeled with type=default or, if there is no
* default, the first element.
*/
static inline QString
getScreenshotPath( const QDomNode& n )
{
QDomElement shotsNode = n.firstChildElement( "screenshots" );
if ( shotsNode.isNull() )
{
return QString();
}
const QDomNodeList shotList = shotsNode.childNodes();
int firstScreenshot = -1; // Use which screenshot node?
for ( int i = 0; i < shotList.count(); ++i )
{
if ( !shotList.at( i ).isElement() )
{
continue;
}
QDomElement e = shotList.at( i ).toElement();
if ( e.tagName() != "screenshot" )
{
continue;
}
// If none has the "type=default" attribute, use the first one
if ( firstScreenshot < 0 )
{
firstScreenshot = i;
}
// But type=default takes precedence.
if ( e.hasAttribute( "type" ) && e.attribute( "type" ) == "default" )
{
firstScreenshot = i;
break;
}
}
if ( firstScreenshot >= 0 )
{
return shotList.at( firstScreenshot ).firstChildElement( "image" ).text();
}
return QString();
}
/** @brief Returns language of the given element @p e
*
* Transforms the attribute value for xml:lang to something
* suitable for TranslatedString (e.g. [lang]).
*/
static inline QString
getLanguage( const QDomElement& e )
{
QString language = e.attribute( "xml:lang" );
if ( !language.isEmpty() )
{
language.replace( '-', '_' );
language.prepend( '[' );
language.append( ']' );
}
return language;
}
/** @brief Scan the list of @p children for @p tagname elements and add them to the map
*
* Uses @p mapname instead of @p tagname for the entries in map @p m
* to allow renaming from XML to map keys (in particular for
* TranslatedString). Also transforms xml:lang attributes to suitable
* key-decorations on @p mapname.
*/
static inline void
fillMap( QVariantMap& m, const QDomNodeList& children, const QString& tagname, const QString& mapname )
{
for ( int i = 0; i < children.count(); ++i )
{
if ( !children.at( i ).isElement() )
{
continue;
}
QDomElement e = children.at( i ).toElement();
if ( e.tagName() != tagname )
{
continue;
}
m[ mapname + getLanguage( e ) ] = e.text();
}
}
/** @brief gets the <name> and <description> elements
*
* Builds up a map of the <name> elements (which may have a *lang*
* attribute to indicate translations and paragraphs of the
* <description> element (also with lang). Uses the <summary>
* elements to supplement the description if no description
* is available for a given language.
*
* Returns a map with keys suitable for use by TranslatedString.
*/
static inline QVariantMap
getNameAndSummary( const QDomNode& n )
{
QVariantMap m;
const QDomNodeList children = n.childNodes();
fillMap( m, children, "name", "name" );
fillMap( m, children, "summary", "description" );
const QDomElement description = n.firstChildElement( "description" );
if ( !description.isNull() )
{
fillMap( m, description.childNodes(), "p", "description" );
}
return m;
}
PackageItem
fromAppData( const QVariantMap& item_map )
{
QString fileName = Calamares::getString( item_map, "appdata" );
if ( fileName.isEmpty() )
{
cWarning() << "Can't load AppData without a suitable key.";
return PackageItem();
}
cDebug() << "Loading AppData XML from" << fileName;
QDomDocument doc = loadAppData( fileName );
if ( doc.isNull() )
{
return PackageItem();
}
QDomElement componentNode = doc.documentElement();
if ( !componentNode.isNull() && componentNode.tagName() == "component" )
{
// An "id" entry in the Calamares config overrides ID in the AppData
QString id = Calamares::getString( item_map, "id" );
if ( id.isEmpty() )
{
id = getChildText( componentNode, "id" );
}
if ( id.isEmpty() )
{
return PackageItem();
}
// A "screenshot" entry in the Calamares config overrides AppData
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( screenshotPath.isEmpty() )
{
screenshotPath = getScreenshotPath( componentNode );
}
QVariantMap map = getNameAndSummary( componentNode );
map.insert( "id", id );
map.insert( "screenshot", screenshotPath );
return PackageItem( map );
}
return PackageItem();
}

View File

@@ -1,28 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPDATA_H
#define ITEMAPPDATA_H
#include "PackageModel.h"
/** @brief Loads an AppData XML file and returns a PackageItem
*
* The @p map must have a key *appdata*. That is used as the
* primary source of information, but keys *id* and *screenshotPath*
* may be used to override parts of the AppData -- so that the
* ID is under the control of Calamares, and the screenshot can be
* forced to a local path available on the installation medium.
*
* Requires XML support in libcalamares, if not present will
* return invalid PackageItems.
*/
PackageItem fromAppData( const QVariantMap& map );
#endif

View File

@@ -1,165 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppStream database.
*
* Only used if AppStreamQt is found, implements PackageItem::fromAppStream().
*/
#include "ItemAppStream.h"
#include "locale/TranslationsModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
/// @brief Return number of pixels in a size, for < ordering purposes
static inline quint64
sizeOrder( const QSize& size )
{
return static_cast<quint64>(size.width()) * static_cast<quint64>(size.height());
}
/// @brief Sets a screenshot in @p map from @p screenshot, if a usable one is found
static void
setScreenshot( QVariantMap& map, const AppStream::Screenshot& screenshot )
{
if ( screenshot.images().count() < 1 )
{
return;
}
// Pick the smallest
QUrl url;
quint64 size = sizeOrder( screenshot.images().first().size() );
for ( const auto& img : screenshot.images() )
{
if ( sizeOrder( img.size() ) <= size )
{
url = img.url();
}
}
if ( url.isValid() )
{
map.insert( "screenshot", url.toString() );
}
}
/// @brief Interpret an AppStream Component
static PackageItem
fromComponent( AppStream::Pool& pool, AppStream::Component& component )
{
#if HAVE_APPSTREAM_VERSION == 0
auto setActiveLocale = [&component](const QString & locale)
{
component.setActiveLocale( locale );
};
#else
auto setActiveLocale = [&pool](const QString & locale)
{
pool.setLocale( locale );
};
#endif
QVariantMap map;
map.insert( "id", component.id() );
map.insert( "package", component.packageNames().join( "," ) );
// Assume that the pool has loaded "ALL" locales, but it might be set
// to any of them; get the en_US locale as "untranslated" and then
// loop over Calamares locales (since there is no way to query for
// available locales in the Component) to see if there's anything else.
setActiveLocale( QStringLiteral( "en_US" ) );
QString en_name = component.name();
QString en_description = component.description();
map.insert( "name", en_name );
map.insert( "description", en_description );
for ( const QString& locale : Calamares::Locale::availableTranslations()->localeIds() )
{
setActiveLocale( locale );
QString name = component.name();
if ( name != en_name )
{
map.insert( QStringLiteral( "name[%1]" ).arg( locale ), name );
}
QString description = component.description();
if ( description != en_description )
{
map.insert( QStringLiteral( "description[%1]" ).arg( locale ), description );
}
}
#if HAVE_APPSTREAM_VERSION == 0
auto screenshots = component.screenshots();
#else
auto screenshots = component.screenshotsAll();
#endif
if ( screenshots.count() > 0 )
{
bool done = false;
for ( const auto& s : screenshots )
{
if ( s.isDefault() )
{
setScreenshot( map, s );
done = true;
break;
}
}
if ( !done )
{
setScreenshot( map, screenshots.first() );
}
}
return PackageItem( map );
}
PackageItem
fromAppStream( AppStream::Pool& pool, const QVariantMap& item_map )
{
QString appstreamId = Calamares::getString( item_map, "appstream" );
if ( appstreamId.isEmpty() )
{
cWarning() << "Can't load AppStream without a suitable appstreamId.";
return PackageItem();
}
cDebug() << "Loading AppStream data for" << appstreamId;
#if HAVE_APPSTREAM_VERSION == 0
auto itemList = pool.componentsById( appstreamId );
#else
auto itemList = pool.componentsById( appstreamId ).toList();
#endif
if ( itemList.count() < 1 )
{
cWarning() << "No AppStream data for" << appstreamId;
return PackageItem();
}
if ( itemList.count() > 1 )
{
cDebug() << "Multiple AppStream data for" << appstreamId << "using first.";
}
auto r = fromComponent( pool, itemList.first() );
if ( r.isValid() )
{
QString id = Calamares::getString( item_map, "id" );
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( !id.isEmpty() )
{
r.id = id;
}
if ( !screenshotPath.isEmpty() )
{
r.screenshot = screenshotPath;
}
}
return r;
}

View File

@@ -1,52 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPSTREAM_H
#define ITEMAPPSTREAM_H
#include "PackageModel.h"
/*
* This weird include mechanism is because an #include line is allowed
* to consist of preprocessor-tokens, which are expanded, and then
* the #include is *re*processed. But if it starts with < or ", then
* preprocessor tokens are not expanded. So we build up a #include <>
* style line with a suitable path -- if we are given a value for
* HAVE_APPSTREAM_HEADERS, that is the directory that the AppStreamQt
* headers live in.
*/
#define CALAMARES_LT <
#define CALAMARES_GT >
#ifndef HAVE_APPSTREAM_HEADERS
#define HAVE_APPSTREAM_HEADERS AppStreamQt
#endif
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/pool.h CALAMARES_GT
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/image.h CALAMARES_GT
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/screenshot.h CALAMARES_GT
#undef CALAMARES_LT
#undef CALAMARES_GT
/** @brief Loads an item from AppStream data.
*
* The @p map must have a key *appstream*. That is used as the
* primary source of information from the AppStream cache, but
* keys *id* and *screenshotPath* may be used to override parts
* of the AppStream data -- so that the ID is under the control
* of Calamares, and the screenshot can be forced to a local path
* available on the installation medium.
*
* Requires AppStreamQt, if not present will return invalid
* PackageItems.
*/
PackageItem fromAppStream( AppStream::Pool& pool, const QVariantMap& map );
#endif

View File

@@ -1,205 +0,0 @@
/*
* SPDX-FileCopyrightText: 2016 Luca Giambonini <almack@chakraos.org>
* SPDX-FileCopyrightText: 2016 Lisa Vitolo <shainer@chakraos.org>
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <krobbertze@gmail.com>
* SPDX-FileCopyrightText: 2017-2018 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "LoaderQueue.h"
#include "Config.h"
#include "network/Manager.h"
#include "utils/Logger.h"
#include "utils/RAII.h"
#include "utils/Yaml.h"
#include <QNetworkReply>
#include <QTimer>
/** @brief Call fetchNext() on the queue if it can
*
* On destruction, a new call to fetchNext() is queued, so that
* the queue continues loading. Calling release() before the
* 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
{
public:
FetchNextUnless( LoaderQueue* q )
: m_q( q )
{
}
~FetchNextUnless()
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "fetchNext", Qt::QueuedConnection );
}
}
void release() { m_q = nullptr; }
void done( bool b )
{
if ( b )
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "done", Qt::QueuedConnection );
}
release();
}
}
private:
LoaderQueue* m_q = nullptr;
};
SourceItem
SourceItem::makeSourceItem( const QString& groupsUrl, const QVariantMap& configurationMap )
{
if ( groupsUrl == QStringLiteral( "local" ) )
{
return SourceItem { QUrl(), configurationMap.value( "groups" ).toList() };
}
else
{
return SourceItem { QUrl { groupsUrl }, QVariantList() };
}
}
LoaderQueue::LoaderQueue( Config* parent )
: QObject( parent )
, m_config( parent )
{
}
void
LoaderQueue::append( SourceItem&& i )
{
m_queue.append( std::move( i ) );
}
void
LoaderQueue::load()
{
QMetaObject::invokeMethod( this, "fetchNext", Qt::QueuedConnection );
}
void
LoaderQueue::fetchNext()
{
if ( m_queue.isEmpty() )
{
emit done();
return;
}
auto source = m_queue.takeFirst();
if ( source.isLocal() )
{
m_config->loadGroupList( source.data );
emit done();
}
else
{
fetch( source.url );
}
}
void
LoaderQueue::fetch( const QUrl& url )
{
FetchNextUnless next( this );
if ( !url.isValid() )
{
m_config->setStatus( Config::Status::FailedBadConfiguration );
cDebug() << "Invalid URL" << url;
return;
}
using namespace Calamares::Network;
cDebug() << "NetInstall loading groups from" << url;
QNetworkReply* reply = Manager().asynchronousGet(
url,
RequestOptions( RequestOptions::FakeUserAgent | RequestOptions::FollowRedirect, std::chrono::seconds( 30 ) ) );
if ( !reply )
{
cDebug() << Logger::SubEntry << "Request failed immediately.";
// If nobody sets a different status, this will remain
m_config->setStatus( Config::Status::FailedBadConfiguration );
}
else
{
// When the network request is done, **then** we might
// do the next item from the queue, so don't call fetchNext() now.
next.release();
m_reply = reply;
connect( reply, &QNetworkReply::finished, this, &LoaderQueue::dataArrived );
}
}
void
LoaderQueue::dataArrived()
{
FetchNextUnless next( this );
if ( !m_reply || !m_reply->isFinished() )
{
cWarning() << "NetInstall data called too early.";
m_config->setStatus( Config::Status::FailedInternalError );
return;
}
cDebug() << "NetInstall group data received" << m_reply->size() << "bytes from" << m_reply->url();
cqDeleter< QNetworkReply > d { m_reply };
// If m_required is *false* then we still say we're ready
// even if the reply is corrupt or missing.
if ( m_reply->error() != QNetworkReply::NoError )
{
cWarning() << "unable to fetch netinstall package lists.";
cDebug() << Logger::SubEntry << "Netinstall reply error: " << m_reply->error();
cDebug() << Logger::SubEntry << "Request for url: " << m_reply->url().toString()
<< " failed with: " << m_reply->errorString();
m_config->setStatus( Config::Status::FailedNetworkError );
return;
}
QByteArray yamlData = m_reply->readAll();
try
{
auto groups = ::YAML::Load( yamlData.constData() );
if ( groups.IsSequence() )
{
m_config->loadGroupList( Calamares::YAML::sequenceToVariant( groups ) );
next.done( m_config->statusCode() == Config::Status::Ok );
}
else if ( groups.IsMap() )
{
auto map = Calamares::YAML::mapToVariant( groups );
m_config->loadGroupList( map.value( "groups" ).toList() );
next.done( m_config->statusCode() == Config::Status::Ok );
}
else
{
cWarning() << "NetInstall groups data does not form a sequence.";
}
}
catch ( ::YAML::Exception& e )
{
Calamares::YAML::explainException( e, yamlData, "netinstall groups data" );
m_config->setStatus( Config::Status::FailedBadData );
}
}

View File

@@ -1,77 +0,0 @@
/*
* SPDX-FileCopyrightText: 2016 Luca Giambonini <almack@chakraos.org>
* SPDX-FileCopyrightText: 2016 Lisa Vitolo <shainer@chakraos.org>
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <krobbertze@gmail.com>
* SPDX-FileCopyrightText: 2017-2018 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef NETINSTALL_LOADERQUEUE_H
#define NETINSTALL_LOADERQUEUE_H
#include <QQueue>
#include <QUrl>
#include <QVariantList>
class Config;
class QNetworkReply;
/** @brief Data about an entry in *groupsUrl*
*
* This can be a specific URL, or "local" which uses data stored
* in the configuration file itself.
*/
struct SourceItem
{
QUrl url;
QVariantList data;
bool isUrl() const { return url.isValid(); }
bool isLocal() const { return !data.isEmpty(); }
bool isValid() const { return isUrl() || isLocal(); }
/** @brief Create a SourceItem
*
* If the @p groupsUrl is @c "local" then the *groups* key in
* the @p configurationMap is used as the source; otherwise the
* string is used as an actual URL.
*/
static SourceItem makeSourceItem( const QString& groupsUrl, const QVariantMap& configurationMap );
};
/** @brief Queue of source items to load
*
* Queue things up by calling append() and then kick things off
* by calling load(). This will try to load the items, in order;
* the first one that succeeds will end the loading process.
*
* Signal done() is emitted when done (also when all of the items fail).
*/
class LoaderQueue : public QObject
{
Q_OBJECT
public:
LoaderQueue( Config* parent );
void append( SourceItem&& i );
int count() const { return m_queue.count(); }
public Q_SLOTS:
void load();
void fetchNext();
void fetch( const QUrl& url );
void dataArrived();
Q_SIGNALS:
void done();
private:
QQueue< SourceItem > m_queue;
Config* m_config = nullptr;
QNetworkReply* m_reply = nullptr;
};
#endif

View File

@@ -1,145 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserPage.h"
#include "ui_page_package.h"
#include "utils/Gui.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QLabel>
PackageChooserPage::PackageChooserPage( PackageChooserMode mode, QWidget* parent )
: QWidget( parent )
, ui( new Ui::PackageChooserPage )
, m_introduction( QString(),
QString(),
tr( "Package Selection" ),
tr( "Please pick a product from the list. The selected product will be installed." ) )
{
m_introduction.screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
ui->setupUi( this );
CALAMARES_RETRANSLATE( updateLabels(); );
switch ( mode )
{
case PackageChooserMode::Optional:
[[fallthrough]];
case PackageChooserMode::Required:
ui->products->setSelectionMode( QAbstractItemView::SingleSelection );
break;
case PackageChooserMode::OptionalMultiple:
[[fallthrough]];
case PackageChooserMode::RequiredMultiple:
ui->products->setSelectionMode( QAbstractItemView::ExtendedSelection );
}
ui->products->setMinimumWidth( 10 * Calamares::defaultFontHeight() );
}
void
PackageChooserPage::currentChanged( const QModelIndex& index )
{
if ( !index.isValid() || !ui->products->selectionModel()->hasSelection() )
{
ui->productName->setText( m_introduction.name.get() );
ui->productScreenshot->setPixmap( m_introduction.screenshot );
ui->productDescription->setText( m_introduction.description.get() );
}
else
{
const auto* model = ui->products->model();
ui->productName->setText( model->data( index, PackageListModel::NameRole ).toString() );
ui->productDescription->setText( model->data( index, PackageListModel::DescriptionRole ).toString() );
QPixmap currentScreenshot = model->data( index, PackageListModel::ScreenshotRole ).value< QPixmap >();
if ( currentScreenshot.isNull() )
{
ui->productScreenshot->setPixmap( m_introduction.screenshot );
}
else
{
ui->productScreenshot->setPixmap( currentScreenshot );
}
}
}
void
PackageChooserPage::updateLabels()
{
if ( ui && ui->products && ui->products->selectionModel() )
{
currentChanged( ui->products->selectionModel()->currentIndex() );
}
else
{
currentChanged( QModelIndex() );
}
emit selectionChanged();
}
void
PackageChooserPage::setModel( QAbstractItemModel* model )
{
ui->products->setModel( model );
currentChanged( QModelIndex() );
connect( ui->products->selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&PackageChooserPage::updateLabels );
}
void
PackageChooserPage::setSelection( const QModelIndex& index )
{
if ( index.isValid() )
{
ui->products->selectionModel()->select( index, QItemSelectionModel::Select );
}
currentChanged( index );
}
bool
PackageChooserPage::hasSelection() const
{
return ui && ui->products && ui->products->selectionModel() && ui->products->selectionModel()->hasSelection();
}
QStringList
PackageChooserPage::selectedPackageIds() const
{
if ( !( ui && ui->products && ui->products->selectionModel() ) )
{
return QStringList();
}
const auto* model = ui->products->model();
QStringList ids;
for ( const auto& index : ui->products->selectionModel()->selectedIndexes() )
{
QString pid = model->data( index, PackageListModel::IdRole ).toString();
if ( !pid.isEmpty() )
{
ids.append( pid );
}
}
return ids;
}
void
PackageChooserPage::setIntroduction( const PackageItem& item )
{
m_introduction.name = item.name;
m_introduction.description = item.description;
m_introduction.screenshot = item.screenshot;
}

View File

@@ -1,57 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERPAGE_H
#define PACKAGECHOOSERPAGE_H
#include "Config.h"
#include "PackageModel.h"
#include <QAbstractItemModel>
#include <QWidget>
namespace Ui
{
class PackageChooserPage;
} // namespace Ui
class PackageChooserPage : public QWidget
{
Q_OBJECT
public:
explicit PackageChooserPage( PackageChooserMode mode, QWidget* parent = nullptr );
/// @brief Sets the data model for the listview
void setModel( QAbstractItemModel* model );
/// @brief Sets the introductory (no-package-selected) texts
void setIntroduction( const PackageItem& item );
/// @brief Selects a listview item
void setSelection( const QModelIndex& index );
/// @brief Is anything selected?
bool hasSelection() const;
/** @brief Get the list of selected ids
*
* This list may be empty (if none is selected).
*/
QStringList selectedPackageIds() const;
public slots:
void currentChanged( const QModelIndex& index );
void updateLabels();
signals:
void selectionChanged();
private:
Ui::PackageChooserPage* ui;
PackageItem m_introduction;
};
#endif // PACKAGECHOOSERPAGE_H

View File

@@ -1,158 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserViewStep.h"
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/Logger.h"
#include "utils/System.h"
#include "utils/Variant.h"
#include <QDesktopServices>
#include <QVariantMap>
CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlugin< PackageChooserViewStep >(); )
PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
{
emit nextStatusChanged( false );
}
PackageChooserViewStep::~PackageChooserViewStep()
{
if ( m_widget && m_widget->parent() == nullptr )
{
m_widget->deleteLater();
}
}
QString
PackageChooserViewStep::prettyName() const
{
return m_config->prettyName();
}
QWidget*
PackageChooserViewStep::widget()
{
if ( !m_widget )
{
m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget,
&PackageChooserPage::selectionChanged,
[ = ]() { emit nextStatusChanged( this->isNextEnabled() ); } );
hookupModel();
}
return m_widget;
}
bool
PackageChooserViewStep::isNextEnabled() const
{
if ( !m_widget )
{
// No way to have changed anything
return true;
}
switch ( m_config->mode() )
{
case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple:
// zero or one OR zero or more
return true;
case PackageChooserMode::Required:
case PackageChooserMode::RequiredMultiple:
// exactly one OR one or more
return m_widget->hasSelection();
}
__builtin_unreachable();
}
bool
PackageChooserViewStep::isBackEnabled() const
{
return true;
}
bool
PackageChooserViewStep::isAtBeginning() const
{
return true;
}
bool
PackageChooserViewStep::isAtEnd() const
{
return true;
}
void
PackageChooserViewStep::onActivate()
{
if ( !m_widget->hasSelection() )
{
m_widget->setSelection( m_config->defaultSelectionIndex() );
}
}
void
PackageChooserViewStep::onLeave()
{
m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
}
Calamares::JobList
PackageChooserViewStep::jobs() const
{
Calamares::JobList l;
return l;
}
void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
if ( m_widget )
{
hookupModel();
}
}
void
PackageChooserViewStep::hookupModel()
{
if ( !m_config->model() || !m_widget )
{
cError() << "Can't hook up model until widget and model both exist.";
return;
}
m_widget->setModel( m_config->model() );
m_widget->setIntroduction( m_config->introductionPackage() );
}

View File

@@ -1,57 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERVIEWSTEP_H
#define PACKAGECHOOSERVIEWSTEP_H
#include "DllMacro.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include <QVariantMap>
class Config;
class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
{
Q_OBJECT
public:
explicit PackageChooserViewStep( QObject* parent = nullptr );
~PackageChooserViewStep() override;
QString prettyName() const override;
QWidget* widget() override;
bool isNextEnabled() const override;
bool isBackEnabled() const override;
bool isAtBeginning() const override;
bool isAtEnd() const override;
void onActivate() override;
void onLeave() override;
Calamares::JobList jobs() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
void hookupModel();
Config* m_config;
PackageChooserPage* m_widget;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )
#endif // PACKAGECHOOSERVIEWSTEP_H

View File

@@ -1,575 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageModel.h"
#include "Branding.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "compat/Variant.h"
#include "utils/Yaml.h"
#include <QFileInfo>
/** @brief A wrapper for Calamares::getSubMap that excludes the success param
*/
static QVariantMap
getSubMap( const QVariantMap& map, const QString& key )
{
bool success;
return Calamares::getSubMap( map, key, success );
}
static QPixmap
loadScreenshot( const QString& path )
{
if ( QFileInfo::exists( path ) )
{
return QPixmap( path );
}
const auto* branding = Calamares::Branding::instance();
if ( !branding )
{
return QPixmap();
}
return QPixmap( branding->componentDirectory() + QStringLiteral( "/" ) + path );
}
PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
: id( a_id )
, name( a_name )
, description( a_description )
{
}
PackageItem::PackageItem( const QString& a_id,
const QString& a_name,
const QString& a_description,
const QString& screenshotPath )
: id( a_id )
, name( a_name )
, description( a_description )
, screenshot( screenshotPath )
{
}
PackageItem::PackageItem( const QVariantMap& item_map )
: id( Calamares::getString( item_map, "id" ) )
, name( Calamares::Locale::TranslatedString( item_map, "name" ) )
, description( Calamares::Locale::TranslatedString( item_map, "description" ) )
, screenshot( loadScreenshot( Calamares::getString( item_map, "screenshot" ) ) )
, packageNames( Calamares::getStringList( item_map, "packages" ) )
, netinstallData( getSubMap( item_map, "netinstall" ) )
{
if ( name.isEmpty() && id.isEmpty() )
{
name = QObject::tr( "No product" );
}
else if ( name.isEmpty() )
{
cWarning() << "PackageChooser item" << id << "has an empty name.";
}
if ( description.isEmpty() )
{
description = QObject::tr( "No description provided." );
}
}
PackageListModel::PackageListModel( QObject* parent )
: QAbstractListModel( parent )
{
}
PackageListModel::PackageListModel( PackageList&& items, QObject* parent )
: QAbstractListModel( parent )
, m_packages( std::move( items ) )
{
}
PackageListModel::~PackageListModel() {}
void
PackageListModel::addPackage( PackageItem&& p )
{
// Only add valid packages
if ( p.isValid() )
{
int c = m_packages.count();
beginInsertRows( QModelIndex(), c, c );
m_packages.append( p );
endInsertRows();
}
}
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;
}
QVariantList
PackageListModel::getNetinstallDataForNames( const QStringList& ids ) const
{
QVariantList l;
for ( auto& p : m_packages )
{
if ( ids.contains( p.id ) )
{
if ( !p.netinstallData.isEmpty() )
{
QVariantMap newData = p.netinstallData;
newData[ "source" ] = QStringLiteral( "packageChooser" );
l.append( newData );
}
}
}
return l;
}
int
PackageListModel::rowCount( const QModelIndex& index ) const
{
// For lists, valid indexes have zero children; only the root index has them
return index.isValid() ? 0 : m_packages.count();
}
QVariant
PackageListModel::data( const QModelIndex& index, int role ) const
{
if ( !index.isValid() )
{
return QVariant();
}
int row = index.row();
if ( row >= m_packages.count() || row < 0 )
{
return QVariant();
}
if ( role == Qt::DisplayRole /* Also PackageNameRole */ )
{
return m_packages[ row ].name.get();
}
else if ( role == DescriptionRole )
{
return m_packages[ row ].description.get();
}
else if ( role == ScreenshotRole )
{
return m_packages[ row ].screenshot;
}
else if ( role == IdRole )
{
return m_packages[ row ].id;
}
return QVariant();
}
/// Recursive helper for setSelections()
static void
setSelections( const QStringList& selectNames, PackageTreeItem* item )
{
for ( int i = 0; i < item->childCount(); i++ )
{
auto* child = item->child( i );
setSelections( selectNames, child );
}
if ( item->isGroup() && selectNames.contains( item->name() ) )
{
item->setSelected( Qt::CheckState::Checked );
}
}
/** @brief Collects all the "source" values from @p groupList
*
* Iterates over @p groupList and returns all nonempty "source"
* values from the maps.
*
*/
static QStringList
collectSources( const QVariantList& groupList )
{
QStringList sources;
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( !groupMap[ "source" ].toString().isEmpty() )
{
sources.append( groupMap[ "source" ].toString() );
}
}
return sources;
}
PackageNetModel::PackageNetModel( QObject* parent )
: QAbstractItemModel( parent )
{
}
PackageNetModel::~PackageNetModel()
{
delete m_rootItem;
}
QModelIndex
PackageNetModel::index( int row, int column, const QModelIndex& parent ) const
{
if ( !m_rootItem || !hasIndex( row, column, parent ) )
{
return QModelIndex();
}
PackageTreeItem* parentItem;
if ( !parent.isValid() )
{
parentItem = m_rootItem;
}
else
{
parentItem = static_cast< PackageTreeItem* >( parent.internalPointer() );
}
PackageTreeItem* childItem = parentItem->child( row );
if ( childItem )
{
return createIndex( row, column, childItem );
}
else
{
return QModelIndex();
}
}
QModelIndex
PackageNetModel::parent( const QModelIndex& index ) const
{
if ( !m_rootItem || !index.isValid() )
{
return QModelIndex();
}
PackageTreeItem* child = static_cast< PackageTreeItem* >( index.internalPointer() );
PackageTreeItem* parent = child->parentItem();
if ( parent == m_rootItem )
{
return QModelIndex();
}
return createIndex( parent->row(), 0, parent );
}
int
PackageNetModel::rowCount( const QModelIndex& parent ) const
{
if ( !m_rootItem || ( parent.column() > 0 ) )
{
return 0;
}
PackageTreeItem* parentItem;
if ( !parent.isValid() )
{
parentItem = m_rootItem;
}
else
{
parentItem = static_cast< PackageTreeItem* >( parent.internalPointer() );
}
return parentItem->childCount();
}
int
PackageNetModel::columnCount( const QModelIndex& ) const
{
return 2;
}
QVariant
PackageNetModel::data( const QModelIndex& index, int role ) const
{
if ( !m_rootItem || !index.isValid() )
{
return QVariant();
}
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
switch ( role )
{
case Qt::CheckStateRole:
return index.column() == NameColumn ? ( item->isImmutable() ? QVariant() : item->isSelected() ) : QVariant();
case Qt::DisplayRole:
return item->isHidden() ? QVariant() : item->data( index.column() );
case MetaExpandRole:
return item->isHidden() ? false : item->expandOnStart();
default:
return QVariant();
}
}
bool
PackageNetModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
if ( !m_rootItem )
{
return false;
}
if ( role == Qt::CheckStateRole && index.isValid() )
{
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
item->setSelected( static_cast< Qt::CheckState >( value.toInt() ) );
emit dataChanged( this->index( 0, 0 ),
index.sibling( index.column(), index.row() + 1 ),
QVector< int >( Qt::CheckStateRole ) );
}
return true;
}
Qt::ItemFlags
PackageNetModel::flags( const QModelIndex& index ) const
{
if ( !m_rootItem || !index.isValid() )
{
return Qt::ItemFlags();
}
if ( index.column() == NameColumn )
{
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
if ( item->isImmutable() || item->isNoncheckable() )
{
return QAbstractItemModel::flags( index ); //Qt::NoItemFlags;
}
return Qt::ItemIsUserCheckable | QAbstractItemModel::flags( index );
}
return QAbstractItemModel::flags( index );
}
QVariant
PackageNetModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
{
return ( section == NameColumn ) ? tr( "Name" ) : tr( "Description" );
}
return QVariant();
}
void
PackageNetModel::setSelections( const QStringList& selectNames )
{
if ( m_rootItem )
{
::setSelections( selectNames, m_rootItem );
}
}
PackageTreeItem::List
PackageNetModel::getPackages() const
{
if ( !m_rootItem )
{
return PackageTreeItem::List();
}
auto items = getItemPackages( m_rootItem );
for ( auto package : m_hiddenItems )
{
if ( package->hiddenSelected() )
{
items.append( getItemPackages( package ) );
}
}
return items;
}
PackageTreeItem::List
PackageNetModel::getItemPackages( PackageTreeItem* item ) const
{
PackageTreeItem::List selectedPackages;
for ( int i = 0; i < item->childCount(); i++ )
{
auto* child = item->child( i );
if ( child->isSelected() == Qt::Unchecked )
{
continue;
}
if ( child->isPackage() ) // package
{
selectedPackages.append( child );
}
else
{
selectedPackages.append( getItemPackages( child ) );
}
}
return selectedPackages;
}
void
PackageNetModel::setupModelData( const QVariantList& groupList, PackageTreeItem* parent )
{
for ( const auto& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.isEmpty() )
{
continue;
}
PackageTreeItem* item = new PackageTreeItem( groupMap, PackageTreeItem::GroupTag { parent } );
if ( groupMap.contains( "selected" ) )
{
item->setSelected( Calamares::getBool( groupMap, "selected", false ) ? Qt::Checked : Qt::Unchecked );
}
if ( groupMap.contains( "packages" ) )
{
for ( const auto& packageName : groupMap.value( "packages" ).toList() )
{
if ( Calamares::typeOf( packageName ) == Calamares::StringVariantType )
{
item->appendChild( new PackageTreeItem( packageName.toString(), item ) );
}
else
{
QVariantMap m = packageName.toMap();
if ( !m.isEmpty() )
{
item->appendChild( new PackageTreeItem( m, PackageTreeItem::PackageTag { item } ) );
}
}
}
if ( !item->childCount() )
{
cWarning() << "*packages* under" << item->name() << "is empty.";
}
}
if ( groupMap.contains( "subgroups" ) )
{
bool haveWarned = false;
const auto& subgroupValue = groupMap.value( "subgroups" );
if ( !subgroupValue.canConvert< QVariantList >() )
{
cWarning() << "*subgroups* under" << item->name() << "is not a list.";
haveWarned = true;
}
QVariantList subgroups = groupMap.value( "subgroups" ).toList();
if ( !subgroups.isEmpty() )
{
setupModelData( subgroups, item );
// The children might be checked while the parent isn't (yet).
// Children are added to their parent (below) without affecting
// the checked-state -- do it manually. Items with subgroups
// but no children have only hidden children -- those get
// handled specially.
if ( item->childCount() > 0 )
{
item->updateSelected();
}
}
else
{
if ( !haveWarned )
{
cWarning() << "*subgroups* list under" << item->name() << "is empty.";
}
}
}
if ( item->isHidden() )
{
m_hiddenItems.append( item );
if ( !item->isSelected() )
{
cWarning() << "Item" << ( item->parentItem() ? item->parentItem()->name() : QString() ) << '.'
<< item->name() << "is hidden, but not selected.";
}
}
else
{
item->setCheckable( true );
parent->appendChild( item );
}
}
}
void
PackageNetModel::setupModelData( const QVariantList& l )
{
beginResetModel();
delete m_rootItem;
m_rootItem = new PackageTreeItem();
setupModelData( l, m_rootItem );
endResetModel();
}
void
PackageNetModel::appendModelData( const QVariantList& groupList )
{
if ( m_rootItem )
{
beginResetModel();
const QStringList sources = collectSources( groupList );
if ( !sources.isEmpty() )
{
// Prune any existing data from the same source
QList< int > removeList;
for ( int i = 0; i < m_rootItem->childCount(); i++ )
{
PackageTreeItem* child = m_rootItem->child( i );
if ( sources.contains( child->source() ) )
{
removeList.insert( 0, i );
}
}
for ( const int& item : qAsConst( removeList ) )
{
m_rootItem->removeChild( item );
}
}
// Add the new data to the model
setupModelData( groupList, m_rootItem );
endResetModel();
}
}

View File

@@ -1,214 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGEMODEL_H
#define PACKAGEMODEL_H
#include "locale/TranslatableConfiguration.h"
#include "utils/NamedEnum.h"
#include "PackageTreeItem.h"
#include <QAbstractItemModel>
#include <QString>
#include <QAbstractListModel>
#include <QObject>
#include <QPixmap>
#include <QVector>
struct PackageItem
{
QString id;
Calamares::Locale::TranslatedString name;
Calamares::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
QVariantMap netinstallData;
/// @brief Create blank PackageItem
PackageItem();
/** @brief Creates a PackageItem from given strings
*
* This constructor sets all the text members,
* but leaves the screenshot blank. Set that separately.
*/
PackageItem( const QString& id, const QString& name, const QString& description );
/** @brief Creates a PackageItem from given strings.
*
* Set all the text members and load the screenshot from the given
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands.
*/
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 );
/** @brief Is this item valid?
*
* A valid item has an untranslated name available.
*/
bool isValid() const { return !name.isEmpty(); }
/** @brief Is this a (the) No-Package package?
*
* There should be at most one No-Package item in a collection
* of PackageItems. That one will be used to describe a
* "no package" situation.
*/
bool isNonePackage() const { return id.isEmpty(); }
};
using PackageList = QVector< PackageItem >;
class PackageListModel : public QAbstractListModel
{
public:
PackageListModel( PackageList&& items, QObject* parent );
PackageListModel( QObject* parent );
~PackageListModel() override;
/** @brief Add a package @p to the model
*
* Only valid packages are added -- that is, they must have a name.
*/
void addPackage( PackageItem&& p );
int rowCount( const QModelIndex& index ) const override;
QVariant data( const QModelIndex& index, int role ) const override;
/// @brief Direct (non-abstract) access to package data
const PackageItem& packageData( int r ) const { return m_packages[ r ]; }
/// @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;
/** @brief Does a name lookup (based on id) and returns the netinstall data
*
* If there is a package with an id in @p ids, returns their netinstall data
*
* returns a list of netinstall data or an emply list if none is found
*/
QVariantList getNetinstallDataForNames( const QStringList& ids ) const;
enum Roles : int
{
NameRole = Qt::DisplayRole,
DescriptionRole = Qt::UserRole,
ScreenshotRole,
IdRole
};
private:
PackageList m_packages;
};
namespace YAML
{
class Node;
} // namespace YAML
class PackageNetModel : public QAbstractItemModel
{
Q_OBJECT
public:
// Names for columns (unused in the code)
static constexpr const int NameColumn = 0;
static constexpr const int DescriptionColumn = 1;
/* The only interesting roles are DisplayRole (with text depending
* on the column, and MetaExpandRole which tells if an index
* should be initially expanded.
*/
static constexpr const int MetaExpandRole = Qt::UserRole + 1;
explicit PackageNetModel( QObject* parent = nullptr );
~PackageNetModel() override;
void setupModelData( const QVariantList& l );
QVariant data( const QModelIndex& index, int role ) const override;
bool setData( const QModelIndex& index, const QVariant& value, int role = Qt::EditRole ) override;
Qt::ItemFlags flags( const QModelIndex& index ) const override;
QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const override;
QModelIndex parent( const QModelIndex& index ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
int rowCount( const QModelIndex& parent = QModelIndex() ) const override;
int columnCount( const QModelIndex& parent = QModelIndex() ) const override;
/** @brief Sets the checked flag on matching groups in the tree
*
* Recursively traverses the tree pointed to by m_rootItem and
* checks if a group name matches any of the items in @p selectNames.
* If a match is found, set check the box for that group and it's children.
*
* Individual packages will not be matched.
*
*/
void setSelections( const QStringList& selectNames );
PackageTreeItem::List getPackages() const;
PackageTreeItem::List getItemPackages( PackageTreeItem* item ) const;
/** @brief Appends groups to the tree
*
* Uses the data from @p groupList to add elements to the
* existing tree that m_rootItem points to. If m_rootItem
* is not valid, it does nothing
*
* Before adding anything to the model, it ensures that there
* is no existing data from the same source. If there is, that
* data is pruned first
*
*/
void appendModelData( const QVariantList& groupList );
private:
friend class ItemTests;
void setupModelData( const QVariantList& l, PackageTreeItem* parent );
PackageTreeItem* m_rootItem = nullptr;
PackageTreeItem::List m_hiddenItems;
};
#endif

View File

@@ -1,311 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <kyle@aims.ac.za>
* SPDX-FileCopyrightText: 2017 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageTreeItem.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
/** @brief Should a package be selected, given its parent's state? */
static Qt::CheckState
parentCheckState( PackageTreeItem* parent )
{
if ( parent )
{
// Avoid partially-checked .. a package can't be partial
return parent->isSelected() == Qt::Unchecked ? Qt::Unchecked : Qt::Checked;
}
else
{
return Qt::Unchecked;
}
}
/** @brief Should a subgroup be marked critical?
*
* If set explicitly, then use that, otherwise use the parent's critical-ness.
*/
static bool
parentCriticality( const QVariantMap& groupData, PackageTreeItem* parent )
{
if ( groupData.contains( "critical" ) )
{
return Calamares::getBool( groupData, "critical", false );
}
return parent ? parent->isCritical() : false;
}
PackageTreeItem::PackageTreeItem( const QString& packageName, PackageTreeItem* parent )
: m_parentItem( parent )
, m_packageName( packageName )
, m_selected( parentCheckState( parent ) )
, m_isGroup( false )
, m_isCritical( parent ? parent->isCritical() : false )
, m_showReadOnly( parent ? parent->isImmutable() : false )
, m_showNoncheckable( false )
{
}
PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, PackageTag&& parent )
: m_parentItem( parent.parent )
, m_packageName( Calamares::getString( groupData, "name" ) )
, m_selected( parentCheckState( parent.parent ) )
, m_description( Calamares::getString( groupData, "description" ) )
, m_isGroup( false )
, m_isCritical( parent.parent ? parent.parent->isCritical() : false )
, m_showReadOnly( parent.parent ? parent.parent->isImmutable() : false )
, m_showNoncheckable( false )
{
}
PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, GroupTag&& parent )
: m_parentItem( parent.parent )
, m_name( Calamares::getString( groupData, "name" ) )
, m_selected( parentCheckState( parent.parent ) )
, m_description( Calamares::getString( groupData, "description" ) )
, m_preScript( Calamares::getString( groupData, "pre-install" ) )
, m_postScript( Calamares::getString( groupData, "post-install" ) )
, m_source( Calamares::getString( groupData, "source" ) )
, m_isGroup( true )
, m_isCritical( parentCriticality( groupData, parent.parent ) )
, m_isHidden( Calamares::getBool( groupData, "hidden", false ) )
, m_showReadOnly( Calamares::getBool( groupData, "immutable", false ) )
, m_showNoncheckable( Calamares::getBool( groupData, "noncheckable", false ) )
, m_startExpanded( Calamares::getBool( groupData, "expanded", false ) )
{
}
PackageTreeItem::PackageTreeItem::PackageTreeItem()
: m_parentItem( nullptr )
, m_name( QStringLiteral( "<root>" ) )
, m_selected( Qt::Checked )
, m_isGroup( true )
{
}
PackageTreeItem::~PackageTreeItem()
{
qDeleteAll( m_childItems );
}
void
PackageTreeItem::appendChild( PackageTreeItem* child )
{
m_childItems.append( child );
}
PackageTreeItem*
PackageTreeItem::child( int row )
{
return m_childItems.value( row );
}
int
PackageTreeItem::childCount() const
{
return m_childItems.count();
}
int
PackageTreeItem::row() const
{
if ( m_parentItem )
{
return m_parentItem->m_childItems.indexOf( const_cast< PackageTreeItem* >( this ) );
}
return 0;
}
QVariant
PackageTreeItem::data( int column ) const
{
switch ( column )
{
case 0:
// packages have a packagename, groups don't
return QVariant( isPackage() ? packageName() : name() );
case 1:
// packages often have a blank description
return QVariant( description() );
default:
return QVariant();
}
}
PackageTreeItem*
PackageTreeItem::parentItem()
{
return m_parentItem;
}
const PackageTreeItem*
PackageTreeItem::parentItem() const
{
return m_parentItem;
}
bool
PackageTreeItem::hiddenSelected() const
{
if ( !m_isHidden )
{
return m_selected != Qt::Unchecked;
}
if ( m_selected == Qt::Unchecked )
{
return false;
}
const PackageTreeItem* currentItem = parentItem();
while ( currentItem != nullptr )
{
if ( !currentItem->isHidden() )
{
return currentItem->isSelected() != Qt::Unchecked;
}
currentItem = currentItem->parentItem();
}
/* Has no non-hidden parents */
return m_selected != Qt::Unchecked;
}
void
PackageTreeItem::setSelected( Qt::CheckState isSelected )
{
if ( parentItem() == nullptr )
{
// This is the root, it is always checked so don't change state
return;
}
m_selected = isSelected;
setChildrenSelected( isSelected );
// Look for suitable parent item which may change checked-state
// when one of its children changes.
PackageTreeItem* currentItem = parentItem();
while ( ( currentItem != nullptr ) && ( currentItem->childCount() == 0 ) )
{
currentItem = currentItem->parentItem();
}
if ( currentItem == nullptr )
{
// Reached the root .. don't bother
return;
}
currentItem->updateSelected();
}
void
PackageTreeItem::updateSelected()
{
// Figure out checked-state based on the children
int childrenSelected = 0;
int childrenPartiallySelected = 0;
for ( int i = 0; i < childCount(); i++ )
{
if ( child( i )->isSelected() == Qt::Checked )
{
childrenSelected++;
}
if ( child( i )->isSelected() == Qt::PartiallyChecked )
{
childrenPartiallySelected++;
}
}
if ( !childrenSelected && !childrenPartiallySelected )
{
setSelected( Qt::Unchecked );
}
else if ( childrenSelected == childCount() )
{
setSelected( Qt::Checked );
}
else
{
setSelected( Qt::PartiallyChecked );
}
}
void
PackageTreeItem::setChildrenSelected( Qt::CheckState isSelected )
{
if ( isSelected != Qt::PartiallyChecked )
{
// Children are never root; don't need to use setSelected on them.
for ( auto child : m_childItems )
{
child->m_selected = isSelected;
child->setChildrenSelected( isSelected );
}
}
}
void
PackageTreeItem::removeChild( int row )
{
if ( 0 <= row && row < m_childItems.count() )
{
m_childItems.removeAt( row );
}
else
{
cWarning() << "Attempt to remove invalid child in removeChild() at row " << row;
}
}
int
PackageTreeItem::type() const
{
return QStandardItem::UserType;
}
QVariant
PackageTreeItem::toOperation() const
{
// If it's a package with a pre- or post-script, replace
// with the more complicated datastructure.
if ( !m_preScript.isEmpty() || !m_postScript.isEmpty() )
{
QMap< QString, QVariant > sdetails;
sdetails.insert( "pre-script", m_preScript );
sdetails.insert( "package", m_packageName );
sdetails.insert( "post-script", m_postScript );
return sdetails;
}
else
{
return m_packageName;
}
}
bool
PackageTreeItem::operator==( const PackageTreeItem& rhs ) const
{
if ( isGroup() != rhs.isGroup() )
{
// Different kinds
return false;
}
if ( isGroup() )
{
return name() == rhs.name() && description() == rhs.description() && preScript() == rhs.preScript()
&& postScript() == rhs.postScript() && isCritical() == rhs.isCritical() && isHidden() == rhs.isHidden()
&& m_showReadOnly == rhs.m_showReadOnly && expandOnStart() == rhs.expandOnStart();
}
else
{
return packageName() == rhs.packageName();
}
}

View File

@@ -1,179 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <kyle@aims.ac.za>
* SPDX-FileCopyrightText: 2017 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGETREEITEM_H
#define PACKAGETREEITEM_H
#include <QList>
#include <QStandardItem>
#include <QVariant>
class PackageTreeItem : public QStandardItem
{
public:
using List = QList< PackageTreeItem* >;
///@brief A tag class to distinguish package-from-map from group-from-map
struct PackageTag
{
PackageTreeItem* parent;
};
///@brief A tag class to distinguish group-from-map from package-from-map
struct GroupTag
{
PackageTreeItem* parent;
};
///@brief A package (individual package)
explicit PackageTreeItem( const QString& packageName, PackageTreeItem* parent = nullptr );
///@brief A package (individual package with description)
explicit PackageTreeItem( const QVariantMap& packageData, PackageTag&& parent );
///@brief A group (sub-items and sub-groups are ignored)
explicit PackageTreeItem( const QVariantMap& groupData, GroupTag&& parent );
///@brief A root item, always selected, named "<root>"
explicit PackageTreeItem();
~PackageTreeItem() override;
void appendChild( PackageTreeItem* child );
PackageTreeItem* child( int row );
int childCount() const;
QVariant data( int column ) const override;
int row() const;
PackageTreeItem* parentItem();
const PackageTreeItem* parentItem() const;
QString name() const { return m_name; }
QString packageName() const { return m_packageName; }
QString description() const { return m_description; }
QString preScript() const { return m_preScript; }
QString postScript() const { return m_postScript; }
QString source() const { return m_source; }
/** @brief Is this item a group-item?
*
* Groups have a (possibly empty) list of packages, and a
* (possibly empty) list of sub-groups, and can be marked
* critical, hidden, etc. Packages, on the other hand, only
* have a meaningful packageName() and selection status.
*
* Root is a group.
*/
bool isGroup() const { return m_isGroup; }
/// @brief Is this item a single package?
bool isPackage() const { return !isGroup(); }
/** @brief Is this item hidden?
*
* Hidden items (generally only groups) are maintained separately,
* not shown to the user, but do enter into the package-installation process.
*/
bool isHidden() const { return m_isHidden; }
/** @brief Is this hidden item, considered "selected"?
*
* This asserts when called on a non-hidden item.
* A hidden item has its own selected state, but really
* falls under the selectedness of the parent item.
*/
bool hiddenSelected() const;
/** @brief Is this group critical?
*
* A critical group must be successfully installed, for the Calamares
* installation to continue.
*/
bool isCritical() const { return m_isCritical; }
/** @brief Is this group expanded on start?
*
* This does not affect installation, only the UI. A group
* that expands on start is shown expanded (not collapsed)
* in the treeview when the page is loaded.
*/
bool expandOnStart() const { return m_startExpanded; }
/** @brief Is this an immutable item?
*
* Groups can be immutable: then you can't toggle the selected
* state of any of its items.
*/
bool isImmutable() const { return m_showReadOnly; }
/** @brief Is this a non-checkable item?
*
* Groups can be non-checkable: then you can't toggle the selected
* state of the group. This does not affect subgroups or packages.
*/
bool isNoncheckable() const { return m_showNoncheckable; }
/** @brief is this item selected?
*
* Groups may be partially selected; packages are only on or off.
*/
Qt::CheckState isSelected() const { return m_selected; }
/** @brief Turns this item into a variant for PackageOperations use
*
* For "plain" items, this is just the package name; items with
* scripts return a map. See the package module for how it's interpreted.
*/
QVariant toOperation() const;
void setSelected( Qt::CheckState isSelected );
void setChildrenSelected( Qt::CheckState isSelected );
void removeChild( int row );
/** @brief Update selectedness based on the children's states
*
* This only makes sense for groups, which might have packages
* or subgroups; it checks only direct children.
*/
void updateSelected();
// QStandardItem methods
int type() const override;
/** @brief Are two items equal
*
* This **disregards** parent-item and the child-items, and compares
* only the fields for the items-proper (name, .. expanded). Note
* also that *isSelected()* is a run-time state, and is **not**
* compared either.
*/
bool operator==( const PackageTreeItem& rhs ) const;
bool operator!=( const PackageTreeItem& rhs ) const { return !( *this == rhs ); }
private:
PackageTreeItem* m_parentItem;
List m_childItems;
// An entry can be a package, or a group.
QString m_name;
QString m_packageName;
Qt::CheckState m_selected = Qt::Unchecked;
// These are only useful for groups
QString m_description;
QString m_preScript;
QString m_postScript;
QString m_source;
bool m_isGroup = false;
bool m_isCritical = false;
bool m_isHidden = false;
bool m_showReadOnly = false;
bool m_showNoncheckable = false;
bool m_startExpanded = false;
};
#endif // PACKAGETREEITEM_H

View File

@@ -1,84 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Tests.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#endif
#include "PackageModel.h"
#include "utils/Logger.h"
#include <QtTest/QtTest>
QTEST_MAIN( PackageChooserTests )
PackageChooserTests::PackageChooserTests() {}
PackageChooserTests::~PackageChooserTests() {}
void
PackageChooserTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
}
void
PackageChooserTests::testBogus()
{
QVERIFY( true );
}
void
PackageChooserTests::testAppData()
{
// Path from the build-dir and from the running-the-test varies,
// for in-source build, for build/, and for tests-in-build/,
// so look in multiple places.
QString appdataName( "io.calamares.calamares.appdata.xml" );
for ( const auto& prefix : QStringList { "", "../", "../../../", "../../../../" } )
{
if ( QFile::exists( prefix + appdataName ) )
{
appdataName = prefix + appdataName;
break;
}
}
QVERIFY( QFile::exists( appdataName ) );
QVariantMap m;
m.insert( "appdata", appdataName );
#ifdef HAVE_XML
PackageItem p1 = fromAppData( m );
QVERIFY( p1.isValid() );
QCOMPARE( p1.id, QStringLiteral( "io.calamares.calamares.desktop" ) );
QCOMPARE( p1.name.get(), QStringLiteral( "Calamares" ) );
// The <description> entry has precedence
QCOMPARE( p1.description.get(), QStringLiteral( "Calamares is an installer program for Linux distributions." ) );
// .. but en_GB doesn't have an entry in description, so uses <summary>
QCOMPARE( p1.description.get( QLocale( "en_GB" ) ), QStringLiteral( "Calamares Linux Installer" ) );
QCOMPARE( p1.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( p1.screenshot.isNull() );
m.insert( "id", "calamares" );
m.insert( "screenshot", ":/images/calamares.png" );
PackageItem p2 = fromAppData( m );
QVERIFY( p2.isValid() );
QCOMPARE( p2.id, QStringLiteral( "calamares" ) );
QCOMPARE( p2.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( !p2.screenshot.isNull() );
#endif
}

View File

@@ -1,28 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERTESTS_H
#define PACKAGECHOOSERTESTS_H
#include <QObject>
class PackageChooserTests : public QObject
{
Q_OBJECT
public:
PackageChooserTests();
~PackageChooserTests() override;
private Q_SLOTS:
void initTestCase();
void testBogus();
void testAppData();
};
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,2 +0,0 @@
SPDX-FileCopyrightText: 2014 Uri Herrera <uri_herrera@nitrux.in> and others
SPDX-License-Identifier: LGPL-3.0-or-later

View File

@@ -1,172 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Configuration for the low-density software chooser
---
# 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 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>*. 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*.
#
# 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.
#
# - "netinstall-select"
# When this is set, the id(s) selected are passed to the netinstall module.
# Any id that matches a group name in that module is set to checked
#
# - "netinstall-add"
# With this method, the packagechooser module is used to add groups to the
# netinstall module. For this to have any effect. You must set netinstall,
# which is described below.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
# module, in the `exec` section to do the actual work.
method: legacy
# 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)
#
# Each key can have a [locale] added to it, which is used as
# the translated string for that locale. For the strings
# associated with the "no-selection" item, see *items*, below
# with the explicit item-*id* "".
#
labels:
step: "Packages"
step[nl]: "Pakketten"
# (Optional) item-*id* of pre-selected list-view item.
# Pre-selects one of the items below.
# default: kde
# Items to display in the chooser. In general, this should be a
# pretty short list to avoid overwhelming the UI. This is a list
# of objects, and the items are displayed in list order.
#
# Either provide the data for an item in the list (using the keys
# below), or use existing AppData XML files, or use AppStream cache
# 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 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. The name and description given for the "no package
# selected" item are displayed when the module starts.
#
# 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". If the path
# is not found (e.g. is a non-existent absolute path, or is a relative
# path that does not exist in the current working directory) then
# an additional attempt is made to load the image from the **branding**
# directory.
#
# The following fields are **optional** for an item:
#
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# - *netinstall* :
# The data in this field should follow the format of a group
# from the netinstall module documented in
# src/modules/netinstall/netinstall.conf. This is only used
# when method is set to "netinstall-add"
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*
# key which points to an AppData XML file in the local filesystem.
# This file is parsed to provide the id (from AppData id), name
# (from AppData name), description (from AppData description paragraphs
# or the summary entries), and a screenshot (the default screenshot
# from AppData). No package is set (but that is unused anyway).
#
# AppData may contain IDs that are not useful inside Calamares,
# and the screenshot URL may be remote -- a remote URL will not
# be loaded and the screenshot will be missing. An item with *appdata*
# **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
# cache for that ID. The package name is set from the AppStream data.
#
# An item for AppStream may also contain an *id* and a *screenshot*
# key which will override the data from AppStream.
items:
- id: ""
# packages: [] # This item installs no packages
name: "No 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[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"
- id: kde
packages: [ kde-frameworks, kde-plasma, kde-gear ]
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."
screenshot: ":/images/kde.png"
- id: gnome
packages: [ gnome-all ]
name: GNOME
description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png"
- id: calamares
appdata: ../io.calamares.calamares.appdata.xml
screenshot: ":/images/calamares.png"
- id: kate
appstream: org.kde.kwrite.desktop

View File

@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/">
<file>images/if.png</file>
</qresource>
</RCC>

View File

@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>
SPDX-FileCopyrightText: 2019 Adriaan de Groot &lt;groot@kde.org&gt;
SPDX-License-Identifier: GPL-3.0-or-later
</author>
<class>PackageChooserPage</class>
<widget class="QWidget" name="PackageChooserPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<item>
<widget class="QListView" name="products">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,30,1">
<item>
<widget class="QLabel" name="productName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Product Name</string>
</property>
</widget>
</item>
<item>
<widget class="FixedAspectRatioLabel" name="productScreenshot">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="productDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Long Product Description</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FixedAspectRatioLabel</class>
<extends>QLabel</extends>
<header>widgets/FixedAspectRatioLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -1,15 +0,0 @@
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
calamares_add_plugin( unpackfsc
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
UnpackFSCJob.cpp
# The workers for differently-packed filesystems
Runners.cpp
FSArchiverRunner.cpp
TarballRunner.cpp
UnsquashRunner.cpp
SHARED_LIB
)

View File

@@ -1,117 +0,0 @@
/* === 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 "FSArchiverRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <QProcess>
static constexpr const int chunk_size = 137;
static const QString&
toolName()
{
static const QString name = QStringLiteral( "fsarchiver" );
return name;
}
void
FSArchiverRunner::fsarchiverProgress( QString line )
{
m_since++;
// Typical line of output is this:
// -[00][ 99%][REGFILEM] /boot/thing
// 5 9 ^21
if ( m_since >= chunk_size && line.length() > 21 && line[ 5 ] == '[' && line[ 9 ] == '%' )
{
m_since = 0;
double p = double( line.mid( 6, 3 ).toInt() ) / 100.0;
const QString filename = line.mid( 22 );
Q_EMIT progress( p, filename );
}
}
Calamares::JobResult
FSArchiverRunner::checkPrerequisites( QString& fsarchiverExecutable ) const
{
if ( !checkToolExists( toolName(), fsarchiverExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName() ),
Calamares::JobResult::MissingRequirements );
}
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid fsarchiver configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
return Calamares::JobResult::ok();
}
Calamares::JobResult
FSArchiverRunner::checkDestination( QString& destinationPath ) const
{
destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid fsarchiver configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
return Calamares::JobResult::ok();
}
Calamares::JobResult
FSArchiverDirRunner::run()
{
QString fsarchiverExecutable;
if ( auto res = checkPrerequisites( fsarchiverExecutable ); !res )
{
return res;
}
QString destinationPath;
if ( auto res = checkDestination( destinationPath ); !res )
{
return res;
}
Calamares::Utils::Runner r(
{ fsarchiverExecutable, QStringLiteral( "-v" ), QStringLiteral( "restdir" ), m_source, destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &FSArchiverDirRunner::fsarchiverProgress );
return r.run().explainProcess( toolName(), std::chrono::seconds( 0 ) );
}
Calamares::JobResult
FSArchiverFSRunner::run()
{
QString fsarchiverExecutable;
if ( auto res = checkPrerequisites( fsarchiverExecutable ); !res )
{
return res;
}
QString destinationPath;
if ( auto res = checkDestination( destinationPath ); !res )
{
return res;
}
Calamares::Utils::Runner r(
{ fsarchiverExecutable, QStringLiteral( "-v" ), QStringLiteral( "restfs" ), m_source, destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &FSArchiverFSRunner::fsarchiverProgress );
return r.run().explainProcess( toolName(), std::chrono::seconds( 0 ) );
}

View File

@@ -1,59 +0,0 @@
/* === 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 UNPACKFSC_FSARCHIVERRUNNER_H
#define UNPACKFSC_FSARCHIVERRUNNER_H
#include "Runners.h"
/** @brief Base class for runners of FSArchiver
*
*/
class FSArchiverRunner : public Runner
{
Q_OBJECT
public:
using Runner::Runner;
protected Q_SLOTS:
void fsarchiverProgress( QString line );
protected:
/** @brief Checks prerequisites, sets full path of fsarchiver in @p executable
*/
Calamares::JobResult checkPrerequisites( QString& executable ) const;
Calamares::JobResult checkDestination( QString& destinationPath ) const;
int m_since = 0;
};
/** @brief Running FSArchiver in **dir** mode
*
*/
class FSArchiverDirRunner : public FSArchiverRunner
{
public:
using FSArchiverRunner::FSArchiverRunner;
Calamares::JobResult run() override;
};
/** @brief Running FSArchiver in **dir** mode
*
*/
class FSArchiverFSRunner : public FSArchiverRunner
{
public:
using FSArchiverRunner::FSArchiverRunner;
Calamares::JobResult run() override;
};
#endif

View File

@@ -1,38 +0,0 @@
/* === 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 "Runners.h"
#include <utils/CalamaresUtilsSystem.h>
#include <utils/Logger.h>
#include <QFileInfo>
#include <QStandardPaths>
Runner::Runner( const QString& source, const QString& destination )
: m_source( source )
, m_destination( destination )
{
}
Runner::~Runner() { }
bool
Runner::checkSourceExists() const
{
QFileInfo fi( m_source );
return fi.exists() && fi.isReadable();
}
bool
Runner::checkToolExists( const QString& toolName, QString& fullPath )
{
fullPath = QStandardPaths::findExecutable( toolName );
return !fullPath.isEmpty();
}

View File

@@ -1,48 +0,0 @@
/* === 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 UNPACKFSC_RUNNERS_H
#define UNPACKFSC_RUNNERS_H
#include <Job.h>
class Runner : public QObject
{
Q_OBJECT
public:
Runner( const QString& source, const QString& destination );
~Runner() override;
virtual Calamares::JobResult run() = 0;
/** @brief Check that the (configured) source file exists.
*
* Returns @c true if it's a file and readable.
*/
bool checkSourceExists() const;
/** @brief Check that a named tool (executable) exists in the search path.
*
* Returns @c true if the tool is found and sets @p fullPath
* to the full path of that tool; returns @c false and clears
* @p fullPath otherwise.
*/
static bool checkToolExists( const QString& toolName, QString& fullPath );
Q_SIGNALS:
// See Calamares Job::progress
void progress( qreal percent, const QString& message );
protected:
QString m_source;
QString m_destination;
};
#endif

View File

@@ -1,86 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "TarballRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <utils/String.h>
#include <QString>
static constexpr const int chunk_size = 107;
Calamares::JobResult
TarballRunner::run()
{
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid tarball configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
const QString toolName = QStringLiteral( "tar" );
QString tarExecutable;
if ( !checkToolExists( toolName, tarExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName ),
Calamares::JobResult::MissingRequirements );
}
const QString destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid tarball configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
// Get the stats (number of inodes) from the FS
{
m_total = 0;
Calamares::Utils::Runner r( { tarExecutable, QStringLiteral( "-tf" ), m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
QObject::connect( &r, &decltype( r )::output, [ & ]( QString line ) { m_total++; } );
/* ignored */ r.run();
}
if ( m_total <= 0 )
{
cWarning() << "No stats could be obtained from" << tarExecutable << "-tf" << m_source;
}
// Now do the actual unpack
{
m_processed = 0;
m_since = 0;
Calamares::Utils::Runner r(
{ tarExecutable, QStringLiteral( "-xpvf" ), m_source, QStringLiteral( "-C" ), destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &TarballRunner::tarballProgress );
return r.run().explainProcess( toolName, std::chrono::seconds( 0 ) );
}
}
void
TarballRunner::tarballProgress( QString line )
{
m_processed++;
m_since++;
if ( m_since > chunk_size )
{
m_since = 0;
double p = m_total > 0 ? ( double( m_processed ) / double( m_total ) ) : 0.5;
Q_EMIT progress( p, tr( "Tarball extract file %1" ).arg( line ) );
}
}

View File

@@ -1,35 +0,0 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_TARBALLRUNNER_H
#define UNPACKFSC_TARBALLRUNNER_H
#include "Runners.h"
/** @brief Use (GNU) tar for extracting a filesystem
*
*/
class TarballRunner : public Runner
{
public:
using Runner::Runner;
Calamares::JobResult run() override;
protected Q_SLOTS:
void tarballProgress( QString line );
private:
// Progress reporting
int m_total = 0;
int m_processed = 0;
int m_since = 0;
};
#endif

View File

@@ -1,130 +0,0 @@
/* === 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 "UnpackFSCJob.h"
#include "FSArchiverRunner.h"
#include "TarballRunner.h"
#include "UnsquashRunner.h"
#include <utils/Logger.h>
#include <utils/NamedEnum.h>
#include <utils/RAII.h>
#include <utils/Variant.h>
#include <memory>
static const NamedEnumTable< UnpackFSCJob::Type >
typeNames()
{
using T = UnpackFSCJob::Type;
// clang-format off
static const NamedEnumTable< T > names
{
{ "none", T::None },
{ "fsarchiver", T::FSArchive },
{ "fsarchive", T::FSArchive },
{ "fsa", T::FSArchive },
{ "fsa-dir", T::FSArchive },
{ "fsa-block", T::FSArchiveFS },
{ "fsa-fs", T::FSArchiveFS },
{ "squashfs", T::Squashfs },
{ "squash", T::Squashfs },
{ "unsquash", T::Squashfs },
{ "tar", T::Tarball },
{ "tarball", T::Tarball },
{ "tgz", T::Tarball },
};
// clang-format on
return names;
}
UnpackFSCJob::UnpackFSCJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
UnpackFSCJob::~UnpackFSCJob() { }
QString
UnpackFSCJob::prettyName() const
{
return tr( "Unpack filesystems" );
}
QString
UnpackFSCJob::prettyStatusMessage() const
{
return m_progressMessage;
}
Calamares::JobResult
UnpackFSCJob::exec()
{
cScopedAssignment messageClearer( &m_progressMessage, QString() );
std::unique_ptr< Runner > r;
switch ( m_type )
{
case Type::FSArchive:
r = std::make_unique< FSArchiverDirRunner >( m_source, m_destination );
break;
case Type::FSArchiveFS:
r = std::make_unique< FSArchiverFSRunner >( m_source, m_destination );
break;
case Type::Squashfs:
r = std::make_unique< UnsquashRunner >( m_source, m_destination );
break;
case Type::Tarball:
r = std::make_unique< TarballRunner >( m_source, m_destination );
break;
case Type::None:
default:
cDebug() << "Nothing to do.";
return Calamares::JobResult::ok();
}
connect( r.get(),
&Runner::progress,
[ = ]( qreal percent, const QString& message )
{
m_progressMessage = message;
Q_EMIT progress( percent );
} );
return r->run();
}
void
UnpackFSCJob::setConfigurationMap( const QVariantMap& map )
{
QString source = CalamaresUtils::getString( map, "source" );
QString sourceTypeName = CalamaresUtils::getString( map, "sourcefs" );
if ( source.isEmpty() || sourceTypeName.isEmpty() )
{
cWarning() << "Skipping item with bad source data:" << map;
return;
}
bool bogus = false;
Type sourceType = typeNames().find( sourceTypeName, bogus );
if ( sourceType == Type::None )
{
cWarning() << "Skipping item with source type None";
return;
}
QString destination = CalamaresUtils::getString( map, "destination" );
if ( destination.isEmpty() )
{
cWarning() << "Skipping item with empty destination";
return;
}
m_source = source;
m_destination = destination;
m_type = sourceType;
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( UnpackFSCFactory, registerPlugin< UnpackFSCJob >(); )

View File

@@ -1,50 +0,0 @@
/* === 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 UNPACKFSC_UNPACKFSCJOB_H
#define UNPACKFSC_UNPACKFSCJOB_H
#include <CppJob.h>
#include <DllMacro.h>
#include <utils/PluginFactory.h>
class PLUGINDLLEXPORT UnpackFSCJob : public Calamares::CppJob
{
Q_OBJECT
public:
enum class Type
{
None, /// << Invalid
FSArchive,
FSArchiveFS,
Squashfs,
Tarball,
};
explicit UnpackFSCJob( QObject* parent = nullptr );
~UnpackFSCJob() override;
QString prettyName() const override;
QString prettyStatusMessage() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
QString m_source;
QString m_destination;
Type m_type = Type::None;
QString m_progressMessage;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( UnpackFSCFactory )
#endif

View File

@@ -1,101 +0,0 @@
/* === 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 "UnsquashRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <utils/String.h>
#include <QString>
static constexpr const int chunk_size = 107;
Calamares::JobResult
UnsquashRunner::run()
{
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid unsquash configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
const QString toolName = QStringLiteral( "unsquashfs" );
QString unsquashExecutable;
if ( !checkToolExists( toolName, unsquashExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName ),
Calamares::JobResult::MissingRequirements );
}
const QString destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid unsquash configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
// Get the stats (number of inodes) from the FS
{
m_inodes = -1;
Calamares::Utils::Runner r( { unsquashExecutable, QStringLiteral( "-s" ), m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
QObject::connect( &r,
&decltype( r )::output,
[ & ]( QString line )
{
if ( line.startsWith( "Number of inodes " ) )
{
m_inodes = line.split( ' ', SplitSkipEmptyParts ).last().toInt();
}
} );
/* ignored */ r.run();
}
if ( m_inodes <= 0 )
{
cWarning() << "No stats could be obtained from" << unsquashExecutable << "-s";
}
// Now do the actual unpack
{
m_processed = 0;
Calamares::Utils::Runner r( { unsquashExecutable,
QStringLiteral( "-i" ), // List files
QStringLiteral( "-f" ), // Force-overwrite
QStringLiteral( "-d" ),
destinationPath,
m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &UnsquashRunner::unsquashProgress );
return r.run().explainProcess( toolName, std::chrono::seconds( 0 ) );
}
}
void
UnsquashRunner::unsquashProgress( QString line )
{
m_processed++;
m_since++;
if ( m_since > chunk_size && line.contains( '/' ) )
{
const QString filename = line.split( '/', SplitSkipEmptyParts ).last().trimmed();
if ( !filename.isEmpty() )
{
m_since = 0;
double p = m_inodes > 0 ? ( double( m_processed ) / double( m_inodes ) ) : 0.5;
Q_EMIT progress( p, tr( "Unsquash file %1" ).arg( filename ) );
}
}
}

View File

@@ -1,36 +0,0 @@
/* === 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 UNPACKFSC_UNSQUASHRUNNER_H
#define UNPACKFSC_UNSQUASHRUNNER_H
#include "Runners.h"
/** @brief Use Unsquash for extracting a filesystem
*
*/
class UnsquashRunner : public Runner
{
public:
using Runner::Runner;
Calamares::JobResult run() override;
protected Q_SLOTS:
void unsquashProgress( QString line );
private:
int m_inodes = 0; // Total in the FS
// Progress reporting
int m_processed = 0;
int m_since = 0;
};
#endif

View File

@@ -1,2 +0,0 @@
---
rootMountPoint: /tmp/fstest

View File

@@ -1,4 +0,0 @@
---
source: /tmp/src.fsa
sourcefs: fsarchive
destination: "/calasrc"

View File

@@ -1,39 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Unpack a filesystem. Supported ways to "pack" the filesystem are:
# - fsarchiver in *savedir/restdir* mode (directories, not block devices)
# - squashfs
#
# Configuration:
#
# from globalstorage: rootMountPoint
# from job configuration: the item to unpack
#
---
# This module is configured a lot like the items in the *unpackfs*
# module, but with only **one** item. Use multiple instances for
# unpacking more than one filesystem.
#
# There are the following **mandatory** keys:
# - *source* path relative to the live / intstalling system to the image
# - *sourcefs* the type of the source files; valid entries are
# - `none` (this entry is ignored; kind of useless)
# - `fsarchiver`
# Aliases of this are `fsarchive`, `fsa` and `fsa-dir`. Uses
# fsarchiver in "restdir" mode.
# - `fsarchiver-block`
# Aliases of this are `fsa-block` and `fsa-fs`. Uses fsarchiver
# in "restfs" mode.
# - `squashfs`
# Aliases of this are `squash` and `unsquash`.
# - `tar`
# - *destination* path relative to rootMountPoint (so in the target
# system) where this filesystem is unpacked. It may be an
# empty string, which effectively is / (the root) of the target
# system.
#
source: /data/rootfs.fsa
sourcefs: fsarchiver
destination: "/"

View File

@@ -1,19 +0,0 @@
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
---
$schema: https://json-schema.org/schema#
$id: https://calamares.io/schemas/unpackfsc
additionalProperties: false
type: object
properties:
unpack:
type: array
items:
type: object
additionalProperties: false
properties:
source: { type: string }
sourcefs: { type: string }
destination: { type: string }
weight: { type: integer, exclusiveMinimum: 0 }
required: [ source , sourcefs, destination ]