1 /* === This file is part of Calamares - <https://calamares.io> ===
2  *
3  *   SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
4  *   SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
5  *   SPDX-FileCopyrightText: 2018 Raul Rodrigo Segura <raurodse@gmail.com>
6  *   SPDX-License-Identifier: GPL-3.0-or-later
7  *
8  *   Calamares is Free Software: see the License-Identifier above.
9  *
10  */
11 
12 #include "PythonQtViewModule.h"
13 
14 #include "CalamaresConfig.h"
15 #include "GlobalStorage.h"
16 #include "JobQueue.h"
17 #include "ViewManager.h"
18 #include "utils/Logger.h"
19 #include "viewpages/PythonQtGlobalStorageWrapper.h"
20 #include "viewpages/PythonQtUtilsWrapper.h"
21 #include "viewpages/PythonQtViewStep.h"
22 #include "viewpages/ViewStep.h"
23 
24 #include <PythonQt.h>
25 #include <PythonQt_QtAll.h>
26 
27 #include <QDir>
28 #include <QPointer>
29 
30 
31 static QPointer< GlobalStorage > s_gs = nullptr;
32 static QPointer< Utils > s_utils = nullptr;
33 
34 namespace Calamares
35 {
36 
37 Module::Type
type() const38 PythonQtViewModule::type() const
39 {
40     return Module::Type::View;
41 }
42 
43 
44 Module::Interface
interface() const45 PythonQtViewModule::interface() const
46 {
47     return Module::Interface::PythonQt;
48 }
49 
50 
51 void
loadSelf()52 PythonQtViewModule::loadSelf()
53 {
54     if ( !m_scriptFileName.isEmpty() )
55     {
56         if ( PythonQt::self() == nullptr )
57         {
58             if ( Py_IsInitialized() )
59                 PythonQt::init( PythonQt::IgnoreSiteModule | PythonQt::RedirectStdOut
60                                 | PythonQt::PythonAlreadyInitialized );
61             else
62             {
63                 PythonQt::init();
64             }
65 
66             PythonQt_QtAll::init();
67             cDebug() << "Initializing PythonQt bindings."
68                      << "This should only happen once.";
69 
70             //TODO: register classes here into the PythonQt environment, like this:
71             //PythonQt::self()->registerClass( &PythonQtViewStep::staticMetaObject,
72             //                                 "calamares" );
73 
74             // We only do the following to force PythonQt to create a submodule
75             // "calamares" for us to put our static objects in
76             PythonQt::self()->registerClass( &::GlobalStorage::staticMetaObject, "calamares" );
77 
78             // Get a PythonQtObjectPtr to the PythonQt.calamares submodule
79             PythonQtObjectPtr pqtm = PythonQt::priv()->pythonQtModule();
80             PythonQtObjectPtr cala = PythonQt::self()->lookupObject( pqtm, "calamares" );
81 
82             // Prepare GlobalStorage object, in module PythonQt.calamares
83             if ( !s_gs )
84             {
85                 s_gs = new ::GlobalStorage( Calamares::JobQueue::instance()->globalStorage() );
86             }
87             cala.addObject( "global_storage", s_gs );
88 
89             // Prepare Utils object, in module PythonQt.calamares
90             if ( !s_utils )
91             {
92                 s_utils = new ::Utils( Calamares::JobQueue::instance()->globalStorage() );
93             }
94             cala.addObject( "utils", s_utils );
95 
96             // Append configuration object, in module PythonQt.calamares
97             cala.addVariable( "configuration", m_configurationMap );
98 
99             // Basic stdout/stderr handling
100             QObject::connect( PythonQt::self(), &PythonQt::pythonStdOut, []( const QString& message ) {
101                 cDebug() << "PythonQt OUT>" << message;
102             } );
103             QObject::connect( PythonQt::self(), &PythonQt::pythonStdErr, []( const QString& message ) {
104                 cDebug() << "PythonQt ERR>" << message;
105             } );
106         }
107 
108         QDir workingDir( m_workingPath );
109         if ( !workingDir.exists() )
110         {
111             cDebug() << "Invalid working directory" << m_workingPath << "for module" << name();
112             return;
113         }
114 
115         QString fullPath = workingDir.absoluteFilePath( m_scriptFileName );
116         QFileInfo scriptFileInfo( fullPath );
117         if ( !scriptFileInfo.isReadable() )
118         {
119             cDebug() << "Invalid main script file path" << fullPath << "for module" << name();
120             return;
121         }
122 
123         // Construct empty Python module with the given name
124         PythonQtObjectPtr cxt = PythonQt::self()->createModuleFromScript( name() );
125         if ( cxt.isNull() )
126         {
127             cDebug() << "Cannot load PythonQt context from file" << fullPath << "for module" << name();
128             return;
129         }
130 
131         static const QLatin1String calamares_module_annotation(
132             "_calamares_module_typename = ''\n"
133             "def calamares_module(viewmodule_type):\n"
134             "    global _calamares_module_typename\n"
135             "    _calamares_module_typename = viewmodule_type.__name__\n"
136             "    return viewmodule_type\n" );
137 
138         // Load in the decorator
139         PythonQt::self()->evalScript( cxt, calamares_module_annotation );
140 
141         // Load the module
142         PythonQt::self()->evalFile( cxt, fullPath );
143 
144         m_viewStep = new PythonQtViewStep( cxt );
145 
146         cDebug() << "PythonQtViewModule loading self for instance" << instanceKey() << "\nPythonQtViewModule at address"
147                  << this << "\nViewStep at address" << m_viewStep;
148 
149         m_viewStep->setModuleInstanceKey( instanceKey() );
150         m_viewStep->setConfigurationMap( m_configurationMap );
151         ViewManager::instance()->addViewStep( m_viewStep );
152         m_loaded = true;
153         cDebug() << "PythonQtViewModule" << instanceKey() << "loading complete.";
154     }
155 }
156 
157 
158 JobList
jobs() const159 PythonQtViewModule::jobs() const
160 {
161     return m_viewStep->jobs();
162 }
163 
164 
165 void
initFrom(const QVariantMap & moduleDescriptor)166 PythonQtViewModule::initFrom( const QVariantMap& moduleDescriptor )
167 {
168     QDir directory( location() );
169     m_workingPath = directory.absolutePath();
170 
171     if ( !moduleDescriptor.value( "script" ).toString().isEmpty() )
172     {
173         m_scriptFileName = moduleDescriptor.value( "script" ).toString();
174     }
175 }
176 
PythonQtViewModule()177 PythonQtViewModule::PythonQtViewModule()
178     : Module()
179 {
180 }
181 
~PythonQtViewModule()182 PythonQtViewModule::~PythonQtViewModule() {}
183 
184 }  // namespace Calamares
185