File indexing completed on 2024-05-12 05:09:53
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 "../utils/cursorsaver.h" 0028 #include "../utils/tellico_utils.h" 0029 #include "../tellico_debug.h" 0030 0031 #include <KTar> 0032 #include <KConfig> 0033 #include <KFileItem> 0034 #include <KConfigGroup> 0035 #include <KSharedConfig> 0036 #include <KDesktopFile> 0037 #include <KIO/Job> 0038 #include <KIO/DeleteJob> 0039 0040 #include <QFileInfo> 0041 #include <QDir> 0042 #include <QStandardPaths> 0043 #include <QTemporaryFile> 0044 #include <QGlobalStatic> 0045 0046 #include <sys/types.h> 0047 #include <sys/stat.h> 0048 // for msvc 0049 #ifndef S_IXUSR 0050 #define S_IXUSR 00100 0051 #endif 0052 0053 namespace Tellico { 0054 namespace NewStuff { 0055 0056 class ManagerSingleton { 0057 public: 0058 Tellico::NewStuff::Manager self; 0059 }; 0060 0061 } 0062 } 0063 0064 Q_GLOBAL_STATIC(Tellico::NewStuff::ManagerSingleton, s_instance) 0065 0066 using Tellico::NewStuff::Manager; 0067 0068 Manager::Manager() : QObject(nullptr) { 0069 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.tellico")); 0070 new NewstuffAdaptor(this); 0071 QDBusConnection::sessionBus().registerObject(QStringLiteral("/NewStuff"), this); 0072 } 0073 0074 Manager::~Manager() { 0075 QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/NewStuff")); 0076 auto interface = QDBusConnection::sessionBus().interface(); 0077 if(interface) { 0078 // the windows build was crashing here when the interface was null 0079 // see https://bugs.kde.org/show_bug.cgi?id=422468 0080 interface->unregisterService(QStringLiteral("org.kde.tellico")); 0081 } 0082 } 0083 0084 Manager* Manager::self() { 0085 return &s_instance->self; 0086 } 0087 0088 bool Manager::installTemplate(const QString& file_) { 0089 if(file_.isEmpty()) { 0090 return false; 0091 } 0092 GUI::CursorSaver cs; 0093 0094 QString xslFile; 0095 QStringList allFiles; 0096 0097 bool success = true; 0098 0099 static const QRegularExpression digitsDashRx(QLatin1String("^\\d+-")); 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(digitsDashRx); // 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 static const QRegularExpression digitsDashRx(QLatin1String("^\\d+-")); 0224 if(archive.open(QIODevice::ReadOnly)) { 0225 const KArchiveDirectory* archiveDir = archive.directory(); 0226 exeFile = findEXE(archiveDir); 0227 if(exeFile.isEmpty()) { 0228 myDebug() << "No exe file found"; 0229 return false; 0230 } 0231 sourceName = QFileInfo(exeFile).baseName(); 0232 if(sourceName.isEmpty()) { 0233 myDebug() << "Invalid packet name"; 0234 return false; 0235 } 0236 // package could have a top-level directory or not 0237 // it should have a directory... 0238 foreach(const QString& entry, archiveDir->entries()) { 0239 if(entry.indexOf(QDir::separator()) < 0) { 0240 // archive does have multiple root items -> extract own dir 0241 copyTarget += sourceName; 0242 scriptFolder = copyTarget + QDir::separator(); 0243 break; 0244 } 0245 } 0246 if(scriptFolder.isEmpty()) { // one root item 0247 scriptFolder = copyTarget + exeFile.left(exeFile.indexOf(QDir::separator())) + QDir::separator(); 0248 } 0249 // overwrites stuff there 0250 archiveDir->copyTo(copyTarget); 0251 } else { // assume it's an script file 0252 exeFile = QFileInfo(file_).fileName(); 0253 0254 exeFile.remove(digitsDashRx); // Remove possible kde-files.org id 0255 sourceName = QFileInfo(exeFile).completeBaseName(); 0256 if(sourceName.isEmpty()) { 0257 myDebug() << "Invalid packet name"; 0258 return false; 0259 } 0260 copyTarget += sourceName; 0261 scriptFolder = copyTarget + QDir::separator(); 0262 QDir().mkpath(scriptFolder); 0263 auto job = KIO::file_copy(QUrl::fromLocalFile(file_), QUrl::fromLocalFile(scriptFolder + exeFile)); 0264 if(!job->exec()) { 0265 myDebug() << "Copy failed"; 0266 return false; 0267 } 0268 realFile = exeFile; 0269 } 0270 0271 QString specFile = scriptFolder + QFileInfo(exeFile).completeBaseName() + QLatin1String(".spec"); 0272 QString sourceExec = scriptFolder + exeFile; 0273 QUrl dest = QUrl::fromLocalFile(sourceExec); 0274 KFileItem item(dest); 0275 item.setDelayedMimeTypes(true); 0276 int out = ::chmod(QFile::encodeName(dest.path()).constData(), item.permissions() | S_IXUSR); 0277 if(out != 0) { 0278 myDebug() << "Failed to set permissions for" << dest.path(); 0279 } 0280 0281 KDesktopFile df(specFile); 0282 KConfigGroup cg = df.desktopGroup(); 0283 // update name 0284 sourceName = cg.readEntry("Name", sourceName); 0285 cg.writeEntry("ExecPath", sourceExec); 0286 cg.writeEntry("NewStuffName", sourceName); 0287 cg.writeEntry("DeleteOnRemove", true); 0288 0289 KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles"); 0290 config.writeEntry(sourceName, realFile); 0291 config.writeEntry(realFile, scriptFolder); 0292 // myDebug() << "exeFile = " << exeFile; 0293 // myDebug() << "sourceExec = " << info->sourceExec; 0294 // myDebug() << "sourceName = " << info->sourceName; 0295 // myDebug() << "specFile = " << info->specFile; 0296 KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("Data Sources")); 0297 int nSources = configGroup.readEntry("Sources Count", 0); 0298 config.writeEntry(sourceName + QLatin1String("_nbr"), nSources); 0299 configGroup.writeEntry("Sources Count", nSources + 1); 0300 KConfigGroup sourceGroup(KSharedConfig::openConfig(), QStringLiteral("Data Source %1").arg(nSources)); 0301 sourceGroup.writeEntry("Name", sourceName); 0302 sourceGroup.writeEntry("ExecPath", sourceExec); 0303 sourceGroup.writeEntry("DeleteOnRemove", true); 0304 sourceGroup.writeEntry("Type", 5); 0305 KSharedConfig::openConfig()->sync(); 0306 return true; 0307 } 0308 0309 bool Manager::removeScriptByName(const QString& name_) { 0310 if(name_.isEmpty()) { 0311 return false; 0312 } 0313 0314 KConfigGroup config(KSharedConfig::openConfig(), "KNewStuffFiles"); 0315 QString file = config.readEntry(name_, QString()); 0316 if(!file.isEmpty()) { 0317 return removeScript(file); 0318 } 0319 return false; 0320 } 0321 0322 bool Manager::removeScript(const QString& file_) { 0323 if(file_.isEmpty()) { 0324 return false; 0325 } 0326 GUI::CursorSaver cs; 0327 0328 QFileInfo fi(file_); 0329 const QString realFile = fi.fileName(); 0330 const QString sourceName = fi.completeBaseName(); 0331 0332 bool success = true; 0333 KConfigGroup fileGroup(KSharedConfig::openConfig(), "KNewStuffFiles"); 0334 QString scriptFolder = fileGroup.readEntry(file_, QString()); 0335 if(scriptFolder.isEmpty()) { 0336 scriptFolder = fileGroup.readEntry(realFile, QString()); 0337 } 0338 int source = fileGroup.readEntry(file_ + QLatin1String("_nbr"), -1); 0339 if(source == -1) { 0340 source = fileGroup.readEntry(sourceName + QLatin1String("_nbr"), -1); 0341 } 0342 0343 if(!scriptFolder.isEmpty()) { 0344 KIO::del(QUrl::fromLocalFile(scriptFolder))->exec(); 0345 } 0346 if(source != -1) { 0347 KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("Data Sources")); 0348 int nSources = configGroup.readEntry("Sources Count", 0); 0349 configGroup.writeEntry("Sources Count", nSources - 1); 0350 KConfigGroup sourceGroup(KSharedConfig::openConfig(), QStringLiteral("Data Source %1").arg(source)); 0351 sourceGroup.deleteGroup(); 0352 } 0353 0354 // remove config entries even if unsuccessful 0355 fileGroup.deleteEntry(file_); 0356 QString key = fileGroup.entryMap().key(file_); 0357 if(!key.isEmpty()) fileGroup.deleteEntry(key); 0358 fileGroup.deleteEntry(realFile); 0359 key = fileGroup.entryMap().key(realFile); 0360 if(!key.isEmpty()) fileGroup.deleteEntry(key); 0361 fileGroup.deleteEntry(file_ + QLatin1String("_nbr")); 0362 fileGroup.deleteEntry(sourceName + QLatin1String("_nbr")); 0363 KSharedConfig::openConfig()->sync(); 0364 return success; 0365 } 0366 0367 QStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const QString& path_) { 0368 QStringList list; 0369 0370 foreach(const QString& entry, dir_->entries()) { 0371 const KArchiveEntry* curEntry = dir_->entry(entry); 0372 if(curEntry->isFile()) { 0373 list += path_ + entry; 0374 } else if(curEntry->isDirectory()) { 0375 list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + QDir::separator()); 0376 // add directory AFTER contents, since we delete from the top down later 0377 list += path_ + entry + QDir::separator(); 0378 } 0379 } 0380 0381 return list; 0382 } 0383 0384 // don't recurse, the .xsl must be in top directory 0385 QString Manager::findXSL(const KArchiveDirectory* dir_) { 0386 foreach(const QString& entry, dir_->entries()) { 0387 if(entry.endsWith(QLatin1String(".xsl"))) { 0388 return entry; 0389 } 0390 } 0391 return QString(); 0392 } 0393 0394 QString Manager::findEXE(const KArchiveDirectory* dir_) { 0395 QStack<const KArchiveDirectory*> dirStack; 0396 QStack<QString> dirNameStack; 0397 0398 dirStack.push(dir_); 0399 dirNameStack.push(QString()); 0400 0401 do { 0402 const QString dirName = dirNameStack.pop(); 0403 const KArchiveDirectory* curDir = dirStack.pop(); 0404 foreach(const QString& entry, curDir->entries()) { 0405 const KArchiveEntry* archEntry = curDir->entry(entry); 0406 0407 if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) { 0408 return dirName + entry; 0409 } else if(archEntry->isDirectory()) { 0410 dirStack.push(static_cast<const KArchiveDirectory*>(archEntry)); 0411 dirNameStack.push(dirName + entry + QDir::separator()); 0412 } 0413 } 0414 } while(!dirStack.isEmpty()); 0415 0416 return QString(); 0417 }