diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index f668722fd..c9a171fd2 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -348,6 +348,7 @@ void CalamaresApplication::initViewSteps() { cDebug() << "STARTUP: loadModules for all modules done"; + m_moduleManager->checkRequirements(); if ( Calamares::Branding::instance()->windowMaximize() ) { m_mainwindow->setWindowFlag( Qt::FramelessWindowHint ); @@ -355,6 +356,7 @@ CalamaresApplication::initViewSteps() } else m_mainwindow->show(); + ProgressTreeModel* m = new ProgressTreeModel( nullptr ); ProgressTreeView::instance()->setModel( m ); cDebug() << "STARTUP: Window now visible and ProgressTreeView populated"; diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index 79598d514..99531e5ff 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -5,6 +5,8 @@ set( calamaresui_SOURCES modulesystem/Module.cpp modulesystem/ModuleManager.cpp modulesystem/ProcessJobModule.cpp + modulesystem/Requirement.cpp + modulesystem/RequirementsChecker.cpp modulesystem/ViewModule.cpp utils/CalamaresUtilsGui.cpp diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index ef629ac4d..20e0517ea 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -281,4 +281,10 @@ Module::initFrom( const QVariantMap& moduleDescriptor ) m_maybe_emergency = moduleDescriptor[ EMERGENCY ].toBool(); } +RequirementsList +Module::checkRequirements() +{ + return RequirementsList(); +} + } //ns diff --git a/src/libcalamaresui/modulesystem/Module.h b/src/libcalamaresui/modulesystem/Module.h index f89c9eedb..218270825 100644 --- a/src/libcalamaresui/modulesystem/Module.h +++ b/src/libcalamaresui/modulesystem/Module.h @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +20,7 @@ #ifndef CALAMARES_MODULE_H #define CALAMARES_MODULE_H +#include "Requirement.h" #include "UiDllMacro.h" #include @@ -178,6 +180,11 @@ public: */ QVariantMap configurationMap(); + /** + * @brief Check the requirements of this module. + */ + virtual RequirementsList checkRequirements(); + protected: explicit Module(); virtual void initFrom( const QVariantMap& moduleDescriptor ); diff --git a/src/libcalamaresui/modulesystem/ModuleManager.cpp b/src/libcalamaresui/modulesystem/ModuleManager.cpp index 86d97d2db..d3705729c 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.cpp +++ b/src/libcalamaresui/modulesystem/ModuleManager.cpp @@ -21,26 +21,23 @@ #include "ExecutionViewStep.h" #include "Module.h" -#include "utils/Logger.h" -#include "utils/YamlUtils.h" +#include "RequirementsChecker.h" #include "Settings.h" #include "ViewManager.h" +#include "utils/Logger.h" +#include "utils/YamlUtils.h" + #include #include #include #include -#define MODULE_CONFIG_FILENAME "module.desc" - namespace Calamares { - - ModuleManager* ModuleManager::s_instance = nullptr; - ModuleManager* ModuleManager::instance() { @@ -94,7 +91,7 @@ ModuleManager::doInit() bool success = currentDir.cd( subdir ); if ( success ) { - QFileInfo descriptorFileInfo( currentDir.absoluteFilePath( MODULE_CONFIG_FILENAME ) ); + QFileInfo descriptorFileInfo( currentDir.absoluteFilePath( QLatin1Literal( "module.desc") ) ); if ( ! ( descriptorFileInfo.exists() && descriptorFileInfo.isReadable() ) ) { cDebug() << Q_FUNC_INFO << "unreadable file: " @@ -307,6 +304,26 @@ ModuleManager::loadModules() } ); } +void +ModuleManager::checkRequirements() +{ + cDebug() << "Checking module requirements .."; + + QVector< Module* > modules( m_loadedModulesByInstanceKey.count() ); + int count = 0; + for (const auto& module : m_loadedModulesByInstanceKey ) + { + modules[count++] = module; + } + + RequirementsChecker *rq = new RequirementsChecker( modules, this ); + connect( rq, &RequirementsChecker::requirementsResult, this, &ModuleManager::requirementsResult ); + connect( rq, &RequirementsChecker::requirementsComplete, this, &ModuleManager::requirementsComplete ); + connect( rq, &RequirementsChecker::requirementsProgress, this, &ModuleManager::requirementsProgress ); + connect( rq, &RequirementsChecker::done, rq, &RequirementsChecker::deleteLater ); + + QTimer::singleShot( 0, rq, &RequirementsChecker::run ); +} QStringList ModuleManager::checkDependencies() @@ -334,8 +351,6 @@ ModuleManager::checkDependencies() break; } } - if ( somethingWasRemovedBecauseOfUnmetDependencies ) - break; } if ( !somethingWasRemovedBecauseOfUnmetDependencies ) break; @@ -369,4 +384,4 @@ ModuleManager::checkDependencies( const Module& m ) return allRequirementsFound; } -} +} // namespace diff --git a/src/libcalamaresui/modulesystem/ModuleManager.h b/src/libcalamaresui/modulesystem/ModuleManager.h index a0edc2528..689d61a77 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.h +++ b/src/libcalamaresui/modulesystem/ModuleManager.h @@ -20,6 +20,7 @@ #ifndef MODULELOADER_H #define MODULELOADER_H +#include "Requirement.h" #include "Typedefs.h" #include @@ -30,6 +31,7 @@ namespace Calamares { class Module; +struct RequirementEntry; // from Requirement.h /** * @brief The ModuleManager class is a singleton which manages Calamares modules. @@ -81,10 +83,20 @@ public: */ void loadModules(); + /** + * @brief Starts asynchronous requirements checking for each module. + * When this is done, the signal modulesChecked is emitted. + */ + void checkRequirements(); + signals: void initDone(); void modulesLoaded(); /// All of the modules were loaded successfully void modulesFailed( QStringList ); /// .. or not + // Below, see RequirementsChecker documentation + void requirementsComplete( bool ); + void requirementsResult( RequirementsList ); + void requirementsProgress( const QString& ); private slots: void doInit(); diff --git a/src/libcalamaresui/modulesystem/Requirement.cpp b/src/libcalamaresui/modulesystem/Requirement.cpp new file mode 100644 index 000000000..3347a2ae8 --- /dev/null +++ b/src/libcalamaresui/modulesystem/Requirement.cpp @@ -0,0 +1,19 @@ +/* === This file is part of Calamares - === + * + * Copyright 2017, Adriaan de Groot + * + * 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 . + */ +#include "Requirement.h" + diff --git a/src/libcalamaresui/modulesystem/Requirement.h b/src/libcalamaresui/modulesystem/Requirement.h new file mode 100644 index 000000000..bf2157f69 --- /dev/null +++ b/src/libcalamaresui/modulesystem/Requirement.h @@ -0,0 +1,64 @@ +/* === This file is part of Calamares - === + * + * Copyright 2017, Adriaan de Groot + * + * 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 . + */ +#ifndef CALAMARES_REQUIREMENT_H +#define CALAMARES_REQUIREMENT_H + +#include +#include +#include + +#include + +namespace Calamares +{ + +/** + * An indication of a requirement, which is checked in preparation + * for system installation. An entry has a name and some explanation functions + * (functions, because they need to respond to translations). + * + * A requirement can be *satisfied* or not. + * A requirement can be optional (i.e. a "good to have") or mandatory. + * + * Requirements which are not satisfied, and also mandatory, will prevent the + * installation from proceeding. + */ +struct RequirementEntry +{ + using TextFunction = std::function< QString() >; + + /// @brief name of this requirement; not shown to user and used as ID + QString name; + + /// @brief Description of this requirement, for use in user-visible lists + TextFunction enumerationText; + + /// @brief User-visible string to show that the requirement is not met + TextFunction negatedText; + + bool satisfied; + bool mandatory; +}; + +using RequirementsList = QList< RequirementEntry >; + +} // namespace Calamares + +Q_DECLARE_METATYPE(Calamares::RequirementEntry) + +#endif diff --git a/src/libcalamaresui/modulesystem/RequirementsChecker.cpp b/src/libcalamaresui/modulesystem/RequirementsChecker.cpp new file mode 100644 index 000000000..c7f4625eb --- /dev/null +++ b/src/libcalamaresui/modulesystem/RequirementsChecker.cpp @@ -0,0 +1,154 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * 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 . + */ + +#include "RequirementsChecker.h" + +#include "Module.h" +#include "Requirement.h" + +#include "utils/Logger.h" + +#include + +#include +#include +#include +#include + + +namespace Calamares +{ + +static void +registerMetatypes() +{ + static bool done = false; + + if ( !done ) + { + qRegisterMetaType< RequirementEntry >( "RequirementEntry" ); + // It's sensitive to the names of types in parameters; in particular + // althrough QList is the same as RequirementsList, + // because we *name* the type as RequirementsList in the parameters, + // we need to register that (as well). Here, be safe and register + // both names. + qRegisterMetaType< QList< RequirementEntry > >( "QList" ); + qRegisterMetaType< RequirementsList >( "RequirementsList" ); + done = true; + } +} + +static void +check( Module * const &m, RequirementsChecker *c ) +{ + RequirementsList l = m->checkRequirements(); + if ( l.count() > 0 ) + c->addCheckedRequirements( l ); + c->requirementsProgress( QObject::tr( "Requirements checking for module %1 is complete." ).arg( m->name() ) ); +} + +RequirementsChecker::RequirementsChecker( QVector< Module* > modules, QObject* parent ) + : QObject( parent ) + , m_modules( std::move( modules ) ) + , m_progressTimer( nullptr ) + , m_progressTimeouts( 0 ) +{ + m_watchers.reserve( m_modules.count() ); + m_collectedRequirements.reserve( m_modules.count() ); + + registerMetatypes(); +} + +RequirementsChecker::~RequirementsChecker() +{ +} + +void +RequirementsChecker::run() +{ + m_progressTimer = new QTimer( this ); + connect( m_progressTimer, &QTimer::timeout, this, &RequirementsChecker::reportProgress ); + m_progressTimer->start( 1200 ); // msec + + for (const auto& module : m_modules ) + { + Watcher *watcher = new Watcher( this ); + watcher->setFuture( QtConcurrent::run( check, module, this ) ); + m_watchers.append( watcher ); + connect( watcher, &Watcher::finished, this, &RequirementsChecker::finished ); + } + + QTimer::singleShot( 0, this, &RequirementsChecker::finished ); +} + +void +RequirementsChecker::finished() +{ + if ( std::all_of( m_watchers.cbegin(), m_watchers.cend(), []( const Watcher *w ) { return w && w->isFinished(); } ) ) + { + cDebug() << "All requirements have been checked."; + + if ( m_progressTimer ) + m_progressTimer->stop(); + + bool acceptable = true; + int count = 0; + for ( const auto& r : m_collectedRequirements ) + { + if ( r.mandatory && !r.satisfied ) + { + cDebug() << " .. requirement" << count << r.name << "is not satisfied."; + acceptable = false; + } + ++count; + } + + emit requirementsComplete( acceptable ); + QTimer::singleShot(0, this, &RequirementsChecker::done ); + } +} + +void +RequirementsChecker::addCheckedRequirements( RequirementsList l ) +{ + static QMutex addMutex; + { + QMutexLocker lock( &addMutex ); + m_collectedRequirements.append( l ); + } + cDebug() << "Added" << l.count() << "requirement results"; + emit requirementsResult( l ); +} + +void +RequirementsChecker::reportProgress() +{ + m_progressTimeouts++; + + auto remaining = std::count_if( m_watchers.cbegin(), m_watchers.cend(), []( const Watcher *w ) { return w && !w->isFinished(); } ); + if ( remaining > 0 ) + { + QString waiting = tr( "Waiting for %n module(s).", "", remaining ); + QString elapsed = tr( "(%n second(s))", "", m_progressTimeouts * m_progressTimer->interval() / 1000 ); + emit requirementsProgress( waiting + QString( " " ) + elapsed ); + } + else + emit requirementsProgress( tr( "System-requirements checking is complete." ) ); +} + +} diff --git a/src/libcalamaresui/modulesystem/RequirementsChecker.h b/src/libcalamaresui/modulesystem/RequirementsChecker.h new file mode 100644 index 000000000..6e681971c --- /dev/null +++ b/src/libcalamaresui/modulesystem/RequirementsChecker.h @@ -0,0 +1,87 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * 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 . + */ +#ifndef CALAMARES_REQUIREMENTSCHECKER_H +#define CALAMARES_REQUIREMENTSCHECKER_H + +#include "Requirement.h" + +#include +#include +#include +#include + + +namespace Calamares +{ + +class Module; + +/** @brief A manager-class that checks all the module requirements + * + * Asynchronously checks the requirements for each module, and + * emits progress signals as appropriate. + */ +class RequirementsChecker : public QObject +{ + Q_OBJECT + +public: + RequirementsChecker( QVector< Module* > modules, QObject* parent = nullptr ); + virtual ~RequirementsChecker() override; + +public slots: + /// @brief Start checking all the requirements + void run(); + + /// @brief Called when requirements are reported by a module + void addCheckedRequirements( RequirementsList ); + + /// @brief Called when all requirements have been checked + void finished(); + + /// @brief Called periodically while requirements are being checked + void reportProgress(); + +signals: + /// @brief Human-readable progress message + void requirementsProgress( const QString& ); + /// @brief Requirements from a single module + void requirementsResult( RequirementsList ); + /** @brief When all requirements are collected + * + * The argument indicates if all mandatory requirements are satisfied. + */ + void requirementsComplete( bool ); + /// @brief Emitted after requirementsComplete + void done(); + +private: + QVector< Module* > m_modules; + + using Watcher = QFutureWatcher< void >; + QVector< Watcher* > m_watchers; + + RequirementsList m_collectedRequirements; + + QTimer *m_progressTimer; + unsigned m_progressTimeouts; +} ; + +} + +#endif diff --git a/src/libcalamaresui/modulesystem/ViewModule.cpp b/src/libcalamaresui/modulesystem/ViewModule.cpp index 473ec6457..e24014621 100644 --- a/src/libcalamaresui/modulesystem/ViewModule.cpp +++ b/src/libcalamaresui/modulesystem/ViewModule.cpp @@ -132,4 +132,10 @@ ViewModule::~ViewModule() delete m_loader; } +RequirementsList +ViewModule::checkRequirements() +{ + return m_viewStep->checkRequirements(); +} + } // namespace Calamares diff --git a/src/libcalamaresui/modulesystem/ViewModule.h b/src/libcalamaresui/modulesystem/ViewModule.h index 735a19a81..7813130d0 100644 --- a/src/libcalamaresui/modulesystem/ViewModule.h +++ b/src/libcalamaresui/modulesystem/ViewModule.h @@ -39,6 +39,8 @@ public: void loadSelf() override; JobList jobs() const override; + RequirementsList checkRequirements() override; + protected: void initFrom( const QVariantMap& moduleDescriptor ) override; diff --git a/src/libcalamaresui/viewpages/ViewStep.cpp b/src/libcalamaresui/viewpages/ViewStep.cpp index ec78c316f..8a76a05ce 100644 --- a/src/libcalamaresui/viewpages/ViewStep.cpp +++ b/src/libcalamaresui/viewpages/ViewStep.cpp @@ -74,4 +74,10 @@ ViewStep::setConfigurationMap( const QVariantMap& configurationMap ) Q_UNUSED( configurationMap ); } + +RequirementsList ViewStep::checkRequirements() +{ + return RequirementsList(); +} + } diff --git a/src/libcalamaresui/viewpages/ViewStep.h b/src/libcalamaresui/viewpages/ViewStep.h index a1275a198..0010f407e 100644 --- a/src/libcalamaresui/viewpages/ViewStep.h +++ b/src/libcalamaresui/viewpages/ViewStep.h @@ -22,6 +22,7 @@ #include +#include "modulesystem/Requirement.h" #include "../UiDllMacro.h" #include "Typedefs.h" @@ -117,6 +118,12 @@ public: */ virtual void onLeave(); + /** + * @brief Jobs needed to run this viewstep + * + * When a ViewStep is listed in the exec section, its jobs are executed instead. + * This function returns that list of jobs; an empty list is ok. + */ virtual JobList jobs() const = 0; void setModuleInstanceKey( const QString& instanceKey ); @@ -127,7 +134,17 @@ public: virtual void setConfigurationMap( const QVariantMap& configurationMap ); + /** + * @brief Can this module proceed, on this machine? + * + * This is called asynchronously at startup, and returns a list of + * the requirements that the module has checked, and their status. + * See Calamares::RequirementEntry for details. + */ + virtual RequirementsList checkRequirements(); + signals: + /// @brief Tells the viewmanager to enable the *next* button according to @p status void nextStatusChanged( bool status ); /* Emitted when the viewstep thinks it needs more space than is currently diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp index 21783c251..9d86a32a1 100644 --- a/src/modules/partition/gui/ChoicePage.cpp +++ b/src/modules/partition/gui/ChoicePage.cpp @@ -882,11 +882,6 @@ ChoicePage::updateDeviceStatePreview() PartitionModel* model = new PartitionModel( m_beforePartitionBarsView ); model->init( deviceBefore, m_core->osproberEntries() ); - // The QObject parents tree is meaningful for memory management here, - // see qDeleteAll above. - deviceBefore->setParent( model ); // Can't reparent across threads - model->setParent( m_beforePartitionBarsView ); - m_beforePartitionBarsView->setModel( model ); m_beforePartitionLabelsView->setModel( model ); diff --git a/src/modules/partition/gui/PartitionViewStep.cpp b/src/modules/partition/gui/PartitionViewStep.cpp index bee5ad965..6fc8b0129 100644 --- a/src/modules/partition/gui/PartitionViewStep.cpp +++ b/src/modules/partition/gui/PartitionViewStep.cpp @@ -60,6 +60,8 @@ #include #include +#include // For sleep(3) + PartitionViewStep::PartitionViewStep( QObject* parent ) : Calamares::ViewStep( parent ) , m_core( nullptr ) @@ -213,8 +215,8 @@ PartitionViewStep::createSummaryWidget() const else // multiple disk previews! { diskInfoLabel->setText( tr( "Disk %1 (%2)" ) - .arg( info.deviceNode ) - .arg( info.deviceName ) ); + .arg( info.deviceNode ) + .arg( info.deviceName ) ); } formLayout->addRow( diskInfoLabel ); @@ -223,9 +225,9 @@ PartitionViewStep::createSummaryWidget() const QVBoxLayout* field; PartitionBarsView::NestedPartitionsMode mode = Calamares::JobQueue::instance()->globalStorage()-> - value( "drawNestedPartitions" ).toBool() ? - PartitionBarsView::DrawNestedPartitions : - PartitionBarsView::NoNestedPartitions; + value( "drawNestedPartitions" ).toBool() ? + PartitionBarsView::DrawNestedPartitions : + PartitionBarsView::NoNestedPartitions; preview = new PartitionBarsView; preview->setNestedPartitionsMode( mode ); previewLabels = new PartitionLabelsView; @@ -263,7 +265,7 @@ PartitionViewStep::createSummaryWidget() const foreach ( const Calamares::job_ptr& job, jobs() ) { if ( !job->prettyDescription().isEmpty() ) - jobsLines.append( job->prettyDescription() ); + jobsLines.append( job->prettyDescription() ); } if ( !jobsLines.isEmpty() ) { @@ -343,8 +345,8 @@ PartitionViewStep::isAtEnd() const if ( m_choicePage == m_widget->currentWidget() ) { if ( m_choicePage->currentChoice() == ChoicePage::Erase || - m_choicePage->currentChoice() == ChoicePage::Replace || - m_choicePage->currentChoice() == ChoicePage::Alongside ) + m_choicePage->currentChoice() == ChoicePage::Replace || + m_choicePage->currentChoice() == ChoicePage::Alongside ) return true; return false; } @@ -357,7 +359,7 @@ PartitionViewStep::onActivate() { // if we're coming back to PVS from the next VS if ( m_widget->currentWidget() == m_choicePage && - m_choicePage->currentChoice() == ChoicePage::Alongside ) + m_choicePage->currentChoice() == ChoicePage::Alongside ) { m_choicePage->applyActionChoice( ChoicePage::Alongside ); // m_choicePage->reset(); @@ -380,7 +382,7 @@ PartitionViewStep::onLeave() if ( PartUtils::isEfiSystem() ) { QString espMountPoint = Calamares::JobQueue::instance()->globalStorage()-> - value( "efiSystemPartition").toString(); + value( "efiSystemPartition" ).toString(); Partition* esp = m_core->findPartitionByMountPoint( espMountPoint ); QString message; @@ -435,7 +437,7 @@ PartitionViewStep::onLeave() // If the root partition is encrypted, and there's a separate boot // partition which is not encrypted if ( root_p->fileSystem().type() == FileSystem::Luks && - boot_p->fileSystem().type() != FileSystem::Luks ) + boot_p->fileSystem().type() != FileSystem::Luks ) { message = tr( "Boot partition not encrypted" ); description = tr( "A separate boot partition was set up together with " @@ -584,17 +586,18 @@ PartitionViewStep::setConfigurationMap( const QVariantMap& configurationMap ) // Now that we have the config, we load the PartitionCoreModule in the background // because it could take a while. Then when it's done, we can set up the widgets // and remove the spinner. - QFutureWatcher< void >* watcher = new QFutureWatcher< void >(); - connect( watcher, &QFutureWatcher< void >::finished, - this, [ this, watcher, choices ] + m_future = new QFutureWatcher< void >(); + connect( m_future, &QFutureWatcher< void >::finished, + this, [ this ] { continueLoading(); - watcher->deleteLater(); + this->m_future->deleteLater(); + this->m_future = nullptr; } ); QFuture< void > future = - QtConcurrent::run( this, &PartitionViewStep::initPartitionCoreModule ); - watcher->setFuture( future ); + QtConcurrent::run( this, &PartitionViewStep::initPartitionCoreModule ); + m_future->setFuture( future ); if ( configurationMap.contains( "partitionLayout" ) ) { @@ -613,5 +616,24 @@ PartitionViewStep::jobs() const return m_core->jobs(); } +Calamares::RequirementsList +PartitionViewStep::checkRequirements() +{ + if ( m_future ) + m_future->waitForFinished(); + + Calamares::RequirementsList l; + l.append( + { + QLatin1Literal( "partitions" ), + []{ return tr( "has at least one disk device available." ); }, + []{ return tr( "There are no partitons to install on." ); }, + m_core->deviceModel()->rowCount() > 0, // satisfied + true // required + } ); + + return l; +} + CALAMARES_PLUGIN_FACTORY_DEFINITION( PartitionViewStepFactory, registerPlugin(); ) diff --git a/src/modules/partition/gui/PartitionViewStep.h b/src/modules/partition/gui/PartitionViewStep.h index f23108316..47f8fa127 100644 --- a/src/modules/partition/gui/PartitionViewStep.h +++ b/src/modules/partition/gui/PartitionViewStep.h @@ -36,6 +36,8 @@ class PartitionPage; class PartitionCoreModule; class QStackedWidget; +template class QFutureWatcher; + /** * The starting point of the module. Instantiates PartitionCoreModule, * ChoicePage and PartitionPage, then connects them. @@ -69,6 +71,8 @@ public: QList< Calamares::job_ptr > jobs() const override; + Calamares::RequirementsList checkRequirements() override; + private: void initPartitionCoreModule(); void continueLoading(); @@ -79,6 +83,7 @@ private: PartitionPage* m_manualPartitionPage; QWidget* m_waitingWidget; + QFutureWatcher* m_future; QSet< PartitionActions::Choices::SwapChoice > m_swapChoices; }; diff --git a/src/modules/welcome/CMakeLists.txt b/src/modules/welcome/CMakeLists.txt index a520aa080..915f5fd2c 100644 --- a/src/modules/welcome/CMakeLists.txt +++ b/src/modules/welcome/CMakeLists.txt @@ -15,9 +15,10 @@ endif() include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui ) set( CHECKER_SOURCES - checker/CheckItemWidget.cpp + checker/CheckerContainer.cpp checker/CheckerWidget.cpp - checker/RequirementsChecker.cpp + checker/CheckItemWidget.cpp + checker/GeneralRequirements.cpp ${PARTMAN_SRC} ) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 8ffb153bf..d4ae6c47a 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -22,10 +22,12 @@ #include "ui_WelcomePage.h" #include "CalamaresVersion.h" -#include "checker/RequirementsChecker.h" +#include "checker/CheckerContainer.h" #include "utils/Logger.h" #include "utils/CalamaresUtilsGui.h" #include "utils/Retranslator.h" + +#include "modulesystem/ModuleManager.h" #include "ViewManager.h" #include @@ -39,11 +41,14 @@ #include "Branding.h" -WelcomePage::WelcomePage( RequirementsChecker* requirementsChecker, QWidget* parent ) +WelcomePage::WelcomePage( QWidget* parent ) : QWidget( parent ) , ui( new Ui::WelcomePage ) - , m_requirementsChecker( requirementsChecker ) + , m_checkingWidget( new CheckerContainer( this ) ) { + connect( Calamares::ModuleManager::instance(), &Calamares::ModuleManager::requirementsResult, m_checkingWidget, &CheckerContainer::requirementsChecked ); + connect( Calamares::ModuleManager::instance(), &Calamares::ModuleManager::requirementsComplete, m_checkingWidget, &CheckerContainer::requirementsComplete ); + connect( Calamares::ModuleManager::instance(), &Calamares::ModuleManager::requirementsProgress, m_checkingWidget, &CheckerContainer::requirementsProgress ); ui->setupUi( this ); ui->verticalLayout->insertSpacing( 1, CalamaresUtils::defaultFontHeight() * 2 ); @@ -102,7 +107,7 @@ WelcomePage::WelcomePage( RequirementsChecker* requirementsChecker, QWidget* par mb.exec(); } ); - ui->verticalLayout->insertWidget( 3, m_requirementsChecker->widget() ); + ui->verticalLayout->insertWidget( 3, m_checkingWidget); } @@ -277,3 +282,8 @@ WelcomePage::focusInEvent( QFocusEvent* e ) ui->languageWidget->setFocus(); e->accept(); } + +bool WelcomePage::verdict() const +{ + return m_checkingWidget->verdict(); +} diff --git a/src/modules/welcome/WelcomePage.h b/src/modules/welcome/WelcomePage.h index cf187aecb..65b619c79 100644 --- a/src/modules/welcome/WelcomePage.h +++ b/src/modules/welcome/WelcomePage.h @@ -26,26 +26,27 @@ namespace Ui class WelcomePage; } -class RequirementsChecker; +class CheckerContainer; class WelcomePage : public QWidget { Q_OBJECT public: - explicit WelcomePage( RequirementsChecker* requirementsChecker, - QWidget* parent = nullptr ); + explicit WelcomePage( QWidget* parent = nullptr ); void setUpLinks( bool showSupportUrl, bool showKnownIssuesUrl, bool showReleaseNotesUrl ); + bool verdict() const; + protected: void focusInEvent( QFocusEvent* e ) override; //choose the child widget to focus private: void initLanguages(); Ui::WelcomePage* ui; - RequirementsChecker* m_requirementsChecker; + CheckerContainer* m_checkingWidget; }; #endif // WELCOMEPAGE_H diff --git a/src/modules/welcome/WelcomeViewStep.cpp b/src/modules/welcome/WelcomeViewStep.cpp index 756c5aa74..88b5f6324 100644 --- a/src/modules/welcome/WelcomeViewStep.cpp +++ b/src/modules/welcome/WelcomeViewStep.cpp @@ -20,9 +20,10 @@ #include "WelcomeViewStep.h" #include "WelcomePage.h" -#include "checker/RequirementsChecker.h" -#include "utils/Logger.h" +#include "checker/GeneralRequirements.h" +#include "modulesystem/ModuleManager.h" +#include "utils/Logger.h" #include @@ -30,12 +31,10 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( WelcomeViewStepFactory, registerPluginverdict(); + return m_widget->verdict(); } @@ -88,10 +87,10 @@ WelcomeViewStep::isAtEnd() const } -QList< Calamares::job_ptr > +Calamares::JobList WelcomeViewStep::jobs() const { - return QList< Calamares::job_ptr >(); + return Calamares::JobList(); } @@ -123,3 +122,7 @@ WelcomeViewStep::setConfigurationMap( const QVariantMap& configurationMap ) "module configuration."; } +Calamares::RequirementsList WelcomeViewStep::checkRequirements() +{ + return m_requirementsChecker->checkRequirements(); +} diff --git a/src/modules/welcome/WelcomeViewStep.h b/src/modules/welcome/WelcomeViewStep.h index b556b1ca1..937cad246 100644 --- a/src/modules/welcome/WelcomeViewStep.h +++ b/src/modules/welcome/WelcomeViewStep.h @@ -21,6 +21,7 @@ #include +#include #include #include @@ -29,7 +30,7 @@ #include class WelcomePage; -class RequirementsChecker; +class GeneralRequirements; class PLUGINDLLEXPORT WelcomeViewStep : public Calamares::ViewStep { @@ -49,14 +50,15 @@ public: bool isAtBeginning() const override; bool isAtEnd() const override; - QList< Calamares::job_ptr > jobs() const override; + Calamares::JobList jobs() const override; void setConfigurationMap( const QVariantMap& configurationMap ) override; + Calamares::RequirementsList checkRequirements() override; + private: WelcomePage* m_widget; - - RequirementsChecker* m_requirementsChecker; + GeneralRequirements* m_requirementsChecker; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( WelcomeViewStepFactory ) diff --git a/src/modules/welcome/checker/CheckerContainer.cpp b/src/modules/welcome/checker/CheckerContainer.cpp new file mode 100644 index 000000000..6f46cd094 --- /dev/null +++ b/src/modules/welcome/checker/CheckerContainer.cpp @@ -0,0 +1,78 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2017, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot + * Copyright 2017, Gabriel Craciunescu + * + * 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 . + */ + +/* Based on code extracted from RequirementsChecker.cpp */ + +#include "CheckerContainer.h" + +#include "CheckerWidget.h" + +#include "utils/CalamaresUtilsGui.h" +#include "utils/Logger.h" +#include "utils/Retranslator.h" +#include "widgets/WaitingWidget.h" + +CheckerContainer::CheckerContainer(QWidget* parent) + : QWidget( parent ) + , m_waitingWidget( new WaitingWidget( QString() ) ) + , m_checkerWidget( new CheckerWidget() ) + , m_verdict( false ) +{ + QBoxLayout* mainLayout = new QHBoxLayout; + setLayout( mainLayout ); + CalamaresUtils::unmarginLayout( mainLayout ); + + mainLayout->addWidget( m_waitingWidget ); + CALAMARES_RETRANSLATE( m_waitingWidget->setText( tr( "Gathering system information..." ) ); ) +} + +CheckerContainer::~CheckerContainer() +{ + delete m_waitingWidget; + delete m_checkerWidget; +} + +void CheckerContainer::requirementsComplete( bool ok ) +{ + m_checkerWidget->init( m_requirements ); + layout()->removeWidget( m_waitingWidget ); + m_waitingWidget->deleteLater(); + m_waitingWidget = nullptr; // Don't delete in destructor + m_checkerWidget->setParent( this ); + layout()->addWidget( m_checkerWidget ); + + m_verdict = ok; +} + +void CheckerContainer::requirementsChecked(const Calamares::RequirementsList& l) +{ + m_requirements.append( l ); +} + +void CheckerContainer::requirementsProgress(const QString& message) +{ + if ( m_waitingWidget ) + m_waitingWidget->setText( message ); +} + +bool CheckerContainer::verdict() const +{ + return m_verdict; +} diff --git a/src/modules/welcome/checker/CheckerContainer.h b/src/modules/welcome/checker/CheckerContainer.h new file mode 100644 index 000000000..5a8a1865e --- /dev/null +++ b/src/modules/welcome/checker/CheckerContainer.h @@ -0,0 +1,64 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2017, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot + * Copyright 2017, Gabriel Craciunescu + * + * 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 . + */ + +/* Based on code extracted from RequirementsChecker.cpp */ + +#ifndef CHECKERCONTAINER_H +#define CHECKERCONTAINER_H + +#include + +#include "modulesystem/Requirement.h" + +class CheckerWidget; +class WaitingWidget; + +/** + * A widget that collects requirements results; until the results are + * all in, displays a spinner / waiting widget. Then it switches to + * a (list) diplay of the results, plus some explanation of the + * overall state of the entire list of results. + */ +class CheckerContainer : public QWidget +{ + Q_OBJECT +public: + explicit CheckerContainer( QWidget* parent = nullptr ); + virtual ~CheckerContainer(); + + bool verdict() const; + +public slots: + void requirementsChecked( const Calamares::RequirementsList& ); + + /** @brief All the requirements are complete, switch to list view */ + void requirementsComplete( bool ); + + void requirementsProgress( const QString& message ); + +protected: + WaitingWidget *m_waitingWidget; + CheckerWidget *m_checkerWidget; + + Calamares::RequirementsList m_requirements; + bool m_verdict; +} ; + +#endif diff --git a/src/modules/welcome/checker/CheckerWidget.cpp b/src/modules/welcome/checker/CheckerWidget.cpp index 07a612a9f..2ad79abfd 100644 --- a/src/modules/welcome/checker/CheckerWidget.cpp +++ b/src/modules/welcome/checker/CheckerWidget.cpp @@ -53,22 +53,22 @@ CheckerWidget::CheckerWidget( QWidget* parent ) void -CheckerWidget::init( const QList< PrepareEntry >& checkEntries ) +CheckerWidget::init( const Calamares::RequirementsList& checkEntries ) { bool allChecked = true; bool requirementsSatisfied = true; - for ( const PrepareEntry& entry : checkEntries ) + for ( const auto& entry : checkEntries ) { - if ( !entry.checked ) + if ( !entry.satisfied ) { - CheckItemWidget* ciw = new CheckItemWidget( entry.checked, entry.required ); + CheckItemWidget* ciw = new CheckItemWidget( entry.satisfied, entry.mandatory ); CALAMARES_RETRANSLATE( ciw->setText( entry.negatedText() ); ) m_entriesLayout->addWidget( ciw ); ciw->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); allChecked = false; - if ( entry.required ) + if ( entry.mandatory ) { requirementsSatisfied = false; } @@ -162,7 +162,7 @@ CheckerWidget::init( const QList< PrepareEntry >& checkEntries ) void -CheckerWidget::showDetailsDialog( const QList< PrepareEntry >& checkEntries ) +CheckerWidget::showDetailsDialog( const Calamares::RequirementsList& checkEntries ) { QDialog* detailsDialog = new QDialog( this ); QBoxLayout* mainLayout = new QVBoxLayout; @@ -177,12 +177,12 @@ CheckerWidget::showDetailsDialog( const QList< PrepareEntry >& checkEntries ) CalamaresUtils::unmarginLayout( entriesLayout ); mainLayout->addLayout( entriesLayout ); - for ( const PrepareEntry& entry : checkEntries ) + for ( const auto& entry : checkEntries ) { if ( entry.enumerationText().isEmpty() ) continue; - CheckItemWidget* ciw = new CheckItemWidget( entry.checked, entry.required ); + CheckItemWidget* ciw = new CheckItemWidget( entry.satisfied, entry.mandatory ); CALAMARES_RETRANSLATE( ciw->setText( entry.enumerationText() ); ) entriesLayout->addWidget( ciw ); ciw->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); diff --git a/src/modules/welcome/checker/CheckerWidget.h b/src/modules/welcome/checker/CheckerWidget.h index 8081e4ee4..81eb6fef0 100644 --- a/src/modules/welcome/checker/CheckerWidget.h +++ b/src/modules/welcome/checker/CheckerWidget.h @@ -19,7 +19,7 @@ #ifndef CHECKERWIDGET_H #define CHECKERWIDGET_H -#include "RequirementsChecker.h" +#include "modulesystem/Requirement.h" #include #include @@ -30,10 +30,10 @@ class CheckerWidget : public QWidget public: explicit CheckerWidget( QWidget* parent = nullptr ); - void init( const QList< PrepareEntry >& checkEntries ); + void init( const Calamares::RequirementsList& checkEntries ); private: - void showDetailsDialog( const QList< PrepareEntry >& checkEntries ); + void showDetailsDialog( const Calamares::RequirementsList& checkEntries ); QBoxLayout* m_mainLayout; QBoxLayout* m_entriesLayout; diff --git a/src/modules/welcome/checker/RequirementsChecker.cpp b/src/modules/welcome/checker/GeneralRequirements.cpp similarity index 52% rename from src/modules/welcome/checker/RequirementsChecker.cpp rename to src/modules/welcome/checker/GeneralRequirements.cpp index a5255058d..7880d3c65 100644 --- a/src/modules/welcome/checker/RequirementsChecker.cpp +++ b/src/modules/welcome/checker/GeneralRequirements.cpp @@ -18,11 +18,12 @@ * along with Calamares. If not, see . */ -#include "RequirementsChecker.h" +#include "GeneralRequirements.h" -#include "CheckerWidget.h" +#include "CheckerContainer.h" #include "partman_devices.h" +#include "modulesystem/Requirement.h" #include "widgets/WaitingWidget.h" #include "utils/CalamaresUtilsGui.h" #include "utils/Logger.h" @@ -30,6 +31,7 @@ #include "utils/CalamaresUtilsSystem.h" #include "utils/Units.h" + #include "JobQueue.h" #include "GlobalStorage.h" @@ -51,166 +53,113 @@ #include //geteuid -RequirementsChecker::RequirementsChecker( QObject* parent ) +GeneralRequirements::GeneralRequirements( QObject* parent ) : QObject( parent ) - , m_widget( new QWidget() ) , m_requiredStorageGB( -1 ) , m_requiredRamGB( -1 ) - , m_actualWidget( new CheckerWidget() ) - , m_verdict( false ) { - QBoxLayout* mainLayout = new QHBoxLayout; - m_widget->setLayout( mainLayout ); - CalamaresUtils::unmarginLayout( mainLayout ); - - WaitingWidget* waitingWidget = new WaitingWidget( QString() ); - mainLayout->addWidget( waitingWidget ); - CALAMARES_RETRANSLATE( waitingWidget->setText( tr( "Gathering system information..." ) ); ) - - QSize availableSize = qApp->desktop()->availableGeometry( m_widget ).size(); - - QTimer* timer = new QTimer; - timer->setSingleShot( true ); - connect( timer, &QTimer::timeout, - [=]() - { - bool enoughStorage = false; - bool enoughRam = false; - bool hasPower = false; - bool hasInternet = false; - bool isRoot = false; - bool enoughScreen = (availableSize.width() >= CalamaresUtils::windowMinimumWidth) && (availableSize.height() >= CalamaresUtils::windowMinimumHeight); - - qint64 requiredStorageB = CalamaresUtils::GiBtoBytes(m_requiredStorageGB); - cDebug() << "Need at least storage bytes:" << requiredStorageB; - if ( m_entriesToCheck.contains( "storage" ) ) - enoughStorage = checkEnoughStorage( requiredStorageB ); - - qint64 requiredRamB = CalamaresUtils::GiBtoBytes(m_requiredRamGB); - cDebug() << "Need at least ram bytes:" << requiredRamB; - if ( m_entriesToCheck.contains( "ram" ) ) - enoughRam = checkEnoughRam( requiredRamB ); - - if ( m_entriesToCheck.contains( "power" ) ) - hasPower = checkHasPower(); - - if ( m_entriesToCheck.contains( "internet" ) ) - hasInternet = checkHasInternet(); - - if ( m_entriesToCheck.contains( "root" ) ) - isRoot = checkIsRoot(); - - using TR = Logger::DebugRow; - - cDebug() << "RequirementsChecker output:" - << TR("enoughStorage", enoughStorage) - << TR("enoughRam", enoughRam) - << TR("hasPower", hasPower) - << TR("hasInternet", hasInternet) - << TR("isRoot", isRoot); - - QList< PrepareEntry > checkEntries; - foreach ( const QString& entry, m_entriesToCheck ) - { - if ( entry == "storage" ) - checkEntries.append( { - entry, - [this]{ return tr( "has at least %1 GB available drive space" ) - .arg( m_requiredStorageGB ); }, - [this]{ return tr( "There is not enough drive space. At least %1 GB is required." ) - .arg( m_requiredStorageGB ); }, - enoughStorage, - m_entriesToRequire.contains( entry ) - } ); - else if ( entry == "ram" ) - checkEntries.append( { - entry, - [this]{ return tr( "has at least %1 GB working memory" ) - .arg( m_requiredRamGB ); }, - [this]{ return tr( "The system does not have enough working memory. At least %1 GB is required." ) - .arg( m_requiredRamGB ); }, - enoughRam, - m_entriesToRequire.contains( entry ) - } ); - else if ( entry == "power" ) - checkEntries.append( { - entry, - [this]{ return tr( "is plugged in to a power source" ); }, - [this]{ return tr( "The system is not plugged in to a power source." ); }, - hasPower, - m_entriesToRequire.contains( entry ) - } ); - else if ( entry == "internet" ) - checkEntries.append( { - entry, - [this]{ return tr( "is connected to the Internet" ); }, - [this]{ return tr( "The system is not connected to the Internet." ); }, - hasInternet, - m_entriesToRequire.contains( entry ) - } ); - else if ( entry == "root" ) - checkEntries.append( { - entry, - [this]{ return QString(); }, //we hide it - [this]{ return tr( "The installer is not running with administrator rights." ); }, - isRoot, - m_entriesToRequire.contains( entry ) - } ); - else if ( entry == "screen" ) - checkEntries.append( { - entry, - [this]{ return QString(); }, // we hide it - [this]{ return tr( "The screen is too small to display the installer." ); }, - enoughScreen, - false - } ); - } - - m_actualWidget->init( checkEntries ); - m_widget->layout()->removeWidget( waitingWidget ); - waitingWidget->deleteLater(); - m_actualWidget->setParent( m_widget ); - m_widget->layout()->addWidget( m_actualWidget ); - - bool canGoNext = true; - foreach ( const PrepareEntry& entry, checkEntries ) - { - if ( !entry.checked && entry.required ) - { - canGoNext = false; - break; - } - } - m_verdict = canGoNext; - emit verdictChanged( m_verdict ); - - if ( canGoNext ) - detectFirmwareType(); - - timer->deleteLater(); - } ); - timer->start( 0 ); - - emit verdictChanged( true ); } - -RequirementsChecker::~RequirementsChecker() +Calamares::RequirementsList GeneralRequirements::checkRequirements() { - if ( m_widget && m_widget->parent() == nullptr ) - m_widget->deleteLater(); -} - - -QWidget* -RequirementsChecker::widget() const -{ - return m_widget; + QSize availableSize = qApp->desktop()->availableGeometry().size(); + + bool enoughStorage = false; + bool enoughRam = false; + bool hasPower = false; + bool hasInternet = false; + bool isRoot = false; + bool enoughScreen = (availableSize.width() >= CalamaresUtils::windowMinimumWidth) && (availableSize.height() >= CalamaresUtils::windowMinimumHeight); + + qint64 requiredStorageB = CalamaresUtils::GiBtoBytes(m_requiredStorageGB); + cDebug() << "Need at least storage bytes:" << requiredStorageB; + if ( m_entriesToCheck.contains( "storage" ) ) + enoughStorage = checkEnoughStorage( requiredStorageB ); + + qint64 requiredRamB = CalamaresUtils::GiBtoBytes(m_requiredRamGB); + cDebug() << "Need at least ram bytes:" << requiredRamB; + if ( m_entriesToCheck.contains( "ram" ) ) + enoughRam = checkEnoughRam( requiredRamB ); + + if ( m_entriesToCheck.contains( "power" ) ) + hasPower = checkHasPower(); + + if ( m_entriesToCheck.contains( "internet" ) ) + hasInternet = checkHasInternet(); + + if ( m_entriesToCheck.contains( "root" ) ) + isRoot = checkIsRoot(); + + using TR = Logger::DebugRow; + cDebug() << "GeneralRequirements output:" + << TR("enoughStorage", enoughStorage) + << TR("enoughRam", enoughRam) + << TR("hasPower", hasPower) + << TR("hasInternet", hasInternet) + << TR("isRoot", isRoot); + + Calamares::RequirementsList checkEntries; + foreach ( const QString& entry, m_entriesToCheck ) + { + if ( entry == "storage" ) + checkEntries.append( { + entry, + [this]{ return tr( "has at least %1 GB available drive space" ) + .arg( m_requiredStorageGB ); }, + [this]{ return tr( "There is not enough drive space. At least %1 GB is required." ) + .arg( m_requiredStorageGB ); }, + enoughStorage, + m_entriesToRequire.contains( entry ) + } ); + else if ( entry == "ram" ) + checkEntries.append( { + entry, + [this]{ return tr( "has at least %1 GB working memory" ) + .arg( m_requiredRamGB ); }, + [this]{ return tr( "The system does not have enough working memory. At least %1 GB is required." ) + .arg( m_requiredRamGB ); }, + enoughRam, + m_entriesToRequire.contains( entry ) + } ); + else if ( entry == "power" ) + checkEntries.append( { + entry, + [this]{ return tr( "is plugged in to a power source" ); }, + [this]{ return tr( "The system is not plugged in to a power source." ); }, + hasPower, + m_entriesToRequire.contains( entry ) + } ); + else if ( entry == "internet" ) + checkEntries.append( { + entry, + [this]{ return tr( "is connected to the Internet" ); }, + [this]{ return tr( "The system is not connected to the Internet." ); }, + hasInternet, + m_entriesToRequire.contains( entry ) + } ); + else if ( entry == "root" ) + checkEntries.append( { + entry, + [this]{ return QString(); }, //we hide it + [this]{ return tr( "The installer is not running with administrator rights." ); }, + isRoot, + m_entriesToRequire.contains( entry ) + } ); + else if ( entry == "screen" ) + checkEntries.append( { + entry, + [this]{ return QString(); }, // we hide it + [this]{ return tr( "The screen is too small to display the installer." ); }, + enoughScreen, + false + } ); + } + return checkEntries; } void -RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) +GeneralRequirements::setConfigurationMap( const QVariantMap& configurationMap ) { bool incompleteConfiguration = false; @@ -222,7 +171,7 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) } else { - cWarning() << "RequirementsChecker entry 'check' is incomplete."; + cWarning() << "GeneralRequirements entry 'check' is incomplete."; incompleteConfiguration = true; } @@ -234,14 +183,26 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) } else { - cWarning() << "RequirementsChecker entry 'required' is incomplete."; + cWarning() << "GeneralRequirements entry 'required' is incomplete."; incompleteConfiguration = true; } +#ifdef WITHOUT_LIBPARTED + if ( m_entriesToCheck.contains( "storage" ) ) + cWarning() << "GeneralRequirements checks 'storage' but libparted is disabled."; + if ( m_entriesToRequire.contains( "storage" ) ) + { + // Warn, but also drop the required bit because otherwise installation + // will be impossible (because the check always returns false). + cWarning() << "GeneralRequirements requires 'storage' check but libparted is disabled."; + m_entriesToRequire.removeAll( "storage" ); + } +#endif + // Help out with consistency, but don't fix for ( const auto& r : m_entriesToRequire ) if ( !m_entriesToCheck.contains( r ) ) - cWarning() << "RequirementsChecker requires" << r << "but does not check it."; + cWarning() << "GeneralRequirements requires" << r << "but does not check it."; if ( configurationMap.contains( "requiredStorage" ) && ( configurationMap.value( "requiredStorage" ).type() == QVariant::Double || @@ -251,7 +212,7 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) m_requiredStorageGB = configurationMap.value( "requiredStorage" ).toDouble( &ok ); if ( !ok ) { - cWarning() << "RequirementsChecker entry 'requiredStorage' is invalid."; + cWarning() << "GeneralRequirements entry 'requiredStorage' is invalid."; m_requiredStorageGB = 3.; } @@ -259,7 +220,7 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) } else { - cWarning() << "RequirementsChecker entry 'requiredStorage' is missing."; + cWarning() << "GeneralRequirements entry 'requiredStorage' is missing."; m_requiredStorageGB = 3.; incompleteConfiguration = true; } @@ -272,14 +233,14 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) m_requiredRamGB = configurationMap.value( "requiredRam" ).toDouble( &ok ); if ( !ok ) { - cWarning() << "RequirementsChecker entry 'requiredRam' is invalid."; + cWarning() << "GeneralRequirements entry 'requiredRam' is invalid."; m_requiredRamGB = 1.; incompleteConfiguration = true; } } else { - cWarning() << "RequirementsChecker entry 'requiredRam' is missing."; + cWarning() << "GeneralRequirements entry 'requiredRam' is missing."; m_requiredRamGB = 1.; incompleteConfiguration = true; } @@ -291,7 +252,7 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) if ( m_checkHasInternetUrl.isEmpty() || !QUrl( m_checkHasInternetUrl ).isValid() ) { - cWarning() << "RequirementsChecker entry 'internetCheckUrl' is invalid in welcome.conf" << m_checkHasInternetUrl + cWarning() << "GeneralRequirements entry 'internetCheckUrl' is invalid in welcome.conf" << m_checkHasInternetUrl << "reverting to default (http://example.com)."; m_checkHasInternetUrl = "http://example.com"; incompleteConfiguration = true; @@ -299,7 +260,7 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) } else { - cWarning() << "RequirementsChecker entry 'internetCheckUrl' is undefined in welcome.conf," + cWarning() << "GeneralRequirements entry 'internetCheckUrl' is undefined in welcome.conf," "reverting to default (http://example.com)."; m_checkHasInternetUrl = "http://example.com"; @@ -308,24 +269,17 @@ RequirementsChecker::setConfigurationMap( const QVariantMap& configurationMap ) if ( incompleteConfiguration ) { - cWarning() << "RequirementsChecker configuration map:" << Logger::DebugMap( configurationMap ); + cWarning() << "GeneralRequirements configuration map:" << Logger::DebugMap( configurationMap ); } } bool -RequirementsChecker::verdict() const -{ - return m_verdict; -} - - -bool -RequirementsChecker::checkEnoughStorage( qint64 requiredSpace ) +GeneralRequirements::checkEnoughStorage( qint64 requiredSpace ) { #ifdef WITHOUT_LIBPARTED Q_UNUSED( requiredSpace ); - cWarning() << "RequirementsChecker is configured without libparted."; + cWarning() << "GeneralRequirements is configured without libparted."; return false; #else return check_big_enough( requiredSpace ); @@ -334,7 +288,7 @@ RequirementsChecker::checkEnoughStorage( qint64 requiredSpace ) bool -RequirementsChecker::checkEnoughRam( qint64 requiredRam ) +GeneralRequirements::checkEnoughRam( qint64 requiredRam ) { // Ignore the guesstimate-factor; we get an under-estimate // which is probably the usable RAM for programs. @@ -344,7 +298,7 @@ RequirementsChecker::checkEnoughRam( qint64 requiredRam ) bool -RequirementsChecker::checkBatteryExists() +GeneralRequirements::checkBatteryExists() { const QFileInfo basePath( "/sys/class/power_supply" ); @@ -370,7 +324,7 @@ RequirementsChecker::checkBatteryExists() bool -RequirementsChecker::checkHasPower() +GeneralRequirements::checkHasPower() { const QString UPOWER_SVC_NAME( "org.freedesktop.UPower" ); const QString UPOWER_INTF_NAME( "org.freedesktop.UPower" ); @@ -401,10 +355,10 @@ RequirementsChecker::checkHasPower() bool -RequirementsChecker::checkHasInternet() +GeneralRequirements::checkHasInternet() { // default to true in the QNetworkAccessManager::UnknownAccessibility case - QNetworkAccessManager qnam( this ); + QNetworkAccessManager qnam; bool hasInternet = qnam.networkAccessible() == QNetworkAccessManager::Accessible; if ( !hasInternet && qnam.networkAccessible() == QNetworkAccessManager::UnknownAccessibility ) @@ -425,14 +379,14 @@ RequirementsChecker::checkHasInternet() bool -RequirementsChecker::checkIsRoot() +GeneralRequirements::checkIsRoot() { return !geteuid(); } void -RequirementsChecker::detectFirmwareType() +GeneralRequirements::detectFirmwareType() { QString fwType = QFile::exists( "/sys/firmware/efi/efivars" ) ? "efi" : "bios"; Calamares::JobQueue::instance()->globalStorage()->insert( "firmwareType", fwType ); diff --git a/src/modules/welcome/checker/RequirementsChecker.h b/src/modules/welcome/checker/GeneralRequirements.h similarity index 53% rename from src/modules/welcome/checker/RequirementsChecker.h rename to src/modules/welcome/checker/GeneralRequirements.h index ceb4eb209..0e3e341b0 100644 --- a/src/modules/welcome/checker/RequirementsChecker.h +++ b/src/modules/welcome/checker/GeneralRequirements.h @@ -17,52 +17,23 @@ * along with Calamares. If not, see . */ -#ifndef REQUIREMENTSCHECKER_H -#define REQUIREMENTSCHECKER_H +#ifndef GENERALREQUIREMENTS_H +#define GENERALREQUIREMENTS_H #include #include -#include +#include "modulesystem/Requirement.h" -class CheckerWidget; -class QWidget; - -/** - * An indication of a requirement, which is checked in preparation - * for system installation. An entry has a name and some explanation, - * as well as three meaningful states: - * - checked = true, the requirement is met (green) - * - checked = false, the requirement is not met - * - required = false, warn about it (yellow), no failure - * - required = true, prohibit installation (red) - */ -struct PrepareEntry -{ - QString name; - std::function< QString() > enumerationText; //Partial string, inserted in a - //list of requirements to satisfy. - std::function< QString() > negatedText; //Complete sentence about this requirement - //not having been met. - bool checked; - bool required; -}; - -class RequirementsChecker : public QObject +class GeneralRequirements : public QObject { Q_OBJECT public: - explicit RequirementsChecker( QObject* parent = nullptr ); - virtual ~RequirementsChecker(); - - QWidget* widget() const; + explicit GeneralRequirements( QObject* parent = nullptr ); void setConfigurationMap( const QVariantMap& configurationMap ); - bool verdict() const; - -signals: - void verdictChanged( bool ); + Calamares::RequirementsList checkRequirements(); private: QStringList m_entriesToCheck; @@ -76,13 +47,9 @@ private: bool checkIsRoot(); void detectFirmwareType(); - QWidget* m_widget; qreal m_requiredStorageGB; qreal m_requiredRamGB; QString m_checkHasInternetUrl; - - CheckerWidget* m_actualWidget; - bool m_verdict; }; #endif // REQUIREMENTSCHECKER_H diff --git a/src/modules/welcome/welcome.conf b/src/modules/welcome/welcome.conf index b7ce5cfcd..52492ffef 100644 --- a/src/modules/welcome/welcome.conf +++ b/src/modules/welcome/welcome.conf @@ -1,18 +1,37 @@ +# Configuration for the welcome module. The welcome page +# displays some information from the branding file. +# Which parts it displays can be configured through +# the show* variables. +# +# In addition to displaying the welcome page, this module +# can check requirements for installation. --- +# Display settings for various buttons on the welcome page. showSupportUrl: true showKnownIssuesUrl: true showReleaseNotesUrl: true +# Requirements checking. These are general, generic, things +# that are checked. They may not match with the actual requirements +# imposed by other modules in the system. requirements: + # Amount of available disk, in GB. Floating-point is allowed here. + # Note that this does not account for *usable* disk, so it is possible + # to pass this requirement, yet have no space to install to. requiredStorage: 5.5 + + # Amount of available RAM, in GB. Floating-point is allowed here. requiredRam: 1.0 + + # To check for internet connectivity, Calamares does a HTTP GET + # on this URL; on success (e.g. HTTP code 200) internet is OK. internetCheckUrl: http://google.com # List conditions to check. Each listed condition will be # probed in some way, and yields true or false according to # the host system satisfying the condition. # - # This sample file lists all the conditions that are know. + # This sample file lists all the conditions that are known. check: - storage - ram @@ -20,7 +39,7 @@ requirements: - internet - root - screen - # List conditions that must be satisfied (from the list + # List conditions that **must** be satisfied (from the list # of conditions, above) for installation to proceed. # If any of these conditions are not met, the user cannot # continue past the welcome page.