diff --git a/interfaces/feedlistmanagementinterface.h b/interfaces/feedlistmanagementinterface.h index ab231024..20bf90b4 100644 --- a/interfaces/feedlistmanagementinterface.h +++ b/interfaces/feedlistmanagementinterface.h @@ -1,54 +1,53 @@ /* This file is part of Akregator. Copyright (C) 2008 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_FEEDLISTMANAGEMENTINTERFACE_H #define AKREGATOR_FEEDLISTMANAGEMENTINTERFACE_H #include "akregatorinterfaces_export.h" class QString; class QStringList; namespace Akregator { class AKREGATORINTERFACES_EXPORT FeedListManagementInterface { public: static FeedListManagementInterface *instance(); static void setInstance(FeedListManagementInterface *); virtual ~FeedListManagementInterface(); virtual QStringList categories() const = 0; virtual QStringList feeds(const QString &catId) const = 0; virtual void addFeed(const QString &url, const QString &catId) = 0; virtual void removeFeed(const QString &url, const QString &catId) = 0; - virtual QString addCategory(const QString &name, const QString &parentId) const = 0; virtual QString getCategoryName(const QString &catId) const = 0; private: static FeedListManagementInterface *m_instance; }; } #endif // AKREGATOR_FEEDLISTMANAGEMENTINTERFACE_H diff --git a/src/feed/feedlist.cpp b/src/feed/feedlist.cpp index ab87ad8e..f64cc4a4 100644 --- a/src/feed/feedlist.cpp +++ b/src/feed/feedlist.cpp @@ -1,640 +1,626 @@ /* This file is part of Akregator. Copyright (C) 2004 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "feedlist.h" #include "storage.h" #include "article.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "treenodevisitor.h" #include "kernel.h" #include "subscriptionlistjobs.h" #include #include "akregator_debug.h" #include #include #include #include #include #include #include using namespace Akregator; class Q_DECL_HIDDEN FeedList::Private { FeedList *const q; public: Private(Backend::Storage *st, FeedList *qq); Akregator::Backend::Storage *storage; QList flatList; Folder *rootNode; QHash idMap; AddNodeVisitor *addNodeVisitor; RemoveNodeVisitor *removeNodeVisitor; QHash > urlMap; mutable int unreadCache; }; class FeedList::AddNodeVisitor : public TreeNodeVisitor { public: AddNodeVisitor(FeedList *list) : m_list(list) { } bool visitFeed(Feed *node) override { m_list->d->idMap.insert(node->id(), node); m_list->d->flatList.append(node); m_list->d->urlMap[node->xmlUrl()].append(node); connect(node, &Feed::fetchStarted, m_list, &FeedList::fetchStarted); connect(node, &Feed::fetched, m_list, &FeedList::fetched); connect(node, &Feed::fetchAborted, m_list, &FeedList::fetchAborted); connect(node, &Feed::fetchError, m_list, &FeedList::fetchError); connect(node, &Feed::fetchDiscovery, m_list, &FeedList::fetchDiscovery); visitTreeNode(node); return true; } void visit2(TreeNode *node, bool preserveID) { m_preserveID = preserveID; TreeNodeVisitor::visit(node); } bool visitTreeNode(TreeNode *node) override { if (!m_preserveID) { node->setId(m_list->generateID()); } m_list->d->idMap[node->id()] = node; m_list->d->flatList.append(node); connect(node, &TreeNode::signalDestroyed, m_list, &FeedList::slotNodeDestroyed); connect(node, &TreeNode::signalChanged, m_list, &FeedList::signalNodeChanged); Q_EMIT m_list->signalNodeAdded(node); return true; } bool visitFolder(Folder *node) override { connect(node, &Folder::signalChildAdded, m_list, &FeedList::slotNodeAdded); connect(node, &Folder::signalAboutToRemoveChild, m_list, &FeedList::signalAboutToRemoveNode); connect(node, &Folder::signalChildRemoved, m_list, &FeedList::slotNodeRemoved); visitTreeNode(node); for (TreeNode *i = node->firstChild(); i && i != node; i = i->next()) { m_list->slotNodeAdded(i); } return true; } private: FeedList *m_list = nullptr; bool m_preserveID = false; }; class FeedList::RemoveNodeVisitor : public TreeNodeVisitor { public: RemoveNodeVisitor(FeedList *list) : m_list(list) { } bool visitFeed(Feed *node) override { visitTreeNode(node); m_list->d->urlMap[node->xmlUrl()].removeAll(node); return true; } bool visitTreeNode(TreeNode *node) override { m_list->d->idMap.remove(node->id()); m_list->d->flatList.removeAll(node); m_list->disconnect(node); return true; } bool visitFolder(Folder *node) override { visitTreeNode(node); return true; } private: FeedList *m_list; }; FeedList::Private::Private(Backend::Storage *st, FeedList *qq) : q(qq) , storage(st) , rootNode(nullptr) , addNodeVisitor(new AddNodeVisitor(q)) , removeNodeVisitor(new RemoveNodeVisitor(q)) , unreadCache(-1) { Q_ASSERT(storage); } FeedList::FeedList(Backend::Storage *storage) : QObject(nullptr) , d(new Private(storage, this)) { Folder *rootNode = new Folder(i18n("All Feeds")); rootNode->setId(1); setRootNode(rootNode); addNode(rootNode, true); } QVector FeedList::feedIds() const { QVector ids; Q_FOREACH (const Feed *const i, feeds()) { ids += i->id(); } return ids; } QVector FeedList::feeds() const { QVector constList; Q_FOREACH (const Feed *const i, d->rootNode->feeds()) { constList.append(i); } return constList; } QVector FeedList::feeds() { return d->rootNode->feeds(); } QVector FeedList::folders() const { QVector constList; Q_FOREACH (const Folder *const i, d->rootNode->folders()) { constList.append(i); } return constList; } QVector FeedList::folders() { return d->rootNode->folders(); } void FeedList::addNode(TreeNode *node, bool preserveID) { d->addNodeVisitor->visit2(node, preserveID); } void FeedList::removeNode(TreeNode *node) { d->removeNodeVisitor->visit(node); } void FeedList::parseChildNodes(QDomNode &node, Folder *parent) { QDomElement e = node.toElement(); // try to convert the node to an element. if (!e.isNull()) { //QString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title"); if (e.hasAttribute(QStringLiteral("xmlUrl")) || e.hasAttribute(QStringLiteral("xmlurl")) || e.hasAttribute(QStringLiteral("xmlURL"))) { Feed *feed = Feed::fromOPML(e, d->storage); if (feed) { if (!d->urlMap[feed->xmlUrl()].contains(feed)) { d->urlMap[feed->xmlUrl()].append(feed); } parent->appendChild(feed); } } else { Folder *fg = Folder::fromOPML(e); parent->appendChild(fg); if (e.hasChildNodes()) { QDomNode child = e.firstChild(); while (!child.isNull()) { parseChildNodes(child, fg); child = child.nextSibling(); } } } } } bool FeedList::readFromOpml(const QDomDocument &doc) { QDomElement root = doc.documentElement(); qCDebug(AKREGATOR_LOG) << "loading OPML feed" << root.tagName().toLower(); qCDebug(AKREGATOR_LOG) << "measuring startup time: START"; QTime spent; spent.start(); if (root.tagName().toLower() != QLatin1String("opml")) { return false; } QDomNode bodyNode = root.firstChild(); while (!bodyNode.isNull() && bodyNode.toElement().tagName().toLower() != QLatin1String("body")) { bodyNode = bodyNode.nextSibling(); } if (bodyNode.isNull()) { qCDebug(AKREGATOR_LOG) << "Failed to acquire body node, markup broken?"; return false; } QDomElement body = bodyNode.toElement(); QDomNode i = body.firstChild(); while (!i.isNull()) { parseChildNodes(i, allFeedsFolder()); i = i.nextSibling(); } for (TreeNode *i = allFeedsFolder()->firstChild(); i && i != allFeedsFolder(); i = i->next()) { if (i->id() == 0) { uint id = generateID(); i->setId(id); d->idMap.insert(id, i); } } qCDebug(AKREGATOR_LOG) << "measuring startup time: STOP," << spent.elapsed() << "ms"; qCDebug(AKREGATOR_LOG) << "Number of articles loaded:" << allFeedsFolder()->totalCount(); return true; } FeedList::~FeedList() { Q_EMIT signalDestroyed(this); setRootNode(nullptr); delete d->addNodeVisitor; delete d->removeNodeVisitor; delete d; } const Feed *FeedList::findByURL(const QString &feedURL) const { if (!d->urlMap.contains(feedURL)) { return nullptr; } const QList &v = d->urlMap[feedURL]; return !v.isEmpty() ? v.front() : nullptr; } Feed *FeedList::findByURL(const QString &feedURL) { if (!d->urlMap.contains(feedURL)) { return nullptr; } const QList &v = d->urlMap[feedURL]; return !v.isEmpty() ? v.front() : nullptr; } const Article FeedList::findArticle(const QString &feedURL, const QString &guid) const { const Feed *feed = findByURL(feedURL); return feed ? feed->findArticle(guid) : Article(); } void FeedList::append(FeedList *list, Folder *parent, TreeNode *after) { if (list == this) { return; } if (!d->flatList.contains(parent)) { parent = allFeedsFolder(); } QList children = list->allFeedsFolder()->children(); QList::ConstIterator end(children.constEnd()); for (QList::ConstIterator it = children.constBegin(); it != end; ++it) { list->allFeedsFolder()->removeChild(*it); parent->insertChild(*it, after); after = *it; } } QDomDocument FeedList::toOpml() const { QDomDocument doc; doc.appendChild(doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\""))); QDomElement root = doc.createElement(QStringLiteral("opml")); root.setAttribute(QStringLiteral("version"), QStringLiteral("1.0")); doc.appendChild(root); QDomElement head = doc.createElement(QStringLiteral("head")); root.appendChild(head); QDomElement ti = doc.createElement(QStringLiteral("text")); head.appendChild(ti); QDomElement body = doc.createElement(QStringLiteral("body")); root.appendChild(body); foreach (const TreeNode *const i, allFeedsFolder()->children()) { body.appendChild(i->toOPML(body, doc)); } return doc; } const TreeNode *FeedList::findByID(int id) const { return d->idMap[id]; } TreeNode *FeedList::findByID(int id) { return d->idMap[id]; } QList FeedList::findByTitle(const QString &title) const { return allFeedsFolder()->namedChildren(title); } QList FeedList::findByTitle(const QString &title) { return allFeedsFolder()->namedChildren(title); } const Folder *FeedList::allFeedsFolder() const { return d->rootNode; } Folder *FeedList::allFeedsFolder() { return d->rootNode; } bool FeedList::isEmpty() const { return d->rootNode->firstChild() == nullptr; } void FeedList::rootNodeChanged() { Q_ASSERT(d->rootNode); const int newUnread = d->rootNode->unread(); if (newUnread == d->unreadCache) { return; } d->unreadCache = newUnread; Q_EMIT unreadCountChanged(newUnread); } void FeedList::setRootNode(Folder *folder) { if (folder == d->rootNode) { return; } delete d->rootNode; d->rootNode = folder; d->unreadCache = -1; if (d->rootNode) { d->rootNode->setOpen(true); connect(d->rootNode, &Folder::signalChildAdded, this, &FeedList::slotNodeAdded); connect(d->rootNode, &Folder::signalAboutToRemoveChild, this, &FeedList::signalAboutToRemoveNode); connect(d->rootNode, &Folder::signalChildRemoved, this, &FeedList::slotNodeRemoved); connect(d->rootNode, &Folder::signalChanged, this, &FeedList::signalNodeChanged); connect(d->rootNode, &Folder::signalChanged, this, &FeedList::rootNodeChanged); } } int FeedList::generateID() const { return KRandom::random(); } void FeedList::slotNodeAdded(TreeNode *node) { if (!node) { return; } Folder *parent = node->parent(); if (!parent || !d->flatList.contains(parent) || d->flatList.contains(node)) { return; } addNode(node, false); } void FeedList::slotNodeDestroyed(TreeNode *node) { if (!node || !d->flatList.contains(node)) { return; } removeNode(node); } void FeedList::slotNodeRemoved(Folder * /*parent*/, TreeNode *node) { if (!node || !d->flatList.contains(node)) { return; } removeNode(node); Q_EMIT signalNodeRemoved(node); } int FeedList::unread() const { if (d->unreadCache == -1) { d->unreadCache = d->rootNode ? d->rootNode->unread() : 0; } return d->unreadCache; } void FeedList::addToFetchQueue(FetchQueue *qu, bool intervalOnly) { if (d->rootNode) { d->rootNode->slotAddToFetchQueue(qu, intervalOnly); } } KJob *FeedList::createMarkAsReadJob() { return d->rootNode ? d->rootNode->createMarkAsReadJob() : nullptr; } FeedListManagementImpl::FeedListManagementImpl(const QSharedPointer &list) : m_feedList(list) { } void FeedListManagementImpl::setFeedList(const QSharedPointer &list) { m_feedList = list; } static QString path_of_folder(const Folder *fol) { Q_ASSERT(fol); QString path; const Folder *i = fol; while (i) { path = QString::number(i->id()) + QLatin1Char('/') + path; i = i->parent(); } return path; } QStringList FeedListManagementImpl::categories() const { if (!m_feedList) { return QStringList(); } QStringList cats; Q_FOREACH (const Folder *const i, m_feedList->folders()) { cats.append(path_of_folder(i)); } return cats; } QStringList FeedListManagementImpl::feeds(const QString &catId) const { if (!m_feedList) { return QStringList(); } uint lastcatid = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); QSet urls; Q_FOREACH (const Feed *const i, m_feedList->feeds()) { if (lastcatid == i->parent()->id()) { urls.insert(i->xmlUrl()); } } return urls.toList(); } void FeedListManagementImpl::addFeed(const QString &url, const QString &catId) { if (!m_feedList) { return; } qCDebug(AKREGATOR_LOG) << "Name:" << url.left(20) << "Cat:" << catId; uint folder_id = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); // Get the folder Folder *m_folder = nullptr; QVector vector = m_feedList->folders(); for (int i = 0; i < vector.size(); ++i) { if (vector.at(i)->id() == folder_id) { m_folder = vector.at(i); i = vector.size(); } } // Create new feed QScopedPointer new_feedlist(new FeedList(Kernel::self()->storage())); Feed *new_feed = new Feed(Kernel::self()->storage()); new_feed->setXmlUrl(url); // new_feed->setTitle(url); new_feedlist->allFeedsFolder()->appendChild(new_feed); // Get last in the folder TreeNode *m_last = m_folder->childAt(m_folder->totalCount()); // Add the feed m_feedList->append(new_feedlist.data(), m_folder, m_last); } void FeedListManagementImpl::removeFeed(const QString &url, const QString &catId) { qCDebug(AKREGATOR_LOG) << "Name:" << url.left(20) << "Cat:" << catId; uint lastcatid = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); Q_FOREACH (const Feed *const i, m_feedList->feeds()) { if (lastcatid == i->parent()->id()) { if (i->xmlUrl().compare(url) == 0) { qCDebug(AKREGATOR_LOG) << "id:" << i->id(); DeleteSubscriptionJob *job = new DeleteSubscriptionJob; job->setSubscriptionId(i->id()); job->start(); } } } } -QString FeedListManagementImpl::addCategory(const QString &name, const QString &parentId) const -{ - Q_UNUSED(parentId) - - if (!m_feedList) { - return QStringLiteral(""); - } - - Folder *m_folder = new Folder(name); - m_feedList->allFeedsFolder()->appendChild(m_folder); - - return QString::number(m_folder->id()); -} - QString FeedListManagementImpl::getCategoryName(const QString &catId) const { QString catname; if (!m_feedList) { return catname; } QStringList list = catId.split(QLatin1Char('/'), QString::SkipEmptyParts); for (int i = 0; i < list.size(); ++i) { int index = list.at(i).toInt(); catname += m_feedList->findByID(index)->title() + QLatin1Char('/'); } return catname; } diff --git a/src/feed/feedlist.h b/src/feed/feedlist.h index eb11f3f3..1652278b 100644 --- a/src/feed/feedlist.h +++ b/src/feed/feedlist.h @@ -1,200 +1,199 @@ /* This file is part of Akregator. Copyright (C) 2004 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_FEEDLIST_H #define AKREGATOR_FEEDLIST_H #include "akregator_export.h" #include "feedlistmanagementinterface.h" #include #include class QDomDocument; class QDomNode; template class QList; template class QHash; class QString; class KJob; namespace Akregator { class Article; class Feed; class FeedList; class FetchQueue; class Folder; class TreeNode; namespace Backend { class Storage; } class AKREGATOR_EXPORT FeedListManagementImpl : public FeedListManagementInterface { public: explicit FeedListManagementImpl(const QSharedPointer &list = QSharedPointer()); void setFeedList(const QSharedPointer &list); QStringList categories() const override; QStringList feeds(const QString &catId) const override; void addFeed(const QString &url, const QString &catId) override; void removeFeed(const QString &url, const QString &catId) override; - QString addCategory(const QString &name, const QString &parentId) const override; QString getCategoryName(const QString &catId) const override; private: QSharedPointer m_feedList; }; /** @class FeedList The model of a feed tree, represents an OPML document. Contains an additional root node "All Feeds" which isn't stored. Note that a node instance must not be in more than one FeedList at a time! When deleting the feed list, all contained nodes are deleted! */ class AKREGATOR_EXPORT FeedList : public QObject { Q_OBJECT public: explicit FeedList(Akregator::Backend::Storage *storage); /** Destructor. Contained nodes are deleted! */ ~FeedList(); const Folder *allFeedsFolder() const; Folder *allFeedsFolder(); bool isEmpty() const; const TreeNode *findByID(int id) const; TreeNode *findByID(int id); QList findByTitle(const QString &title) const; QList findByTitle(const QString &title); /** returns the title of the feed list (as used in the OPML document) */ QString title() const; /** sets the title of the feed list */ void setTitle(const QString &name); /** * returns all feeds in this list */ QVector feeds() const; QVector feeds(); QVector feedIds() const; /** * returns all folders in this list */ QVector folders() const; QVector folders(); /** appends another feed list as sub tree. The root node of @c list is ignored. NOTE: nodes are _moved_ from @c list to this feed list, not copied */ void append(FeedList *list, Folder *parent = nullptr, TreeNode *after = nullptr); /** reads an OPML document and appends the items to this list @param doc the OPML document to parse @return whether parsing was successful or not (TODO: make errors more detailed) */ bool readFromOpml(const QDomDocument &doc); /** exports the feed list as OPML. The root node ("All Feeds") is ignored! */ QDomDocument toOpml() const; /** returns a feed object for a given feed URL. If the feed list does not contain a feed with @c url, NULL is returned. If it contains the same feed multiple times, any of the Feed objects is returned. */ const Feed *findByURL(const QString &feedURL) const; Feed *findByURL(const QString &feedURL); const Article findArticle(const QString &feedURL, const QString &guid) const; int unread() const; void addToFetchQueue(FetchQueue *queue, bool intervalOnly = false); KJob *createMarkAsReadJob(); Q_SIGNALS: void signalDestroyed(Akregator::FeedList *); /** emitted when a node was added to the list */ void signalNodeAdded(Akregator::TreeNode *); /** emitted when a node was removed from the list */ void signalNodeRemoved(Akregator::TreeNode *); void signalAboutToRemoveNode(Akregator::TreeNode *); void signalNodeChanged(Akregator::TreeNode *); /** emitted when fetching started */ void fetchStarted(Akregator::Feed *); /** emitted when feed finished fetching */ void fetched(Akregator::Feed *); /** emitted when a fetch error occurred */ void fetchError(Akregator::Feed *); /** emitted when a feed URL was found by auto discovery */ void fetchDiscovery(Akregator::Feed *); /** emitted when a fetch is aborted */ void fetchAborted(Akregator::Feed *); void unreadCountChanged(int unread); private: void addNode(TreeNode *node, bool preserveID); void removeNode(TreeNode *node); int generateID() const; void setRootNode(Folder *folder); void parseChildNodes(QDomNode &node, Folder *parent); private Q_SLOTS: void slotNodeDestroyed(Akregator::TreeNode *node); void slotNodeAdded(Akregator::TreeNode *node); void slotNodeRemoved(Akregator::Folder *parent, Akregator::TreeNode *node); void rootNodeChanged(); private: friend class AddNodeVisitor; class AddNodeVisitor; friend class RemoveNodeVisitor; class RemoveNodeVisitor; class Private; Private *const d; }; } // namespace Akregator #endif // AKREGATOR_FEEDLIST_H