2020-02-28 23:25:08 +01:00
|
|
|
/**
|
2020-08-14 20:56:04 +02:00
|
|
|
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
2021-04-08 13:16:36 +02:00
|
|
|
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
2020-02-28 23:25:08 +01:00
|
|
|
*
|
2020-08-14 20:56:04 +02:00
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
2020-02-28 23:25:08 +01:00
|
|
|
*/
|
|
|
|
|
2021-07-04 18:35:09 +02:00
|
|
|
#include "fetcher.h"
|
2021-09-08 11:46:22 +02:00
|
|
|
#include "fetcherlogging.h"
|
2021-07-04 18:35:09 +02:00
|
|
|
|
2021-06-19 16:32:39 +02:00
|
|
|
#include <KLocalizedString>
|
2020-06-06 00:05:32 +02:00
|
|
|
#include <QDateTime>
|
2021-03-12 00:19:04 +01:00
|
|
|
#include <QDebug>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <QDir>
|
|
|
|
#include <QDomElement>
|
2020-04-26 23:40:09 +02:00
|
|
|
#include <QFile>
|
|
|
|
#include <QFileInfo>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <QMultiMap>
|
2020-02-28 23:25:08 +01:00
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
2020-05-02 12:26:00 +02:00
|
|
|
#include <QTextDocumentFragment>
|
2021-09-21 20:36:54 +00:00
|
|
|
#include <QTime>
|
2020-02-29 15:34:12 +01:00
|
|
|
#include <Syndication/Syndication>
|
2020-02-28 23:25:08 +01:00
|
|
|
|
2020-03-16 22:37:04 +01:00
|
|
|
#include "database.h"
|
2021-06-23 21:11:04 +02:00
|
|
|
#include "enclosure.h"
|
2021-09-23 19:23:39 +02:00
|
|
|
#include "fetchfeedsjob.h"
|
2021-07-13 16:27:27 +02:00
|
|
|
#include "kasts-version.h"
|
2021-09-23 19:23:39 +02:00
|
|
|
#include "models/errorlogmodel.h"
|
2021-04-10 08:46:14 +02:00
|
|
|
#include "settingsmanager.h"
|
2021-07-04 18:35:09 +02:00
|
|
|
#include "storagemanager.h"
|
2021-10-29 17:00:52 +02:00
|
|
|
#include "sync/sync.h"
|
2020-03-16 22:37:04 +01:00
|
|
|
|
2021-10-07 20:56:33 +02:00
|
|
|
#include <solidextras/networkstatus.h>
|
|
|
|
|
2020-04-22 02:17:57 +02:00
|
|
|
Fetcher::Fetcher()
|
|
|
|
{
|
2021-09-23 19:23:39 +02:00
|
|
|
connect(this, &Fetcher::error, &ErrorLogModel::instance(), &ErrorLogModel::monitorErrorMessages);
|
|
|
|
|
2021-04-20 19:17:46 +02:00
|
|
|
m_updateProgress = -1;
|
|
|
|
m_updateTotal = -1;
|
|
|
|
m_updating = false;
|
|
|
|
|
2020-04-25 22:16:19 +02:00
|
|
|
manager = new QNetworkAccessManager(this);
|
|
|
|
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
|
|
manager->setStrictTransportSecurityEnabled(true);
|
|
|
|
manager->enableStrictTransportSecurityStore(true);
|
2020-02-28 23:25:08 +01:00
|
|
|
}
|
|
|
|
|
2020-11-01 13:18:11 +01:00
|
|
|
void Fetcher::fetch(const QString &url)
|
2020-02-28 23:25:08 +01:00
|
|
|
{
|
2021-04-26 16:58:22 +02:00
|
|
|
QStringList urls(url);
|
|
|
|
fetch(urls);
|
2020-02-28 23:25:08 +01:00
|
|
|
}
|
2020-04-25 22:16:19 +02:00
|
|
|
|
2021-04-20 20:53:24 +02:00
|
|
|
void Fetcher::fetchAll()
|
|
|
|
{
|
2021-10-29 17:00:52 +02:00
|
|
|
if (Sync::instance().syncEnabled() && SettingsManager::self()->syncWhenUpdatingFeeds()) {
|
|
|
|
Sync::instance().doRegularSync(true);
|
|
|
|
} else {
|
|
|
|
QStringList urls;
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT url FROM Feeds;"));
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
|
|
|
urls += query.value(0).toString();
|
|
|
|
}
|
2021-04-20 20:53:24 +02:00
|
|
|
|
2021-10-29 17:00:52 +02:00
|
|
|
if (urls.count() > 0) {
|
|
|
|
fetch(urls);
|
|
|
|
}
|
2021-05-01 18:59:08 +00:00
|
|
|
}
|
2020-05-31 18:17:25 +02:00
|
|
|
}
|
|
|
|
|
2021-09-23 19:23:39 +02:00
|
|
|
void Fetcher::fetch(const QStringList &urls)
|
2021-04-26 16:58:22 +02:00
|
|
|
{
|
2021-09-23 19:23:39 +02:00
|
|
|
if (m_updating)
|
|
|
|
return; // update is already running, do nothing
|
2021-04-26 16:58:22 +02:00
|
|
|
|
2021-09-23 19:23:39 +02:00
|
|
|
m_updating = true;
|
|
|
|
m_updateProgress = 0;
|
|
|
|
m_updateTotal = urls.count();
|
|
|
|
Q_EMIT updatingChanged(m_updating);
|
|
|
|
Q_EMIT updateProgressChanged(m_updateProgress);
|
|
|
|
Q_EMIT updateTotalChanged(m_updateTotal);
|
2021-04-26 16:58:22 +02:00
|
|
|
|
2021-09-23 19:23:39 +02:00
|
|
|
qCDebug(kastsFetcher) << "Create fetchFeedsJob";
|
|
|
|
FetchFeedsJob *fetchFeedsJob = new FetchFeedsJob(urls, this);
|
|
|
|
connect(this, &Fetcher::cancelFetching, fetchFeedsJob, &FetchFeedsJob::abort);
|
|
|
|
connect(fetchFeedsJob, &FetchFeedsJob::processedAmountChanged, this, [this](KJob *job, KJob::Unit unit, qulonglong amount) {
|
|
|
|
qCDebug(kastsFetcher) << "FetchFeedsJob::processedAmountChanged:" << amount;
|
|
|
|
Q_UNUSED(job);
|
|
|
|
Q_ASSERT(unit == KJob::Unit::Items);
|
|
|
|
m_updateProgress = amount;
|
2021-04-26 16:58:22 +02:00
|
|
|
Q_EMIT updateProgressChanged(m_updateProgress);
|
|
|
|
});
|
2021-09-23 19:23:39 +02:00
|
|
|
connect(fetchFeedsJob, &FetchFeedsJob::result, this, [this, fetchFeedsJob]() {
|
|
|
|
qCDebug(kastsFetcher) << "result slot of FetchFeedsJob";
|
2021-10-29 17:00:52 +02:00
|
|
|
if (fetchFeedsJob->error() && !fetchFeedsJob->aborted()) {
|
2021-09-23 19:23:39 +02:00
|
|
|
Q_EMIT error(Error::Type::FeedUpdate, QString(), QString(), fetchFeedsJob->error(), fetchFeedsJob->errorString(), QString());
|
2021-04-05 12:43:35 +02:00
|
|
|
}
|
2021-09-23 19:23:39 +02:00
|
|
|
if (m_updating) {
|
|
|
|
m_updating = false;
|
|
|
|
Q_EMIT updatingChanged(m_updating);
|
2021-05-01 18:59:08 +00:00
|
|
|
}
|
2021-09-23 19:23:39 +02:00
|
|
|
});
|
2021-09-21 20:36:54 +00:00
|
|
|
|
2021-09-23 19:23:39 +02:00
|
|
|
fetchFeedsJob->start();
|
|
|
|
qCDebug(kastsFetcher) << "end of Fetcher::fetch";
|
2021-09-21 20:36:54 +00:00
|
|
|
}
|
|
|
|
|
2021-10-06 16:07:05 +02:00
|
|
|
QString Fetcher::image(const QString &url)
|
2020-04-25 22:16:19 +02:00
|
|
|
{
|
2021-06-19 16:32:39 +02:00
|
|
|
if (url.isEmpty()) {
|
|
|
|
return QLatin1String("no-image");
|
|
|
|
}
|
|
|
|
|
|
|
|
// if image is already cached, then return the path
|
2021-07-04 18:35:09 +02:00
|
|
|
QString path = StorageManager::instance().imagePath(url);
|
2020-05-10 23:25:23 +02:00
|
|
|
if (QFileInfo::exists(path)) {
|
2021-06-19 16:32:39 +02:00
|
|
|
if (QFileInfo(path).size() != 0) {
|
2021-07-04 18:35:09 +02:00
|
|
|
return QUrl::fromLocalFile(path).toString();
|
2021-06-19 16:32:39 +02:00
|
|
|
}
|
2020-04-25 22:16:19 +02:00
|
|
|
}
|
|
|
|
|
2021-10-06 16:07:05 +02:00
|
|
|
// avoid restarting an image download if it's already running
|
|
|
|
if (m_ongoingImageDownloads.contains(url)) {
|
|
|
|
return QLatin1String("fetching");
|
|
|
|
}
|
|
|
|
|
2021-06-19 16:32:39 +02:00
|
|
|
// if image has not yet been cached, then check for network connectivity if
|
|
|
|
// possible; and download the image
|
2021-10-07 20:56:33 +02:00
|
|
|
SolidExtras::NetworkStatus networkStatus;
|
2021-10-29 17:00:52 +02:00
|
|
|
if (networkStatus.connectivity() == SolidExtras::NetworkStatus::No
|
|
|
|
|| (networkStatus.metered() == SolidExtras::NetworkStatus::Yes && !SettingsManager::self()->allowMeteredImageDownloads())) {
|
2021-10-07 20:56:33 +02:00
|
|
|
return QLatin1String("no-image");
|
2021-06-19 16:32:39 +02:00
|
|
|
}
|
2020-05-18 21:20:23 +02:00
|
|
|
|
2021-10-06 16:07:05 +02:00
|
|
|
m_ongoingImageDownloads.insert(url);
|
|
|
|
QNetworkRequest request((QUrl(url)));
|
|
|
|
request.setTransferTimeout();
|
|
|
|
QNetworkReply *reply = get(request);
|
|
|
|
connect(reply, &QNetworkReply::finished, this, [=]() {
|
|
|
|
if (reply->isOpen() && !reply->error()) {
|
|
|
|
QByteArray data = reply->readAll();
|
|
|
|
QFile file(path);
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
file.write(data);
|
|
|
|
file.close();
|
|
|
|
Q_EMIT downloadFinished(url);
|
|
|
|
}
|
|
|
|
m_ongoingImageDownloads.remove(url);
|
|
|
|
reply->deleteLater();
|
|
|
|
});
|
2021-06-19 16:32:39 +02:00
|
|
|
return QLatin1String("fetching");
|
2020-05-18 21:20:23 +02:00
|
|
|
}
|
|
|
|
|
2021-04-07 12:47:46 +02:00
|
|
|
QNetworkReply *Fetcher::download(const QString &url, const QString &filePath) const
|
2020-05-18 21:20:23 +02:00
|
|
|
{
|
2020-04-25 22:16:19 +02:00
|
|
|
QNetworkRequest request((QUrl(url)));
|
2021-04-22 17:05:33 +02:00
|
|
|
request.setTransferTimeout();
|
2021-06-04 21:34:50 +02:00
|
|
|
|
|
|
|
QFile *file = new QFile(filePath);
|
|
|
|
if (file->exists() && file->size() > 0) {
|
|
|
|
// try to resume download
|
2021-10-29 17:00:52 +02:00
|
|
|
int resumedAt = file->size();
|
2021-06-23 22:29:15 +02:00
|
|
|
qCDebug(kastsFetcher) << "Resuming download at" << resumedAt << "bytes";
|
|
|
|
QByteArray rangeHeaderValue = QByteArray("bytes=") + QByteArray::number(resumedAt) + QByteArray("-");
|
2021-06-04 21:34:50 +02:00
|
|
|
request.setRawHeader(QByteArray("Range"), rangeHeaderValue);
|
|
|
|
file->open(QIODevice::WriteOnly | QIODevice::Append);
|
|
|
|
} else {
|
2021-06-04 23:04:24 +02:00
|
|
|
qCDebug(kastsFetcher) << "Starting new download";
|
2021-06-04 21:34:50 +02:00
|
|
|
file->open(QIODevice::WriteOnly);
|
|
|
|
}
|
|
|
|
|
2020-07-02 19:14:07 +02:00
|
|
|
QNetworkReply *reply = get(request);
|
2021-06-04 21:34:50 +02:00
|
|
|
|
|
|
|
connect(reply, &QNetworkReply::readyRead, this, [=]() {
|
|
|
|
if (reply->isOpen() && file) {
|
|
|
|
QByteArray data = reply->readAll();
|
|
|
|
file->write(data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-02-21 19:54:10 +01:00
|
|
|
connect(reply, &QNetworkReply::finished, this, [=]() {
|
2021-06-04 21:34:50 +02:00
|
|
|
if (reply->isOpen() && file) {
|
2021-02-21 19:54:10 +01:00
|
|
|
QByteArray data = reply->readAll();
|
2021-06-04 21:34:50 +02:00
|
|
|
file->write(data);
|
|
|
|
file->close();
|
2020-04-25 22:16:19 +02:00
|
|
|
|
2021-02-21 19:54:10 +01:00
|
|
|
Q_EMIT downloadFinished(url);
|
|
|
|
}
|
2021-06-04 21:34:50 +02:00
|
|
|
|
|
|
|
// clean up; close file if still open in case something has gone wrong
|
|
|
|
if (file) {
|
|
|
|
if (file->isOpen()) {
|
|
|
|
file->close();
|
|
|
|
}
|
|
|
|
delete file;
|
|
|
|
}
|
2021-02-21 19:54:10 +01:00
|
|
|
reply->deleteLater();
|
2020-04-25 22:16:19 +02:00
|
|
|
});
|
2021-02-21 19:54:10 +01:00
|
|
|
|
|
|
|
return reply;
|
2020-04-25 22:16:19 +02:00
|
|
|
}
|
2020-04-26 23:40:09 +02:00
|
|
|
|
2021-04-07 12:47:46 +02:00
|
|
|
QNetworkReply *Fetcher::get(QNetworkRequest &request) const
|
2020-07-02 19:14:07 +02:00
|
|
|
{
|
2021-06-04 22:47:40 +02:00
|
|
|
setHeader(request);
|
2020-07-02 19:14:07 +02:00
|
|
|
return manager->get(request);
|
|
|
|
}
|
2021-06-04 22:47:40 +02:00
|
|
|
|
2021-10-29 17:00:52 +02:00
|
|
|
QNetworkReply *Fetcher::post(QNetworkRequest &request, const QByteArray &data) const
|
|
|
|
{
|
|
|
|
setHeader(request);
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
|
|
|
|
return manager->post(request, data);
|
|
|
|
}
|
|
|
|
|
2021-06-04 22:47:40 +02:00
|
|
|
QNetworkReply *Fetcher::head(QNetworkRequest &request) const
|
|
|
|
{
|
|
|
|
setHeader(request);
|
|
|
|
return manager->head(request);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Fetcher::setHeader(QNetworkRequest &request) const
|
|
|
|
{
|
2021-07-13 16:27:27 +02:00
|
|
|
request.setRawHeader(QByteArray("User-Agent"), QByteArray("Kasts/") + QByteArray(KASTS_VERSION_STRING) + QByteArray("; Syndication"));
|
2021-06-23 22:29:15 +02:00
|
|
|
}
|