1 /* This file is part of the KDE project
2    SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org>
3 
4    SPDX-License-Identifier: LGPL-2.0-only
5 */
6 
7 #include "kioclient.h"
8 #include "kio_version.h"
9 #include "urlinfo.h"
10 
11 #include <KIO/CopyJob>
12 #include <KIO/DeleteJob>
13 #include <kio/listjob.h>
14 #include <kio/transferjob.h>
15 #ifndef KIOCORE_ONLY
16 #include <KIO/ApplicationLauncherJob>
17 #include <KIO/JobUiDelegate>
18 #include <KIO/OpenUrlJob>
19 #include <KIO/StatJob>
20 #include <KIO/UDSEntry>
21 #include <KPropertiesDialog>
22 #include <KService>
23 #endif
24 #include <KAboutData>
25 #include <KLocalizedString>
26 
27 #include <QCommandLineParser>
28 #include <QDBusConnection>
29 #include <QDebug>
30 #include <QFileDialog>
31 #include <QUrlQuery>
32 #include <iostream>
33 
34 bool ClientApp::m_ok = true;
35 static bool s_interactive = false;
36 static KIO::JobFlags s_jobFlags = KIO::DefaultFlags;
37 
makeURL(const QString & urlArg)38 static QUrl makeURL(const QString &urlArg)
39 {
40     return QUrl::fromUserInput(urlArg, QDir::currentPath());
41 }
42 
makeUrls(const QStringList & urlArgs)43 static QList<QUrl> makeUrls(const QStringList &urlArgs)
44 {
45     QList<QUrl> ret;
46     for (const QString &url : urlArgs) {
47         ret += makeURL(url);
48     }
49     return ret;
50 }
51 
52 #ifdef KIOCLIENT_AS_KIOCLIENT5
usage()53 static void usage()
54 {
55     puts(i18n("\nSyntax:\n").toLocal8Bit().constData());
56     puts(i18n("  kioclient5 openProperties 'url'\n"
57               "            # Opens a properties menu\n\n")
58              .toLocal8Bit()
59              .constData());
60     puts(i18n("  kioclient5 exec 'url' ['mimetype']\n"
61               "            # Tries to open the document pointed to by 'url', in the application\n"
62               "            #   associated with it in KDE. You may omit 'mimetype'.\n"
63               "            #   In this case the mimetype is determined\n"
64               "            #   automatically. Of course URL may be the URL of a\n"
65               "            #   document, or it may be a *.desktop file.\n"
66               "            #   'url' can be an executable, too.\n")
67              .toLocal8Bit()
68              .constData());
69     puts(i18n("  kioclient5 move 'src' 'dest'\n"
70               "            # Moves the URL 'src' to 'dest'.\n"
71               "            #   'src' may be a list of URLs.\n")
72              .toLocal8Bit()
73              .constData());
74     puts(i18n("            #   'dest' may be \"trash:/\" to move the files\n"
75               "            #   to the trash.\n")
76              .toLocal8Bit()
77              .constData());
78     puts(i18n("            #   the short version kioclient5 mv\n"
79               "            #   is also available.\n\n")
80              .toLocal8Bit()
81              .constData());
82     puts(i18n("  kioclient5 download ['src']\n"
83               "            # Copies the URL 'src' to a user-specified location'.\n"
84               "            #   'src' may be a list of URLs, if not present then\n"
85               "            #   a URL will be requested.\n\n")
86              .toLocal8Bit()
87              .constData());
88     puts(i18n("  kioclient5 copy 'src' 'dest'\n"
89               "            # Copies the URL 'src' to 'dest'.\n"
90               "            #   'src' may be a list of URLs.\n")
91              .toLocal8Bit()
92              .constData());
93     puts(i18n("            #   the short version kioclient5 cp\n"
94               "            #   is also available.\n\n")
95              .toLocal8Bit()
96              .constData());
97     puts(i18n("  kioclient5 cat 'url'\n"
98               "            # Writes out the contents of 'url' to stdout\n\n")
99              .toLocal8Bit()
100              .constData());
101     puts(i18n("  kioclient5 ls 'url'\n"
102               "            # Lists the contents of the directory 'url' to stdout\n\n")
103              .toLocal8Bit()
104              .constData());
105     puts(i18n("  kioclient5 remove 'url'\n"
106               "            # Removes the URL\n"
107               "            #   'url' may be a list of URLs.\n")
108              .toLocal8Bit()
109              .constData());
110     puts(i18n("            #   the short version kioclient5 rm\n"
111               "            #   is also available.\n\n")
112              .toLocal8Bit()
113              .constData());
114     puts(i18n("  kioclient5 stat 'url'\n"
115               "            # Shows all of the available information for 'url'\n\n")
116              .toLocal8Bit()
117              .constData());
118     puts(i18n("  kioclient5 appmenu\n"
119               "            # Opens a basic application launcher.\n\n")
120              .toLocal8Bit()
121              .constData());
122 
123     puts(i18n("*** Examples:\n").toLocal8Bit().constData());
124     puts(i18n("  kioclient5 exec file:/home/weis/data/test.html\n"
125               "             // Opens the file with default binding\n\n")
126              .toLocal8Bit()
127              .constData());
128     puts(i18n("  kioclient5 exec ftp://localhost/\n"
129               "             // Opens new window with URL\n\n")
130              .toLocal8Bit()
131              .constData());
132     puts(i18n("  kioclient5 exec file:/root/Desktop/emacs.desktop\n"
133               "             // Starts emacs\n\n")
134              .toLocal8Bit()
135              .constData());
136     puts(i18n("  kioclient5 exec .\n"
137               "             // Opens the current directory. Very convenient.\n\n")
138              .toLocal8Bit()
139              .constData());
140 }
141 #endif
142 
main(int argc,char ** argv)143 int main(int argc, char **argv)
144 {
145 #ifdef KIOCORE_ONLY
146     QCoreApplication app(argc, argv);
147 #else
148     QApplication app(argc, argv);
149 #endif
150 
151     KLocalizedString::setApplicationDomain("kioclient5");
152 
153     QString appName = QStringLiteral("kioclient");
154     QString programName = i18n("KIO Client");
155     QString description = i18n("Command-line tool for network-transparent operations");
156     QString version = QLatin1String(PROJECT_VERSION);
157     KAboutData data(appName, programName, version, description, KAboutLicense::LGPL_V2);
158     KAboutData::setApplicationData(data);
159 
160     QCommandLineParser parser;
161     data.setupCommandLine(&parser);
162     parser.addOption(QCommandLineOption(QStringLiteral("interactive"), i18n("Use message boxes and other native notifications")));
163 
164     parser.addOption(QCommandLineOption(QStringLiteral("noninteractive"),
165                                         i18n("Non-interactive use: no message boxes. If you don't want a "
166                                              "graphical connection, use --platform offscreen")));
167 
168 #if !defined(KIOCLIENT_AS_KDEOPEN)
169     parser.addOption(QCommandLineOption(QStringLiteral("overwrite"), i18n("Overwrite destination if it exists (for copy and move)")));
170 #endif
171 
172 #if defined(KIOCLIENT_AS_KDEOPEN)
173     parser.addPositionalArgument(QStringLiteral("url"), i18n("file or URL"), i18n("urls..."));
174 #elif defined(KIOCLIENT_AS_KDECP5)
175     parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls..."));
176     parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url"));
177 #elif defined(KIOCLIENT_AS_KDEMV5)
178     parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls..."));
179     parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url"));
180 #elif defined(KIOCLIENT_AS_KIOCLIENT5)
181     parser.addOption(QCommandLineOption(QStringLiteral("commands"), i18n("Show available commands")));
182     parser.addPositionalArgument(QStringLiteral("command"), i18n("Command (see --commands)"), i18n("command"));
183     parser.addPositionalArgument(QStringLiteral("URLs"), i18n("Arguments for command"), i18n("urls..."));
184 #endif
185 
186     //   KCmdLineArgs::addTempFileOption();
187 
188     parser.process(app);
189     data.processCommandLine(&parser);
190 
191 #ifdef KIOCLIENT_AS_KIOCLIENT5
192     if (argc == 1 || parser.isSet(QStringLiteral("commands"))) {
193         puts(parser.helpText().toLocal8Bit().constData());
194         puts("\n\n");
195         usage();
196         return 0;
197     }
198 #endif
199 
200     ClientApp client;
201     return client.doIt(parser) ? 0 /*no error*/ : 1 /*error*/;
202 }
203 
checkArgumentCount(int count,int min,int max)204 static void checkArgumentCount(int count, int min, int max)
205 {
206     if (count < min) {
207         fputs(i18nc("@info:shell", "%1: Syntax error, not enough arguments\n", qAppName()).toLocal8Bit().constData(), stderr);
208         ::exit(1);
209     }
210     if (max && (count > max)) {
211         fputs(i18nc("@info:shell", "%1: Syntax error, too many arguments\n", qAppName()).toLocal8Bit().constData(), stderr);
212         ::exit(1);
213     }
214 }
215 
216 #ifndef KIOCORE_ONLY
kde_open(const QString & url,const QString & mimeType,bool allowExec)217 bool ClientApp::kde_open(const QString &url, const QString &mimeType, bool allowExec)
218 {
219     UrlInfo info(url);
220 
221     if (!info.atStart()) {
222         QUrlQuery q;
223         q.addQueryItem(QStringLiteral("line"), QString::number(info.line));
224         q.addQueryItem(QStringLiteral("column"), QString::number(info.column));
225         info.url.setQuery(q);
226     }
227 
228     auto *job = new KIO::OpenUrlJob(info.url, mimeType);
229     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
230     job->setRunExecutables(allowExec);
231     job->setFollowRedirections(false);
232     bool job_had_error = false;
233     QObject::connect(job, &KJob::result, this, [&](KJob *job) {
234         if (job->error()) {
235             job_had_error = true;
236         }
237     });
238     job->start();
239     qApp->exec();
240     return !job_had_error;
241 }
242 #endif
243 
doCopy(const QStringList & urls)244 bool ClientApp::doCopy(const QStringList &urls)
245 {
246     QList<QUrl> srcLst(makeUrls(urls));
247     QUrl dest = srcLst.takeLast();
248     KIO::Job *job = KIO::copy(srcLst, dest, s_jobFlags);
249     if (!s_interactive) {
250         job->setUiDelegate(nullptr);
251         job->setUiDelegateExtension(nullptr);
252     }
253     connect(job, &KJob::result, this, &ClientApp::slotResult);
254     qApp->exec();
255     return m_ok;
256 }
257 
slotEntries(KIO::Job *,const KIO::UDSEntryList & list)258 void ClientApp::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
259 {
260     for (const auto &entry : list) {
261         // For each file...
262         std::cout << qPrintable(entry.stringValue(KIO::UDSEntry::UDS_NAME)) << '\n';
263     }
264 
265     std::cout << std::endl;
266 }
267 
doList(const QStringList & urls)268 bool ClientApp::doList(const QStringList &urls)
269 {
270     const QUrl dir = makeURL(urls.at(0));
271 
272     KIO::ListJob *job = KIO::listDir(dir, KIO::HideProgressInfo);
273     if (!s_interactive) {
274         job->setUiDelegate(nullptr);
275         job->setUiDelegateExtension(nullptr);
276     }
277 
278     connect(job, &KIO::ListJob::entries, this, &ClientApp::slotEntries);
279     connect(job, &KJob::result, this, &ClientApp::slotResult);
280 
281     qApp->exec();
282     return m_ok;
283 }
284 
doMove(const QStringList & urls)285 bool ClientApp::doMove(const QStringList &urls)
286 {
287     QList<QUrl> srcLst(makeUrls(urls));
288     const QUrl dest = srcLst.takeLast();
289 
290     KIO::Job *job = KIO::move(srcLst, dest, s_jobFlags);
291     if (!s_interactive) {
292         job->setUiDelegate(nullptr);
293         job->setUiDelegateExtension(nullptr);
294     }
295 
296     connect(job, &KJob::result, this, &ClientApp::slotResult);
297 
298     qApp->exec();
299     return m_ok;
300 }
301 
doRemove(const QStringList & urls)302 bool ClientApp::doRemove(const QStringList &urls)
303 {
304     KIO::Job *job = KIO::del(makeUrls(urls), s_jobFlags);
305     if (!s_interactive) {
306         job->setUiDelegate(nullptr);
307         job->setUiDelegateExtension(nullptr);
308     }
309 
310     connect(job, &KJob::result, this, &ClientApp::slotResult);
311 
312     qApp->exec();
313     return m_ok;
314 }
315 
doStat(const QStringList & urls)316 bool ClientApp::doStat(const QStringList &urls)
317 {
318     KIO::Job *job = KIO::statDetails(makeURL(urls.first()),
319                                      KIO::StatJob::SourceSide,
320                                      (KIO::StatBasic | KIO::StatUser | KIO::StatTime | KIO::StatInode | KIO::StatMimeType | KIO::StatAcl),
321                                      s_jobFlags);
322     if (!s_interactive) {
323         job->setUiDelegate(nullptr);
324         job->setUiDelegateExtension(nullptr);
325     }
326 
327     connect(job, &KJob::result, this, &ClientApp::slotStatResult);
328 
329     qApp->exec();
330     return m_ok;
331 }
332 
doIt(const QCommandLineParser & parser)333 bool ClientApp::doIt(const QCommandLineParser &parser)
334 {
335     const int argc = parser.positionalArguments().count();
336     checkArgumentCount(argc, 1, 0);
337 
338     if (parser.isSet(QStringLiteral("interactive"))) {
339         s_interactive = true;
340     } else {
341         // "noninteractive" is currently the default mode, so we don't check.
342         // The argument still needs to exist for compatibility
343         s_interactive = false;
344         s_jobFlags = KIO::HideProgressInfo;
345     }
346 #if !defined(KIOCLIENT_AS_KDEOPEN)
347     if (parser.isSet(QStringLiteral("overwrite"))) {
348         s_jobFlags |= KIO::Overwrite;
349     }
350 #endif
351 
352 #ifdef KIOCLIENT_AS_KDEOPEN
353     return kde_open(parser.positionalArguments().at(0), QString(), false);
354 #elif defined(KIOCLIENT_AS_KDECP5)
355     checkArgumentCount(argc, 2, 0);
356     return doCopy(parser.positionalArguments());
357 #elif defined(KIOCLIENT_AS_KDEMV5)
358     checkArgumentCount(argc, 2, 0);
359     return doMove(parser.positionalArguments());
360 #else
361     // Normal kioclient mode
362     const QString command = parser.positionalArguments().at(0);
363 #ifndef KIOCORE_ONLY
364     if (command == QLatin1String("openProperties")) {
365         checkArgumentCount(argc, 2, 2); // openProperties <url>
366         const QUrl url = makeURL(parser.positionalArguments().constLast());
367 
368         KPropertiesDialog *dlg = new KPropertiesDialog(url, nullptr /*no parent*/);
369         QObject::connect(dlg, &QObject::destroyed, qApp, &QCoreApplication::quit);
370         QObject::connect(dlg, &KPropertiesDialog::canceled, this, &ClientApp::slotDialogCanceled);
371         dlg->show();
372 
373         qApp->exec();
374         return m_ok;
375     } else
376 #endif
377         if (command == QLatin1String("cat")) {
378         checkArgumentCount(argc, 2, 2); // cat <url>
379         const QUrl url = makeURL(parser.positionalArguments().constLast());
380 
381         KIO::TransferJob *job = KIO::get(url, KIO::NoReload, s_jobFlags);
382         if (!s_interactive) {
383             job->setUiDelegate(nullptr);
384             job->setUiDelegateExtension(nullptr);
385         }
386         connect(job, &KIO::TransferJob::data, this, &ClientApp::slotPrintData);
387         connect(job, &KJob::result, this, &ClientApp::slotResult);
388 
389         qApp->exec();
390         return m_ok;
391     }
392 #ifndef KIOCORE_ONLY
393     else if (command == QLatin1String("exec")) {
394         checkArgumentCount(argc, 2, 3);
395         return kde_open(parser.positionalArguments().at(1), argc == 3 ? parser.positionalArguments().constLast() : QString(), true);
396     } else if (command == QLatin1String("appmenu")) {
397         auto *job = new KIO::ApplicationLauncherJob();
398         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
399         connect(job, &KJob::result, this, &ClientApp::slotResult);
400         job->start();
401 
402         qApp->exec();
403         return m_ok;
404     }
405 #endif
406     else if (command == QLatin1String("download")) {
407         checkArgumentCount(argc, 0, 0);
408         QStringList args = parser.positionalArguments();
409         args.removeFirst();
410         const QList<QUrl> srcLst = makeUrls(args);
411 
412         if (srcLst.isEmpty()) {
413             return m_ok;
414         }
415 
416         const QUrl dsturl = QFileDialog::getSaveFileUrl(nullptr, i18n("Destination where to download the files"), srcLst.at(0));
417 
418         if (dsturl.isEmpty()) { // canceled
419             return m_ok; // AK - really okay?
420         }
421 
422         KIO::Job *job = KIO::copy(srcLst, dsturl, s_jobFlags);
423         if (!s_interactive) {
424             job->setUiDelegate(nullptr);
425             job->setUiDelegateExtension(nullptr);
426         }
427 
428         connect(job, &KJob::result, this, &ClientApp::slotResult);
429 
430         qApp->exec();
431         return m_ok;
432     } else if (command == QLatin1String("copy") || command == QLatin1String("cp")) {
433         checkArgumentCount(argc, 3, 0); // cp <src> <dest>
434         QStringList args = parser.positionalArguments();
435         args.removeFirst();
436         return doCopy(args);
437     } else if (command == QLatin1String("move") || command == QLatin1String("mv")) {
438         checkArgumentCount(argc, 3, 0); // mv <src> <dest>
439         QStringList args = parser.positionalArguments();
440         args.removeFirst();
441         return doMove(args);
442     } else if (command == QLatin1String("list") || command == QLatin1String("ls")) {
443         checkArgumentCount(argc, 2, 2); // ls <url>
444         QStringList args = parser.positionalArguments();
445         args.removeFirst();
446         return doList(args);
447     } else if (command == QLatin1String("remove") || command == QLatin1String("rm")) {
448         checkArgumentCount(argc, 2, 0); // rm <url>
449         QStringList args = parser.positionalArguments();
450         args.removeFirst();
451         return doRemove(args);
452     } else if (command == QLatin1String("stat")) {
453         checkArgumentCount(argc, 2, 2); // stat <url>
454         QStringList args = parser.positionalArguments();
455         args.removeFirst();
456         return doStat(args);
457     } else {
458         fputs(i18nc("@info:shell", "%1: Syntax error, unknown command '%2'\n", qAppName(), command).toLocal8Bit().data(), stderr);
459         return false;
460     }
461     Q_UNREACHABLE();
462 #endif
463 }
464 
slotResult(KJob * job)465 void ClientApp::slotResult(KJob *job)
466 {
467     if (job->error()) {
468 #ifndef KIOCORE_ONLY
469         if (s_interactive) {
470             static_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage();
471         } else
472 #endif
473         {
474             fputs(qPrintable(i18nc("@info:shell", "%1: %2\n", qAppName(), job->errorString())), stderr);
475         }
476     }
477     m_ok = !job->error();
478     if (qApp->topLevelWindows().isEmpty()) {
479         qApp->quit();
480     } else {
481         qApp->setQuitOnLastWindowClosed(true);
482     }
483 }
484 
slotDialogCanceled()485 void ClientApp::slotDialogCanceled()
486 {
487     m_ok = false;
488     qApp->quit();
489 }
490 
slotPrintData(KIO::Job *,const QByteArray & data)491 void ClientApp::slotPrintData(KIO::Job *, const QByteArray &data)
492 {
493     if (!data.isEmpty()) {
494         std::cout.write(data.constData(), data.size());
495     }
496 }
497 
showStatField(const KIO::UDSEntry & entry,uint field,const char * name)498 static void showStatField(const KIO::UDSEntry &entry, uint field, const char *name)
499 {
500     if (!entry.contains(field))
501         return;
502     std::cout << qPrintable(QString::fromLocal8Bit(name).leftJustified(20, ' ')) << "  ";
503 
504     if (field == KIO::UDSEntry::UDS_ACCESS) {
505         std::cout << qPrintable(QString("0%1").arg(entry.numberValue(field), 3, 8, QLatin1Char('0')));
506     } else if (field == KIO::UDSEntry::UDS_FILE_TYPE) {
507         std::cout << qPrintable(QString("0%1").arg((entry.numberValue(field) & S_IFMT), 6, 8, QLatin1Char('0')));
508     } else if (field & KIO::UDSEntry::UDS_STRING) {
509         std::cout << qPrintable(entry.stringValue(field));
510     } else if ((field & KIO::UDSEntry::UDS_TIME) == KIO::UDSEntry::UDS_TIME) {
511         // The previous comparison is necessary because the value
512         // of UDS_TIME is 0x04000000|UDS_NUMBER which is 0x06000000.
513         // So simply testing with (field & KIO::UDSEntry::UDS_TIME)
514         // would be true for both UDS_TIME and UDS_NUMBER fields.
515         // The same would happen if UDS_NUMBER were tested first.
516         const QDateTime dt = QDateTime::fromSecsSinceEpoch(entry.numberValue(field));
517         if (dt.isValid())
518             std::cout << qPrintable(dt.toString(Qt::TextDate));
519     } else if (field & KIO::UDSEntry::UDS_NUMBER) {
520         std::cout << entry.numberValue(field);
521     }
522     std::cout << std::endl;
523 }
524 
slotStatResult(KJob * job)525 void ClientApp::slotStatResult(KJob *job)
526 {
527     if (!job->error()) {
528         KIO::StatJob *statJob = qobject_cast<KIO::StatJob *>(job);
529         Q_ASSERT(statJob != nullptr);
530         const KIO::UDSEntry &result = statJob->statResult();
531 
532         showStatField(result, KIO::UDSEntry::UDS_NAME, "NAME");
533         showStatField(result, KIO::UDSEntry::UDS_DISPLAY_NAME, "DISPLAY_NAME");
534         showStatField(result, KIO::UDSEntry::UDS_COMMENT, "COMMENT");
535         showStatField(result, KIO::UDSEntry::UDS_SIZE, "SIZE");
536         // This is not requested for the StatJob, so should never be seen
537         showStatField(result, KIO::UDSEntry::UDS_RECURSIVE_SIZE, "RECURSIVE_SIZE");
538 
539         showStatField(result, KIO::UDSEntry::UDS_FILE_TYPE, "FILE_TYPE");
540         showStatField(result, KIO::UDSEntry::UDS_USER, "USER");
541         showStatField(result, KIO::UDSEntry::UDS_GROUP, "GROUP");
542         showStatField(result, KIO::UDSEntry::UDS_HIDDEN, "HIDDEN");
543         showStatField(result, KIO::UDSEntry::UDS_DEVICE_ID, "DEVICE_ID");
544         showStatField(result, KIO::UDSEntry::UDS_INODE, "INODE");
545 
546         showStatField(result, KIO::UDSEntry::UDS_LINK_DEST, "LINK_DEST");
547         showStatField(result, KIO::UDSEntry::UDS_URL, "URL");
548         showStatField(result, KIO::UDSEntry::UDS_LOCAL_PATH, "LOCAL_PATH");
549         showStatField(result, KIO::UDSEntry::UDS_TARGET_URL, "TARGET_URL");
550 
551         showStatField(result, KIO::UDSEntry::UDS_MIME_TYPE, "MIME_TYPE");
552         showStatField(result, KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, "GUESSED_MIME_TYPE");
553 
554         showStatField(result, KIO::UDSEntry::UDS_ICON_NAME, "ICON_NAME");
555         showStatField(result, KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES, "ICON_OVERLAY_NAMES");
556 
557         showStatField(result, KIO::UDSEntry::UDS_ACCESS, "ACCESS");
558         showStatField(result, KIO::UDSEntry::UDS_EXTENDED_ACL, "EXTENDED_ACL");
559         showStatField(result, KIO::UDSEntry::UDS_ACL_STRING, "ACL_STRING");
560         showStatField(result, KIO::UDSEntry::UDS_DEFAULT_ACL_STRING, "DEFAULT_ACL_STRING");
561 
562         showStatField(result, KIO::UDSEntry::UDS_MODIFICATION_TIME, "MODIFICATION_TIME");
563         showStatField(result, KIO::UDSEntry::UDS_ACCESS_TIME, "ACCESS_TIME");
564         showStatField(result, KIO::UDSEntry::UDS_CREATION_TIME, "CREATION_TIME");
565 
566         showStatField(result, KIO::UDSEntry::UDS_XML_PROPERTIES, "XML_PROPERTIES");
567         showStatField(result, KIO::UDSEntry::UDS_DISPLAY_TYPE, "DISPLAY_TYPE");
568     }
569 
570     slotResult(job);
571 }
572 
ClientApp()573 ClientApp::ClientApp()
574     : QObject()
575 {
576 }
577