diff --git a/interfaces/article.h b/interfaces/article.h --- a/interfaces/article.h +++ b/interfaces/article.h @@ -49,6 +49,7 @@ namespace Akregator { namespace Backend { +class SmallArticle; class FeedStorage; } @@ -67,11 +68,16 @@ }; Article(); - /** creates am article object for an existing article. - The constructor accesses the archive to load it's data + /** creates an article object for an existing article. + The constructor accesses the archive to load its data */ Article(const QString &guid, Feed *feed, Backend::FeedStorage *archive = nullptr); + /** creates an article object for an existing article. + The constructor uses the cache object. + */ + Article(const Backend::SmallArticle &cachedArticle, Feed *feed, Backend::FeedStorage *archive); + /** creates an article object from a parsed librss Article the article is added to the archive if not yet stored, or updated if stored but modified */ diff --git a/interfaces/feedstorage.h b/interfaces/feedstorage.h --- a/interfaces/feedstorage.h +++ b/interfaces/feedstorage.h @@ -26,16 +26,30 @@ #include #include +#include -class QString; class QStringList; -class QDateTime; namespace Akregator { namespace Backend { class Storage; +struct SmallArticle { + QString guid; + int hash; + int status; + QString title; + QDateTime pubDate; + + SmallArticle(const QString &guid, int hash, int status, const QString &title, QDateTime pubDate) + : guid(guid), hash(hash), status(status), title(title), pubDate(pubDate) { + } + + SmallArticle() { + } +}; + class FeedStorage : public QObject //krazy:exclude=qobject { public: @@ -48,6 +62,7 @@ /** returns the guids of all articles in this storage. */ virtual QStringList articles() const = 0; + virtual QVector articlesForCache() const = 0; virtual void article(const QString &guid, uint &hash, QString &title, int &status, QDateTime &pubDate) const = 0; virtual bool contains(const QString &guid) const = 0; diff --git a/plugins/mk4storage/feedstoragemk4impl.h b/plugins/mk4storage/feedstoragemk4impl.h --- a/plugins/mk4storage/feedstoragemk4impl.h +++ b/plugins/mk4storage/feedstoragemk4impl.h @@ -40,6 +40,7 @@ QDateTime lastFetch() const override; void setLastFetch(const QDateTime &lastFetch) override; + QVector articlesForCache() const override; QStringList articles() const override; void article(const QString &guid, uint &hash, QString &title, int &status, QDateTime &pubDate) const override; diff --git a/plugins/mk4storage/feedstoragemk4impl.cpp b/plugins/mk4storage/feedstoragemk4impl.cpp --- a/plugins/mk4storage/feedstoragemk4impl.cpp +++ b/plugins/mk4storage/feedstoragemk4impl.cpp @@ -201,6 +201,22 @@ return list; } +QVector FeedStorageMK4Impl::articlesForCache() const +{ + QVector result; + int size = d->archiveView.GetSize(); + result.reserve(size); + for (int i = 0; i < size; ++i) { // fill with guids + auto getAt = d->archiveView.GetAt(i); + result.push_back(SmallArticle(QString::fromLatin1(d->pguid(getAt)), + d->phash(getAt), + d->pstatus(getAt), + QString::fromUtf8(d->ptitle(getAt)), + QDateTime::fromTime_t(d->ppubDate(getAt)))); + } + return result; +} + void FeedStorageMK4Impl::addEntry(const QString &guid) { c4_Row row; diff --git a/src/article.cpp b/src/article.cpp --- a/src/article.cpp +++ b/src/article.cpp @@ -80,7 +80,7 @@ Private(); Private(const QString &guid, Feed *feed, Backend::FeedStorage *archive); Private(const ItemPtr &article, Feed *feed, Backend::FeedStorage *archive); - + Private(const Backend::SmallArticle &cachedArticle, Feed *feed, Backend::FeedStorage *archive); /** The status of the article is stored in an int, the bits having the following meaning: @@ -173,6 +173,17 @@ archive->article(guid, hash, title, status, pubDate); } +Article::Private::Private(const Backend::SmallArticle &cachedArticle, Feed *feed_, Backend::FeedStorage *archive_) + : feed(feed_) + , guid(cachedArticle.guid) + , archive(archive_) + , status(cachedArticle.status) + , hash(cachedArticle.hash) + , pubDate(cachedArticle.pubDate) + , title(cachedArticle.title) +{ +} + Article::Private::Private(const ItemPtr &article, Feed *feed_, Backend::FeedStorage *archive_) : feed(feed_) , archive(archive_) @@ -221,7 +232,6 @@ archive->setEnclosure(guid, encs[0]->url(), encs[0]->type(), encs[0]->length()); } } else { - // always update comments count, as it's not used for hash calculation if (hash != archive->hash(guid)) { //article is in archive, was it modified? // if yes, update pubDate = archive->pubDate(guid); @@ -252,6 +262,12 @@ { } +Article::Article(const Backend::SmallArticle &cachedArticle, Feed *feed, Backend::FeedStorage *archive) + : d() +{ + d = new Private(cachedArticle, feed, archive); +} + Article::Article(const QString &guid, Feed *feed, Backend::FeedStorage *archive) : d() { if (!archive) diff --git a/src/dummystorage/feedstoragedummyimpl.h b/src/dummystorage/feedstoragedummyimpl.h --- a/src/dummystorage/feedstoragedummyimpl.h +++ b/src/dummystorage/feedstoragedummyimpl.h @@ -28,6 +28,7 @@ #include namespace Akregator { namespace Backend { + class StorageDummyImpl; class FeedStorageDummyImpl : public FeedStorage { @@ -41,6 +42,7 @@ QDateTime lastFetch() const override; void setLastFetch(const QDateTime &lastFetch) override; + QVector articlesForCache() const override; QStringList articles() const override; void article(const QString &guid, uint &hash, QString &title, int &status, QDateTime &pubDate) const override; diff --git a/src/dummystorage/feedstoragedummyimpl.cpp b/src/dummystorage/feedstoragedummyimpl.cpp --- a/src/dummystorage/feedstoragedummyimpl.cpp +++ b/src/dummystorage/feedstoragedummyimpl.cpp @@ -118,6 +118,21 @@ d->mainStorage->setLastFetchFor(d->url, lastFetch); } +QVector FeedStorageDummyImpl::articlesForCache() const +{ + QVector result; + result.reserve(d->entries.size()); + for (QString &guid: d->entries.keys()) { + auto &entry = d->entries[guid]; + result.push_back(SmallArticle(guid, + entry.hash, + entry.status, + entry.title, + entry.pubDate)); + } + return result; +} + QStringList FeedStorageDummyImpl::articles() const { return QStringList(d->entries.keys()); diff --git a/src/feed/feed.h b/src/feed/feed.h --- a/src/feed/feed.h +++ b/src/feed/feed.h @@ -217,6 +217,8 @@ KJob *createMarkAsReadJob() override; + void clearCache() override; + public Q_SLOTS: /** starts fetching */ void fetch(bool followDiscovery = false); diff --git a/src/feed/feed.cpp b/src/feed/feed.cpp --- a/src/feed/feed.cpp +++ b/src/feed/feed.cpp @@ -241,6 +241,15 @@ return d->storage; } +void Akregator::Feed::clearCache() +{ + if (d->articlesLoaded) { + d->articles.clear(); + d->articlesLoaded = false; + d->deletedArticles.clear(); + } +} + void Akregator::Feed::loadArticles() { if (d->articlesLoaded) { @@ -251,9 +260,9 @@ d->archive = d->storage->archiveFor(xmlUrl()); } - QStringList list = d->archive->articles(); - for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { - Article mya(*it, this, d->archive); + QVector list = d->archive->articlesForCache(); + for (Backend::SmallArticle &article: list) { + Article mya(article, this, d->archive); d->articles[mya.guid()] = mya; if (mya.isDeleted()) { d->deletedArticles.append(mya); @@ -549,19 +558,66 @@ void Akregator::Feed::appendArticles(const Syndication::FeedPtr &feed) { - d->setTotalCountDirty(); - bool changed = false; - const bool notify = useNotification() || Settings::useNotifications(); - QList items = feed->items(); QList::ConstIterator it = items.constBegin(); QList::ConstIterator en = items.constEnd(); + bool changed = false; + const bool notify = useNotification() || Settings::useNotifications(); + + if (!d->articlesLoaded) { + // If we did not load the articles, just ask the storage if articles exist. + // This way, we may skip loading the feed uselessly. + for (; it != en; ++it) { + if (!d->archive->contains((*it)->id())) { + Article mya(*it, this); + appendArticle(mya); + d->addedArticlesNotify.append(mya); + + if (!mya.isDeleted() && !markImmediatelyAsRead()) { + mya.setStatus(New); + } else { + mya.setStatus(Read); + } + if (notify) { + NotificationManager::self()->slotNotifyArticle(mya); + } + changed = true; + } else { + // if the article's guid is no hash but an ID, we have to check if the article was updated. That's done by comparing the hash values. + Article old((*it)->id(), this, d->archive); + Article mya(*it, this); + if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted()) { + mya.setKeep(old.keep()); + int oldstatus = old.status(); + old.setStatus(Read); + + d->articles.remove(old.guid()); + appendArticle(mya); + + mya.setStatus(oldstatus); + + d->updatedArticlesNotify.append(mya); + changed = true; + } + } + } + if (!changed) { + return; + } else { + d->setTotalCountDirty(); + articlesModified(); + return; + } + } + + d->setTotalCountDirty(); + int nudge = 0; QVector
deletedArticles = d->deletedArticles; - for (; it != en; ++it) { + for (it = items.constBegin(); it != en; ++it) { if (!d->articles.contains((*it)->id())) { // article not in list Article mya(*it, this); mya.offsetPubDate(nudge); @@ -714,8 +770,6 @@ return; } - loadArticles(); // TODO: make me fly: make this delayed - loadFavicon(QUrl(xmlUrl())); d->fetchErrorCode = Syndication::Success; @@ -849,9 +903,12 @@ int Akregator::Feed::totalCount() const { if (d->totalCount == -1) { - d->totalCount = std::count_if(d->articles.constBegin(), d->articles.constEnd(), [](const Article &art) -> bool { - return !art.isDeleted(); - }); + if (d->articlesLoaded) + d->totalCount = std::count_if(d->articles.constBegin(), d->articles.constEnd(), [](const Article &art) -> bool { + return !art.isDeleted(); + }); + else + d->totalCount = d->archive->totalCount(); } return d->totalCount; } diff --git a/src/folder.h b/src/folder.h --- a/src/folder.h +++ b/src/folder.h @@ -156,6 +156,8 @@ KJob *createMarkAsReadJob() override; + void clearCache() override; + Q_SIGNALS: /** emitted when a child was added */ void signalChildAdded(Akregator::TreeNode *); diff --git a/src/folder.cpp b/src/folder.cpp --- a/src/folder.cpp +++ b/src/folder.cpp @@ -497,3 +497,11 @@ } return nodeList; } + +void Folder::clearCache() +{ + // A folder should clear the cache of its children + foreach (TreeNode *const child, children()) { + child->clearCache(); + } +} diff --git a/src/selectioncontroller.cpp b/src/selectioncontroller.cpp --- a/src/selectioncontroller.cpp +++ b/src/selectioncontroller.cpp @@ -293,6 +293,9 @@ if (m_selectedSubscription && m_articleLister) { m_selectedSubscription->setListViewScrollBarPositions(m_articleLister->scrollBarPositions()); } + if (m_selectedSubscription) { + m_selectedSubscription->clearCache(); + } m_selectedSubscription = selectedSubscription(); Q_EMIT currentSubscriptionChanged(m_selectedSubscription); diff --git a/src/treenode.h b/src/treenode.h --- a/src/treenode.h +++ b/src/treenode.h @@ -174,6 +174,8 @@ virtual KJob *createMarkAsReadJob() = 0; + virtual void clearCache() = 0; + public Q_SLOTS: /** adds node to a fetch queue