File indexing completed on 2024-05-05 09:27:14
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")) == QLatin1String("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"