File indexing completed on 2024-04-21 05:10:35

0001 /*
0002     This file is part of Akregator.
0003 
0004     SPDX-FileCopyrightText: 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
0005     SPDX-FileCopyrightText: 2004-2005 Frank Osterfeld <osterfeld@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0008 */
0009 #include "folder.h"
0010 #include "article.h"
0011 #include "articlejobs.h"
0012 
0013 #include "feed.h"
0014 #include "fetchqueue.h"
0015 #include "treenodevisitor.h"
0016 
0017 #include <QList>
0018 #include <qdom.h>
0019 
0020 #include "akregator_debug.h"
0021 #include <QIcon>
0022 
0023 using namespace Akregator;
0024 
0025 // efficient alternative so we don't convert first to a temporary QList then to QList
0026 template<typename T>
0027 static QList<T> hashValuesToVector(const QHash<int, T> &hash)
0028 {
0029     QList<T> result;
0030     result.reserve(hash.count());
0031     for (auto it = hash.cbegin(), end = hash.cend(); it != end; ++it) {
0032         result.append(it.value());
0033     }
0034 
0035     return result;
0036 }
0037 
0038 bool Folder::accept(TreeNodeVisitor *visitor)
0039 {
0040     if (visitor->visitFolder(this)) {
0041         return true;
0042     } else {
0043         return visitor->visitTreeNode(this);
0044     }
0045 }
0046 
0047 Folder *Folder::fromOPML(const QDomElement &e)
0048 {
0049     auto fg = new Folder(e.hasAttribute(QStringLiteral("text")) ? e.attribute(QStringLiteral("text")) : e.attribute(QStringLiteral("title")));
0050     fg->setOpen(e.attribute(QStringLiteral("isOpen")) == QLatin1StringView("true"));
0051     fg->setId(e.attribute(QStringLiteral("id")).toUInt());
0052     return fg;
0053 }
0054 
0055 Folder::Folder(const QString &title)
0056     : TreeNode()
0057 {
0058     setTitle(title);
0059 }
0060 
0061 Folder::~Folder()
0062 {
0063     while (!m_children.isEmpty()) {
0064         // child removes itself from list in its destructor
0065         delete m_children.first();
0066     }
0067     emitSignalDestroyed();
0068 }
0069 
0070 QList<Article> Folder::articles()
0071 {
0072     QList<Article> seq;
0073     const auto f = feeds();
0074     for (Feed *const i : f) {
0075         seq += i->articles();
0076     }
0077     return seq;
0078 }
0079 
0080 QDomElement Folder::toOPML(QDomElement parent, QDomDocument document) const
0081 {
0082     QDomElement el = document.createElement(QStringLiteral("outline"));
0083     el.setAttribute(QStringLiteral("text"), title());
0084     parent.appendChild(el);
0085     el.setAttribute(QStringLiteral("isOpen"), m_open ? QStringLiteral("true") : QStringLiteral("false"));
0086     el.setAttribute(QStringLiteral("id"), QString::number(id()));
0087 
0088     const auto children = m_children;
0089     for (const Akregator::TreeNode *i : children) {
0090         el.appendChild(i->toOPML(el, document));
0091     }
0092     return el;
0093 }
0094 
0095 QList<const TreeNode *> Folder::children() const
0096 {
0097     QList<const TreeNode *> children;
0098     children.reserve(m_children.size());
0099     for (const TreeNode *i : std::as_const(m_children)) {
0100         children.append(i);
0101     }
0102     return children;
0103 }
0104 
0105 QList<TreeNode *> Folder::children()
0106 {
0107     return m_children;
0108 }
0109 
0110 QList<const Akregator::Feed *> Folder::feeds() const
0111 {
0112     QHash<int, const Akregator::Feed *> feedsById;
0113     for (const TreeNode *i : std::as_const(m_children)) {
0114         const auto f = i->feeds();
0115         for (const Akregator::Feed *j : f) {
0116             feedsById.insert(j->id(), j);
0117         }
0118     }
0119 
0120     return hashValuesToVector<const Akregator::Feed *>(feedsById);
0121 }
0122 
0123 QList<Akregator::Feed *> Folder::feeds()
0124 {
0125     QHash<int, Akregator::Feed *> feedsById;
0126     for (TreeNode *i : std::as_const(m_children)) {
0127         const auto f = i->feeds();
0128         for (Akregator::Feed *j : f) {
0129             feedsById.insert(j->id(), j);
0130         }
0131     }
0132 
0133     return hashValuesToVector<Akregator::Feed *>(feedsById);
0134 }
0135 
0136 QList<const Folder *> Folder::folders() const
0137 {
0138     QHash<int, const Folder *> foldersById;
0139     foldersById.insert(id(), this);
0140     for (const TreeNode *i : std::as_const(m_children)) {
0141         const auto f = i->folders();
0142         for (const Folder *j : f) {
0143             foldersById.insert(j->id(), j);
0144         }
0145     }
0146 
0147     return hashValuesToVector<const Folder *>(foldersById);
0148 }
0149 
0150 QList<Folder *> Folder::folders()
0151 {
0152     QHash<int, Folder *> foldersById;
0153     foldersById.insert(id(), this);
0154     for (TreeNode *i : std::as_const(m_children)) {
0155         const auto f = i->folders();
0156         for (Folder *j : f) {
0157             foldersById.insert(j->id(), j);
0158         }
0159     }
0160     return hashValuesToVector<Folder *>(foldersById);
0161 }
0162 
0163 int Folder::indexOf(const TreeNode *node) const
0164 {
0165     return children().indexOf(node);
0166 }
0167 
0168 void Folder::insertChild(TreeNode *node, TreeNode *after)
0169 {
0170     int pos = m_children.indexOf(after);
0171 
0172     if (pos < 0) {
0173         prependChild(node);
0174     } else {
0175         insertChild(pos, node);
0176     }
0177 }
0178 
0179 QIcon Folder::icon() const
0180 {
0181     return QIcon::fromTheme(QStringLiteral("folder"));
0182 }
0183 
0184 void Folder::insertChild(int index, TreeNode *node)
0185 {
0186     //    qCDebug(AKREGATOR_LOG) <<"enter Folder::insertChild(int, node)" << node->title();
0187     if (node) {
0188         if (index >= m_children.size()) {
0189             m_children.append(node);
0190         } else {
0191             m_children.insert(index, node);
0192         }
0193         node->setParent(this);
0194         connectToNode(node);
0195         updateUnreadCount();
0196         Q_EMIT signalChildAdded(node);
0197         articlesModified();
0198         nodeModified();
0199     }
0200     //    qCDebug(AKREGATOR_LOG) <<"leave Folder::insertChild(int, node)" << node->title();
0201 }
0202 
0203 void Folder::appendChild(TreeNode *node)
0204 {
0205     //    qCDebug(AKREGATOR_LOG) <<"enter Folder::appendChild()" << node->title();
0206     if (node) {
0207         m_children.append(node);
0208         node->setParent(this);
0209         connectToNode(node);
0210         updateUnreadCount();
0211         Q_EMIT signalChildAdded(node);
0212         articlesModified();
0213         nodeModified();
0214     }
0215     //    qCDebug(AKREGATOR_LOG) <<"leave Folder::appendChild()" << node->title();
0216 }
0217 
0218 void Folder::prependChild(TreeNode *node)
0219 {
0220     //    qCDebug(AKREGATOR_LOG) <<"enter Folder::prependChild()" << node->title();
0221     if (node) {
0222         m_children.prepend(node);
0223         node->setParent(this);
0224         connectToNode(node);
0225         updateUnreadCount();
0226         Q_EMIT signalChildAdded(node);
0227         articlesModified();
0228         nodeModified();
0229     }
0230     //    qCDebug(AKREGATOR_LOG) <<"leave Folder::prependChild()" << node->title();
0231 }
0232 
0233 void Folder::removeChild(TreeNode *node)
0234 {
0235     if (!node || !m_children.contains(node)) {
0236         return;
0237     }
0238 
0239     Q_EMIT signalAboutToRemoveChild(node);
0240     node->setParent(nullptr);
0241     m_children.removeOne(node);
0242     disconnectFromNode(node);
0243     updateUnreadCount();
0244     Q_EMIT signalChildRemoved(this, node);
0245     articlesModified(); // articles were removed, TODO: add guids to a list
0246     nodeModified();
0247 }
0248 
0249 TreeNode *Folder::firstChild()
0250 {
0251     return m_children.isEmpty() ? nullptr : children().first();
0252 }
0253 
0254 const TreeNode *Folder::firstChild() const
0255 {
0256     return m_children.isEmpty() ? nullptr : children().first();
0257 }
0258 
0259 TreeNode *Folder::lastChild()
0260 {
0261     return m_children.isEmpty() ? nullptr : children().last();
0262 }
0263 
0264 const TreeNode *Folder::lastChild() const
0265 {
0266     return m_children.isEmpty() ? nullptr : children().last();
0267 }
0268 
0269 bool Folder::isOpen() const
0270 {
0271     return m_open;
0272 }
0273 
0274 void Folder::setOpen(bool open)
0275 {
0276     m_open = open;
0277 }
0278 
0279 int Folder::unread() const
0280 {
0281     return m_unread;
0282 }
0283 
0284 int Folder::totalCount() const
0285 {
0286     int total = 0;
0287     const auto f = feeds();
0288     for (const Feed *const i : f) {
0289         total += i->totalCount();
0290     }
0291     return total;
0292 }
0293 
0294 void Folder::updateUnreadCount() const
0295 {
0296     int unread = 0;
0297     const auto f = feeds();
0298     for (const Feed *const i : f) {
0299         unread += i->unread();
0300     }
0301     m_unread = unread;
0302 }
0303 
0304 KJob *Folder::createMarkAsReadJob()
0305 {
0306     auto job = new CompositeJob;
0307     const auto f = feeds();
0308     for (Feed *const i : f) {
0309         job->addSubjob(i->createMarkAsReadJob());
0310     }
0311     return job;
0312 }
0313 
0314 void Folder::slotChildChanged(TreeNode * /*node*/)
0315 {
0316     updateUnreadCount();
0317     nodeModified();
0318 }
0319 
0320 void Folder::slotChildDestroyed(TreeNode *node)
0321 {
0322     m_children.removeAll(node);
0323     updateUnreadCount();
0324     nodeModified();
0325 }
0326 
0327 bool Folder::subtreeContains(const TreeNode *node) const
0328 {
0329     if (node == this) {
0330         return false;
0331     }
0332     const Folder *parent = node ? node->parent() : nullptr;
0333     while (parent) {
0334         if (parent == this) {
0335             return true;
0336         }
0337         parent = parent->parent();
0338     }
0339 
0340     return false;
0341 }
0342 
0343 void Folder::slotAddToFetchQueue(FetchQueue *queue, bool intervalFetchOnly)
0344 {
0345     const auto f = feeds();
0346     for (Feed *const i : f) {
0347         if (i->useCustomFetchInterval()) {
0348             if (i->fetchInterval() != -1) {
0349                 i->slotAddToFetchQueue(queue, intervalFetchOnly);
0350             } else {
0351                 qCDebug(AKREGATOR_LOG) << " excluded feeds: " << i->description();
0352             }
0353         } else {
0354             i->slotAddToFetchQueue(queue, intervalFetchOnly);
0355         }
0356     }
0357 }
0358 
0359 void Folder::doArticleNotification()
0360 {
0361 }
0362 
0363 void Folder::connectToNode(TreeNode *child)
0364 {
0365     connect(child, &TreeNode::signalChanged, this, &Folder::slotChildChanged);
0366     connect(child, &TreeNode::signalDestroyed, this, &Folder::slotChildDestroyed);
0367     connect(child, &TreeNode::signalArticlesAdded, this, &TreeNode::signalArticlesAdded);
0368     connect(child, &TreeNode::signalArticlesRemoved, this, &TreeNode::signalArticlesRemoved);
0369     connect(child, &TreeNode::signalArticlesUpdated, this, &TreeNode::signalArticlesUpdated);
0370 }
0371 
0372 void Folder::disconnectFromNode(TreeNode *child)
0373 {
0374     Q_ASSERT(child);
0375     child->disconnect(this);
0376 }
0377 
0378 TreeNode *Folder::childAt(int pos)
0379 {
0380     if (pos < 0 || pos >= m_children.count()) {
0381         return nullptr;
0382     }
0383     return m_children.at(pos);
0384 }
0385 
0386 const TreeNode *Folder::childAt(int pos) const
0387 {
0388     if (pos < 0 || pos >= m_children.count()) {
0389         return nullptr;
0390     }
0391     return m_children.at(pos);
0392 }
0393 
0394 TreeNode *Folder::next()
0395 {
0396     if (firstChild()) {
0397         return firstChild();
0398     }
0399 
0400     if (nextSibling()) {
0401         return nextSibling();
0402     }
0403 
0404     Folder *p = parent();
0405     while (p) {
0406         if (p->nextSibling()) {
0407             return p->nextSibling();
0408         } else {
0409             p = p->parent();
0410         }
0411     }
0412     return nullptr;
0413 }
0414 
0415 const TreeNode *Folder::next() const
0416 {
0417     if (firstChild()) {
0418         return firstChild();
0419     }
0420 
0421     if (nextSibling()) {
0422         return nextSibling();
0423     }
0424 
0425     const Folder *p = parent();
0426     while (p) {
0427         if (p->nextSibling()) {
0428             return p->nextSibling();
0429         } else {
0430             p = p->parent();
0431         }
0432     }
0433     return nullptr;
0434 }
0435 
0436 QList<const TreeNode *> Folder::namedChildren(const QString &title) const
0437 {
0438     QList<const TreeNode *> nodeList;
0439     const auto childs = children();
0440     for (const TreeNode *child : childs) {
0441         if (child->title() == title) {
0442             nodeList.append(child);
0443         }
0444         const auto fld = dynamic_cast<const Folder *>(child);
0445         if (fld) {
0446             nodeList += fld->namedChildren(title);
0447         }
0448     }
0449     return nodeList;
0450 }
0451 
0452 QList<TreeNode *> Folder::namedChildren(const QString &title)
0453 {
0454     QList<TreeNode *> nodeList;
0455     const auto childs = children();
0456     for (TreeNode *const child : childs) {
0457         if (child->title() == title) {
0458             nodeList.append(child);
0459         }
0460         auto const fld = qobject_cast<Folder *>(child);
0461         if (fld) {
0462             nodeList += fld->namedChildren(title);
0463         }
0464     }
0465     return nodeList;
0466 }
0467 
0468 #include "moc_folder.cpp"