LCOV - code coverage report
Current view: top level - src/network - NetworkDownloadCore.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 97.7 % 128 125
Test Date: 2026-03-23 10:19:47 Functions: 100.0 % 11 11

            Line data    Source code
       1              : #include "NetworkDownloadCore.h"
       2              : #include "FangNetworkAccessManager.h"
       3              : 
       4              : #include "../utilities/FangLogging.h"
       5              : 
       6          260 : NetworkDownloadCore::NetworkDownloadCore(NetworkDownloadConfig config,
       7              :                                           QObject* parent,
       8          260 :                                           QNetworkAccessManager* networkManager)
       9              :     : FangObject(parent)
      10          260 :     , manager(networkManager ? networkManager : new FangNetworkAccessManager(this))
      11          260 :     , ownsManager(networkManager == nullptr)
      12          260 :     , config(config)
      13          260 :     , currentReply(nullptr)
      14          260 :     , redirectCount(0)
      15          260 :     , currentAttemptCount(0)
      16          520 :     , lastError(QNetworkReply::NoError)
      17              : {
      18          260 :     inactivityTimer.setSingleShot(true);
      19          260 :     retryTimer.setSingleShot(true);
      20              : 
      21          260 :     connect(manager, &QNetworkAccessManager::finished, this, &NetworkDownloadCore::onRequestFinished);
      22          260 :     connect(&inactivityTimer, &QTimer::timeout, this, &NetworkDownloadCore::onTimeout);
      23          260 :     connect(&retryTimer, &QTimer::timeout, this, &NetworkDownloadCore::onRetryTimer);
      24          260 : }
      25              : 
      26          494 : NetworkDownloadCore::~NetworkDownloadCore()
      27              : {
      28              :     // Disconnect from manager to avoid receiving signals during destruction. This prevents
      29              :     // signals from being emitted on the object as its being destructed.
      30          260 :     if (manager) {
      31          236 :         disconnect(manager, &QNetworkAccessManager::finished, this, &NetworkDownloadCore::onRequestFinished);
      32              :     }
      33              : 
      34          260 :     retryTimer.stop();
      35              : 
      36          260 :     if (currentReply) {
      37            0 :         currentReply->abort();
      38            0 :         currentReply->deleteLater();
      39            0 :         currentReply = nullptr;
      40              :     }
      41          494 : }
      42              : 
      43          112 : void NetworkDownloadCore::download(const QUrl& url)
      44              : {
      45              :     // Reset counters for new download.
      46          112 :     redirectCount = 0;
      47          112 :     currentAttemptCount = 1;  // First attempt
      48          112 :     originalUrl = url;
      49          112 :     lastError = QNetworkReply::NoError;
      50          112 :     lastErrorString.clear();
      51              : 
      52              :     // Stop any pending retries.
      53          112 :     retryTimer.stop();
      54              : 
      55          112 :     downloadInternal(url);
      56          112 : }
      57              : 
      58          142 : void NetworkDownloadCore::downloadInternal(const QUrl& url)
      59              : {
      60              :     // Clean up existing reply if needed.
      61          142 :     if (currentReply) {
      62              :         // Clear currentReply BEFORE aborting to prevent onRequestFinished from processing.
      63           25 :         QNetworkReply* oldReply = currentReply;
      64           25 :         currentReply = nullptr;
      65           25 :         oldReply->disconnect(this);
      66           25 :         oldReply->abort();
      67           25 :         oldReply->deleteLater();
      68              :     }
      69              : 
      70              :     // Stop any existing timer.
      71          142 :     inactivityTimer.stop();
      72              : 
      73              :     // Validate URL.
      74          142 :     if (url.isRelative()) {
      75            2 :         emit error(originalUrl, "Relative URLs are not allowed");
      76            2 :         return;
      77              :     }
      78              : 
      79              :     // Create and send request.
      80          140 :     QNetworkRequest request(url);
      81              : 
      82              :     // Configure timeout based on mode.
      83          140 :     if (!config.useInactivityTimeout) {
      84            1 :         request.setTransferTimeout(config.timeoutMs);
      85              :     }
      86              : 
      87          140 :     currentReply = manager->get(request);
      88              : 
      89              :     // Connect progress signal for inactivity timeout reset.
      90          140 :     if (config.useInactivityTimeout) {
      91          139 :         connect(currentReply, &QNetworkReply::downloadProgress,
      92          139 :                 this, &NetworkDownloadCore::onDownloadProgress);
      93          139 :         inactivityTimer.start(config.timeoutMs);
      94              :     }
      95          140 : }
      96              : 
      97            2 : void NetworkDownloadCore::abort()
      98              : {
      99            2 :     inactivityTimer.stop();
     100            2 :     retryTimer.stop();
     101              : 
     102            2 :     if (currentReply) {
     103              :         // Clear currentReply BEFORE aborting to prevent onRequestFinished from processing.
     104            1 :         QNetworkReply* reply = currentReply;
     105            1 :         currentReply = nullptr;
     106              : 
     107            1 :         reply->abort();
     108            1 :         reply->deleteLater();
     109              :     }
     110            2 : }
     111              : 
     112          359 : void NetworkDownloadCore::onRequestFinished(QNetworkReply* reply)
     113              : {
     114              :     // Sanity check: Only process *our* reply.
     115          359 :     if (reply != currentReply) {
     116          279 :         return;
     117              :     }
     118              : 
     119          135 :     inactivityTimer.stop();
     120              : 
     121              :     // Check for network errors.
     122          135 :     if (reply->error() != QNetworkReply::NoError) {
     123           29 :         lastError = reply->error();
     124           29 :         lastErrorString = reply->errorString();
     125           58 :         qCDebug(logNetwork) << "NetworkDownloadCore error:" << lastErrorString;
     126              : 
     127              :         // Clean up the reply.
     128           29 :         currentReply = nullptr;
     129           29 :         reply->deleteLater();
     130              : 
     131              :         // Check if we should retry.
     132           35 :         if (config.retryPolicy.shouldRetry(currentAttemptCount) &&
     133            6 :             config.retryPolicy.isRetryable(lastError)) {
     134            5 :             scheduleRetry();
     135            5 :             return;
     136              :         }
     137              : 
     138              :         // No more retries - emit error.
     139           24 :         emit error(originalUrl, lastErrorString);
     140           24 :         return;
     141              :     }
     142              : 
     143              :     // Check for HTTP redirect.
     144          106 :     QVariant redirectVariant = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
     145          106 :     if (!redirectVariant.isNull()) {
     146           26 :         if (redirectCount >= config.maxRedirects) {
     147            2 :             currentReply = nullptr;  // Clean up before emitting signal.
     148            2 :             reply->deleteLater();
     149            2 :             emit error(originalUrl, "Maximum HTTP redirects exceeded");
     150            2 :             return;
     151              :         }
     152              : 
     153           24 :         redirectCount++;
     154           24 :         QUrl redirectUrl = redirectVariant.toUrl();
     155              : 
     156              :         // Handle relative redirects.
     157           24 :         if (redirectUrl.isRelative()) {
     158            2 :             redirectUrl = reply->url().resolved(redirectUrl);
     159              :         }
     160              : 
     161           24 :         downloadInternal(redirectUrl);
     162           24 :         return;
     163           24 :     }
     164              : 
     165              :     // Success!
     166           80 :     QUrl finalUrl = reply->url();
     167           80 :     QByteArray data = reply->readAll();
     168           80 :     currentReply = nullptr;  // Clean up before emitting signal.
     169           80 :     reply->deleteLater();
     170           80 :     emit finished(finalUrl, data);
     171          106 : }
     172              : 
     173          134 : void NetworkDownloadCore::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
     174              : {
     175              :     // Reset inactivity timer on progress.
     176          134 :     inactivityTimer.start(config.timeoutMs);
     177              : 
     178              :     // Forward progress signal.
     179          134 :     if (currentReply) {
     180          134 :         emit progress(currentReply->url(), bytesReceived, bytesTotal);
     181              :     }
     182          134 : }
     183              : 
     184            3 : void NetworkDownloadCore::onTimeout()
     185              : {
     186            3 :     if (currentReply) {
     187              :         // Clear currentReply BEFORE aborting to prevent onRequestFinished from processing.
     188            3 :         QNetworkReply* reply = currentReply;
     189            3 :         currentReply = nullptr;
     190              : 
     191            3 :         reply->abort();
     192            3 :         reply->deleteLater();
     193              : 
     194            3 :         lastError = QNetworkReply::TimeoutError;
     195            3 :         lastErrorString = "Download timeout";
     196              : 
     197              :         // Check if we should retry.
     198            4 :         if (config.retryPolicy.shouldRetry(currentAttemptCount) &&
     199            1 :             config.retryPolicy.isRetryable(lastError)) {
     200            1 :             scheduleRetry();
     201            1 :             return;
     202              :         }
     203              : 
     204              :         // No more retries - emit error.
     205            2 :         emit error(originalUrl, lastErrorString);
     206              :     }
     207              : }
     208              : 
     209            6 : void NetworkDownloadCore::scheduleRetry()
     210              : {
     211            6 :     int delay = config.retryPolicy.calculateDelay(currentAttemptCount);
     212           18 :     qCDebug(logNetwork) << "NetworkDownloadCore: scheduling retry" << (currentAttemptCount + 1)
     213           12 :                         << "in" << delay << "ms for" << originalUrl;
     214            6 :     emit retrying(currentAttemptCount + 1, delay);
     215            6 :     retryTimer.start(delay);
     216            6 : }
     217              : 
     218            6 : void NetworkDownloadCore::onRetryTimer()
     219              : {
     220            6 :     currentAttemptCount++;
     221            6 :     redirectCount = 0; // Reset redirect count for new attempt.
     222            6 :     downloadInternal(originalUrl);
     223            6 : }
        

Generated by: LCOV version 2.0-1