2 #include <QCoreApplication>
8 #include <QNetworkCookie>
9 #include <QAuthenticator>
10 #include <QTextStream>
12 #include <QNetworkProxy>
13 #include <QMutexLocker>
27 #include "mythversion.h"
35 #define LOC QString("DownloadManager: ")
50 qRegisterMetaType<QNetworkReply::NetworkError>(
"QNetworkReply::NetworkError");
62 QMutexLocker lock(&
m_lock);
68 QMutexLocker lock(&
m_lock);
92 const QHash<QByteArray, QByteArray> *
m_headers {
nullptr};
94 QNetworkReply::NetworkError
m_errorCode {QNetworkReply::NoError};
160 while (!tmpDLM->getQueueThread())
163 tmpDLM->moveToThread(tmpDLM->getQueueThread());
164 tmpDLM->setRunThread();
166 while (!tmpDLM->isRunning())
196 bool downloading =
false;
197 bool itemsInQueue =
false;
198 bool itemsInCancellationQueue =
false;
199 bool waitAnyway =
false;
206 m_manager =
new QNetworkAccessManager(
this);
210 QCoreApplication::applicationName() +
"-" +
220 m_manager->cookieJar()->setParent(
nullptr);
222 QObject::connect(
m_manager, &QNetworkAccessManager::finished,
230 LOG(VB_GENERAL, LOG_DEBUG,
"Updating DLManager's Cookie Jar");
240 if (itemsInCancellationQueue)
245 QCoreApplication::processEvents();
251 if (!itemsInQueue || waitAnyway)
258 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"waiting 200ms"));
263 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"waiting for more items to download"));
295 if (dlInfo->
m_url.startsWith(
"myth://"))
323 const QString &
dest, QByteArray *data,
330 dlInfo->m_request = req;
331 dlInfo->m_outFile =
dest;
332 dlInfo->m_data = data;
333 dlInfo->m_caller = caller;
334 dlInfo->m_requestType = reqType;
335 dlInfo->m_reload = reload;
355 const QString &
dest, QByteArray *data,
359 const QHash<QByteArray, QByteArray> *
headers,
365 dlInfo->m_request = req;
366 dlInfo->m_outFile =
dest;
367 dlInfo->m_data = data;
368 dlInfo->m_requestType = reqType;
369 dlInfo->m_reload = reload;
370 dlInfo->m_syncMode =
true;
371 dlInfo->m_authCallback = authCallbackFn;
372 dlInfo->m_authArg = authArg;
374 dlInfo->m_finalUrl = finalUrl;
384 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"preCache('%1')").arg(url));
385 queueItem(url,
nullptr, QString(),
nullptr,
nullptr);
399 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"queueDownload('%1', '%2', %3)")
400 .arg(url,
dest, QString::number((
long long)caller)));
414 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"queueDownload('%1', '%2', %3)")
415 .arg(req->url().toString()).arg((
long long)data)
416 .arg((
long long)caller));
418 queueItem(req->url().toString(), req, QString(), data, caller,
420 (QNetworkRequest::AlwaysNetwork == req->attribute(
421 QNetworkRequest::CacheLoadControlAttribute,
422 QNetworkRequest::PreferNetwork).toInt()));
444 const bool reload, QString *finalUrl)
448 nullptr,
nullptr,
nullptr, &redirected))
450 if (!redirected.isEmpty() && finalUrl !=
nullptr)
451 *finalUrl = redirected;
465 QNetworkReply *reply =
nullptr;
468 dlInfo->m_reload = reload;
469 dlInfo->m_syncMode =
true;
470 dlInfo->m_processReply =
false;
476 reply = dlInfo->m_reply;
478 dlInfo->m_reply =
nullptr;
498 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"download('%1', '%2')")
499 .arg(req->url().toString()).arg((
long long)data));
500 return processItem(req->url().toString(), req, QString(), data,
502 (QNetworkRequest::AlwaysNetwork == req->attribute(
503 QNetworkRequest::CacheLoadControlAttribute,
504 QNetworkRequest::PreferNetwork).toInt()));
517 const bool reload,
AuthCallback authCallbackFn,
void *authArg,
518 const QHash<QByteArray, QByteArray> *
headers)
534 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"queuePost('%1', '%2')")
535 .arg(url).arg((
long long)data));
539 LOG(VB_GENERAL, LOG_ERR,
LOC +
"queuePost(), data is NULL!");
555 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"queuePost('%1', '%2')")
556 .arg(req->url().toString()).arg((
long long)data));
560 LOG(VB_GENERAL, LOG_ERR,
LOC +
"queuePost(), data is NULL!");
564 queueItem(req->url().toString(), req, QString(), data, caller,
566 (QNetworkRequest::AlwaysNetwork == req->attribute(
567 QNetworkRequest::CacheLoadControlAttribute,
568 QNetworkRequest::PreferNetwork).toInt()));
579 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"post('%1', '%2')")
580 .arg(url).arg((
long long)data));
584 LOG(VB_GENERAL, LOG_ERR,
LOC +
"post(), data is NULL!");
598 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"post('%1', '%2')")
599 .arg(req->url().toString()).arg((
long long)data));
603 LOG(VB_GENERAL, LOG_ERR,
LOC +
"post(), data is NULL!");
607 return processItem(req->url().toString(), req, QString(), data,
609 (QNetworkRequest::AlwaysNetwork == req->attribute(
610 QNetworkRequest::CacheLoadControlAttribute,
611 QNetworkRequest::PreferNetwork).toInt()));
625 const QHash<QByteArray, QByteArray> *
headers)
627 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"postAuth('%1', '%2')")
628 .arg(url).arg((
long long)data));
632 LOG(VB_GENERAL, LOG_ERR,
LOC +
"postAuth(), data is NULL!");
657 static const QString kDateFormat =
"ddd, dd MMM yyyy hh:mm:ss 'GMT'";
658 QUrl qurl(dlInfo->
m_url);
659 QNetworkRequest request;
669 request.setUrl(qurl);
674 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
675 QNetworkRequest::AlwaysNetwork);
686 while (!(redirectLoc =
getHeader(qurl,
"Location")).isNull())
690 LOG(VB_GENERAL, LOG_WARNING, QString(
"Cache Redirection limit "
692 .arg(qurl.toString()));
695 qurl.setUrl(redirectLoc);
699 LOG(VB_NETWORK, LOG_DEBUG, QString(
"Checking cache for %1")
700 .arg(qurl.toString()));
703 QNetworkCacheMetaData urlData =
m_manager->cache()->metaData(qurl);
705 if ((urlData.isValid()) &&
706 ((!urlData.expirationDate().isValid()) ||
707 (urlData.expirationDate().toUTC().secsTo(now) < 10)))
709 QString dateString =
getHeader(urlData,
"Date");
711 if (!dateString.isNull())
715 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
716 loadDate.setTimeSpec(Qt::UTC);
718 loadDate.setTimeZone(QTimeZone(QTimeZone::UTC));
720 if (loadDate.secsTo(now) <= 720)
723 LOG(VB_NETWORK, LOG_DEBUG, QString(
"Preferring cache for %1")
724 .arg(qurl.toString()));
731 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
732 QNetworkRequest::PreferCache);
734 if (!request.hasRawHeader(
"User-Agent"))
736 request.setRawHeader(
"User-Agent",
737 QByteArray(
"MythTV v") + MYTH_BINARY_VERSION +
738 " MythDownloadManager");
743 QHash<QByteArray, QByteArray>::const_iterator it =
745 for ( ; it != dlInfo->
m_headers->constEnd(); ++it )
747 if (!it.key().isEmpty() && !it.value().isEmpty())
749 request.setRawHeader(it.key(), it.value());
772 connect(
m_manager, &QNetworkAccessManager::authenticationRequired,
776 connect(dlInfo->
m_reply, &QNetworkReply::errorOccurred,
778 connect(dlInfo->
m_reply, &QNetworkReply::downloadProgress,
787 QAuthenticator *authenticator)
799 LOG(VB_FILE, LOG_DEBUG,
"Calling auth callback");
821 if (dlInfo->
m_url.startsWith(
"http://[fe80::",Qt::CaseInsensitive))
822 return downloadNowLinkLocal(dlInfo, deleteInfo);
834 while ((!dlInfo->
IsDone()) &&
836 (((!dlInfo->
m_url.startsWith(
"myth://")) &&
838 ((dlInfo->
m_url.startsWith(
"myth://")) &&
847 bool done = dlInfo->
IsDone();
849 done && (dlInfo->
m_errorCode == QNetworkReply::NoError);
858 LOG(VB_FILE, LOG_DEBUG,
859 LOC + QString(
"Aborting download - lack of data transfer"));
894 bool MythDownloadManager::downloadNowLinkLocal(
MythDownloadInfo *dlInfo,
bool deleteInfo)
901 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"No data buffer provided for %1").arg(dlInfo->
m_url));
908 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Unsupported authentication for %1").arg(dlInfo->
m_url));
914 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Unsupported File output %1 for %2")
923 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Unsupported link-local operation %1")
924 .arg(dlInfo->
m_url));
928 QUrl url(dlInfo->
m_url);
929 QString host(url.host());
930 int port(url.port(80));
944 QByteArray* buffer = dlInfo->
m_data;
945 QHash<QByteArray, QByteArray>
headers;
948 if (!
headers.contains(
"User-Agent"))
949 headers.insert(
"User-Agent", QByteArray(
"MythDownloadManager v") +
950 MYTH_BINARY_VERSION);
951 headers.insert(
"Connection",
"close");
952 headers.insert(
"Accept-Encoding",
"identity");
953 if (!buffer->isEmpty())
954 headers.insert(
"Content-Length", QString::number(buffer->size()).toUtf8());
955 headers.insert(
"Host", (url.host() +
":" + QString::number(port)).toUtf8());
957 QByteArray requestMessage;
958 QString path (url.path());
959 requestMessage.append(
"POST ");
960 requestMessage.append(path.toLatin1());
961 requestMessage.append(
" HTTP/1.1\r\n");
962 QHashIterator<QByteArray, QByteArray> it(
headers);
966 requestMessage.append(it.key());
967 requestMessage.append(
": ");
968 requestMessage.append(it.value());
969 requestMessage.append(
"\r\n");
971 requestMessage.append(
"\r\n");
972 if (!buffer->isEmpty())
973 requestMessage.append(*buffer);
976 socket.connectToHost(host,
static_cast<uint16_t>(port));
978 if (!socket.waitForConnected(5000))
981 ok = socket.write(requestMessage) > 0;
984 ok = socket.waitForDisconnected(5000);
987 *buffer = socket.readAll();
989 QByteArray delim(
"\r\n\r\n");
990 int delimLoc = buffer->indexOf(delim);
992 *buffer = buffer->right(buffer->size() - delimLoc - 4);
1009 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Link Local request failed: %1").arg(url.toString()));
1030 for (
const auto& url : std::as_const(urls))
1033 while (lit.hasNext())
1037 if (dlInfo->
m_url == url)
1060 if (QThread::currentThread() == this->thread())
1083 while (lit.hasNext())
1091 LOG(VB_FILE, LOG_DEBUG,
1092 LOC + QString(
"Aborting download - user request"));
1101 dlInfo->
m_errorCode = QNetworkReply::OperationCanceledError;
1123 dlInfo->
m_data =
nullptr;
1127 QMap <QString, MythDownloadInfo*>::iterator mit =
m_downloadInfos.begin();
1135 dlInfo->
m_data =
nullptr;
1145 auto *reply = qobject_cast<QNetworkReply *>(sender());
1146 if (reply ==
nullptr)
1149 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"downloadError %1 ")
1150 .arg(errorCode) + reply->errorString() );
1155 reply->deleteLater();
1173 const QUrl& oldRedirectUrl)
1175 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"redirectUrl()"));
1178 if(!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl)
1189 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"downloadFinished(%1)")
1190 .arg((
long long)reply));
1195 reply->deleteLater();
1201 if (!dlInfo || !dlInfo->
m_reply)
1215 int statusCode = -1;
1216 static const QString kDateFormat =
"ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1217 QNetworkReply *reply = dlInfo->
m_reply;
1221 QUrl possibleRedirectUrl =
1222 reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
1224 if (!possibleRedirectUrl.isEmpty() &&
1225 possibleRedirectUrl.isValid() &&
1226 possibleRedirectUrl.isRelative())
1227 possibleRedirectUrl = reply->url().resolved(possibleRedirectUrl);
1229 if (!possibleRedirectUrl.isEmpty() && dlInfo->
m_finalUrl !=
nullptr)
1230 *dlInfo->
m_finalUrl = QString(possibleRedirectUrl.toString());
1236 reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
1237 if (status.isValid())
1238 statusCode = status.toInt();
1243 (statusCode == 301 || statusCode == 302 ||
1244 statusCode == 303)))
1246 LOG(VB_FILE, LOG_DEBUG,
LOC +
1247 QString(
"downloadFinished(%1): Redirect: %2 -> %3")
1248 .arg(QString::number((
long long)dlInfo),
1249 reply->url().toString(),
1250 dlInfo->m_redirectedTo.toString()));
1253 dlInfo->m_data->clear();
1255 dlInfo->m_bytesReceived = 0;
1256 dlInfo->m_bytesTotal = 0;
1258 QNetworkRequest request(dlInfo->m_redirectedTo);
1260 if (dlInfo->m_reload)
1262 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1263 QNetworkRequest::AlwaysNetwork);
1265 else if (dlInfo->m_preferCache)
1267 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1268 QNetworkRequest::PreferCache);
1271 request.setRawHeader(
"User-Agent",
1272 "MythDownloadManager v" +
1273 QByteArray(MYTH_BINARY_VERSION));
1275 switch (dlInfo->m_requestType)
1278 dlInfo->m_reply =
m_manager->head(request);
1282 dlInfo->m_reply =
m_manager->get(request);
1288 connect(dlInfo->m_reply, &QNetworkReply::errorOccurred,
1290 connect(dlInfo->m_reply, &QNetworkReply::downloadProgress,
1294 reply->deleteLater();
1298 LOG(VB_FILE, LOG_DEBUG, QString(
"downloadFinished(%1): COMPLETE: %2")
1299 .arg((
long long)dlInfo).arg(dlInfo->m_url));
1303 QUrl fileUrl = dlInfo->m_url;
1304 QString redirectLoc;
1306 while (!(redirectLoc =
getHeader(fileUrl,
"Location")).isNull())
1310 LOG(VB_GENERAL, LOG_WARNING, QString(
"Cache Redirection limit "
1312 .arg(fileUrl.toString()));
1315 fileUrl.setUrl(redirectLoc);
1320 QNetworkCacheMetaData urlData =
m_manager->cache()->metaData(fileUrl);
1322 if (
getHeader(urlData,
"Date").isNull())
1324 QNetworkCacheMetaData::RawHeaderList
headers = urlData.rawHeaders();
1325 QNetworkCacheMetaData::RawHeader newheader;
1327 newheader = QNetworkCacheMetaData::RawHeader(
"Date",
1328 now.toString(kDateFormat).toLatin1());
1330 urlData.setRawHeaders(
headers);
1332 m_manager->cache()->updateMetaData(urlData);
1337 dlInfo->m_redirectedTo.clear();
1343 if (reply && dlInfo->m_processReply)
1345 bool append = (!dlInfo->m_syncMode && dlInfo->m_caller);
1346 QByteArray data = reply->readAll();
1347 dataSize = data.size();
1350 dlInfo->m_bytesReceived += dataSize;
1352 dlInfo->m_bytesReceived = dataSize;
1354 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1359 dlInfo->m_data->append(data);
1361 *dlInfo->m_data = data;
1363 else if (!dlInfo->m_outFile.isEmpty())
1365 saveFile(dlInfo->m_outFile, data, append);
1372 (*dlInfo->m_data) = dlInfo->m_privData;
1374 else if (!dlInfo->m_outFile.isEmpty())
1376 saveFile(dlInfo->m_outFile, dlInfo->m_privData);
1378 dlInfo->m_bytesReceived += dataSize;
1379 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1387 LOG(VB_GENERAL, LOG_ERR,
LOC +
1388 QString(
"ERROR download finished but failed to remove url: %1")
1389 .arg(dlInfo->m_url));
1396 dlInfo->SetDone(
true);
1398 if (!dlInfo->m_syncMode)
1400 if (dlInfo->m_caller)
1402 LOG(VB_FILE, LOG_DEBUG, QString(
"downloadFinished(%1): "
1403 "COMPLETE: %2, sending event to caller")
1404 .arg((
long long)dlInfo).arg(dlInfo->m_url));
1407 args << dlInfo->m_url;
1408 args << dlInfo->m_outFile;
1409 args << QString::number(dlInfo->m_bytesTotal);
1411 args << (reply ? reply->errorString() : QString());
1412 args << QString::number((
int)(reply ? reply->error() :
1413 dlInfo->m_errorCode));
1415 QCoreApplication::postEvent(dlInfo->m_caller,
1434 auto *reply = qobject_cast<QNetworkReply *>(sender());
1435 if (reply ==
nullptr)
1438 LOG(VB_FILE, LOG_DEBUG,
LOC +
1439 QString(
"downloadProgress(%1, %2) (for reply %3)")
1440 .arg(bytesReceived).arg(bytesTotal).arg((
long long)reply));
1453 LOG(VB_FILE, LOG_DEBUG,
LOC +
1454 QString(
"downloadProgress: %1 to %2 is at %3 of %4 bytes downloaded")
1456 .arg(bytesReceived).arg(bytesTotal));
1460 LOG(VB_FILE, LOG_DEBUG, QString(
"downloadProgress(%1): "
1461 "sending event to caller")
1462 .arg(reply->url().toString()));
1465 QByteArray data = reply->readAll();
1470 dlInfo->
m_data->append(data);
1478 args << QString::number(bytesReceived);
1479 args << QString::number(bytesTotal);
1481 QCoreApplication::postEvent(dlInfo->
m_caller,
1494 const QByteArray &data,
1497 if (outFile.isEmpty() || data.isEmpty())
1500 QFile
file(outFile);
1501 QFileInfo fileInfo(outFile);
1502 QDir qdir(fileInfo.absolutePath());
1504 if (!qdir.exists() && !qdir.mkpath(fileInfo.absolutePath()))
1506 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to create: '%1'")
1507 .arg(fileInfo.absolutePath()));
1511 QIODevice::OpenMode mode = QIODevice::Unbuffered|QIODevice::WriteOnly;
1513 mode |= QIODevice::Append;
1515 if (!
file.open(mode))
1517 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to open: '%1'") .arg(outFile));
1522 size_t remaining = data.size();
1523 uint failure_cnt = 0;
1524 while ((remaining > 0) && (failure_cnt < 5))
1526 ssize_t written =
file.write(data.data() + offset, remaining);
1536 remaining -= written;
1539 return remaining <= 0;
1553 static const QString kDateFormat =
"ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1554 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1')").arg(url));
1559 QUrl cacheUrl = QUrl(url);
1562 QString redirectLoc;
1564 while (!(redirectLoc =
getHeader(cacheUrl,
"Location")).isNull())
1568 LOG(VB_GENERAL, LOG_WARNING, QString(
"Cache Redirection limit "
1570 .arg(cacheUrl.toString()));
1573 cacheUrl.setUrl(redirectLoc);
1578 QNetworkCacheMetaData urlData =
m_manager->cache()->metaData(cacheUrl);
1581 if (urlData.isValid() &&
1582 ((!urlData.expirationDate().isValid()) ||
1583 (urlData.expirationDate().secsTo(now) < 0)))
1585 if (urlData.lastModified().toUTC().secsTo(now) <= 3600)
1587 result = urlData.lastModified().toUTC();
1591 QString date =
getHeader(urlData,
"Date");
1594 QDateTime loadDate =
1596 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1597 loadDate.setTimeSpec(Qt::UTC);
1599 loadDate.setTimeZone(QTimeZone(QTimeZone::UTC));
1601 if (loadDate.secsTo(now) <= 1200)
1603 result = urlData.lastModified().toUTC();
1609 if (!result.isValid())
1612 dlInfo->
m_url = url;
1623 QNetworkRequest::LastModifiedHeader);
1624 if (lastMod.isValid())
1625 result = lastMod.toDateTime().toUTC();
1634 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1'): Result %2")
1635 .arg(url, result.toString()));
1663 auto *jar = qobject_cast<MythCookieJar *>(
m_manager->cookieJar());
1685 auto *inJar = qobject_cast<MythCookieJar *>(
m_manager->cookieJar());
1686 if (inJar ==
nullptr)
1702 auto *inJar = qobject_cast<MythCookieJar *>(jar);
1703 if (inJar ==
nullptr)
1721 if (inJar !=
nullptr)
1738 QNetworkCacheMetaData metadata =
m_manager->cache()->metaData(url);
1750 const QString& header)
1752 auto headers = cacheData.rawHeaders();
1753 for (
const auto& rh : std::as_const(
headers))
1754 if (QString(rh.first) == header)
1765 const QList<QNetworkCookie> cookieList = old.allCookies();
1766 setAllCookies(cookieList);
1774 LOG(VB_GENERAL, LOG_DEBUG, QString(
"MythCookieJar: loading cookies from: %1").arg(
filename));
1777 if (!f.open(QIODevice::ReadOnly))
1779 LOG(VB_GENERAL, LOG_WARNING, QString(
"MythCookieJar::load() failed to open file for reading: %1").arg(
filename));
1783 QList<QNetworkCookie> cookieList;
1784 QTextStream stream(&f);
1785 while (!stream.atEnd())
1787 QString cookie = stream.readLine();
1788 cookieList << QNetworkCookie::parseCookies(cookie.toLocal8Bit());
1791 setAllCookies(cookieList);
1799 LOG(VB_GENERAL, LOG_DEBUG, QString(
"MythCookieJar: saving cookies to: %1").arg(
filename));
1802 if (!f.open(QIODevice::WriteOnly))
1804 LOG(VB_GENERAL, LOG_ERR, QString(
"MythCookieJar::save() failed to open file for writing: %1").arg(
filename));
1808 QList<QNetworkCookie> cookieList = allCookies();
1809 QTextStream stream(&f);
1811 for (
const auto& cookie : std::as_const(cookieList))
1812 stream << cookie.toRawForm() << Qt::endl;