File indexing completed on 2024-05-12 16:21:30
0001 /** 0002 * SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org> 0003 * SPDX-FileCopyrightText: 2021-2022 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 "fetcher.h" 0009 #include "fetcherlogging.h" 0010 0011 #include <KLocalizedString> 0012 #include <QDateTime> 0013 #include <QDebug> 0014 #include <QDir> 0015 #include <QFile> 0016 #include <QFileInfo> 0017 #include <QNetworkAccessManager> 0018 #include <QNetworkProxyFactory> 0019 #include <QNetworkReply> 0020 #include <QTime> 0021 #include <QTimer> 0022 0023 #include "database.h" 0024 #include "enclosure.h" 0025 #include "fetchfeedsjob.h" 0026 #include "kasts-version.h" 0027 #include "models/errorlogmodel.h" 0028 #include "networkconnectionmanager.h" 0029 #include "settingsmanager.h" 0030 #include "storagemanager.h" 0031 #include "sync/sync.h" 0032 0033 Fetcher::Fetcher() 0034 { 0035 connect(this, &Fetcher::error, &ErrorLogModel::instance(), &ErrorLogModel::monitorErrorMessages); 0036 0037 m_updateProgress = -1; 0038 m_updateTotal = -1; 0039 m_updating = false; 0040 0041 manager = new QNetworkAccessManager(this); 0042 manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 0043 manager->setStrictTransportSecurityEnabled(true); 0044 manager->enableStrictTransportSecurityStore(true); 0045 0046 QNetworkProxyFactory::setUseSystemConfiguration(true); 0047 } 0048 0049 void Fetcher::fetch(const QString &url) 0050 { 0051 QStringList urls(url); 0052 fetch(urls); 0053 } 0054 0055 void Fetcher::fetchAll() 0056 { 0057 if (Sync::instance().syncEnabled() && SettingsManager::self()->syncWhenUpdatingFeeds()) { 0058 Sync::instance().doRegularSync(true); 0059 } else { 0060 QStringList urls; 0061 QSqlQuery query; 0062 query.prepare(QStringLiteral("SELECT url FROM Feeds;")); 0063 Database::instance().execute(query); 0064 while (query.next()) { 0065 urls += query.value(0).toString(); 0066 } 0067 0068 if (urls.count() > 0) { 0069 fetch(urls); 0070 } 0071 } 0072 } 0073 0074 void Fetcher::fetch(const QStringList &urls) 0075 { 0076 if (m_updating) 0077 return; // update is already running, do nothing 0078 0079 m_updating = true; 0080 m_updateProgress = 0; 0081 m_updateTotal = urls.count(); 0082 Q_EMIT updatingChanged(m_updating); 0083 Q_EMIT updateProgressChanged(m_updateProgress); 0084 Q_EMIT updateTotalChanged(m_updateTotal); 0085 0086 qCDebug(kastsFetcher) << "Create fetchFeedsJob"; 0087 FetchFeedsJob *fetchFeedsJob = new FetchFeedsJob(urls, this); 0088 connect(this, &Fetcher::cancelFetching, fetchFeedsJob, &FetchFeedsJob::abort); 0089 connect(fetchFeedsJob, &FetchFeedsJob::processedAmountChanged, this, [this](KJob *job, KJob::Unit unit, qulonglong amount) { 0090 qCDebug(kastsFetcher) << "FetchFeedsJob::processedAmountChanged:" << amount; 0091 Q_UNUSED(job); 0092 Q_ASSERT(unit == KJob::Unit::Items); 0093 m_updateProgress = amount; 0094 Q_EMIT updateProgressChanged(m_updateProgress); 0095 }); 0096 connect(fetchFeedsJob, &FetchFeedsJob::result, this, [this, fetchFeedsJob]() { 0097 qCDebug(kastsFetcher) << "result slot of FetchFeedsJob"; 0098 if (fetchFeedsJob->error() && !fetchFeedsJob->aborted()) { 0099 Q_EMIT error(Error::Type::FeedUpdate, QString(), QString(), fetchFeedsJob->error(), fetchFeedsJob->errorString(), QString()); 0100 } 0101 if (m_updating) { 0102 m_updating = false; 0103 Q_EMIT updatingChanged(m_updating); 0104 } 0105 }); 0106 0107 fetchFeedsJob->start(); 0108 qCDebug(kastsFetcher) << "end of Fetcher::fetch"; 0109 } 0110 0111 QString Fetcher::image(const QString &url) 0112 { 0113 if (url.isEmpty()) { 0114 return QLatin1String("no-image"); 0115 } 0116 0117 // if image is already cached, then return the path 0118 QString path = StorageManager::instance().imagePath(url); 0119 if (QFileInfo::exists(path)) { 0120 if (QFileInfo(path).size() != 0) { 0121 return QUrl::fromLocalFile(path).toString(); 0122 } 0123 } 0124 0125 // avoid restarting an image download if it's already running 0126 if (m_ongoingImageDownloads.contains(url)) { 0127 return QLatin1String("fetching"); 0128 } 0129 0130 // if image has not yet been cached, then check for network connectivity if 0131 // possible; and download the image 0132 if (!NetworkConnectionManager::instance().imageDownloadsAllowed()) { 0133 return QLatin1String("no-image"); 0134 } 0135 0136 m_ongoingImageDownloads.insert(url); 0137 QNetworkRequest request((QUrl(url))); 0138 request.setTransferTimeout(); 0139 QNetworkReply *reply = get(request); 0140 connect(reply, &QNetworkReply::finished, this, [=]() { 0141 if (reply->isOpen() && !reply->error()) { 0142 QByteArray data = reply->readAll(); 0143 QFile file(path); 0144 file.open(QIODevice::WriteOnly); 0145 file.write(data); 0146 file.close(); 0147 Q_EMIT downloadFinished(url); 0148 } 0149 m_ongoingImageDownloads.remove(url); 0150 reply->deleteLater(); 0151 }); 0152 return QLatin1String("fetching"); 0153 } 0154 0155 QNetworkReply *Fetcher::download(const QString &url, const QString &filePath) const 0156 { 0157 QNetworkRequest request((QUrl(url))); 0158 request.setTransferTimeout(); 0159 0160 QFile *file = new QFile(filePath); 0161 if (file->exists() && file->size() > 0) { 0162 // try to resume download 0163 int resumedAt = file->size(); 0164 qCDebug(kastsFetcher) << "Resuming download at" << resumedAt << "bytes"; 0165 QByteArray rangeHeaderValue = QByteArray("bytes=") + QByteArray::number(resumedAt) + QByteArray("-"); 0166 request.setRawHeader(QByteArray("Range"), rangeHeaderValue); 0167 file->open(QIODevice::WriteOnly | QIODevice::Append); 0168 } else { 0169 qCDebug(kastsFetcher) << "Starting new download"; 0170 file->open(QIODevice::WriteOnly); 0171 } 0172 0173 QNetworkReply *reply = get(request); 0174 0175 connect(reply, &QNetworkReply::readyRead, this, [=]() { 0176 if (reply->isOpen() && file) { 0177 QByteArray data = reply->readAll(); 0178 file->write(data); 0179 } 0180 }); 0181 0182 connect(reply, &QNetworkReply::finished, this, [=]() { 0183 if (reply->isOpen() && file) { 0184 QByteArray data = reply->readAll(); 0185 file->write(data); 0186 file->close(); 0187 0188 Q_EMIT downloadFinished(url); 0189 } 0190 0191 // clean up; close file if still open in case something has gone wrong 0192 if (file) { 0193 if (file->isOpen()) { 0194 file->close(); 0195 } 0196 delete file; 0197 } 0198 reply->deleteLater(); 0199 }); 0200 0201 return reply; 0202 } 0203 0204 void Fetcher::getRedirectedUrl(const QUrl &url) 0205 { 0206 QNetworkRequest request((QUrl(url))); 0207 request.setTransferTimeout(5000); // wait 5 seconds; it will fall back to original url otherwise 0208 0209 QNetworkReply *reply = head(request); 0210 0211 connect(reply, &QNetworkReply::finished, this, [this, reply, url]() { 0212 qCDebug(kastsFetcher) << "finished looking for redirect; this is the old url and the redirected url:" << url << reply->url(); 0213 0214 QUrl newUrl = reply->url(); 0215 QTimer::singleShot(0, this, [this, url, newUrl]() { 0216 Q_EMIT foundRedirectedUrl(url, newUrl); 0217 }); 0218 reply->deleteLater(); 0219 }); 0220 } 0221 0222 QNetworkReply *Fetcher::get(QNetworkRequest &request) const 0223 { 0224 setHeader(request); 0225 return manager->get(request); 0226 } 0227 0228 QNetworkReply *Fetcher::post(QNetworkRequest &request, const QByteArray &data) const 0229 { 0230 setHeader(request); 0231 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0232 return manager->post(request, data); 0233 } 0234 0235 QNetworkReply *Fetcher::head(QNetworkRequest &request) const 0236 { 0237 setHeader(request); 0238 return manager->head(request); 0239 } 0240 0241 void Fetcher::setHeader(QNetworkRequest &request) const 0242 { 0243 request.setRawHeader(QByteArray("User-Agent"), QByteArray("Kasts/") + QByteArray(KASTS_VERSION_STRING) + QByteArray(" Syndication")); 0244 }