KIO

main.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
4 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "main.h"
11#include "filecopyjob.h"
12#include "kio_version.h"
13#include "kioexecdebug.h"
14#include "kioexecdinterface.h"
15#include "statjob.h"
16
17#include <QDir>
18#include <QFile>
19
20#include <KAboutData>
21#include <KDBusService>
22#include <KLocalizedString>
23#include <KMessageBox>
24#include <KService>
25#include <QApplication>
26#include <QDebug>
27#include <copyjob.h>
28#include <desktopexecparser.h>
29#include <job.h>
30
31#include <QCommandLineOption>
32#include <QCommandLineParser>
33#include <QFileInfo>
34#include <QStandardPaths>
35#include <QThread>
36
37#include <config-kioexec.h>
38
39#if HAVE_X11
40#include <KStartupInfo>
41#include <private/qtx11extras_p.h>
42#endif
43
44KIOExec::KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName)
45 : mExited(false)
46 , mTempFiles(tempFiles)
47 , mUseDaemon(false)
48 , mSuggestedFileName(suggestedFileName)
49 , expectedCounter(0)
50 , command(args.first())
51 , jobCounter(0)
52{
53 qCDebug(KIOEXEC) << "command=" << command << "args=" << args;
54
55 for (int i = 1; i < args.count(); i++) {
56 const QUrl urlArg = QUrl::fromUserInput(args.value(i));
57 if (!urlArg.isValid()) {
58 KMessageBox::error(nullptr, i18n("Invalid URL: %1", args.value(i)));
59 exit(1);
60 }
61 KIO::StatJob *mostlocal = KIO::mostLocalUrl(urlArg);
62 bool b = mostlocal->exec();
63 if (!b) {
64 KMessageBox::error(nullptr, i18n("File not found: %1", urlArg.toDisplayString()));
65 exit(1);
66 }
67 Q_ASSERT(b);
68 const QUrl url = mostlocal->mostLocalUrl();
69
70 // kDebug() << "url=" << url.url() << " filename=" << url.fileName();
71 // A local file, not an URL ?
72 // => It is not encoded and not shell escaped, too.
73 if (url.isLocalFile()) {
74 FileInfo file;
75 file.path = url.toLocalFile();
76 file.url = url;
77 fileList.append(file);
78 } else {
79 // It is an URL
80 if (!url.isValid()) {
81 KMessageBox::error(nullptr, i18n("The URL %1\nis malformed", url.url()));
82 } else if (mTempFiles) {
83 KMessageBox::error(nullptr, i18n("Remote URL %1\nnot allowed with --tempfiles switch", url.toDisplayString()));
84 } else {
85 // We must fetch the file
86 QString fileName = KIO::encodeFileName(url.fileName());
87 if (!suggestedFileName.isEmpty()) {
88 fileName = suggestedFileName;
89 }
90 if (fileName.isEmpty()) {
91 fileName = QStringLiteral("unnamed");
92 }
93 // Build the destination filename, in ~/.cache/kioexec/krun/
94 // Unlike KDE-1.1, we put the filename at the end so that the extension is kept
95 // (Some programs rely on it)
97 + QStringLiteral("/krun/%1_%2/").arg(QCoreApplication::applicationPid()).arg(jobCounter++);
98 QDir().mkpath(krun_writable); // error handling will be done by the job
99 QString tmp = krun_writable + fileName;
100 FileInfo file;
101 file.path = tmp;
102 file.url = url;
103 fileList.append(file);
104
105 expectedCounter++;
106 const QUrl dest = QUrl::fromLocalFile(tmp);
107 qCDebug(KIOEXEC) << "Copying" << url << "to" << dest;
108 KIO::Job *job = KIO::file_copy(url, dest);
109 jobList.append(job);
110
111 connect(job, &KJob::result, this, &KIOExec::slotResult);
112 }
113 }
114 }
115
116 if (mTempFiles) {
117 // delay call so QApplication::exit passes the exit code to exec()
118 QTimer::singleShot(0, this, &KIOExec::slotRunApp);
119 return;
120 }
121
122 counter = 0;
123 if (counter == expectedCounter) {
124 slotResult(nullptr);
125 }
126}
127
128void KIOExec::slotResult(KJob *job)
129{
130 if (job) {
131 KIO::FileCopyJob *copyJob = static_cast<KIO::FileCopyJob *>(job);
132 const QString path = copyJob->destUrl().path();
133
134 if (job->error()) {
135 // That error dialog would be queued, i.e. not immediate...
136 // job->showErrorDialog();
137 if (job->error() != KIO::ERR_USER_CANCELED) {
138 KMessageBox::error(nullptr, job->errorString());
139 }
140
141 auto it = std::find_if(fileList.begin(), fileList.end(), [&path](const FileInfo &i) {
142 return i.path == path;
143 });
144 if (it != fileList.end()) {
145 fileList.erase(it);
146 } else {
147 qCDebug(KIOEXEC) << path << "not found in list";
148 }
149 } else {
150 // Tell kioexecd to watch the file for changes.
151 const QString dest = copyJob->srcUrl().toString();
152 qCDebug(KIOEXEC) << "Telling kioexecd to watch path" << path << "dest" << dest;
153 OrgKdeKIOExecdInterface kioexecd(QStringLiteral("org.kde.kioexecd6"), QStringLiteral("/modules/kioexecd"), QDBusConnection::sessionBus());
154 kioexecd.watch(path, dest);
155 mUseDaemon = !kioexecd.lastError().isValid();
156 if (!mUseDaemon) {
157 qCDebug(KIOEXEC) << "Not using kioexecd";
158 }
159 }
160 }
161
162 counter++;
163
164 if (counter < expectedCounter) {
165 return;
166 }
167
168 qCDebug(KIOEXEC) << "All files downloaded, will call slotRunApp shortly";
169 // We know we can run the app now - but let's finish the job properly first.
170 QTimer::singleShot(0, this, &KIOExec::slotRunApp);
171
172 jobList.clear();
173}
174
175void KIOExec::slotRunApp()
176{
177 if (fileList.isEmpty()) {
178 qCDebug(KIOEXEC) << "No files downloaded -> exiting";
179 mExited = true;
181 return;
182 }
183
184 KService service(QStringLiteral("dummy"), command, QString());
185
187 list.reserve(fileList.size());
188 // Store modification times
189 QList<FileInfo>::Iterator it = fileList.begin();
190 for (; it != fileList.end(); ++it) {
191 QFileInfo info(it->path);
192 it->time = info.lastModified();
193 QUrl url = QUrl::fromLocalFile(it->path);
194 list << url;
195 }
196
197 KIO::DesktopExecParser execParser(service, list);
198 QStringList params = execParser.resultingArguments();
199 if (params.isEmpty()) {
200 qWarning() << execParser.errorMessage();
202 return;
203 }
204
205 qCDebug(KIOEXEC) << "EXEC" << params.join(QLatin1Char(' '));
206
207#if HAVE_X11
208 // propagate the startup identification to the started process
210 QByteArray startupId;
211 if (QX11Info::isPlatformX11()) {
212 startupId = QX11Info::nextStartupId();
213 }
214 id.initId(startupId);
215 id.setupStartupEnv();
216#endif
217
218 QString exe(params.takeFirst());
219 const int exit_code = QProcess::execute(exe, params);
220
221#if HAVE_X11
223#endif
224
225 qCDebug(KIOEXEC) << "EXEC done";
226
227 QStringList tempFilesToRemove;
228
229 // Test whether one of the files changed
230 for (it = fileList.begin(); it != fileList.end(); ++it) {
231 QString src = it->path;
232 const QUrl dest = it->url;
233 QFileInfo info(src);
234 const bool uploadChanges = !mUseDaemon && !dest.isLocalFile();
235 if (info.exists() && (it->time != info.lastModified())) {
236 if (mTempFiles) {
237 const auto result = KMessageBox::questionTwoActions(
238 nullptr,
239 i18n("The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?", dest.toDisplayString(QUrl::PreferLocalFile)),
240 i18n("File Changed"),
242 KGuiItem(i18n("Do Not Delete")));
243 if (result != KMessageBox::PrimaryAction) {
244 continue; // don't delete the temp file
245 }
246 } else if (uploadChanges) { // no upload when it's already a local file or kioexecd already did it.
247 const auto result =
249 i18n("The file\n%1\nhas been modified.\nDo you want to upload the changes?", dest.toDisplayString()),
250 i18n("File Changed"),
251 KGuiItem(i18n("Upload")),
252 KGuiItem(i18n("Do Not Upload")));
253 if (result == KMessageBox::PrimaryAction) {
254 qCDebug(KIOEXEC) << "src=" << src << "dest=" << dest;
255 // Do it the synchronous way.
256 KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src), dest);
257 if (!job->exec()) {
258 KMessageBox::error(nullptr, job->errorText());
259 continue; // don't delete the temp file
260 }
261 }
262 }
263 }
264
265 if ((uploadChanges || mTempFiles) && exit_code == 0) {
266 // Note that a temp file needs to be removed later
267 tempFilesToRemove.append(src);
268 }
269 }
270
271 if (!tempFilesToRemove.isEmpty()) {
272 // Wait for a reasonable time so that even if the application forks
273 // on startup (like OOo or amarok) it will have time to start up and
274 // read the file before it gets deleted. #130709.
275 const int sleepSecs = 180;
276 qCDebug(KIOEXEC) << "sleeping for" << sleepSecs << "seconds before deleting" << tempFilesToRemove.count() << "temp files...";
277 QThread::sleep(sleepSecs);
278 qCDebug(KIOEXEC) << sleepSecs << "seconds have passed, deleting temp files";
279
280 for (const QString &src : qAsConst(tempFilesToRemove)) {
281 QFileInfo info(src);
282 const QString parentDir = info.path();
283 qCDebug(KIOEXEC) << "deleting" << info.filePath();
284 QFile(src).remove();
285 // NOTE: this is not necessarily a temporary directory.
286 if (QDir().rmdir(parentDir)) {
287 qCDebug(KIOEXEC) << "Removed empty parent directory" << parentDir;
288 }
289 }
290 }
291
292 mExited = true;
293 QApplication::exit(exit_code);
294}
295
296int main(int argc, char **argv)
297{
298 QApplication app(argc, argv);
299 KAboutData aboutData(QStringLiteral("kioexec"),
300 i18n("KIOExec"),
301 QStringLiteral(KIO_VERSION_STRING),
302 i18n("KIO Exec - Opens remote files, watches modifications, asks for upload"),
304 i18n("(c) 1998-2000,2003 The KFM/Konqueror Developers"));
305 aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org"));
306 aboutData.addAuthor(i18n("Stephan Kulow"), QString(), QStringLiteral("coolo@kde.org"));
307 aboutData.addAuthor(i18n("Bernhard Rosenkraenzer"), QString(), QStringLiteral("bero@arklinux.org"));
308 aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org"));
309 aboutData.addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org"));
312
313 QCommandLineParser parser;
314 parser.addOption(QCommandLineOption(QStringList{QStringLiteral("tempfiles")}, i18n("Treat URLs as local files and delete them afterwards")));
315 parser.addOption(
316 QCommandLineOption(QStringList{QStringLiteral("suggestedfilename")}, i18n("Suggested file name for the downloaded file"), QStringLiteral("filename")));
317 parser.addPositionalArgument(QStringLiteral("command"), i18n("Command to execute"));
318 parser.addPositionalArgument(QStringLiteral("urls"), i18n("URL(s) or local file(s) used for 'command'"));
319
320 app.setQuitOnLastWindowClosed(false);
321
322 aboutData.setupCommandLine(&parser);
323 parser.process(app);
324 aboutData.processCommandLine(&parser);
325
326 if (parser.positionalArguments().count() < 1) {
327 parser.showHelp(-1);
328 return -1;
329 }
330
331 const bool tempfiles = parser.isSet(QStringLiteral("tempfiles"));
332 const QString suggestedfilename = parser.value(QStringLiteral("suggestedfilename"));
333 KIOExec exec(parser.positionalArguments(), tempfiles, suggestedfilename);
334
335 // Don't go into the event loop if we already want to exit (#172197)
336 if (exec.exited()) {
337 return 0;
338 }
339
340 return app.exec();
341}
342
343#include "moc_main.cpp"
static void setApplicationData(const KAboutData &aboutData)
CopyJob is used to move, copy or symlink files and directories.
Definition copyjob.h:41
Parses the Exec= line from a .desktop file, and process all the '%' placeholders, e....
The FileCopyJob copies data from one place to another.
Definition filecopyjob.h:26
QUrl destUrl() const
Returns the destination URL.
QUrl srcUrl() const
Returns the source URL.
The base class for all jobs.
Definition job_base.h:45
A KIO job that retrieves information about a file or directory.
Definition statjob.h:26
QUrl mostLocalUrl() const
most local URL
Definition statjob.cpp:85
bool exec()
virtual QString errorString() const
int error() const
void result(KJob *job)
QString errorText() const
static void resetStartupEnv()
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT SimpleJob * rmdir(const QUrl &url)
Removes a single directory.
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Copy a file or directory src into the destination dest, which can be a file (including the final file...
Definition copyjob.cpp:2604
KIOCORE_EXPORT StatJob * mostLocalUrl(const QUrl &url, JobFlags flags=DefaultFlags)
Tries to map a local URL for the given URL, using a KIO job.
Definition statjob.cpp:193
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Copy a single file.
KIOCORE_EXPORT QString encodeFileName(const QString &str)
Encodes (from the text displayed to the real filename) This translates '/' into a "unicode fraction s...
Definition global.cpp:113
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
KGuiItem del()
bool addOption(const QCommandLineOption &option)
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
bool isSet(const QCommandLineOption &option) const const
QStringList positionalArguments() const const
void process(const QCoreApplication &app)
void showHelp(int exitCode)
QString value(const QCommandLineOption &option) const const
qint64 applicationPid()
void exit(int returnCode)
QDBusConnection sessionBus()
bool mkpath(const QString &dirPath) const const
bool remove()
void append(QList< T > &&value)
iterator begin()
void clear()
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
value_type takeFirst()
T value(qsizetype i) const const
int execute(const QString &program, const QStringList &arguments)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
bool isEmpty() const const
QString join(QChar separator) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void sleep(std::chrono::nanoseconds nsecs)
PreferLocalFile
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
bool isLocalFile() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
QString url(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.