Commit 33a4b08a authored by Adriaan de Groot's avatar Adriaan de Groot

Merge branch 'issue-1343'

- Also add documentation to CHANGES

FIXES #1343
FIXES #1319
parents 7a4f6f7b 433ed838
......@@ -34,9 +34,15 @@ This release contains contributions from (alphabetically by first name):
## Modules ##
- *packages* now reports more details in the installation progress-bar.
- *netinstall* module supports and `expanded` key, which will pre-expand
- *netinstall* module supports an `expanded` key, which will pre-expand
a group (as if the user had pressed the arrow-button in the tree-view).
This only affects the UI.
This only affects the UI, and only the **outermost** level of groups.
- *netinstall* module now supports a special value for *groupsUrl*.
Setting this to *local* will read the groups directly from the
configuration file.
- *netinstall* groups now support a new key `immutable` which prevents
their check-state from being changed (they are shown, or hidden,
as usual and can be expanded).
- Modules that use QML need a new import line. The QML file for the
module is configured through new keys *qmlSearch* and *qmlFilename*
(previously those were without the `qml` prefix, which invites name
......
......@@ -50,7 +50,7 @@ function( calamares_add_test )
Qt5::Test
)
calamares_automoc( ${TEST_NAME} )
target_compile_definitions( ${TEST_NAME} PRIVATE -DBUILD_AS_TEST ${TEST_DEFINITIONS} )
target_compile_definitions( ${TEST_NAME} PRIVATE -DBUILD_AS_TEST="${CMAKE_CURRENT_SOURCE_DIR}" ${TEST_DEFINITIONS} )
if( TEST_GUI )
target_link_libraries( ${TEST_NAME} calamaresui Qt5::Gui )
endif()
......
......@@ -2,6 +2,7 @@ calamares_add_plugin( netinstall
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
NetInstallViewStep.cpp
NetInstallPage.cpp
PackageTreeItem.cpp
......@@ -16,3 +17,14 @@ calamares_add_plugin( netinstall
yamlcpp
SHARED_LIB
)
calamares_add_test(
netinstalltest
SOURCES
Tests.cpp
PackageTreeItem.cpp
PackageModel.cpp
LIBRARIES
Qt5::Gui
)
/*
* Copyright 2016, Luca Giambonini <almack@chakraos.org>
* Copyright 2016, Lisa Vitolo <shainer@chakraos.org>
* Copyright 2017, Kyle Robbertze <krobbertze@gmail.com>
* Copyright 2017-2018, 2020, Adriaan de Groot <groot@kde.org>
* Copyright 2017, Gabriel Craciunescu <crazy@frugalware.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 "network/Manager.h"
#include "utils/Logger.h"
#include "utils/Yaml.h"
#include <QNetworkReply>
Config::Config( QObject* parent )
: QObject( parent )
, m_model( new PackageModel( this ) )
{
}
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)" );
}
NOTREACHED return QString();
}
void
Config::setStatus( Status s )
{
m_status = s;
emit statusChanged( status() );
}
void
Config::loadGroupList( const QVariantList& groupData )
{
m_model->setupModelData( groupData );
emit statusReady();
}
void
Config::loadGroupList( const QUrl& url )
{
if ( !url.isValid() )
{
setStatus( Status::FailedBadConfiguration );
}
using namespace CalamaresUtils::Network;
cDebug() << "NetInstall loading groups from" << url;
QNetworkReply* reply = Manager::instance().asynchronousGet(
url,
RequestOptions( RequestOptions::FakeUserAgent | RequestOptions::FollowRedirect, std::chrono::seconds( 30 ) ) );
if ( !reply )
{
cDebug() << Logger::Continuation << "request failed immediately.";
setStatus( Status::FailedBadConfiguration );
}
else
{
m_reply = reply;
connect( reply, &QNetworkReply::finished, this, &Config::receivedGroupData );
}
}
/// @brief Convenience to zero out and deleteLater on the reply, used in dataIsHere
struct ReplyDeleter
{
QNetworkReply*& p;
~ReplyDeleter()
{
if ( p )
{
p->deleteLater();
}
p = nullptr;
}
};
void
Config::receivedGroupData()
{
if ( !m_reply || !m_reply->isFinished() )
{
cWarning() << "NetInstall data called too early.";
setStatus( Status::FailedInternalError );
return;
}
cDebug() << "NetInstall group data received" << m_reply->size() << "bytes from" << m_reply->url();
ReplyDeleter 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();
setStatus( Status::FailedNetworkError );
return;
}
QByteArray yamlData = m_reply->readAll();
try
{
YAML::Node groups = YAML::Load( yamlData.constData() );
if ( !groups.IsSequence() )
{
cWarning() << "NetInstall groups data does not form a sequence.";
}
loadGroupList( CalamaresUtils::yamlSequenceToVariant( groups ) );
}
catch ( YAML::Exception& e )
{
CalamaresUtils::explainYamlException( e, yamlData, "netinstall groups data" );
setStatus( Status::FailedBadData );
}
}
/*
* Copyright 2016, Luca Giambonini <almack@chakraos.org>
* Copyright 2016, Lisa Vitolo <shainer@chakraos.org>
* Copyright 2017, Kyle Robbertze <krobbertze@gmail.com>
* Copyright 2017-2018, 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 NETINSTALL_CONFIG_H
#define NETINSTALL_CONFIG_H
#include "PackageModel.h"
#include <QObject>
#include <QUrl>
class QNetworkReply;
class Config : public QObject
{
Q_OBJECT
Q_PROPERTY( PackageModel* packageModel MEMBER m_model FINAL )
Q_PROPERTY( QString status READ status NOTIFY statusChanged FINAL )
public:
Config( QObject* parent = nullptr );
virtual ~Config();
enum class Status
{
Ok,
FailedBadConfiguration,
FailedInternalError,
FailedNetworkError,
FailedBadData
};
QString status() const;
void setStatus( Status s );
bool required() const { return m_required; }
void setRequired( bool r ) { m_required = r; }
/** @brief Retrieves the groups, with name, description and packages
*
* Loads data from the given URL. Once done, the data is parsed
* and passed on to the other loadGroupList() method.
*/
void loadGroupList( const QUrl& url );
/** @brief Fill model from parsed data.
*
* Fills the model with a list of groups -- which can contain
* subgroups and packages -- from @p groupData.
*/
void loadGroupList( const QVariantList& groupData );
PackageModel* model() const { return m_model; }
signals:
void statusChanged( QString status ); ///< Something changed
void statusReady(); ///< Loading groups is complete
private slots:
void receivedGroupData(); ///< From async-loading group data
private:
PackageModel* m_model = nullptr;
QNetworkReply* m_reply = nullptr; // For fetching data
Status m_status = Status::Ok;
bool m_required = false;
};
#endif
......@@ -34,22 +34,21 @@
#include <QHeaderView>
#include <QNetworkReply>
NetInstallPage::NetInstallPage( QWidget* parent )
NetInstallPage::NetInstallPage( Config* c, QWidget* parent )
: QWidget( parent )
, m_config( c )
, ui( new Ui::Page_NetInst )
, m_reply( nullptr )
, m_groups( nullptr )
{
ui->setupUi( this );
ui->groupswidget->setModel( c->model() );
connect( c, &Config::statusChanged, this, &NetInstallPage::setStatus );
connect( c, &Config::statusReady, this, &NetInstallPage::expandGroups );
setPageTitle( nullptr );
CALAMARES_RETRANSLATE_SLOT( &NetInstallPage::retranslate );
}
NetInstallPage::~NetInstallPage()
{
delete m_groups;
delete m_reply;
}
NetInstallPage::~NetInstallPage() {}
void
NetInstallPage::setPageTitle( CalamaresUtils::Locale::TranslatedString* t )
......@@ -69,147 +68,34 @@ NetInstallPage::setPageTitle( CalamaresUtils::Locale::TranslatedString* t )
void
NetInstallPage::retranslate()
{
if ( ui && m_title )
if ( m_title )
{
ui->label->setText( m_title->get() ); // That's get() on the TranslatedString
}
ui->netinst_status->setText( m_config->status() );
}
bool
NetInstallPage::readGroups( const QByteArray& yamlData )
{
try
{
YAML::Node groups = YAML::Load( yamlData.constData() );
if ( !groups.IsSequence() )
{
cWarning() << "netinstall groups data does not form a sequence.";
}
Q_ASSERT( groups.IsSequence() );
m_groups = new PackageModel( groups );
return true;
}
catch ( YAML::Exception& e )
{
CalamaresUtils::explainYamlException( e, yamlData, "netinstall groups data" );
return false;
}
}
/// @brief Convenience to zero out and deleteLater on the reply, used in dataIsHere
struct ReplyDeleter
{
QNetworkReply*& p;
~ReplyDeleter()
{
if ( p )
{
p->deleteLater();
}
p = nullptr;
}
};
void
NetInstallPage::dataIsHere()
NetInstallPage::expandGroups()
{
if ( !m_reply || !m_reply->isFinished() )
{
cWarning() << "NetInstall data called too early.";
return;
}
cDebug() << "NetInstall group data received" << m_reply->url();
ReplyDeleter 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();
ui->netinst_status->setText(
tr( "Network Installation. (Disabled: Unable to fetch package lists, check your network connection)" ) );
emit checkReady( !m_required );
return;
}
if ( !readGroups( m_reply->readAll() ) )
{
cWarning() << "netinstall groups data was received, but invalid.";
cDebug() << Logger::SubEntry << "Url: " << m_reply->url().toString();
cDebug() << Logger::SubEntry << "Headers: " << m_reply->rawHeaderList();
ui->netinst_status->setText( tr( "Network Installation. (Disabled: Received invalid groups data)" ) );
emit checkReady( !m_required );
return;
}
retranslate(); // For changed model
ui->groupswidget->setModel( m_groups );
ui->groupswidget->header()->setSectionResizeMode( 0, QHeaderView::ResizeToContents );
ui->groupswidget->header()->setSectionResizeMode( 1, QHeaderView::Stretch );
auto* model = m_config->model();
// Go backwards because expanding a group may cause rows to appear below it
for ( int i = m_groups->rowCount() - 1; i >= 0; --i )
for ( int i = model->rowCount() - 1; i >= 0; --i )
{
auto index = m_groups->index(i,0);
if ( m_groups->data(index, PackageModel::MetaExpandRole).toBool() )
auto index = model->index( i, 0 );
if ( model->data( index, PackageModel::MetaExpandRole ).toBool() )
{
ui->groupswidget->setExpanded(index, true);
ui->groupswidget->setExpanded( index, true );
}
}
emit checkReady( true );
}
PackageModel::PackageItemDataList
NetInstallPage::selectedPackages() const
{
if ( m_groups )
{
return m_groups->getPackages();
}
else
{
cWarning() << "no netinstall groups are available.";
return PackageModel::PackageItemDataList();
}
}
void
NetInstallPage::loadGroupList( const QString& confUrl )
NetInstallPage::setStatus( QString s )
{
using namespace CalamaresUtils::Network;
cDebug() << "NetInstall loading groups from" << confUrl;
QNetworkReply* reply = Manager::instance().asynchronousGet(
QUrl( confUrl ),
RequestOptions( RequestOptions::FakeUserAgent | RequestOptions::FollowRedirect, std::chrono::seconds( 30 ) ) );
if ( !reply )
{
cDebug() << Logger::Continuation << "request failed immediately.";
ui->netinst_status->setText( tr( "Network Installation. (Disabled: Incorrect configuration)" ) );
}
else
{
m_reply = reply;
connect( reply, &QNetworkReply::finished, this, &NetInstallPage::dataIsHere );
}
}
void
NetInstallPage::setRequired( bool b )
{
m_required = b;
ui->netinst_status->setText( s );
}
void
NetInstallPage::onActivate()
{
......
......@@ -21,6 +21,7 @@
#ifndef NETINSTALLPAGE_H
#define NETINSTALLPAGE_H
#include "Config.h"
#include "PackageModel.h"
#include "PackageTreeItem.h"
......@@ -42,7 +43,7 @@ class NetInstallPage : public QWidget
{
Q_OBJECT
public:
NetInstallPage( QWidget* parent = nullptr );
NetInstallPage( Config* config, QWidget* parent = nullptr );
virtual ~NetInstallPage();
/** @brief Sets the page title
......@@ -58,45 +59,22 @@ public:
void onActivate();
/** @brief Retrieves the groups, with name, description and packages
*
* Loads data from the given URL. This should be called before
* displaying the page.
*/
void loadGroupList( const QString& url );
// Sets the "required" state of netinstall data. Influences whether
// corrupt or unavailable data causes checkReady() to be emitted
// true (not-required) or false.
void setRequired( bool );
bool getRequired() const { return m_required; }
// Returns the list of packages belonging to groups that are
// selected in the view in this given moment. No data is cached here, so
// this function does not have constant time.
PackageModel::PackageItemDataList selectedPackages() const;
public slots:
void dataIsHere();
void retranslate();
void setStatus( QString s );
signals:
void checkReady( bool );
/** @brief Expand entries that should be pre-expanded.
*
* Follows the *expanded* key / the startExpanded field in the
* group entries of the model. Call this after filling up the model.
*/
void expandGroups();
private:
// Takes the YAML data representing the groups and reads them into the
// m_groups and m_groupOrder internal structures. See the README.md
// of this module to know the format expected of the YAML files.
bool readGroups( const QByteArray& yamlData );
Config* m_config;
Ui::Page_NetInst* ui;
std::unique_ptr< CalamaresUtils::Locale::TranslatedString > m_title; // Above the treeview
QNetworkReply* m_reply;
PackageModel* m_groups;
bool m_required;
};
#endif // NETINSTALLPAGE_H
......@@ -32,12 +32,11 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( NetInstallViewStepFactory, registerPlugin<
NetInstallViewStep::NetInstallViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_widget( new NetInstallPage() )
, m_nextEnabled( false )
, m_widget( new NetInstallPage( &m_config ) )
, m_sidebarLabel( nullptr )
, m_nextEnabled( false )
{
emit nextStatusChanged( true );
connect( m_widget, &NetInstallPage::checkReady, this, &NetInstallViewStep::nextIsReady );
connect( &m_config, &Config::statusReady, this, &NetInstallViewStep::nextIsReady );
}
......@@ -56,7 +55,7 @@ NetInstallViewStep::prettyName() const
{
return m_sidebarLabel ? m_sidebarLabel->get() : tr( "Package selection" );
#if defined(TABLE_OF_TRANSLATIONS)
#if defined( TABLE_OF_TRANSLATIONS )
NOTREACHED
// This is a table of "standard" labels for this module. If you use them
// in the label: sidebar: section of the config file, the existing
......@@ -86,7 +85,7 @@ NetInstallViewStep::widget()
bool
NetInstallViewStep::isNextEnabled() const
{
return m_nextEnabled;
return !m_config.required() || m_nextEnabled;
}
......@@ -111,10 +110,10 @@ NetInstallViewStep::isAtEnd() const
}
QList< Calamares::job_ptr >
Calamares::JobList
NetInstallViewStep::jobs() const
{
return m_jobs;
return Calamares::JobList();
}
......@@ -127,7 +126,7 @@ NetInstallViewStep::onActivate()
void
NetInstallViewStep::onLeave()
{
PackageModel::PackageItemDataList packages = m_widget->selectedPackages();
auto packages = m_config.model()->getPackages();
cDebug() << "Netinstall: Processing" << packages.length() << "packages.";
static const char PACKAGEOP[] = "packageOperations";
......@@ -158,13 +157,13 @@ NetInstallViewStep::onLeave()
for ( const auto& package : packages )
{
if ( package.isCritical )
if ( package->isCritical() )
{
installPackages.append( package.toOperation() );
installPackages.append( package->toOperation() );
}
else
{
tryInstallPackages.append( package.toOperation() );
tryInstallPackages.append( package->toOperation() );
}
}
......@@ -192,16 +191,16 @@ NetInstallViewStep::onLeave()
}
void
NetInstallViewStep::nextIsReady( bool b )
NetInstallViewStep::nextIsReady()
{
m_nextEnabled = b;
emit nextStatusChanged( b );
m_nextEnabled = true;
emit nextStatusChanged( true );
}
void
NetInstallViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_widget->setRequired( CalamaresUtils::getBool( configurationMap, "required", false ) );
m_config.setRequired( CalamaresUtils::getBool( configurationMap, "required", false ) );
QString groupsUrl = CalamaresUtils::getString( configurationMap, "groupsUrl" );
if ( !groupsUrl.isEmpty() )
......@@ -209,7 +208,15 @@ NetInstallViewStep::setConfigurationMap( const QVariantMap& configurationMap )
// Keep putting groupsUrl into the global storage,
// even though it's no longer used for in-module data-passing.
Calamares::JobQueue::instance()->globalStorage()->insert( "groupsUrl", groupsUrl );
m_widget->loadGroupList( groupsUrl );
if ( groupsUrl == QStringLiteral( "local" ) )
{
QVariantList l = configurationMap.value( "groups" ).toList();
m_config.loadGroupList( l );
}
else
{
m_config.loadGroupList( groupsUrl );
}
}
bool bogus = false;
......
......@@ -20,6 +20,8 @@
#ifndef NETINSTALLVIEWSTEP_H
#define NETINSTALLVIEWSTEP_H
#include "Config.h"
#include "DllMacro.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/PluginFactory.h"
......@@ -47,7 +49,7 @@ public:
bool isAtBeginning() const override;
bool isAtEnd() const override;
QList< Calama