Merge branch 'issue-1287' into calamares

Major update to the 'tracking' module with improved wording,
functionality, QML-preparation, and a working kuserfeedback mode.

FIXES #1287
This commit is contained in:
Adriaan de Groot 2020-06-18 13:39:22 +02:00
commit 0305476f8a
17 changed files with 877 additions and 315 deletions

View File

@ -26,6 +26,7 @@ ReflowComments: "false"
SortIncludes: "true"
SpaceAfterCStyleCast: "false"
SpacesBeforeTrailingComments: "2"
# SpaceInEmptyBlock: "true"
SpacesInAngles: "true"
SpacesInParentheses: "true"
SpacesInSquareBrackets: "true"

View File

@ -6,6 +6,7 @@ website will have to do for older versions.
# 3.2.26 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Anke Boersma
- Gaël PORTAY
- Pablo Ovelleiro Corral
- Philip Müller
@ -26,6 +27,8 @@ This release contains contributions from (alphabetically by first name):
UTC+11, but Calamares still showed it in a zone separate from UTC+11.
- *packages* gained support for the Void Linux package manager,
*xbps*. (thanks Pablo)
- *tracking* now supports kuserfeedback configuration.
# 3.2.25 (2020-06-06) #

View File

@ -13,7 +13,7 @@ export LANG LC_ALL LC_NUMERIC
AS=$( which astyle )
CF_VERSIONS="clang-format-7 clang-format-8 clang-format70 clang-format80 clang-format"
CF_VERSIONS="clang-format-7 clang-format-8 clang-format70 clang-format80 clang-format-9.0.1 clang-format"
for _cf in $CF_VERSIONS
do
# Not an error if this particular clang-format isn't found
@ -26,6 +26,8 @@ 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 ; }
expr `"$CF" --version | tr -dc '[^.0-9]' | cut -d . -f 1` '<' 10 > /dev/null || { echo "! $CF is version 10 or later, needs different .clang-format" ; exit 1 ; }
set -e
any_dirs=no

View File

