File indexing completed on 2024-05-12 16:39:39

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
0003    Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "kexipartmanager.h"
0022 #include "kexipart.h"
0023 #include "kexiinternalpart.h"
0024 #include "kexipartinfo.h"
0025 //! @todo KEXI3 #include "kexistaticpart.h"
0026 #include "KexiVersion.h"
0027 #include "KexiJsonTrader.h"
0028 
0029 #include <KDbConnection>
0030 #include <KDbMessageHandler>
0031 
0032 #include <KLocalizedString>
0033 #include <KPluginFactory>
0034 #include <KConfigGroup>
0035 #include <KSharedConfig>
0036 
0037 #include <QApplication>
0038 #include <QDebug>
0039 #include <QGlobalStatic>
0040 
0041 
0042 using namespace KexiPart;
0043 
0044 typedef QHash<QString, KexiInternalPart*> KexiInternalPartDict;
0045 
0046 Q_GLOBAL_STATIC_WITH_ARGS(KexiJsonTrader, KexiPartTrader_instance, (KEXI_BASE_PATH))
0047 
0048 class Q_DECL_HIDDEN Manager::Private
0049 {
0050 public:
0051     explicit Private(Manager *manager_);
0052     ~Private();
0053 
0054     Manager *manager;
0055     PartDict parts;
0056     KexiInternalPartDict internalParts;
0057     PartInfoList partlist;
0058     PartInfoDict partsByPluginId;
0059     bool lookupDone;
0060     bool lookupResult;
0061 };
0062 
0063 Manager::Private::Private(Manager *manager_)
0064     : manager(manager_)
0065     , lookupDone(false)
0066     , lookupResult(false)
0067 {
0068 }
0069 
0070 Manager::Private::~Private()
0071 {
0072     qDeleteAll(partlist);
0073     partlist.clear();
0074 }
0075 
0076 //---
0077 
0078 Manager::Manager(QObject *parent)
0079     : QObject(parent), KDbResultable(), d(new Private(this))
0080 {
0081 }
0082 
0083 Manager::~Manager()
0084 {
0085     delete d;
0086 }
0087 
0088 template <typename PartClass>
0089 PartClass* Manager::part(Info *info, QHash<QString, PartClass*> *partDict)
0090 {
0091     if (!info) {
0092         return 0;
0093     }
0094     clearResult();
0095     KDbMessageGuard mg(this);
0096     if (!lookup()) {
0097         return 0;
0098     }
0099     if (!info->isValid()) {
0100         m_result = KDbResult(info->errorMessage());
0101         return 0;
0102     }
0103     PartClass *p = partDict->value(info->pluginId());
0104     if (p) {
0105         return p;
0106     }
0107 
0108     // actual loading
0109     KPluginFactory *factory = qobject_cast<KPluginFactory*>(info->instantiate());
0110     if (!factory) {
0111         m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT,
0112                              xi18nc("@info", "Could not load Kexi plugin file <filename>%1</filename>.",
0113                                     info->fileName()));
0114         QPluginLoader loader(info->fileName()); // use this to get the message
0115         (void)loader.load();
0116         m_result.setServerMessage(loader.errorString());
0117         info->setErrorMessage(m_result.message());
0118         qWarning() << m_result.message() << m_result.serverMessage();
0119         return 0;
0120     }
0121     p = factory->create<PartClass>(this);
0122     if (!p) {
0123         m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT,
0124                              xi18nc("@info",
0125                                     "Could not open Kexi plugin <filename>%1</filename>.").arg(info->fileName()));
0126         qWarning() << m_result.message();
0127         return 0;
0128     }
0129     p->setInfo(info);
0130     p->setObjectName(QString("%1 plugin").arg(info->id()));
0131     partDict->insert(info->pluginId(), p);
0132     return p;
0133 }
0134 
0135 //! @return a string list @a list with removed whitespace from the beginning and end of each string.
0136 //! Empty strings are also removed.
0137 static QStringList cleanupStringList(const QStringList &list)
0138 {
0139     QStringList result;
0140     foreach(const QString &item, list) {
0141         QString cleanedItem = item.trimmed();
0142         if (!cleanedItem.isEmpty()) {
0143             result.append(cleanedItem);
0144         }
0145     }
0146     return result;
0147 }
0148 
0149 bool Manager::lookup()
0150 {
0151     if (d->lookupDone) {
0152         return d->lookupResult;
0153     }
0154     d->lookupDone = true;
0155     d->lookupResult = false;
0156     d->partlist.clear();
0157     d->partsByPluginId.clear();
0158     d->parts.clear();
0159 
0160     // load visual order of plugins
0161     KConfigGroup cg(KSharedConfig::openConfig()->group("Parts"));
0162     const QStringList orderedPluginIds = cleanupStringList(
0163         cg.readEntry("Order", "org.kexi-project.table,"
0164                               "org.kexi-project.query,"
0165                               "org.kexi-project.form,"
0166                               "org.kexi-project.report,"
0167                               "org.kexi-project.macro,"
0168                               "org.kexi-project.script").split(','));
0169     QVector<Info*> orderedInfos(orderedPluginIds.count());
0170     QStringList serviceTypes;
0171     serviceTypes << "Kexi/Viewer" << "Kexi/Designer" << "Kexi/Editor"
0172                  << "Kexi/Internal";
0173     QList<QPluginLoader*> offers = KexiPartTrader_instance->query(serviceTypes);
0174     foreach(const QPluginLoader *loader, offers) {
0175         QScopedPointer<Info> info(new Info(*loader));
0176         if (info->id().isEmpty()) {
0177             qWarning() << "No plugin ID specified for Kexi Part"
0178                        << info->fileName() << "-- skipping!";
0179             continue;
0180         }
0181         // check version
0182         const QString expectedVersion = KexiPart::version();
0183         if (info->version() != expectedVersion) {
0184             qWarning() << "Kexi plugin" << info->id() << "has version"
0185                        << info->version() << "but version required by Kexi is"
0186                        << expectedVersion
0187                        << "-- skipping this plugin!";
0188             continue;
0189         }
0190         // skip experimental types
0191         if (   (!Kexi::tempShowMacros() && info->id() == "org.kexi-project.macro")
0192             || (!Kexi::tempShowScripts() && info->id() == "org.kexi-project.script")
0193            )
0194         {
0195             continue;
0196         }
0197         // skip duplicates
0198         if (d->partsByPluginId.contains(info->id())) {
0199             qWarning() << "More than one Kexi plugin with ID"
0200                        << info->id() << info->fileName() << "-- skipping this one";
0201             continue;
0202         }
0203         // find correct place for plugins visible in Navigator
0204         if (info->isVisibleInNavigator()) {
0205             const int index = orderedPluginIds.indexOf(info->id());
0206             if (index != -1) {
0207                 orderedInfos[index] = info.data();
0208             }
0209             else {
0210                 orderedInfos.append(info.data());
0211             }
0212             // append later when we know order
0213         }
0214         else {
0215             // append now
0216             d->partlist.append(info.data());
0217         }
0218         d->partsByPluginId.insert(info->pluginId(), info.data());
0219         info.take();
0220     }
0221     qDeleteAll(offers);
0222     offers.clear();
0223     if (d->partsByPluginId.isEmpty()) {
0224         m_result = KDbResult(
0225             xi18nc("@info", "<para>Could not find any Kexi plugins, e.g. for tables or forms. "
0226                             "Kexi would not be functional so it will exit.</para>"
0227                             "<para><note>Please check if Kexi is properly installed.</note></para>"));
0228         return false;
0229     }
0230 
0231     // fill the final list using computed order
0232     for (int i = 0; i < orderedInfos.size(); i++) {
0233         Info *info = orderedInfos[i];
0234         if (!info) {
0235             continue;
0236         }
0237         //qDebug() << "adding Kexi part info" << info->pluginId();
0238         d->partlist.insert(i, info);
0239     }
0240     // now the d->partlist is: [ordered plugins visible in Navigator] [other plugins in unspecified order]
0241     d->lookupResult = true;
0242     return true;
0243 }
0244 
0245 Part* Manager::part(Info *info)
0246 {
0247     KDbMessageGuard mg(this);
0248     Part *p = part<Part>(info, &d->parts);
0249     if (p) {
0250         emit partLoaded(p);
0251     }
0252     return p;
0253 }
0254 
0255 static QString realPluginId(const QString &pluginId)
0256 {
0257     if (pluginId.contains('.')) {
0258         return pluginId;
0259     }
0260     else {
0261         // not like "org.kexi-project.table" - construct
0262         return QString::fromLatin1("org.kexi-project.")
0263             + QString(pluginId).remove("kexi/");
0264     }
0265 }
0266 
0267 Part* Manager::partForPluginId(const QString &pluginId)
0268 {
0269     Info* info = infoForPluginId(pluginId);
0270     return part(info);
0271 }
0272 
0273 Info* Manager::infoForPluginId(const QString &pluginId)
0274 {
0275     KDbMessageGuard mg(this);
0276     if (!lookup())
0277         return 0;
0278     const QString realId = realPluginId(pluginId);
0279     Info *i = realId.isEmpty() ? 0 : d->partsByPluginId.value(realId);
0280     if (i)
0281         return i;
0282     m_result = KDbResult(kxi18nc("@info", "No plugin for ID <resource>%1</resource>")
0283                              .subs(realId)
0284                              .toString(Kuit::VisualFormat::PlainText));
0285     return 0;
0286 }
0287 
0288 /*! @todo KEXI3
0289 void Manager::insertStaticPart(StaticPart* part)
0290 {
0291     if (!part)
0292         return;
0293     KDbMessageGuard mg(this);
0294     if (!lookup())
0295         return;
0296     d->partlist.append(part->info());
0297     if (!part->info()->pluginId().isEmpty())
0298         d->partsByPluginId.insert(part->info()->pluginId(), part->info());
0299     d->parts.insert(part->info()->pluginId(), part);
0300 }
0301 */
0302 
0303 KexiInternalPart* Manager::internalPartForPluginId(const QString& pluginId)
0304 {
0305     Info* info = infoForPluginId(pluginId);
0306     if (!info || !info->serviceTypes().contains("Kexi/Internal")) {
0307         return nullptr;
0308     }
0309     return part<KexiInternalPart>(info, &d->internalParts);
0310 }
0311 
0312 PartInfoList* Manager::infoList()
0313 {
0314     KDbMessageGuard mg(this);
0315     if (!lookup()) {
0316         return 0;
0317     }
0318     return &d->partlist;
0319 }