source: calamares/trunk/fuentes/src/libcalamaresui/modulesystem/ModuleManager.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 13 months ago

sync with github

File size: 12.1 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
4 *
5 *   Calamares is free software: you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation, either version 3 of the License, or
8 *   (at your option) any later version.
9 *
10 *   Calamares is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "ModuleManager.h"
20
21#include "ExecutionViewStep.h"
22#include "Module.h"
23#include "utils/Logger.h"
24#include "utils/YamlUtils.h"
25#include "Settings.h"
26#include "ViewManager.h"
27
28#include <yaml-cpp/yaml.h>
29
30#include <QApplication>
31#include <QDir>
32#include <QTimer>
33
34#define MODULE_CONFIG_FILENAME "module.desc"
35
36namespace Calamares
37{
38
39
40ModuleManager* ModuleManager::s_instance = nullptr;
41
42
43ModuleManager*
44ModuleManager::instance()
45{
46    return s_instance;
47}
48
49
50ModuleManager::ModuleManager( const QStringList& paths, QObject* parent )
51    : QObject( parent )
52    , m_paths( paths )
53{
54    Q_ASSERT( !s_instance );
55    s_instance = this;
56}
57
58
59ModuleManager::~ModuleManager()
60{
61    // The map is populated with Module::fromDescriptor(), which allocates on the heap.
62    for( auto moduleptr : m_loadedModulesByInstanceKey )
63    {
64        delete moduleptr;
65    }
66}
67
68
69void
70ModuleManager::init()
71{
72    QTimer::singleShot( 0, this, &ModuleManager::doInit );
73}
74
75
76void
77ModuleManager::doInit()
78{
79    // We start from a list of paths in m_paths. Each of those is a directory that
80    // might (should) contain Calamares modules of any type/interface.
81    // For each modules search path (directory), it is expected that each module
82    // lives in its own subdirectory. This subdirectory must have the same name as
83    // the module name, and must contain a settings file named module.desc.
84    // If at any time the module loading procedure finds something unexpected, it
85    // silently skips to the next module or search path. --Teo 6/2014
86    for ( const QString& path : m_paths )
87    {
88        QDir currentDir( path );
89        if ( currentDir.exists() && currentDir.isReadable() )
90        {
91            const QStringList subdirs = currentDir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot );
92            for ( const QString& subdir : subdirs )
93            {
94                currentDir.setPath( path );
95                bool success = currentDir.cd( subdir );
96                if ( success )
97                {
98                    QFileInfo descriptorFileInfo( currentDir.absoluteFilePath( MODULE_CONFIG_FILENAME ) );
99                    if ( ! ( descriptorFileInfo.exists() && descriptorFileInfo.isReadable() ) )
100                    {
101                        cDebug() << Q_FUNC_INFO << "unreadable file: "
102                                 << descriptorFileInfo.absoluteFilePath();
103                        continue;
104                    }
105
106                    bool ok = false;
107                    QVariantMap moduleDescriptorMap = CalamaresUtils::loadYaml( descriptorFileInfo, &ok );
108                    QString moduleName = ok ? moduleDescriptorMap.value( "name" ).toString() : QString();
109
110                    if ( ok && ( moduleName == currentDir.dirName() ) &&
111                            !m_availableDescriptorsByModuleName.contains( moduleName ) )
112                    {
113                        m_availableDescriptorsByModuleName.insert( moduleName, moduleDescriptorMap );
114                        m_moduleDirectoriesByModuleName.insert( moduleName,
115                                                                descriptorFileInfo.absoluteDir().absolutePath() );
116                    }
117                }
118                else
119                {
120                    cWarning() << "Cannot cd into module directory "
121                             << path << "/" << subdir;
122                }
123            }
124        }
125        else
126        {
127            cDebug() << "ModuleManager bad search path" << path;
128        }
129    }
130    // At this point m_availableModules is filled with whatever was found in the
131    // search paths.
132    checkDependencies();
133    emit initDone();
134}
135
136
137QStringList
138ModuleManager::loadedInstanceKeys()
139{
140    return m_loadedModulesByInstanceKey.keys();
141}
142
143
144QVariantMap
145ModuleManager::moduleDescriptor( const QString& name )
146{
147    return m_availableDescriptorsByModuleName.value( name );
148}
149
150Module*
151ModuleManager::moduleInstance( const QString& instanceKey )
152{
153    return m_loadedModulesByInstanceKey.value( instanceKey );
154}
155
156
157/**
158 * @brief Search a list of instance descriptions for one matching @p module and @p id
159 *
160 * @return -1 on failure, otherwise index of the instance that matches.
161 */
162static int findCustomInstance( const Settings::InstanceDescriptionList& customInstances,
163                               const QString& module,
164                               const QString& id )
165{
166    for ( int i = 0; i < customInstances.count(); ++i )
167    {
168        const auto& thisInstance = customInstances[ i ];
169        if ( thisInstance.value( "module" ) == module &&
170                thisInstance.value( "id" ) == id )
171            return i;
172    }
173    return -1;
174}
175
176
177void
178ModuleManager::loadModules()
179{
180    QTimer::singleShot( 0, this, [ this ]()
181    {
182        QStringList failedModules;
183        Settings::InstanceDescriptionList customInstances =
184                Settings::instance()->customModuleInstances();
185
186        const auto modulesSequence = Settings::instance()->modulesSequence();
187        for ( const auto &modulePhase : modulesSequence )
188        {
189            ModuleAction currentAction = modulePhase.first;
190
191            foreach ( const QString& moduleEntry,
192                      modulePhase.second )
193            {
194                QStringList moduleEntrySplit = moduleEntry.split( '@' );
195                QString moduleName;
196                QString instanceId;
197                QString configFileName;
198                if ( moduleEntrySplit.length() < 1 ||
199                     moduleEntrySplit.length() > 2 )
200                {
201                    cError() << "Wrong module entry format for module" << moduleEntry;
202                    failedModules.append( moduleEntry );
203                    continue;
204                }
205                moduleName = moduleEntrySplit.first();
206                instanceId = moduleEntrySplit.last();
207                configFileName = QString( "%1.conf" ).arg( moduleName );
208
209                if ( !m_availableDescriptorsByModuleName.contains( moduleName ) ||
210                     m_availableDescriptorsByModuleName.value( moduleName ).isEmpty() )
211                {
212                    cError() << "Module" << moduleName << "not found in module search paths."
213                        << Logger::DebugList( m_paths );
214                    failedModules.append( moduleName );
215                    continue;
216                }
217
218                if ( moduleName != instanceId ) //means this is a custom instance
219                {
220                    if ( int found = findCustomInstance( customInstances, moduleName, instanceId ) > -1 )
221                    {
222                        configFileName = customInstances[ found ].value( "config" );
223                    }
224                    else //ought to be a custom instance, but cannot find instance entry
225                    {
226                        cError() << "Custom instance" << moduleEntry << "not found in custom instances section.";
227                        failedModules.append( moduleEntry );
228                        continue;
229                    }
230                }
231
232                // So now we can assume that the module entry is at least valid,
233                // that we have a descriptor on hand (and therefore that the
234                // module exists), and that the instance is either default or
235                // defined in the custom instances section.
236                // We still don't know whether the config file for the entry
237                // exists and is valid, but that's the only thing that could fail
238                // from this point on. -- Teo 8/2015
239
240                QString instanceKey = QString( "%1@%2" )
241                                      .arg( moduleName )
242                                      .arg( instanceId );
243
244                Module* thisModule =
245                    m_loadedModulesByInstanceKey.value( instanceKey, nullptr );
246                if ( thisModule && !thisModule->isLoaded() )
247                {
248                    cError() << "Module" << instanceKey << "exists but not loaded.";
249                    failedModules.append( instanceKey );
250                    continue;
251                }
252
253                if ( thisModule && thisModule->isLoaded() )
254                {
255                    cDebug() << "Module" << instanceKey << "already loaded.";
256                }
257                else
258                {
259                    thisModule =
260                        Module::fromDescriptor( m_availableDescriptorsByModuleName.value( moduleName ),
261                                                instanceId,
262                                                configFileName,
263                                                m_moduleDirectoriesByModuleName.value( moduleName ) );
264                    if ( !thisModule )
265                    {
266                        cError() << "Module" << instanceKey << "cannot be created from descriptor" << configFileName;
267                        failedModules.append( instanceKey );
268                        continue;
269                    }
270                    // If it's a ViewModule, it also appends the ViewStep to the ViewManager.
271                    thisModule->loadSelf();
272                    m_loadedModulesByInstanceKey.insert( instanceKey, thisModule );
273                    if ( !thisModule->isLoaded() )
274                    {
275                        cError() << "Module" << instanceKey << "loading FAILED.";
276                        failedModules.append( instanceKey );
277                        continue;
278                    }
279                }
280
281                // At this point we most certainly have a pointer to a loaded module in
282                // thisModule. We now need to enqueue jobs info into an EVS.
283                if ( currentAction == Calamares::Exec )
284                {
285                    ExecutionViewStep* evs =
286                        qobject_cast< ExecutionViewStep* >(
287                            Calamares::ViewManager::instance()->viewSteps().last() );
288                    if ( !evs ) // If the last step is not an EVS, we must create it.
289                    {
290                        evs = new ExecutionViewStep( ViewManager::instance() );
291                        ViewManager::instance()->addViewStep( evs );
292                    }
293
294                    evs->appendJobModuleInstanceKey( instanceKey );
295                }
296            }
297        }
298        if ( !failedModules.isEmpty() )
299        {
300            ViewManager::instance()->onInitFailed( failedModules );
301            emit modulesFailed( failedModules );
302        }
303        else
304            emit modulesLoaded();
305    } );
306}
307
308
309void
310ModuleManager::checkDependencies()
311{
312    // This goes through the map of available modules, and deletes those whose
313    // dependencies are not met, if any.
314    bool somethingWasRemovedBecauseOfUnmetDependencies = false;
315    forever
316    {
317        for ( auto it = m_availableDescriptorsByModuleName.begin();
318              it != m_availableDescriptorsByModuleName.end(); ++it )
319        {
320            foreach ( const QString& depName,
321                      (*it).value( "requiredModules" ).toStringList() )
322            {
323                if ( !m_availableDescriptorsByModuleName.contains( depName ) )
324                {
325                    somethingWasRemovedBecauseOfUnmetDependencies = true;
326                    m_availableDescriptorsByModuleName.erase( it );
327                    break;
328                }
329            }
330            if ( somethingWasRemovedBecauseOfUnmetDependencies )
331                break;
332        }
333        if ( !somethingWasRemovedBecauseOfUnmetDependencies )
334            break;
335    }
336}
337
338
339}
Note: See TracBrowser for help on using the repository browser.