// SPDX-FileCopyrightText: 2018 Kitsune Ral // SPDX-License-Identifier: LGPL-2.1-or-later #include "downloadfilejob.h" #include #include #include #ifdef Quotient_E2EE_ENABLED # include "events/filesourceinfo.h" # include #endif using namespace Quotient; class DownloadFileJob::Private { public: Private() : tempFile(new QTemporaryFile()) {} explicit Private(const QString& localFilename) : targetFile(new QFile(localFilename)) , tempFile(new QFile(targetFile->fileName() + ".qtntdownload")) {} QScopedPointer targetFile; QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED Omittable encryptedFile; #endif }; QUrl DownloadFileJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri) { return makeRequestUrl(std::move(baseUrl), mxcUri.authority(), mxcUri.path().mid(1)); } DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? makeImpl() : makeImpl(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); } #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFileMetadata& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? makeImpl() : makeImpl(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); d->encryptedFile = file; } #endif QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); } void DownloadFileJob::doPrepare() { if (d->targetFile && !d->targetFile->isReadable() && !d->targetFile->open(QIODevice::WriteOnly)) { qCWarning(JOBS) << "Couldn't open the file" << d->targetFile->fileName() << "for writing"; setStatus(FileError, "Could not open the target file for writing"); return; } if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::ReadWrite)) { qCWarning(JOBS) << "Couldn't open the temporary file" << d->tempFile->fileName() << "for writing"; setStatus(FileError, "Could not open the temporary download file"); return; } qCDebug(JOBS) << "Downloading to" << d->tempFile->fileName(); } void DownloadFileJob::onSentRequest(QNetworkReply* reply) { connect(reply, &QNetworkReply::metaDataChanged, this, [this, reply] { if (!status().good()) return; auto sizeHeader = reply->header(QNetworkRequest::ContentLengthHeader); if (sizeHeader.isValid()) { auto targetSize = sizeHeader.toLongLong(); if (targetSize != -1) if (!d->tempFile->resize(targetSize)) { qCWarning(JOBS) << "Failed to allocate" << targetSize << "bytes for" << d->tempFile->fileName(); setStatus(FileError, "Could not reserve disk space for download"); } } }); connect(reply, &QIODevice::readyRead, this, [this, reply] { if (!status().good()) return; auto bytes = reply->read(reply->bytesAvailable()); if (!bytes.isEmpty()) d->tempFile->write(bytes); else qCWarning(JOBS) << "Unexpected empty chunk when downloading from" << reply->url() << "to" << d->tempFile->fileName(); }); } void DownloadFileJob::beforeAbandon() { if (d->targetFile) d->targetFile->remove(); d->tempFile->remove(); } BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { #ifdef Quotient_E2EE_ENABLED if (d->encryptedFile.has_value()) { d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { #endif d->targetFile->close(); if (!d->targetFile->remove()) { qCWarning(JOBS) << "Failed to remove the target file placeholder"; return { FileError, "Couldn't finalise the download" }; } if (!d->tempFile->rename(d->targetFile->fileName())) { qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() << "to" << d->targetFile->fileName(); return { FileError, "Couldn't finalise the download" }; } #ifdef Quotient_E2EE_ENABLED } #endif } else { #ifdef Quotient_E2EE_ENABLED if (d->encryptedFile.has_value()) { d->tempFile->seek(0); const auto encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif d->tempFile->close(); #ifdef Quotient_E2EE_ENABLED } #endif } qCDebug(JOBS) << "Saved a file as" << targetFileName(); return Success; }