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