#include "uploadclient.h" #include #include #include #include #include #include #include UploadClient::UploadClient(QString baseUrl, QString username, QString password, QObject *parent) : QObject(parent) , m_base(std::move(baseUrl)) , m_username(std::move(username)) , m_password(std::move(password)) , m_net(new QNetworkAccessManager(this)) { while (m_base.endsWith(QLatin1Char('/'))) m_base.chop(1); } QByteArray UploadClient::authHeader() const { const QByteArray raw = (m_username + QLatin1Char(':') + m_password).toUtf8(); return "Basic " + raw.toBase64(); } void UploadClient::upload(const QString &filePath, const QString &fileName, const QString &caption, const QString &date) { auto *file = new QFile(filePath); if (!file->open(QIODevice::ReadOnly)) { delete file; emit uploadFinished(Result::Rejected, {}, {}, QStringLiteral("Could not read the prepared image")); return; } auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); const QByteArray contentType = fileName.endsWith(QStringLiteral(".png"), Qt::CaseInsensitive) ? "image/png" : "image/jpeg"; QHttpPart imagePart; imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType)); imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QStringLiteral("form-data; name=\"image\"; filename=\"%1\"").arg(fileName))); file->setParent(multiPart); imagePart.setBodyDevice(file); multiPart->append(imagePart); const auto textPart = [](const QString &name, const QString &value) { QHttpPart part; part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QStringLiteral("form-data; name=\"%1\"").arg(name))); part.setBody(value.toUtf8()); return part; }; multiPart->append(textPart(QStringLiteral("caption"), caption)); multiPart->append(textPart(QStringLiteral("date"), date)); QNetworkRequest request(QUrl(m_base + QStringLiteral("/upload"))); request.setRawHeader("Authorization", authHeader()); QNetworkReply *reply = m_net->post(request, multiPart); multiPart->setParent(reply); const QString fallbackSlug = QFileInfo(fileName).completeBaseName(); connect(reply, &QNetworkReply::finished, this, [this, reply, fallbackSlug]() { reply->deleteLater(); const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); const QString body = QString::fromUtf8(reply->readAll()).trimmed(); if (reply->error() == QNetworkReply::NoError && status >= 200 && status < 300) { // Server replies "uploaded slug.ext"; recover the slug. QString name = body; if (name.startsWith(QStringLiteral("uploaded "))) name = name.mid(QStringLiteral("uploaded ").size()).trimmed(); const int dot = name.lastIndexOf(QLatin1Char('.')); QString slug = dot > 0 ? name.left(dot) : name; if (slug.isEmpty()) slug = fallbackSlug; emit uploadFinished(Result::Success, slug, m_base + QStringLiteral("/photo/") + slug + QLatin1Char('/'), {}); return; } if (status == 401 || status == 403) { emit uploadFinished(Result::AuthFailed, {}, {}, QStringLiteral("Wrong username or password")); return; } if (status >= 500 && status < 600) { emit uploadFinished(Result::Transient, {}, {}, QStringLiteral("Server error %1").arg(status)); return; } if (status == 0) { // No HTTP response — a socket/TLS failure. Worth retrying. emit uploadFinished(Result::Transient, {}, {}, reply->errorString()); return; } emit uploadFinished(Result::Rejected, {}, {}, body.isEmpty() ? QStringLiteral("Upload rejected") : body); }); } void UploadClient::testConnection() { QNetworkRequest request(QUrl(m_base + QStringLiteral("/upload"))); request.setRawHeader("Authorization", authHeader()); QNetworkReply *reply = m_net->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { reply->deleteLater(); const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (reply->error() == QNetworkReply::NoError && status >= 200 && status < 300) { emit testFinished(QString()); return; } if (status == 401 || status == 403) { emit testFinished(QStringLiteral("Wrong username or password")); return; } if (status == 0) { emit testFinished(reply->errorString()); return; } emit testFinished(QStringLiteral("Server returned %1").arg(status)); }); }