diff --git a/kioclient/kioclient.cpp b/kioclient/kioclient.cpp index 2725c35..9160843 100644 --- a/kioclient/kioclient.cpp +++ b/kioclient/kioclient.cpp @@ -1,444 +1,455 @@ /* This file is part of the KDE project Copyright (C) 1999-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kioclient.h" #include "kio_version.h" +#include "urlinfo.h" #include #include #include #include #ifndef KIOCORE_ONLY #include #include #include #include #include #endif #include #include #include #include #include #include +#include #include bool ClientApp::m_ok = true; static bool s_interactive = false; static KIO::JobFlags s_jobFlags = KIO::DefaultFlags; static QUrl makeURL(const QString &urlArg) { return QUrl::fromUserInput(urlArg, QDir::currentPath()); } static QList makeUrls(const QStringList& urlArgs) { QList ret; foreach(const QString& url, urlArgs) { ret += makeURL(url); } return ret; } #ifdef KIOCLIENT_AS_KIOCLIENT5 static void usage() { puts(i18n("\nSyntax:\n").toLocal8Bit().constData()); puts(i18n(" kioclient openProperties 'url'\n" " # Opens a properties menu\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient exec 'url' ['mimetype']\n" " # Tries to open the document pointed to by 'url', in the application\n" " # associated with it in KDE. You may omit 'mimetype'.\n" " # In this case the mimetype is determined\n" " # automatically. Of course URL may be the URL of a\n" " # document, or it may be a *.desktop file.\n" " # 'url' can be an executable, too.\n").toLocal8Bit().constData()); puts(i18n(" kioclient move 'src' 'dest'\n" " # Moves the URL 'src' to 'dest'.\n" " # 'src' may be a list of URLs.\n").toLocal8Bit().constData()); puts(i18n(" # 'dest' may be \"trash:/\" to move the files\n" " # to the trash.\n").toLocal8Bit().constData()); puts(i18n(" # the short version kioclient mv\n" " # is also available.\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient download ['src']\n" " # Copies the URL 'src' to a user-specified location'.\n" " # 'src' may be a list of URLs, if not present then\n" " # a URL will be requested.\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient copy 'src' 'dest'\n" " # Copies the URL 'src' to 'dest'.\n" " # 'src' may be a list of URLs.\n").toLocal8Bit().constData()); puts(i18n(" # the short version kioclient cp\n" " # is also available.\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient cat 'url'\n" " # Writes out the contents of 'url' to stdout\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient ls 'url'\n" " # Lists the contents of the directory 'url' to stdout\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient remove 'url'\n" " # Removes the URL\n" " # 'url' may be a list of URLs.\n").toLocal8Bit().constData()); puts(i18n(" # the short version kioclient rm\n" " # is also available.\n\n").toLocal8Bit().constData()); puts(i18n("*** Examples:\n").toLocal8Bit().constData()); puts(i18n(" kioclient exec file:/home/weis/data/test.html\n" " // Opens the file with default binding\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient exec ftp://localhost/\n" " // Opens new window with URL\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient exec file:/root/Desktop/emacs.desktop\n" " // Starts emacs\n\n").toLocal8Bit().constData()); puts(i18n(" kioclient exec .\n" " // Opens the current directory. Very convenient.\n\n").toLocal8Bit().constData()); } #endif int main( int argc, char **argv ) { #ifdef KIOCORE_ONLY QCoreApplication app(argc, argv); #else QApplication app(argc, argv); #endif KLocalizedString::setApplicationDomain("kioclient5"); QString appName = QStringLiteral("kioclient"); QString programName = i18n("KIO Client"); QString description = i18n("Command-line tool for network-transparent operations"); QString version = QLatin1String(PROJECT_VERSION); KAboutData data(appName, programName, version, description, KAboutLicense::LGPL_V2); KAboutData::setApplicationData(data); QCommandLineParser parser; data.setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringLiteral("interactive"), i18n("Use message boxes and other native notifications"))); parser.addOption(QCommandLineOption(QStringLiteral("noninteractive"), i18n("Non-interactive use: no message boxes. If you don't want a " "graphical connection, use --platform offscreen"))); #if !defined(KIOCLIENT_AS_KDEOPEN) parser.addOption(QCommandLineOption(QStringLiteral("overwrite"), i18n("Overwrite destination if it exists (for copy and move)"))); #endif #if defined(KIOCLIENT_AS_KDEOPEN) parser.addPositionalArgument(QStringLiteral("url"), i18n("file or URL"), i18n("urls...")); #elif defined(KIOCLIENT_AS_KDECP5) parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls...")); parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url")); #elif defined(KIOCLIENT_AS_KDEMV5) parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls...")); parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url")); #elif defined(KIOCLIENT_AS_KIOCLIENT5) parser.addOption(QCommandLineOption(QStringLiteral("commands"), i18n("Show available commands"))); parser.addPositionalArgument(QStringLiteral("command"), i18n("Command (see --commands)"), i18n("command")); parser.addPositionalArgument(QStringLiteral("URLs"), i18n("Arguments for command"), i18n("urls...")); #endif // KCmdLineArgs::addTempFileOption(); parser.process(app); data.processCommandLine(&parser); #ifdef KIOCLIENT_AS_KIOCLIENT5 if ( argc == 1 || parser.isSet(QStringLiteral("commands")) ) { puts(parser.helpText().toLocal8Bit().constData()); puts("\n\n"); usage(); return 0; } #endif ClientApp client; return client.doIt(parser) ? 0 /*no error*/ : 1 /*error*/; } static bool krun_has_error = false; void ClientApp::delayedQuit() { #ifndef KIOCORE_ONLY // don't access the KRun instance later, it will be deleted after calling slots if( static_cast< const KRun* >( sender())->hasError()) krun_has_error = true; #endif } static void checkArgumentCount(int count, int min, int max) { if (count < min) { fputs( i18nc("@info:shell", "%1: Syntax error, not enough arguments\n", qAppName()).toLocal8Bit().constData(), stderr ); ::exit(1); } if (max && (count > max)) { fputs( i18nc("@info:shell", "%1: Syntax error, too many arguments\n", qAppName()).toLocal8Bit().constData(), stderr ); ::exit(1); } } #ifndef KIOCORE_ONLY -bool ClientApp::kde_open(const QUrl& url, const QString& mimeType, bool allowExec) +bool ClientApp::kde_open(const QString& url, const QString& mimeType, bool allowExec) { + UrlInfo info(url); + + if(!info.atStart()) { + QUrlQuery q; + q.addQueryItem(QStringLiteral("line"), QString::number(info.line)); + q.addQueryItem(QStringLiteral("column"), QString::number(info.column)); + info.url.setQuery(q); + } + if ( mimeType.isEmpty() ) { - KRun * run = new KRun( url, nullptr ); + KRun * run = new KRun( info.url, nullptr ); run->setRunExecutables(allowExec); #if KIO_VERSION >= QT_VERSION_CHECK(5,55,0) run->setFollowRedirections(false); #endif QObject::connect( run, SIGNAL( finished() ), this, SLOT( delayedQuit() )); QObject::connect( run, SIGNAL( error() ), this, SLOT( delayedQuit() )); qApp->exec(); return !krun_has_error; } else { - return KRun::runUrl(url, mimeType, nullptr, KRun::RunFlags(KRun::RunExecutables)); + return KRun::runUrl(info.url, mimeType, nullptr, KRun::RunFlags(KRun::RunExecutables)); } } #endif bool ClientApp::doCopy( const QStringList& urls ) { QList srcLst(makeUrls(urls)); QUrl dest = srcLst.takeLast(); KIO::Job * job = KIO::copy( srcLst, dest, s_jobFlags ); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect( job, SIGNAL( result( KJob * ) ), this, SLOT( slotResult( KJob * ) ) ); qApp->exec(); return m_ok; } void ClientApp::slotEntries(KIO::Job*, const KIO::UDSEntryList& list) { KIO::UDSEntryList::ConstIterator it=list.begin(); for (; it != list.end(); ++it) { // For each file... QString name = (*it).stringValue( KIO::UDSEntry::UDS_NAME ); std::cout << qPrintable(name) << std::endl; } } bool ClientApp::doList( const QStringList& urls ) { QUrl dir = makeURL(urls.first()); KIO::Job * job = KIO::listDir(dir, KIO::HideProgressInfo); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); connect(job, SIGNAL(result(KJob *)), this, SLOT(slotResult(KJob *))); qApp->exec(); return m_ok; } bool ClientApp::doMove( const QStringList& urls ) { QList srcLst(makeUrls(urls)); QUrl dest = srcLst.takeLast(); KIO::Job * job = KIO::move( srcLst, dest, s_jobFlags ); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect( job, SIGNAL( result( KJob * ) ), this, SLOT( slotResult( KJob * ) ) ); qApp->exec(); return m_ok; } bool ClientApp::doRemove( const QStringList& urls ) { KIO::Job * job = KIO::del( makeUrls(urls), s_jobFlags ); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect( job, SIGNAL( result( KJob * ) ), this, SLOT( slotResult( KJob * ) ) ); qApp->exec(); return m_ok; } bool ClientApp::doIt(const QCommandLineParser& parser) { const int argc = parser.positionalArguments().count(); checkArgumentCount(argc, 1, 0); if (parser.isSet(QStringLiteral("interactive"))) { s_interactive = true; } else { // "noninteractive" is currently the default mode, so we don't check. // The argument still needs to exist for compatibility s_interactive = false; s_jobFlags = KIO::HideProgressInfo; } #if !defined(KIOCLIENT_AS_KDEOPEN) if (parser.isSet(QStringLiteral("overwrite"))) { s_jobFlags |= KIO::Overwrite; } #endif #ifdef KIOCLIENT_AS_KDEOPEN - return kde_open(makeURL(parser.positionalArguments().at(0)), QString(), false); + return kde_open(parser.positionalArguments().at(0), QString(), false); #elif defined(KIOCLIENT_AS_KDECP5) checkArgumentCount(argc, 2, 0); return doCopy(parser.positionalArguments()); #elif defined(KIOCLIENT_AS_KDEMV5) checkArgumentCount(argc, 2, 0); return doMove(parser.positionalArguments()); #else // Normal kioclient mode QString command = parser.positionalArguments().first(); #ifndef KIOCORE_ONLY if ( command == QLatin1String("openProperties") ) { checkArgumentCount(argc, 2, 2); // openProperties QUrl url = makeURL(parser.positionalArguments().last()); KPropertiesDialog * p = new KPropertiesDialog(url, nullptr /*no parent*/ ); QObject::connect( p, SIGNAL( destroyed() ), qApp, SLOT( quit() )); QObject::connect( p, SIGNAL( canceled() ), this, SLOT( slotDialogCanceled() )); p->show(); qApp->exec(); return m_ok; } else #endif if ( command == QLatin1String("cat") ) { checkArgumentCount(argc, 2, 2); // cat QUrl url = makeURL(parser.positionalArguments().last()); KIO::TransferJob* job = KIO::get(url, KIO::NoReload, s_jobFlags); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect(job, SIGNAL(data(KIO::Job*,QByteArray) ), this, SLOT(slotPrintData(KIO::Job*,QByteArray))); connect(job, SIGNAL( result( KJob * ) ), this, SLOT( slotResult( KJob * ) ) ); qApp->exec(); return m_ok; } #ifndef KIOCORE_ONLY else if ( command ==QLatin1String( "exec") ) { checkArgumentCount(argc, 2, 3); - return kde_open( makeURL(parser.positionalArguments()[1]), + return kde_open( parser.positionalArguments()[1], argc == 3 ? parser.positionalArguments().last() : QString(), true ); } #endif else if ( command == QLatin1String("download") ) { checkArgumentCount(argc, 0, 0); QStringList args = parser.positionalArguments(); args.removeFirst(); QList srcLst = makeUrls(args); if (srcLst.isEmpty()) return m_ok; QUrl dsturl = QFileDialog::getSaveFileUrl(nullptr, i18n("Destination where to download the files"), (!srcLst.isEmpty()) ? QUrl() : srcLst.first() ); if (dsturl.isEmpty()) // canceled return m_ok; // AK - really okay? KIO::Job * job = KIO::copy( srcLst, dsturl, s_jobFlags ); if ( !s_interactive ) { job->setUiDelegate( nullptr ); job->setUiDelegateExtension( nullptr ); } connect( job, SIGNAL( result( KJob * ) ), qApp, SLOT( slotResult( KJob * ) ) ); qApp->exec(); return m_ok; } else if ( command == QLatin1String("copy") || command == QLatin1String("cp") ) { checkArgumentCount(argc, 3, 0); // cp QStringList args = parser.positionalArguments(); args.removeFirst(); return doCopy(args); } else if ( command == QLatin1String("move") || command == QLatin1String("mv") ) { checkArgumentCount(argc, 3, 0); // mv QStringList args = parser.positionalArguments(); args.removeFirst(); return doMove(args); } else if ( command == QLatin1String("list") || command == QLatin1String("ls") ) { checkArgumentCount(argc, 2, 2); // ls QStringList args = parser.positionalArguments(); args.removeFirst(); return doList(args); } else if ( command == QLatin1String("remove") || command == QLatin1String("rm") ) { checkArgumentCount(argc, 2, 0); // rm QStringList args = parser.positionalArguments(); args.removeFirst(); return doRemove(args); } else { fputs( i18nc("@info:shell", "%1: Syntax error, unknown command '%2'\n", qAppName(), command).toLocal8Bit().data(), stderr ); return false; } Q_UNREACHABLE(); #endif } void ClientApp::slotResult( KJob * job ) { if (job->error()) { #ifndef KIOCORE_ONLY if (s_interactive) { static_cast(job)->uiDelegate()->showErrorMessage(); } else #endif { qWarning() << job->errorString(); } } m_ok = !job->error(); if (qApp->topLevelWindows().isEmpty()) { qApp->quit(); } else { qApp->setQuitOnLastWindowClosed(true); } } void ClientApp::slotDialogCanceled() { m_ok = false; qApp->quit(); } void ClientApp::slotPrintData(KIO::Job*, const QByteArray &data) { if (!data.isEmpty()) std::cout.write(data.constData(), data.size()); } ClientApp::ClientApp() : QObject() { } diff --git a/kioclient/kioclient.h b/kioclient/kioclient.h index ccd98f5..3148e15 100644 --- a/kioclient/kioclient.h +++ b/kioclient/kioclient.h @@ -1,56 +1,55 @@ /* This file is part of the KDE project Copyright (C) 1999-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __kioclient_h #define __kioclient_h #include #include class QCommandLineParser; -class QUrl; class KJob; namespace KIO { class Job; } class ClientApp : public QObject { Q_OBJECT public: ClientApp(); /** Parse command-line arguments and "do it" */ bool doIt(const QCommandLineParser& parser); private Q_SLOTS: void slotPrintData(KIO::Job *job, const QByteArray &data); void slotEntries(KIO::Job* job, const KIO::UDSEntryList& ); void slotResult( KJob * ); void delayedQuit(); void slotDialogCanceled(); private: - bool kde_open( const QUrl& url, const QString& mimeType, bool allowExec ); + bool kde_open( const QString& url, const QString& mimeType, bool allowExec); bool doCopy( const QStringList& urls ); bool doMove( const QStringList& urls ); bool doList( const QStringList& urls ); bool doRemove( const QStringList& urls ); static bool m_ok; }; #endif diff --git a/kioclient/urlinfo.h b/kioclient/urlinfo.h new file mode 100644 index 0000000..b9635c7 --- /dev/null +++ b/kioclient/urlinfo.h @@ -0,0 +1,108 @@ +/* This file is part of the KDE project + Copyright (C) 2015 Milian Wolff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef URLINFO_H +#define URLINFO_H + +#include +#include +#include +#include + +/** + * Represents a file to be opened, consisting of its URL and the cursor to jump to. + */ +class UrlInfo +{ +public: + /** + * Parses an argument and determines its line number and column and full path + * @param pathOrUrl path passed on e.g. command line to parse into an URL or just an URL + */ + UrlInfo(const QString& pathOrUrl) + : line(0), column(0) + { + /** + * first try: just check if the path is an existing file + */ + if (QFile::exists(pathOrUrl)) { + /** + * create absolute file path, we will e.g. pass this over dbus to other processes + * and then we are done, no cursor can be detected here! + */ + url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(pathOrUrl)); + return; + } + + /** + * ok, the path as is, is no existing file, now, cut away :xx:yy stuff as cursor + * this will make test:50 to test with line 50 + */ + QString pathOrUrl2 = pathOrUrl; + const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(pathOrUrl2); + if (match.isValid()) { + /** + * cut away the line/column specification from the path + */ + pathOrUrl2.chop(match.capturedLength()); + + /** + * set right cursor position + */ + line = match.capturedRef(1).toUInt(); + column = match.capturedRef(2).toUInt(); + } + + /** + * construct url: + * - make relative paths absolute using the current working directory + * - do not prefer local file, to be able to open things like foo.com in browser + */ + url = QUrl::fromUserInput(pathOrUrl2, QDir::currentPath(), QUrl::DefaultResolution); + + /** + * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1 + * => fallback: assume a local file and just convert it to an url + */ + if (!url.isValid()) { + /** + * create absolute file path, we will e.g. pass this over dbus to other processes + */ + url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(pathOrUrl2)); + } + } + + bool atStart() const { + return (line == 0 || line == 1 ) + && (column == 0 || column == 1); + } + + /** + * url computed out of the passed path or URL + */ + QUrl url; + + /** + * initial cursor position, if any found inside the path as line/column specification at the end + */ + unsigned line, column; +}; + +#endif // URLINFO_H