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 }