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

0001 /***************************************************************************
0002     Copyright (C) 2006-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "manager.h"
0026 #include "newstuffadaptor.h"
0027 #include "../core/filehandler.h"
0028 #include "../utils/cursorsaver.h"
0029 #include "../utils/tellico_utils.h"
0030 #include "../tellico_debug.h"
0031 
0032 #include <KTar>
0033 #include <KConfig>
0034 #include <KFileItem>
0035 #include <KConfigGroup>
0036 #include <KSharedConfig>
0037 #include <KDesktopFile>
0038 #include <KIO/Job>
0039 #include <KIO/DeleteJob>
0040 
0041 #include <QFileInfo>
0042 #include <QDir>
0043 #include <QStandardPaths>
0044 #include <QTemporaryFile>
0045 #include <QGlobalStatic>
0046 
0047 #include <sys/types.h>
0048 #include <sys/stat.h>
0049 // for msvc
0050 #ifndef S_IXUSR
0051 #define S_IXUSR 00100
0052 #endif
0053 
0054 namespace Tellico {
0055   namespace NewStuff {
0056 
0057 class ManagerSingleton {
0058 public:
0059   Tellico::NewStuff::Manager self;
0060 };
0061 
0062   }
0063 }
0064 
0065 Q_GLOBAL_STATIC(Tellico::NewStuff::ManagerSingleton, s_instance)
0066 
0067 using Tellico::NewStuff::Manager;
0068 
0069 Manager::Manager() : QObject(nullptr) {
0070   QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.tellico"));
0071   new NewstuffAdaptor(this);
0072   QDBusConnection::sessionBus().registerObject(QStringLiteral("/NewStuff"), this);
0073 }
0074 
0075 Manager::~Manager() {
0076   QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/NewStuff"));
0077   auto interface = QDBusConnection::sessionBus().interface();
0078   if(interface) {
0079     // the windows build was crashing here when the interface was null
0080     // see https://bugs.kde.org/show_bug.cgi?id=422468
0081     interface->unregisterService(QStringLiteral("org.kde.tellico"));
0082   }
0083 }
0084 
0085 Manager* Manager::self() {
0086   return &s_instance->self;
0087 }
0088 
0089 bool Manager::installTemplate(const QString& file_) {
0090   if(file_.isEmpty()) {
0091     return false;
0092   }
0093   GUI::CursorSaver cs;
0094 
0095   QString xslFile;
0096   QStringList allFiles;
0097 
0098   bool success = true;
0099 
0100   // is there a better way to figure out if the url points to a XSL file or a tar archive
0101   // than just trying to open it?
0102   KTar archive(file_);
0103   if(archive.open(QIODevice::ReadOnly)) {
0104     const KArchiveDirectory* archiveDir = archive.directory();
0105     archiveDir->copyTo(Tellico::saveLocation(QStringLiteral("entry-templates/")));
0106 
0107     allFiles = archiveFiles(archiveDir);
0108     // remember files installed for template
0109     xslFile = findXSL(archiveDir);
0110   } else { // assume it's an xsl file
0111     QString name = QFileInfo(file_).fileName();
0112     if(!name.endsWith(QLatin1String(".xsl"))) {
0113       name += QLatin1String(".xsl");
0114     }
0115     name.remove(QRegularExpression(QLatin1String("^\\d+-"))); // Remove possible kde-files.org id
0116     name = Tellico::saveLocation(QStringLiteral("entry-templates/")) + name;
0117     // Should overwrite since we might be upgrading
0118     if(QFile::exists(name)) {
0119       QFile::remove(name);
0120     }
0121     auto job = KIO::file_copy(QUrl::fromLocalFile(file_), QUrl::fromLocalFile(name));
0122     if(job->exec()) {
0123       xslFile = QFileInfo(name).fileName();
0124       allFiles << xslFile;
0125     }
0126   }
0127 
0128   if(xslFile.isEmpty()) {
0129     success = false;
0130   } else {
0131     KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles");
0132     config.writeEntry(file_, allFiles);
0133     config.writeEntry(xslFile, file_);
0134   }
0135   Tellico::checkCommonXSLFile();
0136   return success;
0137 }
0138 
0139 QMap<QString, QString> Manager::userTemplates() {
0140   QDir dir(Tellico::saveLocation(QStringLiteral("entry-templates/")));
0141   dir.setNameFilters(QStringList() << QStringLiteral("*.xsl"));
0142   dir.setFilter(QDir::Files | QDir::Writable);
0143   QStringList files = dir.entryList();
0144   QMap<QString, QString> nameFileMap;
0145   foreach(const QString& file, files) {
0146     QString name = file;
0147     name.truncate(file.length()-4); // remove ".xsl"
0148     name.replace(QLatin1Char('_'), QLatin1Char(' '));
0149     nameFileMap.insert(name, file);
0150   }
0151   return nameFileMap;
0152 }
0153 
0154 bool Manager::removeTemplateByName(const QString& name_) {
0155   if(name_.isEmpty()) {
0156     return false;
0157   }
0158 
0159   QString xslFile = userTemplates().value(name_);
0160   if(!xslFile.isEmpty()) {
0161     KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles");
0162     QString file = config.readEntry(xslFile, QString());
0163     if(!file.isEmpty()) {
0164       return removeTemplate(file);
0165     }
0166     // At least remove xsl file
0167     QFile::remove(Tellico::saveLocation(QStringLiteral("entry-templates/")) + xslFile);
0168     return true;
0169   }
0170   return false;
0171 }
0172 
0173 bool Manager::removeTemplate(const QString& file_) {
0174   if(file_.isEmpty()) {
0175     return false;
0176   }
0177   GUI::CursorSaver cs;
0178 
0179   KConfigGroup fileGroup(KSharedConfig::openConfig(), "KNewStuffFiles");
0180   QStringList files = fileGroup.readEntry(file_, QStringList());
0181 
0182   if(files.isEmpty()) {
0183     myWarning() << "No file list found for" << file_;
0184     return false;
0185   }
0186 
0187   bool success = true;
0188   QString path = Tellico::saveLocation(QStringLiteral("entry-templates/"));
0189   foreach(const QString& file, files) {
0190     if(file.endsWith(QDir::separator())) {
0191       // ok to not delete all directories
0192       QDir().rmdir(path + file);
0193     } else {
0194       success = QFile::remove(path + file) && success;
0195       if(!success) {
0196         myDebug() << "Failed to remove" << (path+file);
0197       }
0198     }
0199   }
0200 
0201   // remove config entries even if unsuccessful
0202   fileGroup.deleteEntry(file_);
0203   QString key = fileGroup.entryMap().key(file_);
0204   fileGroup.deleteEntry(key);
0205   KSharedConfig::openConfig()->sync();
0206   return success;
0207 }
0208 
0209 bool Manager::installScript(const QString& file_) {
0210   if(file_.isEmpty()) {
0211     return false;
0212   }
0213   GUI::CursorSaver cs;
0214 
0215   QString realFile = file_;
0216 
0217   KTar archive(file_);
0218   QString copyTarget = Tellico::saveLocation(QStringLiteral("data-sources/"));
0219   QString scriptFolder;
0220   QString exeFile;
0221   QString sourceName;
0222 
0223   if(archive.open(QIODevice::ReadOnly)) {
0224     const KArchiveDirectory* archiveDir = archive.directory();
0225     exeFile = findEXE(archiveDir);
0226     if(exeFile.isEmpty()) {
0227       myDebug() << "No exe file found";
0228       return false;
0229     }
0230     sourceName = QFileInfo(exeFile).baseName();
0231     if(sourceName.isEmpty()) {
0232       myDebug() << "Invalid packet name";
0233       return false;
0234     }
0235     // package could have a top-level directory or not
0236     // it should have a directory...
0237     foreach(const QString& entry, archiveDir->entries()) {
0238       if(entry.indexOf(QDir::separator()) < 0) {
0239         // archive does have multiple root items -> extract own dir
0240         copyTarget += sourceName;
0241         scriptFolder = copyTarget + QDir::separator();
0242         break;
0243       }
0244     }
0245     if(scriptFolder.isEmpty()) { // one root item
0246       scriptFolder = copyTarget + exeFile.left(exeFile.indexOf(QDir::separator())) + QDir::separator();
0247     }
0248     // overwrites stuff there
0249     archiveDir->copyTo(copyTarget);
0250   } else { // assume it's an script file
0251     exeFile = QFileInfo(file_).fileName();
0252 
0253     exeFile.remove(QRegularExpression(QLatin1String("^\\d+-"))); // Remove possible kde-files.org id
0254     sourceName = QFileInfo(exeFile).completeBaseName();
0255     if(sourceName.isEmpty()) {
0256       myDebug() << "Invalid packet name";
0257       return false;
0258     }
0259     copyTarget += sourceName;
0260     scriptFolder = copyTarget + QDir::separator();
0261     QDir().mkpath(scriptFolder);
0262     auto job = KIO::file_copy(QUrl::fromLocalFile(file_), QUrl::fromLocalFile(scriptFolder + exeFile));
0263     if(!job->exec()) {
0264       myDebug() << "Copy failed";
0265       return false;
0266     }
0267     realFile = exeFile;
0268   }
0269 
0270   QString specFile = scriptFolder + QFileInfo(exeFile).completeBaseName() + QLatin1String(".spec");
0271   QString sourceExec = scriptFolder + exeFile;
0272   QUrl dest = QUrl::fromLocalFile(sourceExec);
0273   KFileItem item(dest);
0274   item.setDelayedMimeTypes(true);
0275   int out = ::chmod(QFile::encodeName(dest.path()).constData(), item.permissions() | S_IXUSR);
0276   if(out != 0) {
0277     myDebug() << "Failed to set permissions for" << dest.path();
0278   }
0279 
0280   KDesktopFile df(specFile);
0281   KConfigGroup cg = df.desktopGroup();
0282   // update name
0283   sourceName = cg.readEntry("Name", sourceName);
0284   cg.writeEntry("ExecPath", sourceExec);
0285   cg.writeEntry("NewStuffName", sourceName);
0286   cg.writeEntry("DeleteOnRemove", true);
0287 
0288   KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles");
0289   config.writeEntry(sourceName, realFile);
0290   config.writeEntry(realFile, scriptFolder);
0291   //  myDebug() << "exeFile = " << exeFile;
0292   //  myDebug() << "sourceExec = " << info->sourceExec;
0293   //  myDebug() << "sourceName = " << info->sourceName;
0294   //  myDebug() << "specFile = " << info->specFile;
0295   KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("Data Sources"));
0296   int nSources = configGroup.readEntry("Sources Count", 0);
0297   config.writeEntry(sourceName + QLatin1String("_nbr"), nSources);
0298   configGroup.writeEntry("Sources Count", nSources + 1);
0299   KConfigGroup sourceGroup(KSharedConfig::openConfig(), QStringLiteral("Data Source %1").arg(nSources));
0300   sourceGroup.writeEntry("Name", sourceName);
0301   sourceGroup.writeEntry("ExecPath", sourceExec);
0302   sourceGroup.writeEntry("DeleteOnRemove", true);
0303   sourceGroup.writeEntry("Type", 5);
0304   KSharedConfig::openConfig()->sync();
0305   return true;
0306 }
0307 
0308 bool Manager::removeScriptByName(const QString& name_) {
0309   if(name_.isEmpty()) {
0310     return false;
0311   }
0312 
0313   KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles");
0314   QString file = config.readEntry(name_, QString());
0315   if(!file.isEmpty()) {
0316     return removeScript(file);
0317   }
0318   return false;
0319 }
0320 
0321 bool Manager::removeScript(const QString& file_) {
0322   if(file_.isEmpty()) {
0323     return false;
0324   }
0325   GUI::CursorSaver cs;
0326 
0327   QFileInfo fi(file_);
0328   const QString realFile = fi.fileName();
0329   const QString sourceName = fi.completeBaseName();
0330 
0331   bool success = true;
0332   KConfigGroup fileGroup(KSharedConfig::openConfig(), "KNewStuffFiles");
0333   QString scriptFolder = fileGroup.readEntry(file_, QString());
0334   if(scriptFolder.isEmpty()) {
0335     scriptFolder = fileGroup.readEntry(realFile, QString());
0336   }
0337   int source = fileGroup.readEntry(file_ + QLatin1String("_nbr"), -1);
0338   if(source == -1) {
0339     source = fileGroup.readEntry(sourceName + QLatin1String("_nbr"), -1);
0340   }
0341 
0342   if(!scriptFolder.isEmpty()) {
0343     KIO::del(QUrl::fromLocalFile(scriptFolder))->exec();
0344   }
0345   if(source != -1) {
0346     KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("Data Sources"));
0347     int nSources = configGroup.readEntry("Sources Count", 0);
0348     configGroup.writeEntry("Sources Count", nSources - 1);
0349     KConfigGroup sourceGroup(KSharedConfig::openConfig(), QStringLiteral("Data Source %1").arg(source));
0350     sourceGroup.deleteGroup();
0351   }
0352 
0353   // remove config entries even if unsuccessful
0354   fileGroup.deleteEntry(file_);
0355   QString key = fileGroup.entryMap().key(file_);
0356   if(!key.isEmpty()) fileGroup.deleteEntry(key);
0357   fileGroup.deleteEntry(realFile);
0358   key = fileGroup.entryMap().key(realFile);
0359   if(!key.isEmpty()) fileGroup.deleteEntry(key);
0360   fileGroup.deleteEntry(file_ + QLatin1String("_nbr"));
0361   fileGroup.deleteEntry(sourceName + QLatin1String("_nbr"));
0362   KSharedConfig::openConfig()->sync();
0363   return success;
0364 }
0365 
0366 QStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const QString& path_) {
0367   QStringList list;
0368 
0369   foreach(const QString& entry, dir_->entries()) {
0370     const KArchiveEntry* curEntry = dir_->entry(entry);
0371     if(curEntry->isFile()) {
0372       list += path_ + entry;
0373     } else if(curEntry->isDirectory()) {
0374       list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + QDir::separator());
0375       // add directory AFTER contents, since we delete from the top down later
0376       list += path_ + entry + QDir::separator();
0377     }
0378   }
0379 
0380   return list;
0381 }
0382 
0383 // don't recurse, the .xsl must be in top directory
0384 QString Manager::findXSL(const KArchiveDirectory* dir_) {
0385   foreach(const QString& entry, dir_->entries()) {
0386     if(entry.endsWith(QLatin1String(".xsl"))) {
0387       return entry;
0388     }
0389   }
0390   return QString();
0391 }
0392 
0393 QString Manager::findEXE(const KArchiveDirectory* dir_) {
0394   QStack<const KArchiveDirectory*> dirStack;
0395   QStack<QString> dirNameStack;
0396 
0397   dirStack.push(dir_);
0398   dirNameStack.push(QString());
0399 
0400   do {
0401     const QString dirName = dirNameStack.pop();
0402     const KArchiveDirectory* curDir = dirStack.pop();
0403     foreach(const QString& entry, curDir->entries()) {
0404       const KArchiveEntry* archEntry = curDir->entry(entry);
0405 
0406       if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) {
0407         return dirName + entry;
0408       } else if(archEntry->isDirectory()) {
0409         dirStack.push(static_cast<const KArchiveDirectory*>(archEntry));
0410         dirNameStack.push(dirName + entry + QDir::separator());
0411       }
0412     }
0413   } while(!dirStack.isEmpty());
0414 
0415   return QString();
0416 }