File indexing completed on 2024-05-12 05:44:27

0001 /***************************************************************************
0002  *   Copyright (C) 2008 by Rajko Albrecht  ral@alwins-world.de             *
0003  *   https://kde.org/applications/development/org.kde.kdesvn               *
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 
0021 #include "svnitemmodel.h"
0022 #include "getinfothread.h"
0023 #include "helpers/kdesvn_debug.h"
0024 #include "settings/kdesvnsettings.h"
0025 #include "svnactions.h"
0026 #include "svnfrontend/maintreewidget.h"
0027 #include "svnitemnode.h"
0028 
0029 #include "svnqt/client.h"
0030 #include "svnqt/path.h"
0031 #include "svnqt/status.h"
0032 #include "svnqt/svnqt_defines.h"
0033 
0034 #include <KDirWatch>
0035 #include <KLocalizedString>
0036 #include <KUrlMimeData>
0037 
0038 #include <QBrush>
0039 #include <QDir>
0040 #include <QFileInfo>
0041 #include <QItemSelectionModel>
0042 #include <QMimeData>
0043 #include <QUuid>
0044 
0045 /*****************************
0046  * Internal data class begin *
0047  *****************************/
0048 class SvnItemModelData
0049 {
0050     SvnItemModelData(const SvnItemModelData &);
0051     SvnItemModelData &operator=(const SvnItemModelData &);
0052 
0053 public:
0054     SvnItemModelData(SvnItemModel *aCb, MainTreeWidget *display)
0055         : m_rootNode(nullptr)
0056         , m_SvnActions(nullptr)
0057         , m_Cb(aCb)
0058         , m_Display(display)
0059         , m_DirWatch(nullptr)
0060     {
0061         m_Uid = QUuid::createUuid().toString();
0062         m_InfoThread = new GetInfoThread(aCb);
0063     }
0064 
0065     ~SvnItemModelData()
0066     {
0067         m_InfoThread->cancelMe();
0068         if (!m_InfoThread->wait(500)) {
0069             m_InfoThread->terminate();
0070         }
0071         delete m_InfoThread;
0072 
0073         delete m_rootNode;
0074         delete m_DirWatch;
0075         m_rootNode = nullptr;
0076     }
0077 
0078     void clear()
0079     {
0080         delete m_rootNode;
0081         delete m_DirWatch;
0082         m_DirWatch = nullptr;
0083         m_rootNode = new SvnItemModelNodeDir(m_SvnActions, m_Display);
0084     }
0085 
0086     SvnItemModelNode *nodeForIndex(const QModelIndex &index) const
0087     {
0088         return index.isValid() ? static_cast<SvnItemModelNode *>(index.internalPointer()) : m_rootNode;
0089     }
0090 
0091     QModelIndex indexForNode(SvnItemModelNode *node, int rowNumber = -1) const
0092     {
0093         if (!node || node == m_rootNode) {
0094             return QModelIndex();
0095         }
0096         return m_Cb->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
0097     }
0098 
0099     bool isRemoteAdded(const svn::Status &_Stat) const
0100     {
0101         return m_SvnActions->isUpdated(_Stat.path()) && _Stat.validReposStatus() && !_Stat.validLocalStatus();
0102     }
0103 
0104     bool MustCreateDir(const svn::Status &_Stat) const
0105     {
0106         // keep in sync with SvnItem::isDir()
0107         if (_Stat.entry().isValid() || isRemoteAdded(_Stat)) {
0108             if (_Stat.entry().kind() != svn_node_unknown) {
0109                 return _Stat.entry().kind() == svn_node_dir;
0110             }
0111         }
0112         /* must be a local file */
0113         QFileInfo f(_Stat.path());
0114         return f.isDir();
0115     }
0116 
0117     void addWatchFile(const QString &aFile)
0118     {
0119         if (m_DirWatch) {
0120             m_DirWatch->addFile(aFile);
0121         }
0122     }
0123     void addWatchDir(const QString &aDir)
0124     {
0125         if (m_DirWatch) {
0126             m_DirWatch->addDir(aDir);
0127         }
0128     }
0129 
0130     SvnItemModelNodeDir *m_rootNode;
0131 
0132     SvnActions *m_SvnActions;
0133     SvnItemModel *m_Cb;
0134     MainTreeWidget *m_Display;
0135     KDirWatch *m_DirWatch;
0136     QString m_Uid;
0137     mutable GetInfoThread *m_InfoThread;
0138 };
0139 /*****************************
0140  * Internal data class end   *
0141  *****************************/
0142 
0143 SvnItemModel::SvnItemModel(MainTreeWidget *display, QObject *parent)
0144     : QAbstractItemModel(parent)
0145     , m_Data(new SvnItemModelData(this, display))
0146 {
0147     m_Data->m_SvnActions = new SvnActions(display);
0148     m_Data->m_rootNode = new SvnItemModelNodeDir(m_Data->m_SvnActions, display);
0149 }
0150 
0151 SvnItemModel::~SvnItemModel()
0152 {
0153 }
0154 
0155 SvnItemModelNode *SvnItemModel::firstRootChild()
0156 {
0157     if (!m_Data->m_rootNode) {
0158         return nullptr;
0159     }
0160     return m_Data->m_rootNode->child(0);
0161 }
0162 
0163 QModelIndex SvnItemModel::firstRootIndex()
0164 {
0165     return m_Data->indexForNode(firstRootChild());
0166 }
0167 
0168 SvnItemModelNode *SvnItemModel::nodeForIndex(const QModelIndex &index)
0169 {
0170     return m_Data->nodeForIndex(index);
0171 }
0172 
0173 void SvnItemModel::setRootNodeStat(const svn::StatusPtr &stat)
0174 {
0175     m_Data->m_rootNode->setStat(stat);
0176 }
0177 
0178 void SvnItemModel::clear()
0179 {
0180     int numRows = m_Data->m_rootNode->childList().count();
0181     if (numRows > 0)
0182         beginRemoveRows(QModelIndex(), 0, numRows - 1);
0183     m_Data->clear();
0184     if (numRows > 0)
0185         endRemoveRows();
0186 }
0187 
0188 void SvnItemModel::beginRemoveRows(const QModelIndex &parent, int first, int last)
0189 {
0190     m_Data->m_InfoThread->clearNodes();
0191     m_Data->m_InfoThread->cancelMe();
0192     if (!m_Data->m_InfoThread->wait(1000)) { }
0193     QAbstractItemModel::beginRemoveRows(parent, first, last);
0194 }
0195 
0196 void SvnItemModel::clearNodeDir(SvnItemModelNodeDir *node)
0197 {
0198     QModelIndex ind = m_Data->indexForNode(node);
0199     if (!node) {
0200         node = m_Data->m_rootNode;
0201     }
0202     int numRows = node->childList().size();
0203     beginRemoveRows(ind, 0, numRows);
0204     node->clear();
0205     endRemoveRows();
0206 }
0207 
0208 bool SvnItemModel::hasChildren(const QModelIndex &parent) const
0209 {
0210     if (!parent.isValid()) {
0211         return true;
0212     }
0213     return static_cast<SvnItemModelNode *>(parent.internalPointer())->NodeHasChilds();
0214 }
0215 
0216 bool SvnItemModel::filterIndex(const QModelIndex &parent, int childRow, svnmodel::ItemTypeFlag showOnly) const
0217 {
0218     SvnItemModelNode *node = m_Data->nodeForIndex(parent);
0219     if (childRow < 0) {
0220         return false;
0221     }
0222     if (!node->NodeIsDir()) {
0223         qCDebug(KDESVN_LOG) << "Parent ist kein Dir" << Qt::endl;
0224         return false;
0225     }
0226     SvnItemModelNode *child = static_cast<SvnItemModelNodeDir *>(node)->child(childRow);
0227     if (child) {
0228         if ((child->isDir() && !showOnly.testFlag(svnmodel::Dir)) || (!child->isDir() && !showOnly.testFlag(svnmodel::File))) {
0229             return true;
0230         }
0231         return ItemDisplay::filterOut(child);
0232     }
0233     return false;
0234 }
0235 
0236 QVariant SvnItemModel::data(const QModelIndex &index, int role) const
0237 {
0238     SvnItemModelNode *node = m_Data->nodeForIndex(index);
0239     switch (role) {
0240     case Qt::DisplayRole:
0241     case SORT_ROLE:
0242         switch (index.column()) {
0243         case Name:
0244             return node->shortName();
0245         case Status:
0246             return node->infoText();
0247         case LastRevision:
0248             return QString::number(node->cmtRev());
0249         case LastAuthor:
0250             return node->cmtAuthor();
0251         case LastDate:
0252             return node->fullDate();
0253         case Locked:
0254             return node->lockOwner();
0255         }
0256         break;
0257     case Qt::DecorationRole:
0258         if (index.column() == 0) {
0259             int size = Kdesvnsettings::listview_icon_size();
0260             bool overlay = Kdesvnsettings::display_overlays();
0261             return node->getPixmap(size, overlay);
0262         }
0263         break;
0264     case Qt::EditRole:
0265         switch (index.column()) {
0266         case Name:
0267             return node->shortName();
0268         }
0269         break;
0270     case Qt::BackgroundRole: {
0271         QColor cl = node->backgroundColor();
0272         if (cl.isValid()) {
0273             return QBrush(cl);
0274         }
0275         break;
0276     }
0277     case Qt::ToolTipRole: {
0278         switch (index.column()) {
0279         case Name:
0280             if (node->hasToolTipText()) {
0281                 return node->getToolTipText();
0282             } else {
0283                 m_Data->m_InfoThread->appendNode(node);
0284                 return QVariant();
0285             }
0286         }
0287         break;
0288     }
0289     }
0290     return QVariant();
0291 }
0292 
0293 QModelIndex SvnItemModel::index(int row, int column, const QModelIndex &parent) const
0294 {
0295     SvnItemModelNode *node = m_Data->nodeForIndex(parent);
0296     if (row < 0) {
0297         return QModelIndex();
0298     }
0299     Q_ASSERT(node->NodeIsDir());
0300     SvnItemModelNode *child = static_cast<SvnItemModelNodeDir *>(node)->child(row);
0301     if (child) {
0302         return createIndex(row, column, child);
0303     } else {
0304         return QModelIndex();
0305     }
0306 }
0307 
0308 QVariant SvnItemModel::headerData(int section, Qt::Orientation orientation, int role) const
0309 {
0310     if (orientation == Qt::Vertical) {
0311         return QVariant();
0312     }
0313     switch (role) {
0314     case Qt::DisplayRole:
0315         switch (section) {
0316         case Name:
0317             return (i18n("Name"));
0318         case Status:
0319             return (i18n("Status"));
0320         case LastRevision:
0321             return (i18n("Last changed Revision"));
0322         case LastAuthor:
0323             return (i18n("Last author"));
0324         case LastDate:
0325             return (i18n("Last change date"));
0326         case Locked:
0327             return (i18n("Locked by"));
0328         }
0329     }
0330     return QVariant();
0331 }
0332 
0333 int SvnItemModel::columnCount(const QModelIndex & /*parent*/) const
0334 {
0335     return ColumnCount;
0336 }
0337 
0338 int SvnItemModel::rowCount(const QModelIndex &parent) const
0339 {
0340     if (!m_Data || !m_Data->m_rootNode) {
0341         return 0;
0342     }
0343 
0344     if (!parent.isValid()) {
0345         return m_Data->m_rootNode->childList().count();
0346     }
0347     SvnItemModelNodeDir *node = static_cast<SvnItemModelNodeDir *>(m_Data->nodeForIndex(parent));
0348     return node->childList().count();
0349 }
0350 
0351 QModelIndex SvnItemModel::parent(const QModelIndex &index) const
0352 {
0353     if (!index.isValid()) {
0354         return QModelIndex();
0355     }
0356     SvnItemModelNode *child = static_cast<SvnItemModelNode *>(index.internalPointer());
0357     return m_Data->indexForNode(child->parent());
0358 }
0359 
0360 SvnActions *SvnItemModel::svnWrapper()
0361 {
0362     return m_Data->m_SvnActions;
0363 }
0364 
0365 int SvnItemModel::checkDirs(const QString &_what, SvnItemModelNode *_parent)
0366 {
0367     QString what = _what;
0368     svn::StatusEntries dlist;
0369     while (what.endsWith(QLatin1Char('/'))) {
0370         what.chop(1);
0371     }
0372     // prevent this from checking unversioned folder. FIXME: what happen when we do open url on a non-working-copy folder??
0373 #ifdef DEBUG_TIMER
0374     QTime _counttime;
0375     _counttime.start();
0376 #endif
0377 
0378     if (!m_Data->m_Display->isWorkingCopy() || (!_parent) || ((_parent) && (_parent->isVersioned()))) {
0379         if (!svnWrapper()->makeStatus(what, dlist, m_Data->m_Display->baseRevision(), false, true, true)) {
0380             return -1;
0381         }
0382     } else {
0383         return checkUnversionedDirs(_parent);
0384     }
0385 #ifdef DEBUG_TIMER
0386     qCDebug(KDESVN_LOG) << "Time for getting entries: " << _counttime.elapsed();
0387     _counttime.restart();
0388 #endif
0389     svn::StatusEntries neweritems;
0390     svnWrapper()->getaddedItems(what, neweritems);
0391     dlist += neweritems;
0392     SvnItemModelNode *node = nullptr;
0393     for (auto it = dlist.begin(); it != dlist.end(); ++it) {
0394         const svn::StatusPtr &sp = *it;
0395         if (sp->path() == what || sp->entry().url().toString() == what) {
0396             if (!_parent) {
0397                 // toplevel item
0398                 beginInsertRows(m_Data->indexForNode(m_Data->m_rootNode), 0, 0);
0399                 if (sp->entry().kind() == svn_node_dir) {
0400                     node = new SvnItemModelNodeDir(m_Data->m_rootNode, svnWrapper(), m_Data->m_Display);
0401                 } else {
0402                     node = new SvnItemModelNode(m_Data->m_rootNode, svnWrapper(), m_Data->m_Display);
0403                 }
0404                 node->setStat(sp);
0405                 m_Data->m_rootNode->m_Children.prepend(node);
0406                 endInsertRows();
0407             }
0408             dlist.erase(it);
0409             break;
0410         }
0411     }
0412     if (_parent) {
0413         node = _parent;
0414     }
0415 #ifdef DEBUG_TIMER
0416     qCDebug(KDESVN_LOG) << "Time finding parent node: " << _counttime.elapsed();
0417 #endif
0418     insertDirs(node, dlist);
0419     return dlist.size();
0420 }
0421 
0422 void SvnItemModel::insertDirs(SvnItemModelNode *_parent, svn::StatusEntries &dlist)
0423 {
0424     if (dlist.isEmpty()) {
0425         return;
0426     }
0427     QModelIndex ind = m_Data->indexForNode(_parent);
0428     SvnItemModelNodeDir *parent;
0429     if (!_parent) {
0430         parent = m_Data->m_rootNode;
0431     } else {
0432         parent = static_cast<SvnItemModelNodeDir *>(_parent);
0433     }
0434     SvnItemModelNode *node = nullptr;
0435     beginInsertRows(ind, parent->childList().count(), parent->childList().count() + dlist.count() - 1);
0436 #ifdef DEBUG_TIMER
0437     QTime _counttime;
0438     _counttime.start();
0439 #endif
0440     for (const svn::StatusPtr &sp : dlist) {
0441 #ifdef DEBUG_TIMER
0442         _counttime.restart();
0443 #endif
0444         if (m_Data->MustCreateDir(*sp)) {
0445             node = new SvnItemModelNodeDir(parent, svnWrapper(), m_Data->m_Display);
0446         } else {
0447             node = new SvnItemModelNode(parent, svnWrapper(), m_Data->m_Display);
0448         }
0449         node->setStat(sp);
0450 #ifdef DEBUG_TIMER
0451         //        qCDebug(KDESVN_LOG)<<"Time creating item: "<<_counttime.elapsed();
0452         _counttime.restart();
0453 #endif
0454         if (m_Data->m_Display->isWorkingCopy() && m_Data->m_DirWatch) {
0455             if (node->isDir()) {
0456                 m_Data->addWatchDir(node->fullName());
0457             } else {
0458                 m_Data->addWatchFile(node->fullName());
0459             }
0460         }
0461 #ifdef DEBUG_TIMER
0462         //        qCDebug(KDESVN_LOG)<<"Time add watch: "<<_counttime.elapsed();
0463         _counttime.restart();
0464 #endif
0465         parent->m_Children.append(node);
0466 #ifdef DEBUG_TIMER
0467 //        qCDebug(KDESVN_LOG)<<"Time append node: "<<_counttime.elapsed();
0468 #endif
0469     }
0470 #ifdef DEBUG_TIMER
0471     _counttime.restart();
0472 #endif
0473     endInsertRows();
0474 #ifdef DEBUG_TIMER
0475 //    qCDebug(KDESVN_LOG)<<"Time append all node: "<<_counttime.elapsed();
0476 #endif
0477 }
0478 
0479 bool SvnItemModel::canFetchMore(const QModelIndex &parent) const
0480 {
0481     if (!parent.isValid()) {
0482         return false;
0483     }
0484     SvnItemModelNode *node = static_cast<SvnItemModelNode *>(parent.internalPointer());
0485     return node->NodeHasChilds() && static_cast<SvnItemModelNodeDir *>(node)->childList().isEmpty();
0486 }
0487 
0488 void SvnItemModel::fetchMore(const QModelIndex &parent)
0489 {
0490     SvnItemModelNode *node = static_cast<SvnItemModelNode *>(parent.internalPointer());
0491     if (!node->isDir()) {
0492         return;
0493     }
0494     if (checkDirs(node->fullName(), node) > 0) {
0495         emit itemsFetched(parent);
0496     }
0497 }
0498 
0499 bool SvnItemModel::insertRows(int, int, const QModelIndex &)
0500 {
0501     return false;
0502 }
0503 
0504 bool SvnItemModel::insertColumns(int, int, const QModelIndex &)
0505 {
0506     return false;
0507 }
0508 
0509 bool SvnItemModel::removeRows(int, int, const QModelIndex &)
0510 {
0511     return false;
0512 }
0513 
0514 bool SvnItemModel::removeColumns(int, int, const QModelIndex &)
0515 {
0516     return false;
0517 }
0518 Qt::ItemFlags SvnItemModel::flags(const QModelIndex &index) const
0519 {
0520     Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0521     if (index.column() == Name) {
0522         f |= /*Qt::ItemIsEditable |*/ Qt::ItemIsDragEnabled;
0523     }
0524     if (!index.isValid()) {
0525         f |= Qt::ItemIsDropEnabled;
0526     } else {
0527         SvnItemModelNode *node = m_Data->nodeForIndex(index);
0528         if (node && node->isDir()) {
0529             f |= Qt::ItemIsDropEnabled;
0530         }
0531     }
0532     return f;
0533 }
0534 
0535 Qt::DropActions SvnItemModel::supportedDropActions() const
0536 {
0537     return Qt::CopyAction | Qt::MoveAction;
0538 }
0539 
0540 QStringList SvnItemModel::mimeTypes() const
0541 {
0542     return QStringList() << QLatin1String("text/uri-list")
0543                          /*                         << QLatin1String( "application/x-kde-cutselection" ) */ // TODO
0544                          //<< QLatin1String( "text/plain" )
0545                          << QLatin1String("application/x-kde-urilist");
0546 }
0547 
0548 bool SvnItemModel::dropUrls(const QList<QUrl> &data, Qt::DropAction action, int row, int column, const QModelIndex &parent, bool intern)
0549 {
0550     Q_UNUSED(row);
0551     Q_UNUSED(column);
0552     if (action == Qt::IgnoreAction) {
0553         return true;
0554     }
0555     if (action == Qt::LinkAction) {
0556         return false;
0557     }
0558     emit urlDropped(data, action, parent, intern);
0559     return true;
0560 }
0561 
0562 QMimeData *SvnItemModel::mimeData(const QModelIndexList &indexes) const
0563 {
0564     QList<QUrl> urls;
0565     for (const QModelIndex &index : indexes) {
0566         if (index.column() == 0) {
0567             urls << m_Data->nodeForIndex(index)->kdeName(m_Data->m_Display->baseRevision());
0568         }
0569     }
0570     QMimeData *mimeData = new QMimeData();
0571     mimeData->setUrls(urls);
0572 
0573     KUrlMimeData::MetaDataMap metaMap;
0574     metaMap[QStringLiteral("kdesvn-source")] = QLatin1Char('t');
0575     metaMap[QStringLiteral("kdesvn-id")] = uniqueIdentifier();
0576     KUrlMimeData::setMetaData(metaMap, mimeData);
0577 
0578     return mimeData;
0579 }
0580 
0581 void SvnItemModel::makeIgnore(const QModelIndex &index)
0582 {
0583     if (!index.isValid()) {
0584         return;
0585     }
0586     SvnItemModelNode *node = m_Data->nodeForIndex(index);
0587     if (!node || node == m_Data->m_rootNode || node->isRealVersioned()) {
0588         return;
0589     }
0590     SvnItemModelNodeDir *pa = node->parent();
0591     if (!pa) {
0592         return;
0593     }
0594     if (m_Data->m_SvnActions->makeIgnoreEntry(node, node->isIgnored())) {
0595         refreshIndex(index);
0596         refreshItem(pa);
0597     }
0598 }
0599 
0600 bool SvnItemModel::refreshItem(SvnItemModelNode *item)
0601 {
0602     if (!item || item == m_Data->m_rootNode) {
0603         return false;
0604     }
0605     try {
0606         item->setStat(m_Data->m_SvnActions->svnclient()->singleStatus(item->fullName(), false, m_Data->m_Display->baseRevision()));
0607     } catch (const svn::ClientException &e) {
0608         item->setStat(svn::StatusPtr(new svn::Status));
0609         return false;
0610     }
0611     return true;
0612 }
0613 
0614 bool SvnItemModel::refreshIndex(const QModelIndex &idx)
0615 {
0616     bool ret = refreshItem(m_Data->nodeForIndex(idx));
0617     emitDataChangedRow(idx);
0618     return ret;
0619 }
0620 
0621 void SvnItemModel::emitDataChangedRow(const QModelIndex &idx)
0622 {
0623     const auto colS(index(idx.row(), 0, idx.parent()));
0624     const auto colE(index(idx.row(), columnCount() - 1, idx.parent()));
0625     emit dataChanged(colS, colE);
0626 }
0627 
0628 SvnItemModelNode *SvnItemModel::findPath(const svn::Path &_p)
0629 {
0630     QString ip = _p.path();
0631     SvnItemModelNode *n1 = firstRootChild();
0632     if (n1) {
0633         if (n1->fullName().length() < ip.length()) {
0634             ip = ip.right(ip.length() - n1->fullName().length());
0635         } else if (n1->fullName() == ip) {
0636             return n1;
0637         }
0638         if (!n1->isDir()) {
0639             return nullptr;
0640         }
0641         const QVector<QStringRef> lp = ip.splitRef(QLatin1Char('/'), QString::SkipEmptyParts);
0642         SvnItemModelNodeDir *d1 = static_cast<SvnItemModelNodeDir *>(n1);
0643         return d1->findPath(lp);
0644     }
0645     return nullptr;
0646 }
0647 
0648 QModelIndex SvnItemModel::findIndex(const svn::Path &_p)
0649 {
0650     return m_Data->indexForNode(findPath(_p));
0651 }
0652 
0653 void SvnItemModel::initDirWatch()
0654 {
0655     delete m_Data->m_DirWatch;
0656     m_Data->m_DirWatch = nullptr;
0657     if (m_Data->m_Display->isWorkingCopy()) {
0658         m_Data->m_DirWatch = new KDirWatch(this);
0659         connect(m_Data->m_DirWatch, &KDirWatch::dirty, this, &SvnItemModel::slotDirty);
0660         connect(m_Data->m_DirWatch, &KDirWatch::created, this, &SvnItemModel::slotCreated);
0661         connect(m_Data->m_DirWatch, &KDirWatch::deleted, this, &SvnItemModel::slotDeleted);
0662         if (m_Data->m_DirWatch) {
0663             m_Data->m_DirWatch->addDir(m_Data->m_Display->baseUri() + QLatin1Char('/'), KDirWatch::WatchDirOnly);
0664             m_Data->m_DirWatch->startScan(true);
0665         }
0666     }
0667 }
0668 
0669 void SvnItemModel::slotCreated(const QString &what)
0670 {
0671     QModelIndex ind = findIndex(what);
0672     if (!ind.isValid()) {
0673         return;
0674     }
0675     SvnItemModelNode *n = static_cast<SvnItemModelNode *>(ind.internalPointer());
0676     if (!n) {
0677         return;
0678     }
0679     if (n->isRealVersioned()) {
0680         refreshIndex(ind);
0681     }
0682 }
0683 
0684 void SvnItemModel::slotDeleted(const QString &what)
0685 {
0686     QModelIndex ind = findIndex(what);
0687     if (!ind.isValid()) {
0688         m_Data->m_DirWatch->removeDir(what);
0689         m_Data->m_DirWatch->removeFile(what);
0690         return;
0691     }
0692     SvnItemModelNode *n = static_cast<SvnItemModelNode *>(ind.internalPointer());
0693     if (!n) {
0694         return;
0695     }
0696     if (!n->isRealVersioned()) {
0697         SvnItemModelNodeDir *p = n->parent();
0698         QModelIndex pi = m_Data->indexForNode(p);
0699         if (!pi.isValid()) {
0700             return;
0701         }
0702         if (ind.row() >= p->m_Children.count()) {
0703             return;
0704         }
0705         beginRemoveRows(pi, ind.row(), ind.row());
0706         p->m_Children.removeAt(ind.row());
0707         endRemoveRows();
0708         if (n->isDir()) {
0709             m_Data->m_DirWatch->removeDir(what);
0710         } else {
0711             m_Data->m_DirWatch->removeFile(what);
0712         }
0713     } else {
0714         refreshIndex(ind);
0715     }
0716 }
0717 
0718 void SvnItemModel::checkAddNewItems(const QModelIndex &ind)
0719 {
0720     SvnItemModelNodeDir *n = static_cast<SvnItemModelNodeDir *>(ind.internalPointer());
0721     QString what = n->fullName();
0722     svn::StatusEntries dlist;
0723     while (what.endsWith(QLatin1Char('/'))) {
0724         what.chop(1);
0725     }
0726     if (!svnWrapper()->makeStatus(what, dlist, m_Data->m_Display->baseRevision(), false, true, true)) {
0727         return;
0728     }
0729     const auto pred = [&](const svn::StatusPtr &sp) -> bool {
0730         return n->contains(sp->path()) || sp->path() == what;
0731     };
0732     dlist.erase(std::remove_if(dlist.begin(), dlist.end(), pred), dlist.end());
0733     if (!dlist.isEmpty()) {
0734         insertDirs(n, dlist);
0735     }
0736 }
0737 
0738 void SvnItemModel::slotDirty(const QString &what)
0739 {
0740     QModelIndex ind = findIndex(what);
0741     if (!ind.isValid()) {
0742         return;
0743     }
0744     SvnItemModelNode *n = static_cast<SvnItemModelNode *>(ind.internalPointer());
0745     if (!n) {
0746         return;
0747     }
0748     if (n->isRealVersioned()) {
0749         if (!n->isDir()) {
0750             refreshIndex(ind);
0751         } else {
0752             checkAddNewItems(ind);
0753         }
0754     } else if (n->isDir()) {
0755         checkUnversionedDirs(n);
0756     }
0757 }
0758 
0759 bool SvnItemModel::checkRootNode()
0760 {
0761     if (!m_Data->m_rootNode) {
0762         return false;
0763     }
0764     try {
0765         m_Data->m_rootNode->setStat(m_Data->m_SvnActions->svnclient()->singleStatus(m_Data->m_Display->baseUri(), false, m_Data->m_Display->baseRevision()));
0766     } catch (const svn::ClientException &e) {
0767         m_Data->m_rootNode->setStat(svn::StatusPtr(new svn::Status));
0768         emit clientException(e.msg());
0769         return false;
0770     }
0771     return true;
0772 }
0773 
0774 bool SvnItemModel::refreshCurrentTree()
0775 {
0776     bool check_created = false;
0777     if (!m_Data->m_rootNode) {
0778         return false;
0779     }
0780     SvnItemModelNodeDir *_start = m_Data->m_rootNode;
0781     if (m_Data->m_Display->isWorkingCopy()) {
0782         if (!m_Data->m_rootNode->m_Children.isEmpty() && m_Data->m_rootNode->m_Children.at(0)->NodeIsDir()) {
0783             _start = static_cast<SvnItemModelNodeDir *>(m_Data->m_rootNode->m_Children.at(0));
0784             refreshItem(_start);
0785         } else {
0786             return false;
0787         }
0788     } else {
0789         if (!checkRootNode()) {
0790             return false;
0791         }
0792         _start = m_Data->m_rootNode;
0793         check_created = true;
0794     }
0795     return refreshDirnode(_start, check_created);
0796 }
0797 
0798 bool SvnItemModel::refreshDirnode(SvnItemModelNodeDir *node, bool check_empty, bool notrec)
0799 {
0800     if (!node) {
0801         if (m_Data->m_Display->isWorkingCopy()) {
0802             return false;
0803         } else {
0804             if (!checkRootNode()) {
0805                 return false;
0806             }
0807             node = m_Data->m_rootNode;
0808         }
0809     }
0810     QString what = (node != m_Data->m_rootNode) ? node->fullName() : m_Data->m_Display->baseUri();
0811 
0812     if (node->m_Children.isEmpty() && !check_empty) {
0813         if (node->fullName() == m_Data->m_Display->baseUri()) {
0814             return refreshItem(node);
0815         }
0816         return true;
0817     }
0818     svn::StatusEntries dlist;
0819 
0820     if (!svnWrapper()->makeStatus(what, dlist, m_Data->m_Display->baseRevision())) {
0821         return false;
0822     }
0823     if (m_Data->m_Display->isWorkingCopy()) {
0824         svn::StatusEntries neweritems;
0825         svnWrapper()->getaddedItems(what, neweritems);
0826         dlist += neweritems;
0827     }
0828 
0829     for (auto it = dlist.begin(); it != dlist.end(); ++it) {
0830         if ((*it)->path() == what) {
0831             dlist.erase(it);
0832             break;
0833         }
0834     }
0835     QModelIndex ind = m_Data->indexForNode(node);
0836     for (int i = 0; i < node->m_Children.size(); ++i) {
0837         const SvnItemModelNode *n = node->m_Children[i];
0838         bool found = false;
0839         for (const auto &entry : qAsConst(dlist)) {
0840             if (entry->path() == n->fullName()) {
0841                 found = true;
0842                 break;
0843             }
0844         }
0845         if (!found) {
0846             beginRemoveRows(ind, i, i);
0847             node->m_Children.removeAt(i);
0848             delete n;
0849             endRemoveRows();
0850             --i;
0851         }
0852     }
0853 
0854     for (auto it = dlist.begin(); it != dlist.end();) {
0855         int index = node->indexOf((*it)->path());
0856         if (index != -1) {
0857             SvnItemModelNode *n = node->m_Children[index];
0858             n->setStat((*it));
0859             if (n->NodeIsDir() != n->isDir()) {
0860                 beginRemoveRows(ind, index, index);
0861                 node->m_Children.removeAt(index);
0862                 delete n;
0863                 endRemoveRows();
0864             } else {
0865                 it = dlist.erase(it);
0866             }
0867         } else {
0868             ++it;
0869         }
0870     }
0871 
0872     // make sure that we do not read in the whole tree when just refreshing the current tree.
0873     if (!node->m_Children.isEmpty() && !notrec) {
0874         for (auto &child : node->m_Children) {
0875             if (child->NodeIsDir()) {
0876                 // both other parameters makes no sense at this point - defaults
0877                 refreshDirnode(static_cast<SvnItemModelNodeDir *>(child), false, false);
0878             }
0879         }
0880     }
0881     // after so we don't recurse about it.
0882     insertDirs(node, dlist);
0883     if (!dlist.isEmpty()) {
0884         emit itemsFetched(m_Data->indexForNode(node));
0885     }
0886     return true;
0887 }
0888 
0889 int SvnItemModel::checkUnversionedDirs(SvnItemModelNode *_parent)
0890 {
0891     if (!_parent || !_parent->isDir()) {
0892         // no toplevel unversioned - kdesvn is not a filemanager
0893         return 0;
0894     }
0895     QDir d(_parent->fullName());
0896     d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0897     QFileInfoList list = d.entryInfoList();
0898     if (list.isEmpty()) {
0899         return 0;
0900     }
0901     svn::StatusEntries dlist;
0902     SvnItemModelNodeDir *n = static_cast<SvnItemModelNodeDir *>(_parent);
0903     for (const auto &fi : list) {
0904         if (!(n->contains(fi.absoluteFilePath()) || fi.absoluteFilePath() == n->fullName())) {
0905             svn::StatusPtr stat(new svn::Status(fi.absoluteFilePath()));
0906             dlist.append(stat);
0907         }
0908     }
0909     if (!dlist.isEmpty()) {
0910         insertDirs(_parent, dlist);
0911     }
0912     return dlist.size();
0913 }
0914 
0915 const QString &SvnItemModel::uniqueIdentifier() const
0916 {
0917     return m_Data->m_Uid;
0918 }
0919 
0920 void SvnItemModel::slotNotifyMessage(const QString &msg)
0921 {
0922     qCDebug(KDESVN_LOG) << msg;
0923 }
0924 
0925 #include "moc_svnitemmodel.cpp"