This commit is contained in:
BruebachL 2026-11-04 10:30:36 -07:00 • committed by GitHub
commit 5b48617468
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 21 deletions

View file

@ -1,12 +1,12 @@
#include "update_downloader.h" #include "update_downloader.h"
#include <QByteArray>
#include <QChar>
#include <QDir> #include <QDir>
#include <QFile> #include <QFileInfo>
#include <QIODevice> #include <QIODevice>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QSaveFile>
#include <QTemporaryDir>
#include <QUrl> #include <QUrl>
#include <QVariant> #include <QVariant>
#include <libcockatrice/network/network_manager.h> #include <libcockatrice/network/network_manager.h>
@ -23,48 +23,116 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
originalUrl = downloadUrl; originalUrl = downloadUrl;
} }
if (response != nullptr) {
response->abort();
response->deleteLater();
}
errorReported = false;
cancelRequested = false;
// The transfer timeout comes from the manager (NetworkManagerFactory).
response = netMan->get(QNetworkRequest(downloadUrl)); response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished); connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
connect(response, &QNetworkReply::errorOccurred, this, &UpdateDownloader::downloadError);
connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress); connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress);
connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort); connect(this, &UpdateDownloader::stopDownload, this, &UpdateDownloader::cancelDownload);
} }
void UpdateDownloader::downloadError(QNetworkReply::NetworkError) void UpdateDownloader::downloadError(QNetworkReply::NetworkError networkError)
{ {
if (response == nullptr) { // A reply that was superseded by beginDownload() can still deliver a
// queued error; it must not be reported against the current reply.
if (response == nullptr || sender() != response || errorReported) {
return; return;
} }
emit error(response->errorString().toUtf8()); // A transfer timeout aborts the reply, so it arrives as OperationCanceledError.
// Only an abort the user asked for is suppressed; every other cancel is a
// stalled transfer and has to be reported.
if (networkError == QNetworkReply::OperationCanceledError) {
if (cancelRequested) {
return;
}
errorReported = true;
emit error(tr("The download timed out. Check your connection and try again."));
return;
}
errorReported = true;
emit error(response->errorString());
}
void UpdateDownloader::cancelDownload()
{
cancelRequested = true;
if (response != nullptr) {
response->abort();
}
} }
void UpdateDownloader::fileFinished() void UpdateDownloader::fileFinished()
{ {
// If we finished but there's a redirect, follow it // An aborted, superseded reply also emits finished(). Only the reply
QVariant redirect = response->attribute(QNetworkRequest::RedirectionTargetAttribute); // currently stored in response may be consumed.
if (!redirect.isNull()) { if (response == nullptr || sender() != response) {
beginDownload(redirect.toUrl());
return; return;
} }
// Handle any errors we had // errorOccurred normally reports this first; keep the check for errors that
if (response->error()) { // only surface when the reply finishes.
emit error(response->errorString()); if (response->error() != QNetworkReply::NoError) {
downloadError(response->error());
response->deleteLater();
response = nullptr;
return; return;
} }
// Work out the file name of the download // Give the update its own private directory. The name is unpredictable, so a
QString fileName = QDir::temp().path() + QDir::separator() + originalUrl.toString().section('/', -1); // local user cannot pre-create or symlink the path, and the original file
// name (including the installer suffix) is preserved inside it.
QTemporaryDir downloadDir(QDir::temp().filePath(QStringLiteral("cockatrice-update-XXXXXX")));
if (!downloadDir.isValid()) {
emit error(tr("Could not create a temporary directory for the download."));
response->deleteLater();
response = nullptr;
return;
}
downloadDir.setAutoRemove(false);
// Save the build in a temporary directory const QString remoteName = QFileInfo(originalUrl.path()).fileName();
QFile file(fileName); const QString fileName = downloadDir.filePath(remoteName.isEmpty() ? QStringLiteral("update") : remoteName);
QSaveFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
emit error(tr("Could not open the file for reading.")); downloadDir.remove();
emit error(tr("Could not open the file for writing: %1").arg(file.errorString()));
response->deleteLater();
response = nullptr;
return; return;
} }
file.write(response->readAll()); const QByteArray data = response->readAll();
file.close(); if (file.write(data) != data.size()) {
file.cancelWriting();
downloadDir.remove();
emit error(tr("Could not write the downloaded file to disk."));
response->deleteLater();
response = nullptr;
return;
}
if (!file.commit()) {
downloadDir.remove();
emit error(tr("Could not save the downloaded file: %1").arg(file.errorString()));
response->deleteLater();
response = nullptr;
return;
}
response->deleteLater();
response = nullptr;
// Emit the success signal with a URL to the download file // Emit the success signal with a URL to the download file
emit downloadSuccessful(QUrl::fromLocalFile(fileName)); emit downloadSuccessful(QUrl::fromLocalFile(fileName));

View file

@ -32,10 +32,13 @@ private:
QUrl originalUrl; QUrl originalUrl;
QNetworkAccessManager *netMan; QNetworkAccessManager *netMan;
QNetworkReply *response; QNetworkReply *response;
bool errorReported = false;
bool cancelRequested = false;
private slots: private slots:
void fileFinished(); void fileFinished();
void downloadProgress(qint64 bytesRead, qint64 totalBytes); void downloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadError(QNetworkReply::NetworkError); void downloadError(QNetworkReply::NetworkError);
void cancelDownload();
}; };
#endif // COCKATRICE_UPDATEDOWNLOADER_H #endif // COCKATRICE_UPDATEDOWNLOADER_H