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