@ -1,5 +1,5 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
*
* SPDX-FileCopyrightText: 2013-2016 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
*
@ -65,6 +65,20 @@ getString( const QVariantMap& map, const QString& key )
return QString();
}
QStringList
getStringList( const QVariantMap& map, const QString& key )
{
if ( map.contains( key ) )
{
auto v = map.value( key );
if ( v.type() == QVariant::StringList )
{
return v.toStringList();
}
}
return QStringList();
}
qint64
getInteger( const QVariantMap& map, const QString& key, qint64 d )
{

View File

@ -1,5 +1,5 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
*
* SPDX-FileCopyrightText: 2013-2016 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
*
@ -43,6 +43,11 @@ DLLEXPORT bool getBool( const QVariantMap& map, const QString& key, bool d );
*/
DLLEXPORT QString getString( const QVariantMap& map, const QString& key );
/**
* Get a string list from a mapping; returns empty list if no value.
*/
DLLEXPORT QStringList getStringList( const QVariantMap& map, const QString& key );
/**
* Get an integer value from a mapping; returns @p d if no value.
*/

View File

@ -2,6 +2,7 @@ calamares_add_plugin( tracking
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
TrackingJobs.cpp
TrackingPage.cpp
TrackingViewStep.cpp

View File

@ -0,0 +1,256 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2020, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Config.h"
#include "TrackingType.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QUrl>
const NamedEnumTable< TrackingType >&
trackingNames()
{
// *INDENT-OFF*
// clang-format off
static const NamedEnumTable< TrackingType > names {
{ QStringLiteral( "none" ), TrackingType::NoTracking },
{ QStringLiteral( "install" ), TrackingType::InstallTracking },
{ QStringLiteral( "machine" ), TrackingType::MachineTracking },
{ QStringLiteral( "user" ), TrackingType::UserTracking }
};
// clang-format on
// *INDENT-ON*
return names;
}
TrackingStyleConfig::TrackingStyleConfig( QObject* parent )
: QObject( parent )
{
}
TrackingStyleConfig::~TrackingStyleConfig() {}
void
TrackingStyleConfig::setTracking( bool enabled )
{
setTracking( enabled ? EnabledByUser : DisabledByUser );
}
void
TrackingStyleConfig::setTracking( TrackingStyleConfig::TrackingState state )
{
if ( m_state != TrackingState::DisabledByConfig )
{
m_state = state;
}
emit trackingChanged();
}
void
TrackingStyleConfig::validate( QString& s, std::function< bool( const QString& ) >&& pred )
{
if ( !pred( s ) )
{
if ( m_state != DisabledByConfig )
{
cError() << "Configuration string" << s << "is not valid; disabling this tracking type.";
m_state = DisabledByConfig;
emit trackingChanged();
}
s = QString();
}
}
void
TrackingStyleConfig::validateUrl( QString& urlString )
{
if ( !QUrl( urlString ).isValid() )
{
if ( m_state != DisabledByConfig )
{
cError() << "URL" << urlString << "is not valid; disabling tracking type" << objectName();
m_state = DisabledByConfig;
emit trackingChanged();
}
urlString = QString();
}
}
void
TrackingStyleConfig::setConfigurationMap( const QVariantMap& config )
{
m_state = CalamaresUtils::getBool( config, "enabled", false ) ? DisabledByUser : DisabledByConfig;
m_policy = CalamaresUtils::getString( config, "policy" );
validateUrl( m_policy );
emit policyChanged( m_policy );
emit trackingChanged();
}
InstallTrackingConfig::InstallTrackingConfig( QObject* parent )
: TrackingStyleConfig( parent )
{
setObjectName( "InstallTrackingConfig" );
}
void
InstallTrackingConfig::setConfigurationMap( const QVariantMap& configurationMap )
{
TrackingStyleConfig::setConfigurationMap( configurationMap );
m_installTrackingUrl = CalamaresUtils::getString( configurationMap, "url" );
validateUrl( m_installTrackingUrl );
}
MachineTrackingConfig::MachineTrackingConfig( QObject* parent )
: TrackingStyleConfig( parent )
{
setObjectName( "MachineTrackingConfig" );
}
/** @brief Is @p s a valid machine-tracking style. */
static bool
isValidMachineTrackingStyle( const QString& s )
{
static QStringList knownStyles { "updatemanager" };
return knownStyles.contains( s );
}
void
MachineTrackingConfig::setConfigurationMap( const QVariantMap& configurationMap )
{
TrackingStyleConfig::setConfigurationMap( configurationMap );
m_machineTrackingStyle = CalamaresUtils::getString( configurationMap, "style" );
validate( m_machineTrackingStyle, isValidMachineTrackingStyle );
}
UserTrackingConfig::UserTrackingConfig( QObject* parent )
: TrackingStyleConfig( parent )
{
setObjectName( "UserTrackingConfig" );
}
static bool
isValidUserTrackingStyle( const QString& s )
{
static QStringList knownStyles { "kuserfeedback" };
return knownStyles.contains( s );
}
void
UserTrackingConfig::setConfigurationMap( const QVariantMap& configurationMap )
{
TrackingStyleConfig::setConfigurationMap( configurationMap );
m_userTrackingStyle = CalamaresUtils::getString( configurationMap, "style" );
validate( m_userTrackingStyle, isValidUserTrackingStyle );
m_userTrackingAreas = CalamaresUtils::getStringList( configurationMap, "areas" );
}
Config::Config( QObject* parent )
: QObject( parent )
, m_installTracking( new InstallTrackingConfig( this ) )
, m_machineTracking( new MachineTrackingConfig( this ) )
, m_userTracking( new UserTrackingConfig( this ) )
{
}
static void
enableLevelsBelow( Config* config, TrackingType level )
{
switch ( level )
{
case TrackingType::UserTracking:
config->userTracking()->setTracking( TrackingStyleConfig::TrackingState::EnabledByUser );
FALLTHRU;
case TrackingType::MachineTracking:
config->machineTracking()->setTracking( TrackingStyleConfig::TrackingState::EnabledByUser );
FALLTHRU;
case TrackingType::InstallTracking:
config->installTracking()->setTracking( TrackingStyleConfig::TrackingState::EnabledByUser );
break;
case TrackingType::NoTracking:
config->noTracking( true );
break;
}
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
m_generalPolicy = CalamaresUtils::getString( configurationMap, "policy" );
if ( !QUrl( m_generalPolicy ).isValid() )
{
m_generalPolicy = QString();
}
emit generalPolicyChanged( m_generalPolicy );
bool success = false;
auto subconfig = CalamaresUtils::getSubMap( configurationMap, "install", success );
if ( success )
{
m_installTracking->setConfigurationMap( subconfig );
}
subconfig = CalamaresUtils::getSubMap( configurationMap, "machine", success );
if ( success )
{
m_machineTracking->setConfigurationMap( subconfig );
}
subconfig = CalamaresUtils::getSubMap( configurationMap, "user", success );
if ( success )
{
m_userTracking->setConfigurationMap( subconfig );
}
auto level = trackingNames().find( CalamaresUtils::getString( configurationMap, "default" ), success );
if ( !success )
{
cWarning() << "Default tracking level unknown:" << CalamaresUtils::getString( configurationMap, "default" );
level = TrackingType::NoTracking;
}
enableLevelsBelow( this, level );
}
QString
Config::generalPolicy() const
{
return m_generalPolicy;
}
void
Config::noTracking( bool switchOffAllTracking )
{
if ( !switchOffAllTracking )
{
return;
}
m_installTracking->setTracking( TrackingStyleConfig::TrackingState::DisabledByUser );
m_machineTracking->setTracking( TrackingStyleConfig::TrackingState::DisabledByUser );
m_userTracking->setTracking( TrackingStyleConfig::TrackingState::DisabledByUser );
}

View File

@ -0,0 +1,192 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2020, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRACKING_CONFIG_H
#define TRACKING_CONFIG_H
#include <QObject>
#include <QString>
#include <QVariantMap>
/** @brief Base class for configuring a specific kind of tracking.
*
* All tracking types have a policy URL, which is used to explain what
* kind of tracking is involved, what data is sent, etc. The content
* of that URL is the responsibility of the distro.
*
* A tracking type is disabled by default: if it isn't specifically
* enabled (for configuration) in the config file, it will always be disabled.
* If it is enabled (for configuration) in the config file, it still
* defaults to disabled, but the user can choose to enable it.
*/
class TrackingStyleConfig : public QObject
{
Q_OBJECT
Q_PROPERTY( TrackingState trackingStatus READ tracking WRITE setTracking NOTIFY trackingChanged FINAL )
Q_PROPERTY( bool isEnabled READ isEnabled NOTIFY trackingChanged FINAL )
Q_PROPERTY( bool isConfigurable READ isConfigurable NOTIFY trackingChanged FINAL )
Q_PROPERTY( QString policy READ policy NOTIFY policyChanged FINAL )
public:
TrackingStyleConfig( QObject* parent );
virtual ~TrackingStyleConfig();
void setConfigurationMap( const QVariantMap& );
enum TrackingState
{
DisabledByConfig,
DisabledByUser,
EnabledByUser
};
Q_ENUM( TrackingState );
public Q_SLOTS:
TrackingState tracking() const { return m_state; }
/// @brief Has the user specifically enabled this kind of tracking?
bool isEnabled() const { return m_state == EnabledByUser; }
/// @brief Is this tracking enabled for configuration?
bool isConfigurable() const { return m_state != DisabledByConfig; }
/** @brief Sets the tracking state
*
* Unless the tracking is enabled for configuration, it always
* remains disabled.
*/
void setTracking( TrackingState );
/** @brief Sets the tracking state
*
* Use @c true for @c EnabledByUser, @c false for DisabledByUser,
* but keep in mind that if the tracking is not enabled for
* configuration, it will always remain disabled.
*/
void setTracking( bool );
/// @brief URL for the policy explaining this tracking
QString policy() const { return m_policy; }
signals:
void trackingChanged();
void policyChanged( QString );
protected:
/// @brief Validates the @p urlString, disables tracking if invalid
void validateUrl( QString& urlString );
/// @brief Validates the @p string, disables tracking if invalid
void validate( QString& s, std::function< bool( const QString& s ) >&& pred );
private:
TrackingState m_state = DisabledByConfig;
QString m_policy; // URL
};
/** @brief Install tracking pings a URL at the end of installation
*
* Install tracking will do a single GET on the given URL at
* the end of installation. The information included in the GET
* request depends on the URL configuration, see also the tracking
* jobs.
*/
class InstallTrackingConfig : public TrackingStyleConfig
{
public:
InstallTrackingConfig( QObject* parent );
void setConfigurationMap( const QVariantMap& configurationMap );
QString installTrackingUrl() { return m_installTrackingUrl; }
private:
QString m_installTrackingUrl;
};
/** @brief Machine tracking reports from the installed system
*
* When machine tracking is on, the installed system will report
* back ("call home") at some point. This can mean Debian pop-con,
* or updatemanager maching tracking, or something else. The kind
* of configuration depends on the style of tracking that is enabled.
*/
class MachineTrackingConfig : public TrackingStyleConfig
{
public:
MachineTrackingConfig( QObject* parent );
void setConfigurationMap( const QVariantMap& configurationMap );
QString machineTrackingStyle() { return m_machineTrackingStyle; }
private:
QString m_machineTrackingStyle;
};
/** @brief User tracking reports user actions
*
* When user tracking is on, it is enabled for the user configured
* in Calamares -- not for users created afterwards in the target
* system, unless the target system defaults to tracking them.
* The kind of user tracking depends on the target system and
* environment; KDE user tracking is one example, which can be
* configured in a fine-grained way and defaults to off.
*/
class UserTrackingConfig : public TrackingStyleConfig
{
public:
UserTrackingConfig( QObject* parent );
void setConfigurationMap( const QVariantMap& configurationMap );
QString userTrackingStyle() { return m_userTrackingStyle; }
QStringList userTrackingAreas() const { return m_userTrackingAreas; }
private:
QString m_userTrackingStyle;
QStringList m_userTrackingAreas; // fine-grained areas
};
class Config : public QObject
{
Q_OBJECT
Q_PROPERTY( QString generalPolicy READ generalPolicy NOTIFY generalPolicyChanged FINAL )
Q_PROPERTY( TrackingStyleConfig* installTracking READ installTracking FINAL )
Q_PROPERTY( TrackingStyleConfig* machineTracking READ machineTracking FINAL )
Q_PROPERTY( TrackingStyleConfig* userTracking READ userTracking FINAL )
public:
Config( QObject* parent = nullptr );
void setConfigurationMap( const QVariantMap& );
public Q_SLOTS:
QString generalPolicy() const;
InstallTrackingConfig* installTracking() const { return m_installTracking; }
MachineTrackingConfig* machineTracking() const { return m_machineTracking; }
UserTrackingConfig* userTracking() const { return m_userTracking; }
/// @brief Call with @c true to turn off all the trackings
void noTracking( bool );
signals:
void generalPolicyChanged( QString );
private:
QString m_generalPolicy;
InstallTrackingConfig* m_installTracking;
MachineTrackingConfig* m_machineTracking;
UserTrackingConfig* m_userTracking;
};
#endif

View File

@ -18,10 +18,16 @@
#include "TrackingJobs.h"
#include "Config.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "network/Manager.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include <KMacroExpander>
#include <QSemaphore>
#include <QTimer>
@ -32,7 +38,7 @@ TrackingInstallJob::TrackingInstallJob( const QString& url )
{
}
TrackingInstallJob::~TrackingInstallJob() { }
TrackingInstallJob::~TrackingInstallJob() {}
QString
TrackingInstallJob::prettyName() const
@ -72,38 +78,72 @@ TrackingInstallJob::exec()
return Calamares::JobResult::ok();
}
void
TrackingInstallJob::addJob( Calamares::JobList& list, InstallTrackingConfig* config )
{
if ( config->isEnabled() )
{
const auto* s = CalamaresUtils::System::instance();
QHash< QString, QString > map { std::initializer_list< std::pair< QString, QString > > {
{ QStringLiteral( "CPU" ), s->getCpuDescription() },
{ QStringLiteral( "MEMORY" ), QString::number( s->getTotalMemoryB().first ) },
{ QStringLiteral( "DISK" ), QString::number( s->getTotalDiskB() ) } } };
QString installUrl = KMacroExpander::expandMacros( config->installTrackingUrl(), map );
cDebug() << Logger::SubEntry << "install-tracking URL" << installUrl;
list.append( Calamares::job_ptr( new TrackingInstallJob( installUrl ) ) );
}
}
void
TrackingMachineJob::addJob( Calamares::JobList& list, MachineTrackingConfig* config )
{
if ( config->isEnabled() )
{
const auto style = config->machineTrackingStyle();
if ( style == "updatemanager" )
{
list.append( Calamares::job_ptr( new TrackingMachineUpdateManagerJob() ) );
}
else
{
cWarning() << "Unsupported machine tracking style" << style;
}
}
}
QString
TrackingMachineNeonJob::prettyName() const
TrackingMachineUpdateManagerJob::prettyName() const
{
return tr( "Machine feedback" );
}
QString
TrackingMachineNeonJob::prettyDescription() const
TrackingMachineUpdateManagerJob::prettyDescription() const
{
return prettyName();
}
QString
TrackingMachineNeonJob::prettyStatusMessage() const
TrackingMachineUpdateManagerJob::prettyStatusMessage() const
{
return tr( "Configuring machine feedback." );
}
Calamares::JobResult
TrackingMachineNeonJob::exec()
TrackingMachineUpdateManagerJob::exec()
{
static const auto script = QStringLiteral(
R"x(
MACHINE_ID=`cat /etc/machine-id`
sed -i "s,URI =.*,URI = http://releases.neon.kde.org/meta-release/${MACHINE_ID}," /etc/update-manager/meta-release
sed -i "s,URI_LTS =.*,URI_LTS = http://releases.neon.kde.org/meta-release-lts/${MACHINE_ID}," /etc/update-manager/meta-release
true
)x" );
int r = CalamaresUtils::System::instance()->targetEnvCall( "/bin/sh",
"sed -i '/^URI/s,${MACHINE_ID},'`cat /etc/machine-id`',' /etc/update-manager/meta-release || true" );
auto res = CalamaresUtils::System::instance()->runCommand( CalamaresUtils::System::RunLocation::RunInTarget,
QStringList { QStringLiteral( "/bin/sh" ) },
QString(), // Working dir
script,
script, // standard input
std::chrono::seconds( 1 ) );
int r = res.first;
if ( r == 0 )
{
@ -122,3 +162,87 @@ true
tr( "Could not configure machine feedback correctly, Calamares error %1." ).arg( r ) );
}
}
void
TrackingUserJob::addJob( Calamares::JobList& list, UserTrackingConfig* config )
{
if ( config->isEnabled() )
{
const auto* gs = Calamares::JobQueue::instance()->globalStorage();
static const auto key = QStringLiteral( "username" );
QString username = ( gs && gs->contains( key ) ) ? gs->value( key ).toString() : QString();
if ( username.isEmpty() )
{
cWarning() << "No username is set in GlobalStorage, skipping user-tracking.";
return;
}
const auto style = config->userTrackingStyle();
if ( style == "kuserfeedback" )
{
list.append( Calamares::job_ptr( new TrackingKUserFeedbackJob( username, config->userTrackingAreas() ) ) );
}
else
{
cWarning() << "Unsupported user tracking style" << style;
}
}
}
TrackingKUserFeedbackJob::TrackingKUserFeedbackJob( const QString& username, const QStringList& areas )
: m_username( username )
, m_areas( areas )
{
}
QString
TrackingKUserFeedbackJob::prettyName() const
{
return tr( "KDE user feedback" );
}
QString
TrackingKUserFeedbackJob::prettyDescription() const
{
return prettyName();
}
QString
TrackingKUserFeedbackJob::prettyStatusMessage() const
{
return tr( "Configuring KDE user feedback." );
}
Calamares::JobResult
TrackingKUserFeedbackJob::exec()
{
// This is the contents of a config file to turn on some kind
// of KUserFeedback tracking; the level (16) is chosen for minimal
// but not zero tracking.
static const char config[] = R"x([Global]
FeedbackLevel=16
)x";
for ( const QString& area : m_areas )
{
QString path = QStringLiteral( "/home/%1/.config/%2" ).arg( m_username, area );
cDebug() << "Configuring KUserFeedback" << path;
int r = CalamaresUtils::System::instance()->createTargetFile( path, config );
if ( r > 0 )
{
return Calamares::JobResult::error(
tr( "Error in KDE user feedback configuration." ),
tr( "Could not configure KDE user feedback correctly, script error %1." ).arg( r ) );
}
else if ( r < 0 )
{
return Calamares::JobResult::error(
tr( "Error in KDE user feedback configuration." ),
tr( "Could not configure KDE user feedback correctly, Calamares error %1." ).arg( r ) );
}
}
return Calamares::JobResult::ok();
}

