File indexing completed on 2024-05-12 05:04:15

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 #include "maintimelinemodel.h"
0005 
0006 #include <KLocalizedString>
0007 
0008 MainTimelineModel::MainTimelineModel(QObject *parent)
0009     : TimelineModel(parent)
0010 {
0011     init();
0012 }
0013 
0014 QString MainTimelineModel::name() const
0015 {
0016     return m_timelineName;
0017 }
0018 
0019 QString MainTimelineModel::displayName() const
0020 {
0021     if (m_timelineName == QStringLiteral("home")) {
0022         return i18nc("@title", "Home");
0023     } else if (m_timelineName == QStringLiteral("public")) {
0024         return i18nc("@title", "Local Timeline");
0025     } else if (m_timelineName == QStringLiteral("federated")) {
0026         return i18nc("@title", "Global Timeline");
0027     } else if (m_timelineName == QStringLiteral("bookmarks")) {
0028         return i18nc("@title", "Bookmarks");
0029     } else if (m_timelineName == QStringLiteral("favourites")) {
0030         return i18nc("@title", "Favourites");
0031     } else if (m_timelineName == QStringLiteral("trending")) {
0032         return i18nc("@title", "Trending");
0033     } else if (m_timelineName == QStringLiteral("list")) {
0034         return m_listId;
0035     }
0036 
0037     return {};
0038 }
0039 
0040 QString MainTimelineModel::listId() const
0041 {
0042     return m_listId;
0043 }
0044 
0045 void MainTimelineModel::setListId(const QString &id)
0046 {
0047     if (m_listId == id) {
0048         return;
0049     }
0050 
0051     m_listId = id;
0052     Q_EMIT listIdChanged();
0053 
0054     setLoading(false);
0055     fillTimeline({});
0056 }
0057 
0058 void MainTimelineModel::setName(const QString &timelineName)
0059 {
0060     if (timelineName == m_timelineName) {
0061         return;
0062     }
0063 
0064     m_timelineName = timelineName;
0065     Q_EMIT nameChanged();
0066     setLoading(false);
0067     fillTimeline({});
0068 }
0069 
0070 void MainTimelineModel::fillTimeline(const QString &from_id)
0071 {
0072     static const QSet<QString> validTimelines = {QStringLiteral("home"),
0073                                                  QStringLiteral("public"),
0074                                                  QStringLiteral("federated"),
0075                                                  QStringLiteral("bookmarks"),
0076                                                  QStringLiteral("favourites"),
0077                                                  QStringLiteral("trending"),
0078                                                  QStringLiteral("list")};
0079     static const QSet<QString> publicTimelines = {QStringLiteral("home"), QStringLiteral("public"), QStringLiteral("federated")};
0080 
0081     if (!m_account || m_loading || !validTimelines.contains(m_timelineName)) {
0082         return;
0083     }
0084 
0085     if (m_timelineName == QStringLiteral("list") && m_listId.isEmpty()) {
0086         return;
0087     }
0088 
0089     setLoading(true);
0090 
0091     const bool local = m_timelineName == QStringLiteral("public");
0092 
0093     QUrlQuery q;
0094     if (local) {
0095         q.addQueryItem(QStringLiteral("local"), QStringLiteral("true"));
0096     }
0097     if (!from_id.isEmpty()) {
0098         q.addQueryItem(QStringLiteral("max_id"), from_id);
0099     }
0100 
0101     QUrl uri;
0102     if (m_timelineName == QStringLiteral("list")) {
0103         const QString apiUrl = QStringLiteral("/api/v1/timelines/list/%1").arg(m_listId);
0104         uri = m_account->apiUrl(apiUrl);
0105         uri.setQuery(q);
0106     } else if (publicTimelines.contains(m_timelineName)) {
0107         // federated timeline is really "public" without local set
0108         const QString apiUrl =
0109             QStringLiteral("/api/v1/timelines/%1").arg(m_timelineName == QStringLiteral("federated") ? QStringLiteral("public") : m_timelineName);
0110         uri = m_account->apiUrl(apiUrl);
0111         uri.setQuery(q);
0112     } else {
0113         // Fixes issues where on reaching the end the data is fetched from the start
0114         if (m_next.isEmpty() && !m_timeline.isEmpty()) {
0115             setLoading(false);
0116             return;
0117         }
0118         uri = m_next.isEmpty() ? m_account->apiUrl(
0119                   QStringLiteral("/api/v1/%1").arg(m_timelineName == QStringLiteral("trending") ? QStringLiteral("trends/statuses") : m_timelineName))
0120                                : m_next;
0121     }
0122 
0123     auto account = m_account;
0124     auto currentTimelineName = m_timelineName;
0125     m_account->get(
0126         uri,
0127         true,
0128         this,
0129         [this, currentTimelineName, account](QNetworkReply *reply) {
0130             if (m_account != account || m_timelineName != currentTimelineName) {
0131                 setLoading(false);
0132                 return;
0133             }
0134 
0135             static QRegularExpression re(QStringLiteral("<(.*)>; rel=\"next\""));
0136             const auto next = reply->rawHeader(QByteArrayLiteral("Link"));
0137             const auto match = re.match(QString::fromUtf8(next));
0138             m_next = QUrl::fromUserInput(match.captured(1));
0139             Q_EMIT atEndChanged();
0140 
0141             fetchedTimeline(reply->readAll(), !publicTimelines.contains(m_timelineName));
0142             setLoading(false);
0143         },
0144         [this](QNetworkReply *reply) {
0145             Q_UNUSED(reply)
0146             setLoading(false);
0147         });
0148 }
0149 
0150 void MainTimelineModel::handleEvent(AbstractAccount::StreamingEventType eventType, const QByteArray &payload)
0151 {
0152     TimelineModel::handleEvent(eventType, payload);
0153     if (eventType == AbstractAccount::StreamingEventType::UpdateEvent && m_timelineName == QStringLiteral("home")) {
0154         const auto doc = QJsonDocument::fromJson(payload);
0155         const auto post = new Post(m_account, doc.object(), this);
0156         beginInsertRows({}, 0, 0);
0157         m_timeline.push_front(post);
0158         endInsertRows();
0159     }
0160 }
0161 
0162 bool MainTimelineModel::atEnd() const
0163 {
0164     return m_next.isEmpty();
0165 }
0166 
0167 void MainTimelineModel::reset()
0168 {
0169     beginResetModel();
0170     qDeleteAll(m_timeline);
0171     m_timeline.clear();
0172     endResetModel();
0173     m_next.clear();
0174 }
0175 
0176 #include "moc_maintimelinemodel.cpp"