35d8d6752d
This uses a nicer filename with context. We also build the user agent more correctly. Last, this makes work the usage of HEAD to keep up to date without full download.
181 lines
4.9 KiB
C++
181 lines
4.9 KiB
C++
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2025 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "FeedDataProvider.h"
|
|
#include <utils/Logger.h>
|
|
|
|
#include <FloweePay.h>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QJsonDocument>
|
|
#include <QNetworkRequest>
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
#include <QCoreApplication>
|
|
|
|
constexpr const char *FEED = "https://flowee.org/v/index.json";
|
|
constexpr const char *FLOWEE_JSON = "social-feed-flowee.json";
|
|
|
|
FeedDataProvider::FeedDataProvider(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
QFileInfo info(FLOWEE_JSON);
|
|
bool useOld = info.exists();
|
|
if (useOld) {
|
|
m_lastCheck = info.lastModified();
|
|
readFeed();
|
|
}
|
|
if (m_lastCheck.isNull()
|
|
|| m_lastCheck.msecsTo(QDateTime::currentDateTime()) > 4 * 60 * 60 * 1000) {
|
|
QTimer::singleShot(10, this, SLOT(start()));
|
|
}
|
|
}
|
|
|
|
void FeedDataProvider::start()
|
|
{
|
|
QUrl uri(FEED);
|
|
QNetworkRequest req(uri);
|
|
auto app = QCoreApplication::instance();
|
|
QString useragent = QString("%1%2/%3")
|
|
.arg(app->organizationName(),
|
|
app->applicationName(),
|
|
app->applicationVersion());
|
|
req.setHeader(QNetworkRequest::UserAgentHeader, useragent);
|
|
|
|
if (m_lastCheck.isNull())
|
|
m_reply = FloweePay::instance()->network()->get(req);
|
|
else
|
|
m_reply = FloweePay::instance()->network()->head(req);
|
|
logInfo() << "fetching social feed file from:" << FEED;
|
|
connect(m_reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
|
connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)),
|
|
this, SLOT(errored(QNetworkReply::NetworkError)));
|
|
connect(m_reply, SIGNAL(sslErrors(QList<QSslError>)),
|
|
this, SLOT(sslErrors(QList<QSslError>)));
|
|
}
|
|
|
|
void FeedDataProvider::downloadFinished()
|
|
{
|
|
assert(m_reply);
|
|
if (m_reply->operation() == QNetworkAccessManager::HeadOperation) {
|
|
auto lastModified = m_reply->header(QNetworkRequest::LastModifiedHeader);
|
|
m_reply->deleteLater();
|
|
m_reply = nullptr;
|
|
// check if the server has a newer one.
|
|
if (lastModified.toDateTime() > m_lastCheck) {
|
|
m_lastCheck = QDateTime();
|
|
assert(m_lastCheck.isNull());
|
|
start(); // this will now do a download of the actual data.
|
|
}
|
|
return;
|
|
}
|
|
auto data = m_reply->readAll();
|
|
m_reply->deleteLater();
|
|
m_reply = nullptr;
|
|
QFile feed(FLOWEE_JSON);
|
|
if (feed.open(QIODevice::WriteOnly)) {
|
|
feed.write(data);
|
|
feed.close();
|
|
}
|
|
readFeed();
|
|
}
|
|
|
|
void FeedDataProvider::readFeed()
|
|
{
|
|
QFile feed(FLOWEE_JSON);
|
|
if (!feed.open(QIODevice::ReadOnly))
|
|
return;
|
|
auto data = feed.readAll();
|
|
feed.close();
|
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
|
if (doc.isNull()) {
|
|
logWarning() << "Parsing error of social json";
|
|
return;
|
|
}
|
|
m_listItems.clear();
|
|
QJsonArray list = doc.array();
|
|
for (auto i = list.begin(); i != list.end(); ++i) {
|
|
auto obj = i->toObject();
|
|
auto text = obj["text"];
|
|
if (!text.isString()) {
|
|
logWarning() << "Skipping item in JSON, missing text attribute";
|
|
continue;
|
|
}
|
|
auto *item = new ListItem(this);
|
|
item->m_text = text.toString();
|
|
item->m_url = obj["url"].toString();
|
|
item->m_videoLength = obj["length"].toString();
|
|
item->m_title = obj["title"].toString();
|
|
auto date = obj["date"].toString();
|
|
item->m_date = QDate::fromString(date, Qt::ISODate);
|
|
|
|
m_listItems.append(item);
|
|
}
|
|
|
|
emit listItemsChanged();
|
|
}
|
|
|
|
QList<ListItem *> FeedDataProvider::listItems() const
|
|
{
|
|
return m_listItems;
|
|
}
|
|
|
|
void FeedDataProvider::errored(QNetworkReply::NetworkError err)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void FeedDataProvider::sslErrors(const QList<QSslError> &errors)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
|
|
// ------------------------------------
|
|
|
|
ListItem::ListItem(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
}
|
|
|
|
QDate ListItem::date() const
|
|
{
|
|
return m_date;
|
|
}
|
|
|
|
QString ListItem::videoLength() const
|
|
{
|
|
return m_videoLength;
|
|
}
|
|
|
|
QString ListItem::text() const
|
|
{
|
|
return m_text;
|
|
}
|
|
|
|
QString ListItem::title() const
|
|
{
|
|
return m_title;
|
|
}
|
|
|
|
QString ListItem::url() const
|
|
{
|
|
return m_url;
|
|
}
|