View File

@ -16,13 +16,36 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRACKINGJOBS
#define TRACKINGJOBS
#ifndef TRACKING_TRACKINGJOBS_H
#define TRACKING_TRACKINGJOBS_H
#include "Job.h"
class InstallTrackingConfig;
class MachineTrackingConfig;
class UserTrackingConfig;
class QSemaphore;
/** @section Tracking Jobs
*
* The tracking jobs do the actual work of configuring tracking on the
* target machine. Tracking jobs may have *styles*, variations depending
* on the distro or environment of the target system. At the root of
* each family of tracking jobs (installation, machine, user) there is
* a class with static method `addJob()` that takes the configuration
* information from the relevant Config sub-object and optionally
* adds the right job (subclass!) to the list of jobs.
*/
/** @brief Install-tracking job (gets a URL)
*
* The install-tracking job (there is only one kind) does a GET
* on a configured URL with some additional information about
* the machine (if configured into the URL).
*
* No persistent tracking is done.
*/
class TrackingInstallJob : public Calamares::Job
{
Q_OBJECT
@ -35,11 +58,31 @@ public:
QString prettyStatusMessage() const override;
Calamares::JobResult exec() override;
static void addJob( Calamares::JobList& list, InstallTrackingConfig* config );
private:
const QString m_url;
};
class TrackingMachineNeonJob : public Calamares::Job
/** @brief Base class for machine-tracking jobs
*
* Machine-tracking configuraiton depends on the distro / style of machine
* being tracked, so it has subclasses to switch on the relevant kind
* of tracking. A machine is tracked persistently.
*/
class TrackingMachineJob : public Calamares::Job
{
public:
static void addJob( Calamares::JobList& list, MachineTrackingConfig* config );
};
/** @brief Tracking machines, update-manager style
*
* The machine has a machine-id, and this is sed(1)'ed into the
* update-manager configuration, to report the machine-id back
* to distro servers.
*/
class TrackingMachineUpdateManagerJob : public TrackingMachineJob
{
Q_OBJECT
public:
@ -49,5 +92,38 @@ public:
Calamares::JobResult exec() override;
};
/** @brief Base class for user-tracking jobs
*
* User-tracking configuration depends on the distro / style of user
* tracking being implemented, so there are subclasses to switch on the
* relevant kind of tracking. Users are tracked persistently (the user
* can of course configure the tracking again once the system is restarted).
*/
class TrackingUserJob : public Calamares::Job
{
public:
static void addJob( Calamares::JobList& list, UserTrackingConfig* config );
};
/** @brief Turn on KUserFeedback in target system
*
* This writes suitable files for turning on KUserFeedback for the
* normal user configured in Calamares. The feedback can be reconfigured
* by the user through Plasma's user-feedback dialog.
*/
class TrackingKUserFeedbackJob : public Calamares::Job
{
public:
TrackingKUserFeedbackJob( const QString& username, const QStringList& areas );
QString prettyName() const override;
QString prettyDescription() const override;
QString prettyStatusMessage() const override;
Calamares::JobResult exec() override;
private:
QString m_username;
QStringList m_areas;
};
#endif

