File indexing completed on 2024-05-12 17:16:01

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