File indexing completed on 2024-05-05 05:44:49

0001 /***************************************************************************
0002  *   Copyright (C) 2005-2009 by Rajko Albrecht                             *
0003  *   ral@alwins-world.de                                                   *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0019  ***************************************************************************/
0020 #include "kiosvn.h"
0021 #include "kdesvn-config.h"
0022 #include "kiolistener.h"
0023 
0024 #include <QFile>
0025 #include <QTemporaryDir>
0026 #include <QTemporaryFile>
0027 
0028 #include "helpers/kdesvn_debug.h"
0029 #include "helpers/sshagent.h"
0030 #include "helpers/stringhelper.h"
0031 #include "kdesvndinterface.h"
0032 #include "kio_macros.h"
0033 #include "settings/kdesvnsettings.h"
0034 #include "svnqt/client_commit_parameter.h"
0035 #include "svnqt/client_parameter.h"
0036 #include "svnqt/client_update_parameter.h"
0037 #include "svnqt/dirent.h"
0038 #include "svnqt/info_entry.h"
0039 #include "svnqt/status.h"
0040 #include "svnqt/svnqttypes.h"
0041 #include "svnqt/targets.h"
0042 #include "svnqt/url.h"
0043 
0044 #include <kio_version.h>
0045 #include <klocalizedstring.h>
0046 #include <math.h>
0047 #include <netdb.h>
0048 #include <netinet/in.h>
0049 #include <stdlib.h>
0050 #include <sys/socket.h>
0051 #include <unistd.h>
0052 
0053 namespace KIO
0054 {
0055 
0056 class KioSvnData
0057 {
0058 public:
0059     explicit KioSvnData(kio_svnProtocol *);
0060     ~KioSvnData();
0061 
0062     void reInitClient();
0063     void resetListener();
0064 
0065     KioListener m_Listener;
0066     bool first_done;
0067     bool dispProgress;
0068     bool dispWritten;
0069     svn::ContextP m_CurrentContext;
0070     svn::ClientP m_Svnclient;
0071     svn::Revision urlToRev(const QUrl &);
0072     QTime m_last;
0073     qulonglong m_Id;
0074 };
0075 
0076 KioSvnData::KioSvnData(kio_svnProtocol *par)
0077     : m_Listener(par)
0078     , first_done(false)
0079     , dispProgress(false)
0080     , dispWritten(false)
0081     , m_Svnclient(svn::Client::getobject(svn::ContextP()))
0082     , m_last(QTime::currentTime())
0083     , m_Id(0) // null is an invalid id
0084 {
0085     reInitClient();
0086 }
0087 
0088 void KioSvnData::reInitClient()
0089 {
0090     if (first_done) {
0091         return;
0092     }
0093     SshAgent ag;
0094     ag.querySshAgent();
0095 
0096     first_done = true;
0097     m_CurrentContext = svn::ContextP(new svn::Context);
0098     m_CurrentContext->setListener(&m_Listener);
0099     m_Svnclient->setContext(m_CurrentContext);
0100 }
0101 
0102 void KioSvnData::resetListener()
0103 {
0104     if (!first_done) {
0105         reInitClient();
0106     }
0107     m_Listener.uncancel();
0108 }
0109 
0110 KioSvnData::~KioSvnData()
0111 {
0112     m_Listener.setCancel(true);
0113     /* wait a little bit */
0114     sleep(1);
0115     m_CurrentContext->setListener(nullptr);
0116 }
0117 
0118 svn::Revision KioSvnData::urlToRev(const QUrl &url)
0119 {
0120     const QList<QPair<QString, QString>> q = QUrlQuery(url).queryItems();
0121 
0122     /* we try to check if it is ssh and try to get a password for it */
0123     const QString proto = url.scheme();
0124 
0125     if (proto.contains(QLatin1String("ssh"))) {
0126         SshAgent ag;
0127         ag.addSshIdentities();
0128     }
0129 
0130     svn::Revision rev = svn::Revision::UNDEFINED;
0131     typedef QPair<QString, QString> myStrPair;
0132     for (const myStrPair &p : q) {
0133         if (p.first == QLatin1String("rev")) {
0134             const QString v = p.second;
0135             svn::Revision tmp;
0136             m_Svnclient->url2Revision(v, rev, tmp);
0137         }
0138     }
0139     return rev;
0140 }
0141 
0142 kio_svnProtocol::kio_svnProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
0143     : WorkerBase("kio_ksvn", pool_socket, app_socket)
0144     , StreamWrittenCb()
0145 {
0146     m_pData = new KioSvnData(this);
0147     m_pData->m_Id = reinterpret_cast<qulonglong>(this);
0148 }
0149 
0150 kio_svnProtocol::~kio_svnProtocol()
0151 {
0152     unregisterFromDaemon();
0153     delete m_pData;
0154 }
0155 
0156 }
0157 
0158 extern "C" {
0159 Q_DECL_EXPORT int kdemain(int argc, char **argv);
0160 }
0161 
0162 int kdemain(int argc, char **argv)
0163 {
0164     QCoreApplication app(argc, argv);
0165     QCoreApplication::setApplicationName(QLatin1String("kio_ksvn"));
0166     qCDebug(KDESVN_LOG) << "*** Starting kio_ksvn " << Qt::endl;
0167 
0168     if (argc != 4) {
0169         qCDebug(KDESVN_LOG) << "Usage: kio_ksvn  protocol domain-socket1 domain-socket2" << Qt::endl;
0170         exit(-1);
0171     }
0172     KIO::kio_svnProtocol worker(argv[2], argv[3]);
0173     worker.dispatchLoop();
0174     qCDebug(KDESVN_LOG) << "*** kio_ksvn Done" << Qt::endl;
0175     return 0;
0176 }
0177 
0178 namespace KIO
0179 {
0180 
0181 void kio_svnProtocol::listSendDirEntry(const svn::DirEntry &direntry)
0182 {
0183     const QDateTime dt(direntry.time().toQDateTime());
0184     KIO::UDSEntry entry;
0185     if (direntry.name().isEmpty()) {
0186         qCDebug(KDESVN_LOG) << "Skipping empty entry!" << Qt::endl;
0187         return;
0188     }
0189     listEntry(createUDSEntry(direntry.name(), direntry.lastAuthor(), direntry.size(), direntry.kind() == svn_node_dir ? true : false, dt));
0190 }
0191 
0192 /*!
0193     \fn kio_svnProtocol::listDir (const QUrl&url)
0194  */
0195 KIO::WorkerResult kio_svnProtocol::listDir(const QUrl &url)
0196 {
0197     qCDebug(KDESVN_LOG) << "kio_svn::listDir(const QUrl& url) : " << url.url() << Qt::endl;
0198     m_pData->resetListener();
0199     svn::DirEntries dlist;
0200     svn::Revision rev = m_pData->urlToRev(url);
0201     if (rev == svn::Revision::UNDEFINED) {
0202         rev = svn::Revision::HEAD;
0203     }
0204 
0205     try {
0206         // we ignoring the result 'cause it is done via kiolistener for a smoother insert of items.
0207         dlist = m_pData->m_Svnclient->list(makeSvnPath(url), rev, rev, svn::DepthImmediates, false);
0208     } catch (const svn::ClientException &e) {
0209         QString ex = e.msg();
0210         qCDebug(KDESVN_LOG) << ex << Qt::endl;
0211         return extraError(KIO::ERR_WORKER_DEFINED, ex);
0212     }
0213     qCDebug(KDESVN_LOG) << "Listing finished" << Qt::endl;
0214     return KIO::WorkerResult::pass();
0215 }
0216 
0217 KIO::WorkerResult kio_svnProtocol::stat(const QUrl &url)
0218 {
0219     qCDebug(KDESVN_LOG) << "kio_svn::stat " << url << Qt::endl;
0220     m_pData->resetListener();
0221     svn::Revision rev = m_pData->urlToRev(url);
0222     if (rev == svn::Revision::UNDEFINED) {
0223         rev = svn::Revision::HEAD;
0224     }
0225     svn::Revision peg = rev;
0226     bool dummy = false;
0227     svn::InfoEntries e;
0228     try {
0229         e = m_pData->m_Svnclient->info(makeSvnPath(url), svn::DepthEmpty, rev, peg);
0230     } catch (const svn::ClientException &e) {
0231         QString ex = e.msg();
0232         qCDebug(KDESVN_LOG) << ex << Qt::endl;
0233         return extraError(KIO::ERR_WORKER_DEFINED, ex);
0234     }
0235 
0236     if (e.isEmpty()) {
0237         dummy = true;
0238     }
0239 
0240     KIO::UDSEntry entry;
0241     if (dummy) {
0242         entry = createUDSEntry(url.fileName(), QString(), 0, true, QDateTime());
0243     } else {
0244         const QDateTime dt(e[0].cmtDate().toQDateTime());
0245         if (e[0].kind() == svn_node_file) {
0246             entry = createUDSEntry(url.fileName(), QString(), 0, false, dt);
0247         } else {
0248             entry = createUDSEntry(url.fileName(), QString(), 0, true, dt);
0249         }
0250     }
0251     statEntry(entry);
0252     return KIO::WorkerResult::pass();
0253 }
0254 
0255 KIO::WorkerResult kio_svnProtocol::get(const QUrl &url)
0256 {
0257     if (m_pData->m_Listener.contextCancel()) {
0258         return KIO::WorkerResult::pass();
0259     }
0260     svn::Revision rev = m_pData->urlToRev(url);
0261     if (rev == svn::Revision::UNDEFINED) {
0262         rev = svn::Revision::HEAD;
0263     }
0264     KioByteStream dstream(this, url.fileName());
0265     try {
0266         const svn::Path path = makeSvnPath(url);
0267         svn::InfoEntries e;
0268         e = m_pData->m_Svnclient->info(path, svn::DepthEmpty, rev, rev);
0269         if (!e.isEmpty()) {
0270             totalSize(e.at(0).size());
0271         }
0272         m_pData->m_Svnclient->cat(dstream, path, rev, rev);
0273     } catch (const svn::ClientException &e) {
0274         QString ex = e.msg();
0275         // dolphin / Konqueror try to get the content without check if it is a folder when listing a folder
0276         // which results in a lot of error messages via kio notify
0277         if (e.apr_err() != SVN_ERR_CLIENT_IS_DIRECTORY) {
0278             return extraError(KIO::ERR_WORKER_DEFINED, QStringLiteral("Subversion error ") + ex);
0279         }
0280         return KIO::WorkerResult::pass();
0281     }
0282     data(QByteArray()); // empty array means we're done sending the data
0283     return KIO::WorkerResult::pass();
0284 }
0285 
0286 KIO::WorkerResult kio_svnProtocol::mkdir(const QUrl &url, int)
0287 {
0288     qCDebug(KDESVN_LOG) << "kio_svn::mkdir " << url << Qt::endl;
0289     m_pData->resetListener();
0290     svn::Revision rev = m_pData->urlToRev(url);
0291     if (rev == svn::Revision::UNDEFINED) {
0292         rev = svn::Revision::HEAD;
0293     }
0294     if (rev != svn::Revision::HEAD) {
0295         return extraError(KIO::ERR_WORKER_DEFINED, i18n("Can only write on HEAD revision."));
0296     }
0297     m_pData->m_CurrentContext->setLogMessage(getDefaultLog());
0298     try {
0299         m_pData->m_Svnclient->mkdir(makeSvnPath(url), getDefaultLog());
0300     } catch (const svn::ClientException &e) {
0301         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0302     }
0303     return KIO::WorkerResult::pass();
0304 }
0305 
0306 KIO::WorkerResult kio_svnProtocol::mkdir(const QList<QUrl> &urls, int)
0307 {
0308     try {
0309         m_pData->m_Svnclient->mkdir(svn::Targets::fromUrlList(urls, svn::Targets::UrlConversion::PreferLocalPath), getDefaultLog());
0310     } catch (const svn::ClientException &e) {
0311         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0312     }
0313     return KIO::WorkerResult::pass();
0314 }
0315 
0316 KIO::WorkerResult kio_svnProtocol::rename(const QUrl &src, const QUrl &target, KIO::JobFlags flags)
0317 {
0318     qCDebug(KDESVN_LOG) << "kio_svn::rename " << src << " to " << target << Qt::endl;
0319     m_pData->resetListener();
0320     Q_UNUSED(flags);
0321     // bool force  = flags&KIO::Overwrite;
0322     m_pData->m_CurrentContext->setLogMessage(getDefaultLog());
0323     try {
0324         m_pData->m_Svnclient->move(svn::CopyParameter(makeSvnPath(src), makeSvnPath(target)));
0325     } catch (const svn::ClientException &e) {
0326         if (e.apr_err() == SVN_ERR_ENTRY_EXISTS) {
0327             return KIO::WorkerResult::fail(KIO::ERR_DIR_ALREADY_EXIST, e.msg());
0328         }
0329         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0330     }
0331     notify(i18n("Renaming %1 to %2 successful", src.toDisplayString(), target.toDisplayString()));
0332     return KIO::WorkerResult::pass();
0333 }
0334 
0335 KIO::WorkerResult kio_svnProtocol::put(const QUrl &url, int permissions, KIO::JobFlags flags)
0336 {
0337     Q_UNUSED(permissions);
0338     m_pData->resetListener();
0339     svn::Revision rev = m_pData->urlToRev(url);
0340     if (rev == svn::Revision::UNDEFINED) {
0341         rev = svn::Revision::HEAD;
0342     }
0343     if (rev != svn::Revision::HEAD) {
0344         return extraError(KIO::ERR_WORKER_DEFINED, i18n("Can only write on HEAD revision."));
0345     }
0346     svn::Revision peg = rev;
0347     svn::InfoEntries e;
0348     bool exists = true;
0349     try {
0350         e = m_pData->m_Svnclient->info(makeSvnPath(url), svn::DepthEmpty, rev, peg);
0351     } catch (const svn::ClientException &e) {
0352         if (e.apr_err() == SVN_ERR_ENTRY_NOT_FOUND || e.apr_err() == SVN_ERR_RA_ILLEGAL_URL) {
0353             exists = false;
0354         } else {
0355             return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0356         }
0357     }
0358     QSharedPointer<QFile> tmpfile;
0359     QSharedPointer<QTemporaryDir> _codir;
0360     if (exists) {
0361         if (flags & KIO::Overwrite) {
0362             if (!supportOverwrite()) {
0363                 return extraError(KIO::ERR_WORKER_DEFINED, i18n("Overwriting existing items is disabled in settings."));
0364             }
0365             _codir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
0366             _codir->setAutoRemove(true);
0367             svn::Path path = makeSvnPath(url);
0368             path.removeLast();
0369             try {
0370                 notify(i18n("Start checking out to temporary folder"));
0371                 m_pData->dispWritten = true;
0372                 registerToDaemon();
0373                 startOp(-1, i18n("Checking out %1", path.native()));
0374                 svn::CheckoutParameter params;
0375                 params.moduleName(path).destination(svn::Path(_codir->path())).revision(rev).peg(peg).depth(svn::DepthFiles);
0376                 m_pData->m_Svnclient->checkout(params);
0377             } catch (const svn::ClientException &e) {
0378                 return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0379             }
0380             m_pData->dispWritten = false;
0381             stopOp(i18n("Temporary checkout done."));
0382             tmpfile = QSharedPointer<QFile>(new QFile(_codir->path() + url.fileName()));
0383             tmpfile->open(QIODevice::ReadWrite | QIODevice::Truncate);
0384         } else {
0385             return extraError(KIO::ERR_FILE_ALREADY_EXIST, i18n("Could not write to existing item."));
0386         }
0387     } else {
0388         QTemporaryFile *_tmpfile = new QTemporaryFile();
0389         if (!_tmpfile->open()) {
0390             delete _tmpfile;
0391             return extraError(KIO::ERR_WORKER_DEFINED, i18n("Could not open temporary file"));
0392         }
0393         tmpfile = QSharedPointer<QFile>(_tmpfile);
0394     }
0395     int result = 0;
0396     QByteArray buffer;
0397     KIO::fileoffset_t processed_size = 0;
0398     do {
0399         dataReq();
0400         result = readData(buffer);
0401         if (result > 0) {
0402             tmpfile->write(buffer);
0403             processed_size += result;
0404             processedSize(processed_size);
0405         }
0406         buffer.clear();
0407     } while (result > 0);
0408 
0409     tmpfile->flush();
0410 
0411     if (result != 0) {
0412         return KIO::WorkerResult::fail(KIO::ERR_ABORTED, i18n("Could not retrieve data for write."));
0413     }
0414 
0415     totalSize(processed_size);
0416     written(0);
0417     m_pData->dispWritten = true;
0418     registerToDaemon();
0419     startOp(processed_size, i18n("Committing %1", makeSvnPath(url).path()));
0420     if (exists) {
0421         svn::CommitParameter commit_parameters;
0422         commit_parameters.targets(svn::Targets(tmpfile->fileName())).message(getDefaultLog()).depth(svn::DepthEmpty).keepLocks(false);
0423         try {
0424             m_pData->m_Svnclient->commit(commit_parameters);
0425         } catch (const svn::ClientException &e) {
0426             m_pData->dispWritten = false;
0427             return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0428         }
0429     } else {
0430         try {
0431             m_pData->m_Svnclient->import(tmpfile->fileName(), svn::Url(makeSvnPath(url)), getDefaultLog(), svn::DepthEmpty, false, false);
0432         } catch (const svn::ClientException &e) {
0433             QString ex = e.msg();
0434             stopOp(ex);
0435             m_pData->dispWritten = false;
0436             return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0437         }
0438     }
0439     m_pData->dispWritten = false;
0440 
0441     stopOp(i18n("Wrote %1 to repository", helpers::ByteToString(processed_size)));
0442     return KIO::WorkerResult::pass();
0443 }
0444 
0445 KIO::WorkerResult kio_svnProtocol::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags)
0446 {
0447     Q_UNUSED(permissions);
0448     Q_UNUSED(flags);
0449     // bool force = flags&KIO::Overwrite;
0450     m_pData->resetListener();
0451     qCDebug(KDESVN_LOG) << "kio_svn::copy " << src << " to " << dest << Qt::endl;
0452     svn::Revision rev = m_pData->urlToRev(src);
0453     if (rev == svn::Revision::UNDEFINED) {
0454         rev = svn::Revision::HEAD;
0455     }
0456     m_pData->dispProgress = true;
0457     m_pData->m_CurrentContext->setLogMessage(getDefaultLog());
0458     try {
0459         m_pData->m_Svnclient->copy(makeSvnPath(src), rev, makeSvnPath(dest));
0460     } catch (const svn::ClientException &e) {
0461         qCDebug(KDESVN_LOG) << "kio_svn::copy aborted" << Qt::endl;
0462         if (e.apr_err() == SVN_ERR_ENTRY_EXISTS) {
0463             return KIO::WorkerResult::fail(KIO::ERR_DIR_ALREADY_EXIST, e.msg());
0464         }
0465         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0466     }
0467     m_pData->dispProgress = false;
0468     qCDebug(KDESVN_LOG) << "kio_svn::copy finished" << Qt::endl;
0469     notify(i18n("Copied %1 to %2", makeSvnPath(src).path(), makeSvnPath(dest).path()));
0470     return KIO::WorkerResult::pass();
0471 }
0472 
0473 KIO::WorkerResult kio_svnProtocol::del(const QUrl &src, bool isfile)
0474 {
0475     Q_UNUSED(isfile);
0476     m_pData->resetListener();
0477     qCDebug(KDESVN_LOG) << "kio_svn::del " << src << Qt::endl;
0478     // m_pData->reInitClient();
0479     svn::Revision rev = m_pData->urlToRev(src);
0480     if (rev == svn::Revision::UNDEFINED) {
0481         rev = svn::Revision::HEAD;
0482     }
0483     if (rev != svn::Revision::HEAD) {
0484         return extraError(KIO::ERR_WORKER_DEFINED, i18n("Can only write on HEAD revision."));
0485     }
0486     m_pData->m_CurrentContext->setLogMessage(getDefaultLog());
0487     try {
0488         svn::Targets target(makeSvnPath(src));
0489         m_pData->m_Svnclient->remove(target, false);
0490     } catch (const svn::ClientException &e) {
0491         qCDebug(KDESVN_LOG) << "kio_svn::del aborted" << Qt::endl;
0492         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0493     }
0494     qCDebug(KDESVN_LOG) << "kio_svn::del finished" << Qt::endl;
0495     return KIO::WorkerResult::pass();
0496 }
0497 
0498 bool kio_svnProtocol::getLogMsg(QString &t)
0499 {
0500     svn::CommitItemList _items;
0501     return m_pData->m_Listener.contextGetLogMessage(t, _items);
0502 }
0503 
0504 bool kio_svnProtocol::checkWc(const svn::Path &localPath) const
0505 {
0506     m_pData->resetListener();
0507     if (!localPath.isSet()) {
0508         return false;
0509     }
0510     svn::Revision peg(svn_opt_revision_unspecified);
0511     svn::Revision rev(svn_opt_revision_unspecified);
0512     svn::InfoEntries e;
0513     try {
0514         e = m_pData->m_Svnclient->info(localPath, svn::DepthEmpty, rev, peg);
0515     } catch (const svn::ClientException &e) {
0516         if (SVN_ERR_WC_NOT_DIRECTORY == e.apr_err()) {
0517             return false;
0518         }
0519         return true;
0520     }
0521     return false;
0522 }
0523 
0524 svn::Path kio_svnProtocol::makeSvnPath(const QUrl &url) const
0525 {
0526     const QString scheme = svn::Url::transformProtokoll(url.scheme());
0527     if (scheme == QLatin1String("file")) {
0528         const svn::Path path(url.toLocalFile());
0529         if (checkWc(path)) {
0530             return path;
0531         }
0532     }
0533     if (url.path().isEmpty()) {
0534         throw svn::ClientException(QLatin1Char('\'') + url.url() + QLatin1String("' is not a valid subversion url"));
0535     }
0536 
0537     QUrl tmpUrl(url);
0538     tmpUrl.setScheme(scheme);
0539     tmpUrl.setQuery(QString()); // svn doesn't know anything about queries (e.g ?rev=X)
0540 
0541     return svn::Path(tmpUrl.toString(QUrl::NormalizePathSegments));
0542 }
0543 
0544 KIO::UDSEntry kio_svnProtocol::createUDSEntry(const QString &filename, const QString &user, long long int size, bool isdir, const QDateTime &mtime)
0545 {
0546     KIO::UDSEntry entry;
0547     entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
0548     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isdir ? S_IFDIR : S_IFREG);
0549     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size);
0550     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, mtime.toSecsSinceEpoch());
0551     entry.fastInsert(KIO::UDSEntry::UDS_USER, user);
0552     return entry;
0553 }
0554 
0555 KIO::WorkerResult kio_svnProtocol::special(const QByteArray &data)
0556 {
0557     qCDebug(KDESVN_LOG) << "kio_svnProtocol::special" << Qt::endl;
0558     QByteArray tmpData(data);
0559     QDataStream stream(&tmpData, QIODevice::ReadOnly);
0560     m_pData->resetListener();
0561     int tmp;
0562     stream >> tmp;
0563     qCDebug(KDESVN_LOG) << "kio_svnProtocol::special " << tmp << Qt::endl;
0564     switch (tmp) {
0565     case SVN_CHECKOUT: {
0566         QUrl repository, wc;
0567         int revnumber;
0568         QString revkind;
0569         stream >> repository;
0570         stream >> wc;
0571         stream >> revnumber;
0572         stream >> revkind;
0573         qCDebug(KDESVN_LOG) << "kio_svnProtocol CHECKOUT from " << repository.url() << " to " << wc.url() << " at " << revnumber << " or " << revkind
0574                             << Qt::endl;
0575         return checkout(repository, wc, revnumber, revkind);
0576     }
0577     case SVN_UPDATE: {
0578         QUrl wc;
0579         int revnumber;
0580         QString revkind;
0581         stream >> wc;
0582         stream >> revnumber;
0583         stream >> revkind;
0584         qCDebug(KDESVN_LOG) << "kio_svnProtocol UPDATE " << wc.url() << " at " << revnumber << " or " << revkind << Qt::endl;
0585         return update(wc, revnumber, revkind);
0586     }
0587     case SVN_COMMIT: {
0588         QList<QUrl> wclist;
0589         while (!stream.atEnd()) {
0590             QUrl tmp;
0591             stream >> tmp;
0592             wclist << tmp;
0593         }
0594         qCDebug(KDESVN_LOG) << "kio_svnProtocol COMMIT" << Qt::endl;
0595         return commit(wclist);
0596     }
0597     case SVN_LOG: {
0598         qCDebug(KDESVN_LOG) << "kio_svnProtocol LOG" << Qt::endl;
0599         int revstart, revend;
0600         QString revkindstart, revkindend;
0601         QList<QUrl> targets;
0602         stream >> revstart;
0603         stream >> revkindstart;
0604         stream >> revend;
0605         stream >> revkindend;
0606         while (!stream.atEnd()) {
0607             QUrl tmp;
0608             stream >> tmp;
0609             targets << tmp;
0610         }
0611         return svnlog(revstart, revkindstart, revend, revkindend, targets);
0612     }
0613     case SVN_IMPORT: {
0614         QUrl wc, repos;
0615         stream >> repos;
0616         stream >> wc;
0617         qCDebug(KDESVN_LOG) << "kio_ksvnProtocol IMPORT" << Qt::endl;
0618         return import(repos, wc);
0619     }
0620     case SVN_ADD: {
0621         QUrl wc;
0622         qCDebug(KDESVN_LOG) << "kio_ksvnProtocol ADD" << Qt::endl;
0623         stream >> wc;
0624         return add(wc);
0625     }
0626     case SVN_DEL: {
0627         QList<QUrl> wclist;
0628         while (!stream.atEnd()) {
0629             QUrl tmp;
0630             stream >> tmp;
0631             wclist << tmp;
0632         }
0633         return wc_delete(wclist);
0634     }
0635     case SVN_REVERT: {
0636         QList<QUrl> wclist;
0637         while (!stream.atEnd()) {
0638             QUrl tmp;
0639             stream >> tmp;
0640             wclist << tmp;
0641         }
0642         qCDebug(KDESVN_LOG) << "kio_svnProtocol REVERT" << Qt::endl;
0643         return revert(wclist);
0644     }
0645     case SVN_STATUS: {
0646         QUrl wc;
0647         bool checkRepos = false;
0648         bool fullRecurse = false;
0649         stream >> wc;
0650         stream >> checkRepos;
0651         stream >> fullRecurse;
0652         qCDebug(KDESVN_LOG) << "kio_svnProtocol STATUS" << Qt::endl;
0653         return status(wc, checkRepos, fullRecurse);
0654     }
0655     case SVN_MKDIR: {
0656         QList<QUrl> list;
0657         stream >> list;
0658         qCDebug(KDESVN_LOG) << "kio_svnProtocol MKDIR" << Qt::endl;
0659         return this->mkdir(list, 0);
0660     }
0661     case SVN_RESOLVE: {
0662         QUrl url;
0663         bool recurse;
0664         stream >> url;
0665         stream >> recurse;
0666         qCDebug(KDESVN_LOG) << "kio_svnProtocol RESOLVE" << Qt::endl;
0667         return wc_resolve(url, recurse);
0668     }
0669     case SVN_SWITCH: {
0670         QUrl wc, url;
0671         bool recurse;
0672         int revnumber;
0673         QString revkind;
0674         stream >> wc;
0675         stream >> url;
0676         stream >> recurse;
0677         stream >> revnumber;
0678         stream >> revkind;
0679         qCDebug(KDESVN_LOG) << "kio_svnProtocol SWITCH" << Qt::endl;
0680         return wc_switch(wc, url, recurse, revnumber, revkind);
0681     }
0682     case SVN_DIFF: {
0683         QUrl url1, url2;
0684         int rev1, rev2;
0685         bool recurse;
0686         QString revkind1, revkind2;
0687         stream >> url1;
0688         stream >> url2;
0689         stream >> rev1;
0690         stream >> revkind1;
0691         stream >> rev2;
0692         stream >> revkind2;
0693         stream >> recurse;
0694         return diff(url1, url2, rev1, revkind1, rev2, revkind2, recurse);
0695     }
0696     default: {
0697         qCDebug(KDESVN_LOG) << "Unknown special" << Qt::endl;
0698         return KIO::WorkerResult::fail();
0699     }
0700     }
0701 }
0702 
0703 KIO::WorkerResult kio_svnProtocol::update(const QUrl &url, int revnumber, const QString &revkind)
0704 {
0705     svn::Revision where(revnumber, revkind);
0706     m_pData->resetListener();
0707     /* update is always local - so make a path instead URI */
0708     svn::Path p(url.path());
0709     try {
0710         svn::Targets pathes(p.path());
0711         // always update externals, too. (third last parameter)
0712         // no unversioned items allowed (second last parameter)
0713         // sticky depth (last parameter)
0714         svn::UpdateParameter _params;
0715         _params.targets(p.path()).revision(revnumber).depth(svn::DepthInfinity).ignore_externals(false).allow_unversioned(false).sticky_depth(true);
0716         m_pData->m_Svnclient->update(_params);
0717     } catch (const svn::ClientException &e) {
0718         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0719     }
0720     return KIO::WorkerResult::pass();
0721 }
0722 
0723 KIO::WorkerResult kio_svnProtocol::status(const QUrl &wc, bool cR, bool rec)
0724 {
0725     svn::StatusEntries dlist;
0726     svn::StatusParameter params(wc.path());
0727     m_pData->resetListener();
0728     try {
0729         dlist = m_pData->m_Svnclient->status(
0730             params.depth(rec ? svn::DepthInfinity : svn::DepthEmpty).all(false).update(cR).noIgnore(false).revision(svn::Revision::UNDEFINED));
0731     } catch (const svn::ClientException &e) {
0732         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0733     }
0734     qCDebug(KDESVN_LOG) << "Status got " << dlist.count() << " entries." << Qt::endl;
0735     for (const svn::StatusPtr &s : qAsConst(dlist)) {
0736         if (!s) {
0737             continue;
0738         }
0739         const QString cntStr(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0740         // QDataStream stream(params, QIODevice::WriteOnly);
0741         setMetaData(cntStr + QLatin1String("path"), s->path());
0742         setMetaData(cntStr + QLatin1String("node"), QString::number(s->nodeStatus()));
0743         setMetaData(cntStr + QLatin1String("text"), QString::number(s->textStatus()));
0744         setMetaData(cntStr + QLatin1String("prop"), QString::number(s->propStatus()));
0745         setMetaData(cntStr + QLatin1String("reptxt"), QString::number(s->reposTextStatus()));
0746         setMetaData(cntStr + QLatin1String("repprop"), QString::number(s->reposPropStatus()));
0747         setMetaData(cntStr + QLatin1String("rev"), QString::number(s->entry().cmtRev()));
0748         m_pData->m_Listener.incCounter();
0749     }
0750     return KIO::WorkerResult::pass();
0751 }
0752 
0753 KIO::WorkerResult kio_svnProtocol::commit(const QList<QUrl> &urls)
0754 {
0755     /// @todo replace with direct call to kdesvn?
0756     QString msg;
0757 
0758     CON_DBUS_VAL(KIO::WorkerResult::fail());
0759 
0760     QDBusReply<QStringList> res = kdesvndInterface.get_logmsg();
0761     if (!res.isValid()) {
0762         qWarning() << "Unexpected reply type";
0763         return KIO::WorkerResult::fail();
0764     }
0765     QStringList lt = res;
0766 
0767     if (lt.count() != 1) {
0768         msg = i18n("Wrong or missing log (may cancel pressed).");
0769         qCDebug(KDESVN_LOG) << msg << Qt::endl;
0770         return KIO::WorkerResult::fail();
0771     }
0772     msg = lt[0];
0773     svn::Revision nnum = svn::Revision::UNDEFINED;
0774     svn::CommitParameter commit_parameters;
0775     commit_parameters.targets(svn::Targets::fromUrlList(urls, svn::Targets::UrlConversion::PreferLocalPath))
0776         .message(msg)
0777         .depth(svn::DepthInfinity)
0778         .keepLocks(false);
0779 
0780     try {
0781         nnum = m_pData->m_Svnclient->commit(commit_parameters);
0782     } catch (const svn::ClientException &e) {
0783         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0784     }
0785     for (const auto &url : urls) {
0786         QString userstring;
0787         if (nnum != svn::Revision::UNDEFINED) {
0788             userstring = i18n("Committed revision %1.", nnum.toString());
0789         } else {
0790             userstring = i18n("Nothing to commit.");
0791         }
0792         const QString num(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0793         const QString zero(QStringLiteral("0"));
0794         setMetaData(num + QLatin1String("path"), url.path());
0795         setMetaData(num + QLatin1String("action"), zero);
0796         setMetaData(num + QLatin1String("kind"), zero);
0797         setMetaData(num + QLatin1String("mime_t"), QString());
0798         setMetaData(num + QLatin1String("content"), zero);
0799         setMetaData(num + QLatin1String("prop"), zero);
0800         setMetaData(num + QLatin1String("rev"), QString::number(nnum));
0801         setMetaData(num + QLatin1String("string"), userstring);
0802         m_pData->m_Listener.incCounter();
0803     }
0804     return KIO::WorkerResult::pass();
0805 }
0806 
0807 KIO::WorkerResult kio_svnProtocol::checkout(const QUrl &src, const QUrl &target, const int rev, const QString &revstring)
0808 {
0809     svn::Revision where(rev, revstring);
0810     try {
0811         svn::CheckoutParameter params;
0812         params.moduleName(makeSvnPath(src)).destination(target.path()).revision(where).peg(svn::Revision::UNDEFINED).depth(svn::DepthInfinity);
0813         m_pData->m_Svnclient->checkout(params);
0814     } catch (const svn::ClientException &e) {
0815         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0816     }
0817     return KIO::WorkerResult::pass();
0818 }
0819 
0820 KIO::WorkerResult kio_svnProtocol::svnlog(int revstart, const QString &revstringstart, int revend, const QString &revstringend, const QList<QUrl> &urls)
0821 {
0822     svn::Revision start(revstart, revstringstart);
0823     svn::Revision end(revend, revstringend);
0824     svn::LogParameter params;
0825     params.revisionRange(start, end).peg(svn::Revision::UNDEFINED).limit(0).discoverChangedPathes(true).strictNodeHistory(true);
0826 
0827     for (const auto &url : urls) {
0828         svn::LogEntriesMap logs;
0829         try {
0830             m_pData->m_Svnclient->log(params.targets(makeSvnPath(url)), logs);
0831         } catch (const svn::ClientException &e) {
0832             return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0833         }
0834         if (logs.isEmpty()) {
0835             const QString num(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0836             setMetaData(num + QStringLiteral("path"), url.path());
0837             setMetaData(num + QStringLiteral("string"), i18n("Empty logs"));
0838             m_pData->m_Listener.incCounter();
0839             continue;
0840         }
0841 
0842         for (const svn::LogEntry &logEntry : qAsConst(logs)) {
0843             const QString num(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0844             setMetaData(num + QStringLiteral("path"), url.path());
0845             setMetaData(num + QStringLiteral("rev"), QString::number(logEntry.revision));
0846             setMetaData(num + QStringLiteral("author"), logEntry.author);
0847             setMetaData(num + QStringLiteral("logmessage"), logEntry.message);
0848             m_pData->m_Listener.incCounter();
0849             for (const svn::LogChangePathEntry &logPathEntry : logEntry.changedPaths) {
0850                 const QString num(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0851                 setMetaData(num + QStringLiteral("rev"), QString::number(logEntry.revision));
0852                 setMetaData(num + QStringLiteral("path"), url.path());
0853                 setMetaData(num + QStringLiteral("loggedpath"), logPathEntry.path);
0854                 setMetaData(num + QStringLiteral("loggedaction"), QString(QLatin1Char(logPathEntry.action)));
0855                 setMetaData(num + QStringLiteral("loggedcopyfrompath"), logPathEntry.copyFromPath);
0856                 setMetaData(num + QStringLiteral("loggedcopyfromrevision"), QString::number(logPathEntry.copyFromRevision));
0857                 m_pData->m_Listener.incCounter();
0858             }
0859         }
0860     }
0861     return KIO::WorkerResult::pass();
0862 }
0863 
0864 KIO::WorkerResult kio_svnProtocol::revert(const QList<QUrl> &urls)
0865 {
0866     try {
0867         m_pData->m_Svnclient->revert(svn::Targets::fromUrlList(urls, svn::Targets::UrlConversion::PreferLocalPath), svn::DepthEmpty);
0868     } catch (const svn::ClientException &e) {
0869         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0870     }
0871     return KIO::WorkerResult::pass();
0872 }
0873 
0874 KIO::WorkerResult kio_svnProtocol::wc_switch(const QUrl &wc, const QUrl &target, bool rec, int rev, const QString &revstring)
0875 {
0876     svn::Revision where(rev, revstring);
0877     svn::Path wc_path(wc.path());
0878     try {
0879         m_pData->m_Svnclient->doSwitch(wc_path, svn::Url(makeSvnPath(target)), where, rec ? svn::DepthInfinity : svn::DepthFiles);
0880     } catch (const svn::ClientException &e) {
0881         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0882     }
0883     return KIO::WorkerResult::pass();
0884 }
0885 
0886 KIO::WorkerResult kio_svnProtocol::diff(const QUrl &uri1, const QUrl &uri2, int rnum1, const QString &rstring1, int rnum2, const QString &rstring2, bool rec)
0887 {
0888     QByteArray ex;
0889     /// @todo read settings for diff (ignore contentype)
0890     try {
0891         const svn::Revision r1(rnum1, rstring1);
0892         const svn::Revision r2(rnum2, rstring2);
0893         const svn::Path u1 = makeSvnPath(uri1);
0894         const svn::Path u2 = makeSvnPath(uri2);
0895         QTemporaryDir tdir;
0896         qCDebug(KDESVN_LOG) << "kio_ksvn::diff : " << u1.path() << " at revision " << r1.toString() << " with " << u2.path() << " at revision " << r2.toString()
0897                             << Qt::endl;
0898         svn::DiffParameter _opts;
0899         // no peg revision required
0900         _opts.path1(u1)
0901             .path2(u2)
0902             .tmpPath(tdir.path())
0903             .rev1(r1)
0904             .rev2(r2)
0905             .ignoreContentType(false)
0906             .extra(svn::StringArray())
0907             .depth(rec ? svn::DepthInfinity : svn::DepthEmpty)
0908             .ignoreAncestry(false)
0909             .noDiffDeleted(false)
0910             .relativeTo((u1.path() == u2.path() ? u1 : svn::Path()))
0911             .changeList(svn::StringArray());
0912 
0913         tdir.setAutoRemove(true);
0914         ex = m_pData->m_Svnclient->diff(_opts);
0915     } catch (const svn::ClientException &e) {
0916         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0917     }
0918     QString out = QString::fromUtf8(ex);
0919     const QString num(QString::number(m_pData->m_Listener.counter()).rightJustified(10, QLatin1Char('0')));
0920     QTextStream stream(&out);
0921     while (!stream.atEnd()) {
0922         setMetaData(num + QStringLiteral("diffresult"), stream.readLine());
0923         m_pData->m_Listener.incCounter();
0924     }
0925     return KIO::WorkerResult::pass();
0926 }
0927 
0928 KIO::WorkerResult kio_svnProtocol::import(const QUrl &repos, const QUrl &wc)
0929 {
0930     try {
0931         const svn::Path target = makeSvnPath(repos);
0932         const QString path = wc.path();
0933         m_pData->m_Svnclient->import(path, svn::Url(target), QString(), svn::DepthInfinity, false, false);
0934     } catch (const svn::ClientException &e) {
0935         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0936     }
0937     return KIO::WorkerResult::pass();
0938 }
0939 
0940 KIO::WorkerResult kio_svnProtocol::add(const QUrl &wc)
0941 {
0942     QString path = wc.path();
0943     try {
0944         /* rec */
0945         m_pData->m_Svnclient->add(svn::Path(path), svn::DepthInfinity);
0946     } catch (const svn::ClientException &e) {
0947         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0948     }
0949     return KIO::WorkerResult::pass();
0950 }
0951 
0952 KIO::WorkerResult kio_svnProtocol::wc_delete(const QList<QUrl> &urls)
0953 {
0954     try {
0955         m_pData->m_Svnclient->remove(svn::Targets::fromUrlList(urls, svn::Targets::UrlConversion::PreferLocalPath), false);
0956     } catch (const svn::ClientException &e) {
0957         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0958     }
0959     return KIO::WorkerResult::pass();
0960 }
0961 
0962 KIO::WorkerResult kio_svnProtocol::wc_resolve(const QUrl &url, bool recurse)
0963 {
0964     try {
0965         svn::Depth depth = recurse ? svn::DepthInfinity : svn::DepthEmpty;
0966         m_pData->m_Svnclient->resolve(url.path(), depth);
0967     } catch (const svn::ClientException &e) {
0968         return extraError(KIO::ERR_WORKER_DEFINED, e.msg());
0969     }
0970     return KIO::WorkerResult::pass();
0971 }
0972 
0973 void kio_svnProtocol::streamWritten(const KIO::filesize_t current)
0974 {
0975     processedSize(current);
0976 }
0977 
0978 void kio_svnProtocol::streamSendMime(const QMimeType &mt)
0979 {
0980     if (mt.isValid()) {
0981         mimeType(mt.name());
0982     }
0983 }
0984 
0985 void kio_svnProtocol::streamPushData(const QByteArray &streamData)
0986 {
0987     data(streamData);
0988 }
0989 
0990 void kio_svnProtocol::contextProgress(long long int current, long long int max)
0991 {
0992     if (max > -1) {
0993         totalSize(KIO::filesize_t(max));
0994     }
0995 
0996     bool to_dbus = false;
0997     if (m_pData->dispProgress || m_pData->dispWritten || max > -1) {
0998         QTime now = QTime::currentTime();
0999         if (m_pData->m_last.msecsTo(now) >= 90) {
1000             if (m_pData->dispProgress) {
1001                 processedSize(KIO::filesize_t(current));
1002             } else {
1003                 written(current);
1004                 to_dbus = useKioprogress();
1005             }
1006             m_pData->m_last = now;
1007         }
1008     }
1009     if (to_dbus) {
1010         CON_DBUS;
1011         if (max > -1) {
1012             kdesvndInterface.maxTransferKioOperation(m_pData->m_Id, max);
1013         }
1014         kdesvndInterface.transferredKioOperation(m_pData->m_Id, current);
1015     }
1016 }
1017 
1018 bool kio_svnProtocol::supportOverwrite() const
1019 {
1020     Kdesvnsettings::self()->load();
1021     return Kdesvnsettings::kio_can_overwrite();
1022 }
1023 
1024 bool kio_svnProtocol::useKioprogress() const
1025 {
1026     Kdesvnsettings::self()->load();
1027     return Kdesvnsettings::display_dockmsg();
1028 }
1029 
1030 /*!
1031     \fn kio_svnProtocol::getDefaultLog()
1032  */
1033 QString kio_svnProtocol::getDefaultLog()
1034 {
1035     QString res;
1036     Kdesvnsettings::self()->load();
1037     if (Kdesvnsettings::kio_use_standard_logmsg()) {
1038         res = Kdesvnsettings::kio_standard_logmsg();
1039     }
1040     return res;
1041 }
1042 
1043 void kio_svnProtocol::notify(const QString &text)
1044 {
1045     if (!useKioprogress()) {
1046         return;
1047     }
1048     CON_DBUS;
1049     kdesvndInterface.notifyKioOperation(text);
1050 }
1051 
1052 KIO::WorkerResult kio_svnProtocol::extraError(int _errid, const QString &text)
1053 {
1054     const KIO::WorkerResult failResult = KIO::WorkerResult::fail(_errid, text);
1055     if (!text.isNull()) {
1056         CON_DBUS_VAL(failResult);
1057         kdesvndInterface.errorKioOperation(text);
1058     }
1059     return failResult;
1060 }
1061 
1062 void kio_svnProtocol::registerToDaemon()
1063 {
1064     if (!useKioprogress()) {
1065         return;
1066     }
1067     CON_DBUS;
1068     kdesvndInterface.registerKioFeedback(m_pData->m_Id);
1069 }
1070 
1071 void kio_svnProtocol::unregisterFromDaemon()
1072 {
1073     CON_DBUS;
1074     kdesvndInterface.unRegisterKioFeedback(m_pData->m_Id);
1075 }
1076 bool kio_svnProtocol::checkKioCancel() const
1077 {
1078     if (!useKioprogress()) {
1079         return false;
1080     }
1081     CON_DBUS_VAL(false);
1082     QDBusReply<bool> res = kdesvndInterface.canceldKioOperation(m_pData->m_Id);
1083     return res.isValid() ? res.value() : false;
1084 }
1085 
1086 void kio_svnProtocol::startOp(qulonglong max, const QString &title)
1087 {
1088     if (!useKioprogress()) {
1089         return;
1090     }
1091     CON_DBUS;
1092     kdesvndInterface.maxTransferKioOperation(m_pData->m_Id, max);
1093     kdesvndInterface.titleKioOperation(m_pData->m_Id, title, title);
1094     kdesvndInterface.setKioStatus(m_pData->m_Id, 1, QString());
1095 }
1096 
1097 void kio_svnProtocol::stopOp(const QString &message)
1098 {
1099     if (!useKioprogress()) {
1100         return;
1101     }
1102     CON_DBUS;
1103     kdesvndInterface.setKioStatus(m_pData->m_Id, 0, message);
1104     unregisterFromDaemon();
1105 }
1106 
1107 } // namespace KIO
1108 
1109 // Pseudo plugin class to embed metadata
1110 class KIOPluginForMetaData : public QObject
1111 {
1112     Q_OBJECT
1113     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.svn" FILE "svn.json")
1114 };
1115 
1116 #include "kiosvn.moc"