View File

@ -18,6 +18,7 @@
#include "TrackingPage.h"
#include "Config.h"
#include "ui_page_trackingstep.h"
#include "Branding.h"
@ -28,178 +29,128 @@
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QButtonGroup>
#include <QDesktopServices>
#include <QLabel>
TrackingPage::TrackingPage( QWidget* parent )
TrackingPage::TrackingPage( Config* config, QWidget* parent )
: QWidget( parent )
, ui( new Ui::TrackingPage )
{
ui->setupUi( this );
CALAMARES_RETRANSLATE(
QString product = Calamares::Branding::instance()->shortProductName(); ui->retranslateUi( this );
ui->generalExplanation->setText(
tr( "Install tracking helps %1 to see how many users they have, what hardware they install %1 to and (with "
"the last two options below), get continuous information about preferred applications. To see what "
"will be sent, please click the help icon next to each area." )
.arg( product ) );
ui->installExplanation->setText(
tr( "By selecting this you will send information about your installation and hardware. This information "
"will <b>only be sent once</b> after the installation finishes." ) );
ui->machineExplanation->setText( tr( "By selecting this you will <b>periodically</b> send information about "
"your installation, hardware and applications, to %1." )
.arg( product ) );
ui->userExplanation->setText( tr( "By selecting this you will <b>regularly</b> send information about your "
"installation, hardware, applications and usage patterns, to %1." )
.arg( product ) ); )
CALAMARES_RETRANSLATE_SLOT( &TrackingPage::retranslate );
QButtonGroup* group = new QButtonGroup( this );
group->setExclusive( true );
group->addButton( ui->noneRadio );
group->addButton( ui->installRadio );
group->addButton( ui->machineRadio );
group->addButton( ui->userRadio );
ui->noneRadio->setChecked( true );
ui->noneCheckBox->setChecked( true );
ui->noneCheckBox->setEnabled( false );
connect( ui->noneCheckBox, &QCheckBox::stateChanged, this, &TrackingPage::buttonNoneChecked );
// Each "panel" of configuration has the same kind of setup,
// where the xButton and xCheckBox is connected to the xTracking
// configuration object; that takes macro-trickery, unfortunately.
#define trackingSetup( x ) \
do \
{ \
connect( ui->x##CheckBox, &QCheckBox::stateChanged, this, &TrackingPage::buttonChecked ); \
connect( ui->x##CheckBox, \
&QCheckBox::stateChanged, \
config->x##Tracking(), \
QOverload< bool >::of( &TrackingStyleConfig::setTracking ) ); \
connect( config->x##Tracking(), &TrackingStyleConfig::trackingChanged, this, [this, config]() { \
this->trackerChanged( config->x##Tracking(), this->ui->x##Group, this->ui->x##CheckBox ); \
} ); \
connect( ui->x##PolicyButton, &QAbstractButton::clicked, config, [config] { \
QString url( config->x##Tracking()->policy() ); \
if ( !url.isEmpty() ) \
{ \
QDesktopServices::openUrl( url ); \
} \
} ); \
} while ( false )
trackingSetup( install );
trackingSetup( machine );
trackingSetup( user );
#undef trackingSetup
connect( config, &Config::generalPolicyChanged, [this]( const QString& url ) {
this->ui->generalPolicyLabel->setVisible( !url.isEmpty() );
} );
connect( ui->generalPolicyLabel, &QLabel::linkActivated, [config] {
QString url( config->generalPolicy() );
if ( !url.isEmpty() )
{
QDesktopServices::openUrl( url );
}
} );
retranslate();
}
void
TrackingPage::enableTrackingOption( TrackingType t, bool enabled )
TrackingPage::retranslate()
{
QWidget* group = nullptr;
switch ( t )
{
case TrackingType::InstallTracking:
group = ui->installGroup;
break;
case TrackingType::MachineTracking:
group = ui->machineGroup;
break;
case TrackingType::UserTracking:
group = ui->userGroup;
break;
}
if ( group != nullptr )
{
if ( enabled )
{
group->show();
}
else
{
group->hide();
}
}
else
{
cWarning() << "unknown tracking option" << int( t );
}
QString product = Calamares::Branding::instance()->shortProductName();
ui->retranslateUi( this );
ui->generalExplanation->setText(
tr( "Tracking helps %1 to see how often it is installed, what hardware it is installed on and "
"which applications are used. To see what "
"will be sent, please click the help icon next to each area." )
.arg( product ) );
ui->installExplanation->setText(
tr( "By selecting this you will send information about your installation and hardware. This information "
"will only be sent <b>once</b> after the installation finishes." ) );
ui->machineExplanation->setText(
tr( "By selecting this you will periodically send information about your <b>machine</b> installation, "
"hardware and applications, to %1." )
.arg( product ) );
ui->userExplanation->setText(
tr( "By selecting this you will regularly send information about your "
"<b>user</b> installation, hardware, applications and application usage patterns, to %1." )
.arg( product ) );
}
bool
TrackingPage::getTrackingOption( TrackingType t )
TrackingPage::anyOtherChecked() const
{
bool enabled = false;
return ui->installCheckBox->isChecked() || ui->machineCheckBox->isChecked() || ui->userCheckBox->isChecked();
}
// A tracking type is enabled if it is checked, or
// any higher level is checked.
#define ch( x ) ui->x->isChecked()
switch ( t )
void
TrackingPage::buttonNoneChecked( int state )
{
if ( state )
{
case TrackingType::InstallTracking:
enabled = ch( installRadio ) || ch( machineRadio ) || ch( userRadio );
break;
case TrackingType::MachineTracking:
enabled = ch( machineRadio ) || ch( userRadio );
break;
case TrackingType::UserTracking:
enabled = ch( userRadio );
break;
cDebug() << "Unchecking all other buttons because 'None' was checked";
ui->installCheckBox->setChecked( false );
ui->machineCheckBox->setChecked( false );
ui->userCheckBox->setChecked( false );
ui->noneCheckBox->setEnabled( false );
}
#undef ch
return enabled;
}
void
TrackingPage::setTrackingPolicy( TrackingType t, QString url )
TrackingPage::buttonChecked( int state )
{
QToolButton* button = nullptr;
switch ( t )
if ( state )
{
case TrackingType::InstallTracking:
button = ui->installPolicyButton;
break;
case TrackingType::MachineTracking:
button = ui->machinePolicyButton;
break;
case TrackingType::UserTracking:
button = ui->userPolicyButton;
break;
// Can't have none checked, if another one is
ui->noneCheckBox->setEnabled( true );
ui->noneCheckBox->setChecked( false );
}
if ( button != nullptr )
if ( url.isEmpty() )
else
{
if ( !anyOtherChecked() )
{
button->hide();
ui->noneCheckBox->setChecked( true );
ui->noneCheckBox->setEnabled( false );
}
else
{
connect( button, &QToolButton::clicked, [ url ] { QDesktopServices::openUrl( url ); } );
cDebug() << "Tracking policy" << int( t ) << "set to" << url;
}
else
{
cWarning() << "unknown tracking option" << int( t );
}
}
void
TrackingPage::setGeneralPolicy( QString url )
TrackingPage::trackerChanged( TrackingStyleConfig* config, QWidget* panel, QCheckBox* check )
{
if ( url.isEmpty() )
{
ui->generalPolicyLabel->hide();
}
else
{
ui->generalPolicyLabel->show();
ui->generalPolicyLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
ui->generalPolicyLabel->show();
connect( ui->generalPolicyLabel, &QLabel::linkActivated, [ url ] { QDesktopServices::openUrl( url ); } );
}
}
void
TrackingPage::setTrackingLevel( const QString& l )
{
QString level = l.toLower();
QRadioButton* button = nullptr;
if ( level.isEmpty() || level == "none" )
{
button = ui->noneRadio;
}
else if ( level == "install" )
{
button = ui->installRadio;
}
else if ( level == "machine" )
{
button = ui->machineRadio;
}
else if ( level == "user" )
{
button = ui->userRadio;
}
if ( button != nullptr )
{
button->setChecked( true );
}
else
{
cWarning() << "unknown default tracking level" << l;
}
panel->setVisible( config->isConfigurable() );
check->setChecked( config->isEnabled() );
}

View File

@ -21,6 +21,7 @@
#include "TrackingType.h"
#include <QCheckBox>
#include <QUrl>
#include <QWidget>
@ -29,35 +30,48 @@ namespace Ui
class TrackingPage;
}
class Config;
class TrackingStyleConfig;
class TrackingPage : public QWidget
{
Q_OBJECT
public:
explicit TrackingPage( QWidget* parent = nullptr );
explicit TrackingPage( Config* config, QWidget* parent = nullptr );
/**
* Enables or disables the tracking-option block for the given
* tracking option @p t, and sets the initial state of the
* checkbox to the @p user default.
/** @brief is any of the enable-tracking buttons checked?
*
* Call this in ascending order of tracking type.
* Returns true if any one or more of install, machine or user
* tracking is enabled.
*/
void enableTrackingOption( TrackingType t, bool enabled );
/**
* Returns whether tracking type @p is selected by the user
* (i.e. is the radio button for that level, or for a higher
* tracking level, enabled).
*/
bool getTrackingOption( TrackingType t );
bool anyOtherChecked() const;
/* URL for given level @p t */
void setTrackingPolicy( TrackingType t, QString url );
/* URL for the global link */
void setGeneralPolicy( QString url );
/* Select one of the four levels by name */
void setTrackingLevel( const QString& level );
public Q_SLOTS:
void retranslate();
/** @brief When the *no tracking* checkbox is changed
*
* @p state will be non-zero when the box is checked; this
* **unchecks** all the other boxes.
*/
void buttonNoneChecked( int state );
/** @brief Some other checkbox changed
*
* This may check the *none* button if all the others are
* now unchecked.
*/
void buttonChecked( int state );
private:
/** @brief Apply the tracking configuration to the UI
*
* If the config cannot be changed (disabled in config) then
* hide the UI parts on the @p panel; otherwise show it
* and set @p check state to whether the user has enabled it.
*/
void trackerChanged( TrackingStyleConfig* subconfig, QWidget* panel, QCheckBox* check );
Ui::TrackingPage* ui;
};

View File

@ -19,11 +19,17 @@
#ifndef TRACKINGTYPE_H
#define TRACKINGTYPE_H
#include "utils/NamedEnum.h"
enum class TrackingType
{
InstallTracking,
MachineTracking,
UserTracking
NoTracking, // Do not enable tracking at all
InstallTracking, // Track that *this* install has happened
MachineTracking, // Track the machine, ongoing
UserTracking // Track the user, ongoing
};
// Implemented in Config.cpp
const NamedEnumTable< TrackingType >& trackingNames();
#endif //TRACKINGTYPE_H

View File

@ -18,6 +18,7 @@
#include "TrackingViewStep.h"
#include "Config.h"
#include "TrackingJobs.h"
#include "TrackingPage.h"
@ -33,17 +34,10 @@
CALAMARES_PLUGIN_FACTORY_DEFINITION( TrackingViewStepFactory, registerPlugin< TrackingViewStep >(); )
/** @brief Is @p s a valid machine-tracking style. */
static bool
isValidStyle( const QString& s )
{
static QStringList knownStyles { "neon" };
return knownStyles.contains( s );
}
TrackingViewStep::TrackingViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_widget( new TrackingPage )
, m_config( new Config( this ) )
, m_widget( new TrackingPage( m_config ) )
{
emit nextStatusChanged( false );
}
@ -103,89 +97,28 @@ TrackingViewStep::isAtEnd() const
void
TrackingViewStep::onLeave()
{
m_installTracking.userEnabled = m_widget->getTrackingOption( TrackingType::InstallTracking );
m_machineTracking.userEnabled = m_widget->getTrackingOption( TrackingType::MachineTracking );
m_userTracking.userEnabled = m_widget->getTrackingOption( TrackingType::UserTracking );
cDebug() << "Install tracking:" << m_installTracking.enabled();
cDebug() << "Machine tracking:" << m_machineTracking.enabled();
cDebug() << " User tracking:" << m_userTracking.enabled();
cDebug() << "Install tracking:" << m_config->installTracking()->isEnabled();
cDebug() << Logger::SubEntry << "Machine tracking:" << m_config->machineTracking()->isEnabled();
cDebug() << Logger::SubEntry << " User tracking:" << m_config->userTracking()->isEnabled();
}
Calamares::JobList
TrackingViewStep::jobs() const
{
Calamares::JobList l;
cDebug() << "Creating tracking jobs ..";
if ( m_installTracking.enabled() && !m_installTrackingUrl.isEmpty() )
{
QString installUrl = m_installTrackingUrl;
const auto s = CalamaresUtils::System::instance();
QString memory, disk;
memory.setNum( s->getTotalMemoryB().first );
disk.setNum( s->getTotalDiskB() );
installUrl.replace( "$CPU", s->getCpuDescription() ).replace( "$MEMORY", memory ).replace( "$DISK", disk );
cDebug() << Logger::SubEntry << "install-tracking URL" << installUrl;
l.append( Calamares::job_ptr( new TrackingInstallJob( installUrl ) ) );
}
if ( m_machineTracking.enabled() && !m_machineTrackingStyle.isEmpty() )
{
Q_ASSERT( isValidStyle( m_machineTrackingStyle ) );
if ( m_machineTrackingStyle == "neon" )
{
l.append( Calamares::job_ptr( new TrackingMachineNeonJob() ) );
}
}
Calamares::JobList l;
TrackingInstallJob::addJob( l, m_config->installTracking() );
TrackingMachineJob::addJob( l, m_config->machineTracking() );
TrackingUserJob::addJob( l, m_config->userTracking() );
cDebug() << Logger::SubEntry << l.count() << "jobs queued.";
return l;
}
QVariantMap
TrackingViewStep::setTrackingOption( const QVariantMap& configurationMap, const QString& key, TrackingType t )
{
bool settingEnabled = false;
bool success = false;
auto config = CalamaresUtils::getSubMap( configurationMap, key, success );
if ( success )
{
settingEnabled = CalamaresUtils::getBool( config, "enabled", false );
}
TrackingEnabled& trackingConfiguration = tracking( t );
trackingConfiguration.settingEnabled = settingEnabled;
trackingConfiguration.userEnabled = false;
m_widget->enableTrackingOption( t, settingEnabled );
m_widget->setTrackingPolicy( t, CalamaresUtils::getString( config, "policy" ) );
return config;
}
void
TrackingViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
QVariantMap config;
config = setTrackingOption( configurationMap, "install", TrackingType::InstallTracking );
m_installTrackingUrl = CalamaresUtils::getString( config, "url" );
config = setTrackingOption( configurationMap, "machine", TrackingType::MachineTracking );
auto s = CalamaresUtils::getString( config, "style" );
if ( isValidStyle( s ) )
{
m_machineTrackingStyle = s;
}
setTrackingOption( configurationMap, "user", TrackingType::UserTracking );
m_widget->setGeneralPolicy( CalamaresUtils::getString( configurationMap, "policy" ) );
m_widget->setTrackingLevel( CalamaresUtils::getString( configurationMap, "default" ) );
m_config->setConfigurationMap( configurationMap );
}

