File indexing completed on 2024-05-19 15:10:51

0001 /*
0002     SPDX-FileCopyrightText: 2007 Carlo Segato <brandon.ml@gmail.com>
0003     SPDX-FileCopyrightText: 2008 Montel Laurent <montel@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "kemoticons.h"
0009 #include "kemoticonsglobal_p.h"
0010 #include "kemoticonsprovider.h"
0011 
0012 #include <QFile>
0013 #include <QDir>
0014 #include <QMimeDatabase>
0015 #include <QStandardPaths>
0016 #include "kemoticons_core_debug.h"
0017 #include <QFileSystemWatcher>
0018 
0019 #include <KPluginLoader>
0020 #include <KPluginMetaData>
0021 #include <KConfigGroup>
0022 #include <KSharedConfig>
0023 #include <KTar>
0024 #include <kzip.h>
0025 
0026 Q_GLOBAL_STATIC(KEmoticonsGlobal, s_global)
0027 
0028 class KEmoticonsPrivate
0029 {
0030 public:
0031     KEmoticonsPrivate(KEmoticons *parent);
0032     ~KEmoticonsPrivate();
0033     void loadServiceList();
0034     KEmoticonsProvider *loadProvider(const KPluginMetaData &plugin);
0035     KEmoticonsProvider *loadProvider(const KService::Ptr &service);
0036     KEmoticonsTheme loadTheme(const QString &name);
0037 
0038     QList<KService::Ptr> m_oldStylePlugins;
0039     QVector<KPluginMetaData> m_plugins;
0040     QHash<QString, KEmoticonsTheme> m_themes;
0041     QFileSystemWatcher m_fileWatcher;
0042     KEmoticons *q;
0043     QSize m_preferredSize;
0044 
0045     //private slots
0046     void changeTheme(const QString &path);
0047 };
0048 
0049 KEmoticonsPrivate::KEmoticonsPrivate(KEmoticons *parent)
0050     : q(parent)
0051 {
0052 }
0053 
0054 KEmoticonsPrivate::~KEmoticonsPrivate()
0055 {
0056 }
0057 
0058 static bool priorityLessThan(const KService::Ptr &s1, const KService::Ptr &s2)
0059 {
0060     return (s1->property(QStringLiteral("X-KDE-Priority")).toInt() > s2->property(QStringLiteral("X-KDE-Priority")).toInt());
0061 }
0062 
0063 void KEmoticonsPrivate::loadServiceList()
0064 {
0065     const QString constraint(QStringLiteral("(exist Library)"));
0066     m_oldStylePlugins = KServiceTypeTrader::self()->query(QStringLiteral("KEmoticons"), constraint);
0067     std::sort(m_oldStylePlugins.begin(), m_oldStylePlugins.end(), priorityLessThan);
0068 
0069     m_plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/emoticonsthemes"));
0070     std::sort(m_plugins.begin(), m_plugins.end(), [](const KPluginMetaData &s1, const KPluginMetaData &s2) {
0071             return s1.rawData().value(QStringLiteral("X-KDE-Priority")).toInt() > s2.rawData().value(QStringLiteral("X-KDE-Priority")).toInt();
0072     });
0073 }
0074 
0075 KEmoticonsProvider *KEmoticonsPrivate::loadProvider(const KPluginMetaData &plugin)
0076 {
0077     KPluginFactory *factory = qobject_cast<KPluginFactory *>(plugin.instantiate());
0078     if (!factory) {
0079         qCWarning(KEMOTICONS_CORE) << "Invalid plugin factory for" << plugin.fileName();
0080         return nullptr;
0081     }
0082     KEmoticonsProvider *provider = factory->create<KEmoticonsProvider>(nullptr);
0083     return provider;
0084 }
0085 
0086 KEmoticonsProvider *KEmoticonsPrivate::loadProvider(const KService::Ptr &service)
0087 {
0088     KPluginFactory *factory = KPluginLoader(service->library()).factory();
0089     if (!factory) {
0090         qCWarning(KEMOTICONS_CORE) << "Invalid plugin factory for" << service->library();
0091         return nullptr;
0092     }
0093     KEmoticonsProvider *provider = factory->create<KEmoticonsProvider>(nullptr);
0094     return provider;
0095 }
0096 
0097 void KEmoticonsPrivate::changeTheme(const QString &path)
0098 {
0099     const QFileInfo info(path);
0100     const QString name = info.dir().dirName();
0101 
0102     if (m_themes.contains(name)) {
0103         loadTheme(name);
0104     }
0105 }
0106 
0107 KEmoticonsTheme KEmoticonsPrivate::loadTheme(const QString &name)
0108 {
0109     const auto registerProvider = [this](const QString &name, const QString &path, KEmoticonsProvider *provider) {
0110         if (m_preferredSize.isValid()) {
0111             provider->setPreferredEmoticonSize(m_preferredSize);
0112         }
0113         KEmoticonsTheme theme(provider);
0114         provider->loadTheme(path);
0115         m_themes.insert(name, theme);
0116         m_fileWatcher.addPath(path);
0117         return theme;
0118     };
0119 
0120     for (const KPluginMetaData &plugin : std::as_const(m_plugins)) {
0121         const QString fName = plugin.rawData().value(QStringLiteral("X-KDE-EmoticonsFileName")).toString();
0122         const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("emoticons/") + name + QLatin1Char('/') + fName);
0123 
0124         if (QFile::exists(path)) {
0125             KEmoticonsProvider *provider = loadProvider(plugin);
0126             if (provider) {
0127                 return registerProvider(name, path, provider);
0128             }
0129         }
0130     }
0131     // KF6: remove support for old plugins
0132     for (const KService::Ptr &service : std::as_const(m_oldStylePlugins)) {
0133         const QString fName = service->property(QStringLiteral("X-KDE-EmoticonsFileName")).toString();
0134         const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("emoticons/") + name + QLatin1Char('/') + fName);
0135 
0136         if (QFile::exists(path)) {
0137             KEmoticonsProvider *provider = loadProvider(service);
0138             if (provider) {
0139                 return registerProvider(name, path, provider);
0140             }
0141         }
0142     }
0143     return KEmoticonsTheme();
0144 }
0145 
0146 KEmoticons::KEmoticons()
0147     : d(new KEmoticonsPrivate(this))
0148 {
0149     d->loadServiceList();
0150     connect(&d->m_fileWatcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &str) {d->changeTheme(str);});
0151 }
0152 
0153 KEmoticons::~KEmoticons()
0154 {
0155 }
0156 
0157 KEmoticonsTheme KEmoticons::theme() const
0158 {
0159     return theme(currentThemeName());
0160 }
0161 
0162 KEmoticonsTheme KEmoticons::theme(const QString &name) const
0163 {
0164     KEmoticonsTheme theme = d->m_themes.value(name);
0165     if (!theme.isNull()) {
0166         return theme;
0167     }
0168 
0169     return d->loadTheme(name);
0170 }
0171 
0172 QString KEmoticons::currentThemeName()
0173 {
0174     return s_global()->m_themeName;
0175 }
0176 
0177 QStringList KEmoticons::themeList()
0178 {
0179     const QStringList themeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("emoticons"), QStandardPaths::LocateDirectory);
0180     QStringList ls;
0181     ls.reserve(themeDirs.count());
0182 
0183     for (int i = 0; i < themeDirs.count(); ++i) {
0184         QDir themeQDir(themeDirs[i]);
0185         themeQDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
0186         themeQDir.setSorting(QDir::Name);
0187         ls << themeQDir.entryList();
0188     }
0189     return ls;
0190 }
0191 
0192 void KEmoticons::setTheme(const KEmoticonsTheme &theme)
0193 {
0194     setTheme(theme.themeName());
0195 }
0196 
0197 void KEmoticons::setTheme(const QString &theme)
0198 {
0199     s_global()->setThemeName(theme);
0200 }
0201 
0202 KEmoticonsTheme::ParseMode KEmoticons::parseMode()
0203 {
0204     return s_global()->m_parseMode;
0205 }
0206 
0207 void KEmoticons::setParseMode(KEmoticonsTheme::ParseMode mode)
0208 {
0209     s_global()->setParseMode(mode);
0210 }
0211 
0212 KEmoticonsTheme KEmoticons::newTheme(const QString &name, const KService::Ptr &service)
0213 {
0214     KEmoticonsProvider *provider = d->loadProvider(service);
0215     if (provider) {
0216         KEmoticonsTheme theme(provider);
0217         theme.setThemeName(name);
0218 
0219         provider->newTheme();
0220 
0221         return theme;
0222     }
0223     return KEmoticonsTheme();
0224 }
0225 
0226 QStringList KEmoticons::installTheme(const QString &archiveName)
0227 {
0228     QStringList foundThemes;
0229     const KArchiveEntry *currentEntry = nullptr;
0230     const KArchiveDirectory *currentDir = nullptr;
0231     KArchive *archive = nullptr;
0232 
0233     const QString localThemesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/emoticons"));
0234 
0235     if (localThemesDir.isEmpty()) {
0236         qCCritical(KEMOTICONS_CORE) << "Could not find a suitable place in which to install the emoticon theme";
0237         return QStringList();
0238     }
0239 
0240     QMimeDatabase db;
0241     const QString currentBundleMimeType = db.mimeTypeForFile(archiveName).name();
0242 
0243     if (currentBundleMimeType == QLatin1String("application/zip") ||
0244             currentBundleMimeType == QLatin1String("application/x-zip") ||
0245             currentBundleMimeType == QLatin1String("application/x-zip-compressed")) {
0246         archive = new KZip(archiveName);
0247     } else if (currentBundleMimeType == QLatin1String("application/x-compressed-tar") ||
0248                currentBundleMimeType == QLatin1String("application/x-bzip-compressed-tar") ||
0249                currentBundleMimeType == QLatin1String("application/x-lzma-compressed-tar") ||
0250                currentBundleMimeType == QLatin1String("application/x-xz-compressed-tar") ||
0251                currentBundleMimeType == QLatin1String("application/x-gzip") ||
0252                currentBundleMimeType == QLatin1String("application/x-bzip") ||
0253                currentBundleMimeType == QLatin1String("application/x-lzma") ||
0254                currentBundleMimeType == QLatin1String("application/x-xz")) {
0255         archive = new KTar(archiveName);
0256     } else if (archiveName.endsWith(QLatin1String("jisp")) || archiveName.endsWith(QLatin1String("zip"))) {
0257         archive = new KZip(archiveName);
0258     } else {
0259         archive = new KTar(archiveName);
0260     }
0261 
0262     if (!archive || !archive->open(QIODevice::ReadOnly)) {
0263         qCCritical(KEMOTICONS_CORE) << "Could not open" << archiveName << "for unpacking";
0264         delete archive;
0265         return QStringList();
0266     }
0267 
0268     const KArchiveDirectory *rootDir = archive->directory();
0269 
0270     // iterate all the dirs looking for an emoticons.xml file
0271     const QStringList entries = rootDir->entries();
0272     for (QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
0273         currentEntry = const_cast<KArchiveEntry *>(rootDir->entry(*it));
0274 
0275         if (currentEntry->isDirectory()) {
0276             currentDir = dynamic_cast<const KArchiveDirectory *>(currentEntry);
0277 
0278             for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) {
0279                 const QString fName = plugin.rawData().value(QStringLiteral("X-KDE-EmoticonsFileName")).toString();
0280                 if (currentDir && currentDir->entry(fName) != nullptr) {
0281                     foundThemes.append(currentDir->name());
0282                 }
0283             }
0284             for (const KService::Ptr &service : std::as_const(d->m_oldStylePlugins)) {
0285                 const QString fName = service->property(QStringLiteral("X-KDE-EmoticonsFileName")).toString();
0286                 if (currentDir && currentDir->entry(fName) != nullptr) {
0287                     foundThemes.append(currentDir->name());
0288                 }
0289             }
0290         }
0291     }
0292 
0293     if (foundThemes.isEmpty()) {
0294         qCCritical(KEMOTICONS_CORE) << "The file" << archiveName << "is not a valid emoticon theme archive";
0295         archive->close();
0296         delete archive;
0297         return QStringList();
0298     }
0299 
0300     for (int themeIndex = 0; themeIndex < foundThemes.size(); ++themeIndex) {
0301         const QString &theme = foundThemes[themeIndex];
0302 
0303         currentEntry = const_cast<KArchiveEntry *>(rootDir->entry(theme));
0304         if (currentEntry == nullptr) {
0305             // qCDebug(KEMOTICONS_CORE) << "couldn't get next archive entry";
0306             continue;
0307         }
0308 
0309         if (currentEntry->isDirectory()) {
0310             currentDir = dynamic_cast<const KArchiveDirectory *>(currentEntry);
0311 
0312             if (currentDir == nullptr) {
0313                 // qCDebug(KEMOTICONS_CORE) << "couldn't cast archive entry to KArchiveDirectory";
0314                 continue;
0315             }
0316 
0317             currentDir->copyTo(localThemesDir + theme);
0318         }
0319     }
0320 
0321     archive->close();
0322     delete archive;
0323 
0324     return foundThemes;
0325 }
0326 
0327 void KEmoticons::setPreferredEmoticonSize(const QSize &size)
0328 {
0329     d->m_preferredSize = size;
0330 }
0331 
0332 QSize KEmoticons::preferredEmoticonSize() const
0333 {
0334     return d->m_preferredSize;
0335 }
0336 
0337 #include "moc_kemoticons.cpp"