File indexing completed on 2024-05-12 16:21:29

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
0003  * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include <QVariant>
0009 
0010 #include "author.h"
0011 #include "database.h"
0012 #include "datamanager.h"
0013 #include "error.h"
0014 #include "feed.h"
0015 #include "feedlogging.h"
0016 #include "fetcher.h"
0017 
0018 Feed::Feed(const QString &feedurl)
0019     : QObject(&DataManager::instance())
0020 {
0021     QSqlQuery query;
0022     query.prepare(QStringLiteral("SELECT * FROM Feeds WHERE url=:feedurl;"));
0023     query.bindValue(QStringLiteral(":feedurl"), feedurl);
0024     Database::instance().execute(query);
0025     if (!query.next())
0026         qWarning() << "Failed to load feed" << feedurl;
0027 
0028     m_subscribed.setSecsSinceEpoch(query.value(QStringLiteral("subscribed")).toInt());
0029 
0030     m_lastUpdated.setSecsSinceEpoch(query.value(QStringLiteral("lastUpdated")).toInt());
0031 
0032     m_url = query.value(QStringLiteral("url")).toString();
0033     m_name = query.value(QStringLiteral("name")).toString();
0034     m_image = query.value(QStringLiteral("image")).toString();
0035     m_link = query.value(QStringLiteral("link")).toString();
0036     m_description = query.value(QStringLiteral("description")).toString();
0037     m_deleteAfterCount = query.value(QStringLiteral("deleteAfterCount")).toInt();
0038     m_deleteAfterType = query.value(QStringLiteral("deleteAfterType")).toInt();
0039     m_notify = query.value(QStringLiteral("notify")).toBool();
0040     m_dirname = query.value(QStringLiteral("dirname")).toString();
0041 
0042     m_errorId = 0;
0043     m_errorString = QLatin1String("");
0044 
0045     updateAuthors();
0046     updateUnreadEntryCountFromDB();
0047     updateNewEntryCountFromDB();
0048     updateFavoriteEntryCountFromDB();
0049 
0050     connect(&Fetcher::instance(), &Fetcher::feedUpdateStatusChanged, this, [this](const QString &url, bool status) {
0051         if (url == m_url) {
0052             setRefreshing(status);
0053         }
0054     });
0055     connect(&DataManager::instance(), &DataManager::feedEntriesUpdated, this, [this](const QString &url) {
0056         if (url == m_url) {
0057             Q_EMIT entryCountChanged();
0058             updateUnreadEntryCountFromDB();
0059             Q_EMIT DataManager::instance().unreadEntryCountChanged(m_url);
0060             Q_EMIT unreadEntryCountChanged();
0061             Q_EMIT DataManager::instance().newEntryCountChanged(m_url);
0062             Q_EMIT newEntryCountChanged();
0063             setErrorId(0);
0064             setErrorString(QLatin1String(""));
0065         }
0066     });
0067     connect(&DataManager::instance(), &DataManager::newEntryCountChanged, this, [this](const QString &url) {
0068         if (url == m_url) {
0069             updateNewEntryCountFromDB();
0070             Q_EMIT newEntryCountChanged();
0071         }
0072     });
0073     connect(&DataManager::instance(), &DataManager::favoriteEntryCountChanged, this, [this](const QString &url) {
0074         if (url == m_url) {
0075             updateFavoriteEntryCountFromDB();
0076             Q_EMIT favoriteEntryCountChanged();
0077         }
0078     });
0079     connect(&Fetcher::instance(),
0080             &Fetcher::error,
0081             this,
0082             [this](const Error::Type type, const QString &url, const QString &id, int errorId, const QString &errorString) {
0083                 Q_UNUSED(type)
0084                 Q_UNUSED(id)
0085                 if (url == m_url) {
0086                     setErrorId(errorId);
0087                     setErrorString(errorString);
0088                     setRefreshing(false);
0089                 }
0090             });
0091     connect(&Fetcher::instance(), &Fetcher::downloadFinished, this, [this](QString url) {
0092         if (url == m_image) {
0093             Q_EMIT imageChanged(url);
0094             Q_EMIT cachedImageChanged(cachedImage());
0095         }
0096     });
0097 
0098     m_entries = new EntriesProxyModel(this);
0099 }
0100 
0101 Feed::~Feed()
0102 {
0103 }
0104 
0105 void Feed::updateAuthors()
0106 {
0107     QVector<Author *> newAuthors;
0108     bool haveAuthorsChanged = false;
0109 
0110     QSqlQuery authorQuery;
0111     authorQuery.prepare(QStringLiteral("SELECT * FROM Authors WHERE id='' AND feed=:feed"));
0112     authorQuery.bindValue(QStringLiteral(":feed"), m_url);
0113     Database::instance().execute(authorQuery);
0114     while (authorQuery.next()) {
0115         // check if author already exists, if so, then reuse
0116         bool existingAuthor = false;
0117         QString name = authorQuery.value(QStringLiteral("name")).toString();
0118         QString email = authorQuery.value(QStringLiteral("email")).toString();
0119         QString url = authorQuery.value(QStringLiteral("uri")).toString();
0120         qCDebug(kastsFeed) << name << email << url;
0121         for (int i = 0; i < m_authors.count(); i++) {
0122             qCDebug(kastsFeed) << "old authors" << m_authors[i]->name() << m_authors[i]->email() << m_authors[i]->url();
0123             if (m_authors[i] && m_authors[i]->name() == name && m_authors[i]->email() == email && m_authors[i]->url() == url) {
0124                 existingAuthor = true;
0125                 newAuthors += m_authors[i];
0126             }
0127         }
0128         if (!existingAuthor) {
0129             newAuthors += new Author(name, email, url, nullptr);
0130             haveAuthorsChanged = true;
0131         }
0132     }
0133 
0134     // Finally check whether m_authors and newAuthors are identical
0135     // if not, then delete the authors that were removed
0136     for (int i = 0; i < m_authors.count(); i++) {
0137         if (!newAuthors.contains(m_authors[i])) {
0138             delete m_authors[i];
0139             haveAuthorsChanged = true;
0140         }
0141     }
0142 
0143     m_authors = newAuthors;
0144 
0145     if (haveAuthorsChanged)
0146         Q_EMIT authorsChanged(m_authors);
0147     qCDebug(kastsFeed) << "feed" << m_name << "authors have changed?" << haveAuthorsChanged;
0148 }
0149 
0150 void Feed::updateUnreadEntryCountFromDB()
0151 {
0152     QSqlQuery query;
0153     query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND read=0;"));
0154     query.bindValue(QStringLiteral(":feed"), m_url);
0155     Database::instance().execute(query);
0156     if (!query.next())
0157         m_unreadEntryCount = -1;
0158     m_unreadEntryCount = query.value(0).toInt();
0159 }
0160 
0161 void Feed::updateNewEntryCountFromDB()
0162 {
0163     QSqlQuery query;
0164     query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND new=1;"));
0165     query.bindValue(QStringLiteral(":feed"), m_url);
0166     Database::instance().execute(query);
0167     if (!query.next())
0168         m_newEntryCount = -1;
0169     m_newEntryCount = query.value(0).toInt();
0170 }
0171 
0172 void Feed::updateFavoriteEntryCountFromDB()
0173 {
0174     QSqlQuery query;
0175     query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND favorite=1;"));
0176     query.bindValue(QStringLiteral(":feed"), m_url);
0177     Database::instance().execute(query);
0178     if (!query.next())
0179         m_favoriteEntryCount = -1;
0180     m_favoriteEntryCount = query.value(0).toInt();
0181 }
0182 
0183 QString Feed::url() const
0184 {
0185     return m_url;
0186 }
0187 
0188 QString Feed::name() const
0189 {
0190     return m_name;
0191 }
0192 
0193 QString Feed::image() const
0194 {
0195     return m_image;
0196 }
0197 
0198 QString Feed::cachedImage() const
0199 {
0200     return Fetcher::instance().image(m_image);
0201 }
0202 
0203 QString Feed::link() const
0204 {
0205     return m_link;
0206 }
0207 
0208 QString Feed::description() const
0209 {
0210     return m_description;
0211 }
0212 
0213 QVector<Author *> Feed::authors() const
0214 {
0215     return m_authors;
0216 }
0217 
0218 int Feed::deleteAfterCount() const
0219 {
0220     return m_deleteAfterCount;
0221 }
0222 
0223 int Feed::deleteAfterType() const
0224 {
0225     return m_deleteAfterType;
0226 }
0227 
0228 QDateTime Feed::subscribed() const
0229 {
0230     return m_subscribed;
0231 }
0232 
0233 QDateTime Feed::lastUpdated() const
0234 {
0235     return m_lastUpdated;
0236 }
0237 
0238 bool Feed::notify() const
0239 {
0240     return m_notify;
0241 }
0242 
0243 QString Feed::dirname() const
0244 {
0245     return m_dirname;
0246 }
0247 
0248 int Feed::entryCount() const
0249 {
0250     return DataManager::instance().entryCount(this);
0251 }
0252 
0253 int Feed::unreadEntryCount() const
0254 {
0255     return m_unreadEntryCount;
0256 }
0257 
0258 int Feed::newEntryCount() const
0259 {
0260     return m_newEntryCount;
0261 }
0262 
0263 int Feed::favoriteEntryCount() const
0264 {
0265     return m_favoriteEntryCount;
0266 }
0267 
0268 bool Feed::refreshing() const
0269 {
0270     return m_refreshing;
0271 }
0272 
0273 int Feed::errorId() const
0274 {
0275     return m_errorId;
0276 }
0277 
0278 QString Feed::errorString() const
0279 {
0280     return m_errorString;
0281 }
0282 
0283 void Feed::setName(const QString &name)
0284 {
0285     if (name != m_name) {
0286         m_name = name;
0287         Q_EMIT nameChanged(m_name);
0288     }
0289 }
0290 
0291 void Feed::setImage(const QString &image)
0292 {
0293     if (image != m_image) {
0294         m_image = image;
0295         Q_EMIT imageChanged(m_image);
0296         Q_EMIT cachedImageChanged(cachedImage());
0297     }
0298 }
0299 
0300 void Feed::setLink(const QString &link)
0301 {
0302     if (link != m_link) {
0303         m_link = link;
0304         Q_EMIT linkChanged(m_link);
0305     }
0306 }
0307 
0308 void Feed::setDescription(const QString &description)
0309 {
0310     if (description != m_description) {
0311         m_description = description;
0312         Q_EMIT descriptionChanged(m_description);
0313     }
0314 }
0315 
0316 void Feed::setAuthors(const QVector<Author *> &authors)
0317 {
0318     for (auto &author : m_authors) {
0319         delete author;
0320     }
0321     m_authors.clear();
0322     m_authors = authors;
0323     Q_EMIT authorsChanged(m_authors);
0324 }
0325 
0326 void Feed::setDeleteAfterCount(int count)
0327 {
0328     m_deleteAfterCount = count;
0329     Q_EMIT deleteAfterCountChanged(m_deleteAfterCount);
0330 }
0331 
0332 void Feed::setDeleteAfterType(int type)
0333 {
0334     m_deleteAfterType = type;
0335     Q_EMIT deleteAfterTypeChanged(m_deleteAfterType);
0336 }
0337 
0338 void Feed::setLastUpdated(const QDateTime &lastUpdated)
0339 {
0340     if (lastUpdated != m_lastUpdated) {
0341         m_lastUpdated = lastUpdated;
0342         Q_EMIT lastUpdatedChanged(m_lastUpdated);
0343     }
0344 }
0345 
0346 void Feed::setNotify(bool notify)
0347 {
0348     if (notify != m_notify) {
0349         m_notify = notify;
0350         Q_EMIT notifyChanged(m_notify);
0351     }
0352 }
0353 
0354 void Feed::setDirname(const QString &dirname)
0355 {
0356     if (dirname != m_dirname) {
0357         m_dirname = dirname;
0358         Q_EMIT dirnameChanged(m_dirname);
0359     }
0360 }
0361 
0362 void Feed::setUnreadEntryCount(const int count)
0363 {
0364     if (count != m_unreadEntryCount) {
0365         m_unreadEntryCount = count;
0366         Q_EMIT unreadEntryCountChanged();
0367         Q_EMIT DataManager::instance().unreadEntryCountChanged(m_url);
0368         // TODO: can one of the two slots be removed??
0369     }
0370 }
0371 
0372 void Feed::setRefreshing(bool refreshing)
0373 {
0374     if (refreshing != m_refreshing) {
0375         m_refreshing = refreshing;
0376         if (!m_refreshing) {
0377             m_errorId = 0;
0378             m_errorString = QString();
0379         }
0380         Q_EMIT refreshingChanged(m_refreshing);
0381     }
0382 }
0383 
0384 void Feed::setErrorId(int errorId)
0385 {
0386     if (errorId != m_errorId) {
0387         m_errorId = errorId;
0388         Q_EMIT errorIdChanged(m_errorId);
0389     }
0390 }
0391 
0392 void Feed::setErrorString(const QString &errorString)
0393 {
0394     if (errorString != m_errorString) {
0395         m_errorString = errorString;
0396         Q_EMIT errorStringChanged(m_errorString);
0397     }
0398 }
0399 
0400 void Feed::refresh()
0401 {
0402     Fetcher::instance().fetch(m_url);
0403 }