View File

@ -29,6 +29,7 @@
#include <QUrl>
#include <QVariantMap>
class Config;
class TrackingPage;
class PLUGINDLLEXPORT TrackingViewStep : public Calamares::ViewStep
@ -56,42 +57,8 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
QVariantMap setTrackingOption( const QVariantMap& configurationMap, const QString& key, TrackingType t );
Config* m_config;
TrackingPage* m_widget;
QString m_installTrackingUrl;
QString m_machineTrackingStyle;
struct TrackingEnabled
{
bool settingEnabled; // Enabled in config file
bool userEnabled; // User checked "yes"
TrackingEnabled()
: settingEnabled( false )
, userEnabled( false )
{
}
bool enabled() const { return settingEnabled && userEnabled; }
};
TrackingEnabled m_installTracking, m_machineTracking, m_userTracking;
inline TrackingEnabled& tracking( TrackingType t )
{
if ( t == TrackingType::UserTracking )
{
return m_userTracking;
}
else if ( t == TrackingType::MachineTracking )
{
return m_machineTracking;
}
else
{
return m_installTracking;
}
}
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( TrackingViewStepFactory )

View File

@ -32,7 +32,7 @@ margin-left: 2em;</string>
<widget class="QWidget" name="noneGroup" native="true">
<layout class="QHBoxLayout" name="noneLayout">
<item>
<widget class="QRadioButton" name="noneRadio">
<widget class="QCheckBox" name="noneCheckBox">
<property name="text">
<string/>
</property>
@ -69,7 +69,7 @@ margin-left: 2em;</string>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By selecting this, you will send &lt;span style=&quot; font-weight:600;&quot;&gt;no information at all&lt;/span&gt; about your installation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click here to send &lt;span style=&quot; font-weight:600;&quot;&gt;no information at all&lt;/span&gt; about your installation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@ -83,7 +83,7 @@ margin-left: 2em;</string>
<widget class="QWidget" name="installGroup" native="true">
<layout class="QHBoxLayout" name="installLayout">
<item>
<widget class="QRadioButton" name="installRadio">
<widget class="QCheckBox" name="installCheckBox">
<property name="text">
<string/>
</property>
@ -145,7 +145,7 @@ margin-left: 2em;</string>
<widget class="QWidget" name="machineGroup" native="true">
<layout class="QHBoxLayout" name="machineLayout">
<item>
<widget class="QRadioButton" name="machineRadio">
<widget class="QCheckBox" name="machineCheckBox">
<property name="text">
<string/>
</property>
@ -207,7 +207,7 @@ margin-left: 2em;</string>
<widget class="QWidget" name="userGroup" native="true">
<layout class="QHBoxLayout" name="userLayout">
<item>
<widget class="QRadioButton" name="userRadio">
<widget class="QCheckBox" name="userCheckBox">
<property name="text">
<string/>
</property>
@ -279,6 +279,9 @@ margin-left: 2em;</string>
<property name="openExternalLinks">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
<item>

