File indexing completed on 2024-10-06 09:39:48
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org> 0004 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "main.h" 0011 #include "kio_version.h" 0012 #include "kioexecdinterface.h" 0013 0014 #include <QDir> 0015 #include <QFile> 0016 0017 #include <KAboutData> 0018 #include <KDBusService> 0019 #include <KLocalizedString> 0020 #include <KMessageBox> 0021 #include <KService> 0022 #include <QApplication> 0023 #include <QDebug> 0024 #include <copyjob.h> 0025 #include <desktopexecparser.h> 0026 #include <job.h> 0027 0028 #include <QCommandLineOption> 0029 #include <QCommandLineParser> 0030 #include <QFileInfo> 0031 #include <QStandardPaths> 0032 #include <QThread> 0033 0034 #include <KStartupInfo> 0035 #include <config-kioexec.h> 0036 0037 #if HAVE_X11 0038 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0039 #include <private/qtx11extras_p.h> 0040 #else 0041 #include <QX11Info> 0042 #endif 0043 #endif 0044 0045 KIOExec::KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName) 0046 : mExited(false) 0047 , mTempFiles(tempFiles) 0048 , mUseDaemon(false) 0049 , mSuggestedFileName(suggestedFileName) 0050 , expectedCounter(0) 0051 , command(args.first()) 0052 , jobCounter(0) 0053 { 0054 qDebug() << "command=" << command << "args=" << args; 0055 0056 for (int i = 1; i < args.count(); i++) { 0057 const QUrl urlArg = QUrl::fromUserInput(args.value(i)); 0058 if (!urlArg.isValid()) { 0059 KMessageBox::error(nullptr, i18n("Invalid URL: %1", args.value(i))); 0060 exit(1); 0061 } 0062 KIO::StatJob *mostlocal = KIO::mostLocalUrl(urlArg); 0063 bool b = mostlocal->exec(); 0064 if (!b) { 0065 KMessageBox::error(nullptr, i18n("File not found: %1", urlArg.toDisplayString())); 0066 exit(1); 0067 } 0068 Q_ASSERT(b); 0069 const QUrl url = mostlocal->mostLocalUrl(); 0070 0071 // kDebug() << "url=" << url.url() << " filename=" << url.fileName(); 0072 // A local file, not an URL ? 0073 // => It is not encoded and not shell escaped, too. 0074 if (url.isLocalFile()) { 0075 FileInfo file; 0076 file.path = url.toLocalFile(); 0077 file.url = url; 0078 fileList.append(file); 0079 } else { 0080 // It is an URL 0081 if (!url.isValid()) { 0082 KMessageBox::error(nullptr, i18n("The URL %1\nis malformed", url.url())); 0083 } else if (mTempFiles) { 0084 KMessageBox::error(nullptr, i18n("Remote URL %1\nnot allowed with --tempfiles switch", url.toDisplayString())); 0085 } else { 0086 // We must fetch the file 0087 QString fileName = KIO::encodeFileName(url.fileName()); 0088 if (!suggestedFileName.isEmpty()) { 0089 fileName = suggestedFileName; 0090 } 0091 if (fileName.isEmpty()) { 0092 fileName = QStringLiteral("unnamed"); 0093 } 0094 // Build the destination filename, in ~/.cache/kioexec/krun/ 0095 // Unlike KDE-1.1, we put the filename at the end so that the extension is kept 0096 // (Some programs rely on it) 0097 QString krun_writable = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) 0098 + QStringLiteral("/krun/%1_%2/").arg(QCoreApplication::applicationPid()).arg(jobCounter++); 0099 QDir().mkpath(krun_writable); // error handling will be done by the job 0100 QString tmp = krun_writable + fileName; 0101 FileInfo file; 0102 file.path = tmp; 0103 file.url = url; 0104 fileList.append(file); 0105 0106 expectedCounter++; 0107 const QUrl dest = QUrl::fromLocalFile(tmp); 0108 qDebug() << "Copying" << url << " to" << dest; 0109 KIO::Job *job = KIO::file_copy(url, dest); 0110 jobList.append(job); 0111 0112 connect(job, &KJob::result, this, &KIOExec::slotResult); 0113 } 0114 } 0115 } 0116 0117 if (mTempFiles) { 0118 // delay call so QApplication::exit passes the exit code to exec() 0119 QTimer::singleShot(0, this, &KIOExec::slotRunApp); 0120 return; 0121 } 0122 0123 counter = 0; 0124 if (counter == expectedCounter) { 0125 slotResult(nullptr); 0126 } 0127 } 0128 0129 void KIOExec::slotResult(KJob *job) 0130 { 0131 if (job) { 0132 KIO::FileCopyJob *copyJob = static_cast<KIO::FileCopyJob *>(job); 0133 const QString path = copyJob->destUrl().path(); 0134 0135 if (job->error()) { 0136 // That error dialog would be queued, i.e. not immediate... 0137 // job->showErrorDialog(); 0138 if (job->error() != KIO::ERR_USER_CANCELED) { 0139 KMessageBox::error(nullptr, job->errorString()); 0140 } 0141 0142 auto it = std::find_if(fileList.begin(), fileList.end(), [&path](const FileInfo &i) { 0143 return i.path == path; 0144 }); 0145 if (it != fileList.end()) { 0146 fileList.erase(it); 0147 } else { 0148 qDebug() << path << " not found in list"; 0149 } 0150 } else { 0151 // Tell kioexecd to watch the file for changes. 0152 const QString dest = copyJob->srcUrl().toString(); 0153 qDebug() << "Telling kioexecd to watch path" << path << "dest" << dest; 0154 OrgKdeKIOExecdInterface kioexecd(QStringLiteral("org.kde.kioexecd"), QStringLiteral("/modules/kioexecd"), QDBusConnection::sessionBus()); 0155 kioexecd.watch(path, dest); 0156 mUseDaemon = !kioexecd.lastError().isValid(); 0157 if (!mUseDaemon) { 0158 qDebug() << "Not using kioexecd"; 0159 } 0160 } 0161 } 0162 0163 counter++; 0164 0165 if (counter < expectedCounter) { 0166 return; 0167 } 0168 0169 qDebug() << "All files downloaded, will call slotRunApp shortly"; 0170 // We know we can run the app now - but let's finish the job properly first. 0171 QTimer::singleShot(0, this, &KIOExec::slotRunApp); 0172 0173 jobList.clear(); 0174 } 0175 0176 void KIOExec::slotRunApp() 0177 { 0178 if (fileList.isEmpty()) { 0179 qDebug() << "No files downloaded -> exiting"; 0180 mExited = true; 0181 QApplication::exit(1); 0182 return; 0183 } 0184 0185 KService service(QStringLiteral("dummy"), command, QString()); 0186 0187 QList<QUrl> list; 0188 list.reserve(fileList.size()); 0189 // Store modification times 0190 QList<FileInfo>::Iterator it = fileList.begin(); 0191 for (; it != fileList.end(); ++it) { 0192 QFileInfo info(it->path); 0193 it->time = info.lastModified(); 0194 QUrl url = QUrl::fromLocalFile(it->path); 0195 list << url; 0196 } 0197 0198 KIO::DesktopExecParser execParser(service, list); 0199 QStringList params = execParser.resultingArguments(); 0200 if (params.isEmpty()) { 0201 qWarning() << execParser.errorMessage(); 0202 QApplication::exit(-1); 0203 return; 0204 } 0205 0206 qDebug() << "EXEC" << params.join(QLatin1Char(' ')); 0207 0208 // propagate the startup identification to the started process 0209 KStartupInfoId id; 0210 QByteArray startupId; 0211 #if HAVE_X11 0212 if (QX11Info::isPlatformX11()) { 0213 startupId = QX11Info::nextStartupId(); 0214 } 0215 #endif 0216 id.initId(startupId); 0217 id.setupStartupEnv(); 0218 0219 QString exe(params.takeFirst()); 0220 const int exit_code = QProcess::execute(exe, params); 0221 0222 KStartupInfo::resetStartupEnv(); 0223 0224 qDebug() << "EXEC done"; 0225 0226 // Test whether one of the files changed 0227 for (it = fileList.begin(); it != fileList.end(); ++it) { 0228 QString src = it->path; 0229 const QUrl dest = it->url; 0230 QFileInfo info(src); 0231 const bool uploadChanges = !mUseDaemon && !dest.isLocalFile(); 0232 if (info.exists() && (it->time != info.lastModified())) { 0233 if (mTempFiles) { 0234 const auto result = KMessageBox::questionTwoActions( 0235 nullptr, 0236 i18n("The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?", dest.toDisplayString(QUrl::PreferLocalFile)), 0237 i18n("File Changed"), 0238 KStandardGuiItem::del(), 0239 KGuiItem(i18n("Do Not Delete"))); 0240 if (result != KMessageBox::PrimaryAction) { 0241 continue; // don't delete the temp file 0242 } 0243 } else if (uploadChanges) { // no upload when it's already a local file or kioexecd already did it. 0244 const auto result = 0245 KMessageBox::questionTwoActions(nullptr, 0246 i18n("The file\n%1\nhas been modified.\nDo you want to upload the changes?", dest.toDisplayString()), 0247 i18n("File Changed"), 0248 KGuiItem(i18n("Upload")), 0249 KGuiItem(i18n("Do Not Upload"))); 0250 if (result == KMessageBox::PrimaryAction) { 0251 qDebug() << "src='" << src << "' dest='" << dest << "'"; 0252 // Do it the synchronous way. 0253 KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src), dest); 0254 if (!job->exec()) { 0255 KMessageBox::error(nullptr, job->errorText()); 0256 continue; // don't delete the temp file 0257 } 0258 } 0259 } 0260 } 0261 0262 if ((uploadChanges || mTempFiles) && exit_code == 0) { 0263 // Wait for a reasonable time so that even if the application forks on startup (like OOo or amarok) 0264 // it will have time to start up and read the file before it gets deleted. #130709. 0265 const int sleepSecs = 180; 0266 qDebug() << "sleeping for" << sleepSecs << "seconds before deleting file..."; 0267 QThread::sleep(sleepSecs); 0268 const QString parentDir = info.path(); 0269 qDebug() << sleepSecs << "seconds have passed, deleting" << info.filePath(); 0270 QFile(src).remove(); 0271 // NOTE: this is not necessarily a temporary directory. 0272 if (QDir().rmdir(parentDir)) { 0273 qDebug() << "Removed empty parent directory" << parentDir; 0274 } 0275 } 0276 } 0277 0278 mExited = true; 0279 QApplication::exit(exit_code); 0280 } 0281 0282 int main(int argc, char **argv) 0283 { 0284 QApplication app(argc, argv); 0285 KAboutData aboutData(QStringLiteral("kioexec"), 0286 i18n("KIOExec"), 0287 QStringLiteral(KIO_VERSION_STRING), 0288 i18n("KIO Exec - Opens remote files, watches modifications, asks for upload"), 0289 KAboutLicense::GPL, 0290 i18n("(c) 1998-2000,2003 The KFM/Konqueror Developers")); 0291 aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org")); 0292 aboutData.addAuthor(i18n("Stephan Kulow"), QString(), QStringLiteral("coolo@kde.org")); 0293 aboutData.addAuthor(i18n("Bernhard Rosenkraenzer"), QString(), QStringLiteral("bero@arklinux.org")); 0294 aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org")); 0295 aboutData.addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org")); 0296 KAboutData::setApplicationData(aboutData); 0297 KDBusService service(KDBusService::Multiple); 0298 0299 QCommandLineParser parser; 0300 parser.addOption(QCommandLineOption(QStringList{QStringLiteral("tempfiles")}, i18n("Treat URLs as local files and delete them afterwards"))); 0301 parser.addOption( 0302 QCommandLineOption(QStringList{QStringLiteral("suggestedfilename")}, i18n("Suggested file name for the downloaded file"), QStringLiteral("filename"))); 0303 parser.addPositionalArgument(QStringLiteral("command"), i18n("Command to execute")); 0304 parser.addPositionalArgument(QStringLiteral("urls"), i18n("URL(s) or local file(s) used for 'command'")); 0305 0306 app.setQuitOnLastWindowClosed(false); 0307 0308 aboutData.setupCommandLine(&parser); 0309 parser.process(app); 0310 aboutData.processCommandLine(&parser); 0311 0312 if (parser.positionalArguments().count() < 1) { 0313 parser.showHelp(-1); 0314 return -1; 0315 } 0316 0317 const bool tempfiles = parser.isSet(QStringLiteral("tempfiles")); 0318 const QString suggestedfilename = parser.value(QStringLiteral("suggestedfilename")); 0319 KIOExec exec(parser.positionalArguments(), tempfiles, suggestedfilename); 0320 0321 // Don't go into the event loop if we already want to exit (#172197) 0322 if (exec.exited()) { 0323 return 0; 0324 } 0325 0326 return app.exec(); 0327 } 0328 0329 #include "moc_main.cpp"