1 /* === This file is part of Calamares - <https://calamares.io> ===
2 *
3 * SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
4 * SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
5 * SPDX-License-Identifier: GPL-3.0-or-later
6 *
7 * Calamares is Free Software: see the License-Identifier above.
8 *
9 */
10
11 #include "Config.h"
12
13 #ifdef HAVE_APPDATA
14 #include "ItemAppData.h"
15 #endif
16
17 #ifdef HAVE_APPSTREAM
18 #include "ItemAppStream.h"
19 #include <AppStreamQt/pool.h>
20 #include <memory>
21 #endif
22
23
24 #include "GlobalStorage.h"
25 #include "JobQueue.h"
26 #include "packages/Globals.h"
27 #include "utils/Logger.h"
28 #include "utils/Variant.h"
29
30 const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()31 packageChooserModeNames()
32 {
33 static const NamedEnumTable< PackageChooserMode > names {
34 { "optional", PackageChooserMode::Optional },
35 { "required", PackageChooserMode::Required },
36 { "optionalmultiple", PackageChooserMode::OptionalMultiple },
37 { "requiredmultiple", PackageChooserMode::RequiredMultiple },
38 // and a bunch of aliases
39 { "zero-or-one", PackageChooserMode::Optional },
40 { "radio", PackageChooserMode::Required },
41 { "one", PackageChooserMode::Required },
42 { "set", PackageChooserMode::OptionalMultiple },
43 { "zero-or-more", PackageChooserMode::OptionalMultiple },
44 { "multiple", PackageChooserMode::RequiredMultiple },
45 { "one-or-more", PackageChooserMode::RequiredMultiple }
46 };
47 return names;
48 }
49
50 const NamedEnumTable< PackageChooserMethod >&
PackageChooserMethodNames()51 PackageChooserMethodNames()
52 {
53 static const NamedEnumTable< PackageChooserMethod > names {
54 { "legacy", PackageChooserMethod::Legacy },
55 { "custom", PackageChooserMethod::Legacy },
56 { "contextualprocess", PackageChooserMethod::Legacy },
57 { "packages", PackageChooserMethod::Packages },
58 };
59 return names;
60 }
61
Config(QObject * parent)62 Config::Config( QObject* parent )
63 : Calamares::ModuleSystem::Config( parent )
64 , m_model( new PackageListModel( this ) )
65 , m_mode( PackageChooserMode::Required )
66 {
67 }
68
~Config()69 Config::~Config() {}
70
71 const PackageItem&
introductionPackage() const72 Config::introductionPackage() const
73 {
74 for ( int i = 0; i < m_model->packageCount(); ++i )
75 {
76 const auto& package = m_model->packageData( i );
77 if ( package.isNonePackage() )
78 {
79 return package;
80 }
81 }
82
83 static PackageItem* defaultIntroduction = nullptr;
84 if ( !defaultIntroduction )
85 {
86 const auto name = QT_TR_NOOP( "Package Selection" );
87 const auto description
88 = QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." );
89 defaultIntroduction = new PackageItem( QString(), name, description );
90 defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
91 defaultIntroduction->name = CalamaresUtils::Locale::TranslatedString( name, metaObject()->className() );
92 defaultIntroduction->description
93 = CalamaresUtils::Locale::TranslatedString( description, metaObject()->className() );
94 }
95 return *defaultIntroduction;
96 }
97
98 static inline QString
make_gs_key(const Calamares::ModuleSystem::InstanceKey & key)99 make_gs_key( const Calamares::ModuleSystem::InstanceKey& key )
100 {
101 return QStringLiteral( "packagechooser_" ) + key.id();
102 }
103
104 void
updateGlobalStorage(const QStringList & selected) const105 Config::updateGlobalStorage( const QStringList& selected ) const
106 {
107 if ( m_packageChoice.has_value() )
108 {
109 cWarning() << "Inconsistent package choices -- both model and single-selection QML";
110 }
111 if ( m_method == PackageChooserMethod::Legacy )
112 {
113 QString value = selected.join( ',' );
114 Calamares::JobQueue::instance()->globalStorage()->insert( make_gs_key( m_defaultId ), value );
115 cDebug() << m_defaultId << "selected" << value;
116 }
117 else if ( m_method == PackageChooserMethod::Packages )
118 {
119 QStringList packageNames = m_model->getInstallPackagesForNames( selected );
120 cDebug() << m_defaultId << "packages to install" << packageNames;
121 CalamaresUtils::Packages::setGSPackageAdditions(
122 Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
123 }
124 else
125 {
126 cWarning() << "Unknown packagechooser method" << smash( m_method );
127 }
128 }
129
130 void
updateGlobalStorage() const131 Config::updateGlobalStorage() const
132 {
133 if ( m_model->packageCount() > 0 )
134 {
135 cWarning() << "Inconsistent package choices -- both model and single-selection QML";
136 }
137 if ( m_method == PackageChooserMethod::Legacy )
138 {
139 auto* gs = Calamares::JobQueue::instance()->globalStorage();
140 if ( m_packageChoice.has_value() )
141 {
142 gs->insert( make_gs_key( m_defaultId ), m_packageChoice.value() );
143 }
144 else
145 {
146 gs->remove( make_gs_key( m_defaultId ) );
147 }
148 }
149 else if ( m_method == PackageChooserMethod::Packages )
150 {
151 cWarning() << "Unsupported single-selection packagechooser method 'Packages'";
152 }
153 else
154 {
155 cWarning() << "Unknown packagechooser method" << smash( m_method );
156 }
157 }
158
159
160 void
setPackageChoice(const QString & packageChoice)161 Config::setPackageChoice( const QString& packageChoice )
162 {
163 if ( packageChoice.isEmpty() )
164 {
165 m_packageChoice.reset();
166 }
167 else
168 {
169 m_packageChoice = packageChoice;
170 }
171 emit packageChoiceChanged( m_packageChoice.value_or( QString() ) );
172 }
173
174 QString
prettyStatus() const175 Config::prettyStatus() const
176 {
177 return tr( "Install option: <strong>%1</strong>" ).arg( m_packageChoice.value_or( tr( "None" ) ) );
178 }
179
180 static void
fillModel(PackageListModel * model,const QVariantList & items)181 fillModel( PackageListModel* model, const QVariantList& items )
182 {
183 if ( items.isEmpty() )
184 {
185 cWarning() << "No *items* for PackageChooser module.";
186 return;
187 }
188
189 #ifdef HAVE_APPSTREAM
190 std::unique_ptr< AppStream::Pool > pool;
191 bool poolOk = false;
192 #endif
193
194 cDebug() << "Loading PackageChooser model items from config";
195 int item_index = 0;
196 for ( const auto& item_it : items )
197 {
198 ++item_index;
199 QVariantMap item_map = item_it.toMap();
200 if ( item_map.isEmpty() )
201 {
202 cWarning() << "PackageChooser entry" << item_index << "is not valid.";
203 continue;
204 }
205
206 if ( item_map.contains( "appdata" ) )
207 {
208 #ifdef HAVE_XML
209 model->addPackage( fromAppData( item_map ) );
210 #else
211 cWarning() << "Loading AppData XML is not supported.";
212 #endif
213 }
214 else if ( item_map.contains( "appstream" ) )
215 {
216 #ifdef HAVE_APPSTREAM
217 if ( !pool )
218 {
219 pool = std::make_unique< AppStream::Pool >();
220 pool->setLocale( QStringLiteral( "ALL" ) );
221 poolOk = pool->load();
222 }
223 if ( pool && poolOk )
224 {
225 model->addPackage( fromAppStream( *pool, item_map ) );
226 }
227 #else
228 cWarning() << "Loading AppStream data is not supported.";
229 #endif
230 }
231 else
232 {
233 model->addPackage( PackageItem( item_map ) );
234 }
235 }
236 cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries.";
237 }
238
239 void
setConfigurationMap(const QVariantMap & configurationMap)240 Config::setConfigurationMap( const QVariantMap& configurationMap )
241 {
242 m_mode = packageChooserModeNames().find( CalamaresUtils::getString( configurationMap, "mode" ),
243 PackageChooserMode::Required );
244 m_method = PackageChooserMethodNames().find( CalamaresUtils::getString( configurationMap, "method" ),
245 PackageChooserMethod::Legacy );
246
247 if ( m_method == PackageChooserMethod::Legacy )
248 {
249 cDebug() << "Using module ID" << m_defaultId;
250 }
251
252 if ( configurationMap.contains( "items" ) )
253 {
254 fillModel( m_model, configurationMap.value( "items" ).toList() );
255
256 QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
257 if ( !default_item_id.isEmpty() )
258 {
259 for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
260 {
261 QModelIndex item_idx = m_model->index( item_n, 0 );
262 QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
263
264 if ( item_id.toString() == default_item_id )
265 {
266 m_defaultModelIndex = item_idx;
267 break;
268 }
269 }
270 }
271 }
272 else
273 {
274 setPackageChoice( CalamaresUtils::getString( configurationMap, "packageChoice" ) );
275 if ( m_method != PackageChooserMethod::Legacy )
276 {
277 cWarning() << "Single-selection QML module must use 'Legacy' method.";
278 }
279 }
280 }
281