View File

@ -28,14 +28,16 @@
# policy applies.
#
# Each area has a key *enabled*. If the area is enabled, it is shown to
# the user. This defaults to off, which means no tracking would be
# the user. This defaults to false, which means no tracking would be
# configured or enabled by Calamares.
#
# Each area has a key *policy*, which is a Url to be opened when
# the user clicks on the corresponding Help button for an explanation
# of the details of that particular kind of tracking. If no policy
# is set, the help button is hidden. The example policy links
# go to Calamares' generic user manual.
# is set, that tracking style is disabled. The example policy links
# go to Calamares' generic user manual (which is a terrible idea
# for distro's: you have GDPR obligations under most of these tracking
# styles, so do your homework).
#
# Each area may have other configuration keys, depending on the
# area and how it needs to be configured.
@ -48,13 +50,14 @@
---
# This is the global policy; it is displayed as a link on the page.
# If blank or commented out, no link is displayed on the tracking
# page. It is recommended to either provide policy URLs for each
# area, *or* one general link, and not to mix them.
# page. You **must** provide policy links per-area as well.
policy: "https://github.com/calamares/calamares/wiki/Use-Guide#installation-tracking"
# This is the default level to enable for tracking. If commented out,
# This is the default area to enable for tracking. If commented out,
# empty, or otherwise invalid, "none" is used, so no tracking by default.
default: user
# Setting an area here also checks the areas before it (install, machine,
# then user) by default -- subject to those areas being enabled at all.
# default: user
# The install area has one specific configuration key:
# url: this URL (remember to include the protocol, and prefer https)
@ -72,17 +75,28 @@ default: user
install:
enabled: false
policy: "https://github.com/calamares/calamares/wiki/Use-Guide#installation-tracking"
# url: "https://example.com/install.php?c=$CPU&m=$MEMORY"
url: "https://example.com/install.php?c=$CPU&m=$MEMORY"
# The machine area has one specific configuration key:
# style: This string specifies what kind of tracking configuration
# needs to be done. There is currently only one valid
# style, "neon", which edits two files in the installed
# system to enable system-tracking.
# needs to be done. See below for valid styles.
#
# Available styles:
# - *updatemanager* replaces the literal string "${MACHINE_ID}" with the contents of
# /etc/machine-id, in lines starting with "URI" in the file /etc/update-manager/meta-release
machine:
enabled: false
style: neon
style: updatemanager
policy: "https://github.com/calamares/calamares/wiki/Use-Guide#machine-tracking"
# The user area is not yet implemented, and has no specific configuration.
# The user area has one specific configuration key:
# style: This string specifies what kind of tracking configuration
# needs to be done. See below for valid styles.
#
# Available styles:
# - *kuserfeedback* sets up KUserFeedback tracking (applicable to the KDE
# Plasma Desktop) for each KUserFeedback area listed in *areas*.
user:
enabled: false
style: kuserfeedback
areas: [ PlasmaUserFeedback ]