File indexing completed on 2024-05-19 16:01:01

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "profileeditor.h"
0005 #include "abstractaccount.h"
0006 #include <KLocalizedString>
0007 #include <QFile>
0008 #include <QFileInfo>
0009 #include <QHttpMultiPart>
0010 #include <QMimeDatabase>
0011 
0012 ProfileEditorBackend::ProfileEditorBackend(QObject *parent)
0013     : QObject(parent)
0014 {
0015 }
0016 
0017 ProfileEditorBackend::~ProfileEditorBackend() = default;
0018 
0019 AbstractAccount *ProfileEditorBackend::account() const
0020 {
0021     return m_account;
0022 }
0023 
0024 void ProfileEditorBackend::setAccount(AbstractAccount *account)
0025 {
0026     if (m_account == account) {
0027         return;
0028     }
0029     m_account = account;
0030     Q_EMIT accountChanged();
0031     if (m_account) {
0032         fetchAccountInfo();
0033     }
0034 }
0035 
0036 QString ProfileEditorBackend::displayName() const
0037 {
0038     return m_displayName;
0039 }
0040 
0041 void ProfileEditorBackend::setDisplayName(const QString &displayName)
0042 {
0043     if (m_displayName == displayName) {
0044         return;
0045     }
0046     m_displayName = displayName;
0047     Q_EMIT displayNameChanged();
0048 }
0049 
0050 QString ProfileEditorBackend::note() const
0051 {
0052     return m_note;
0053 }
0054 
0055 void ProfileEditorBackend::setNote(const QString &note)
0056 {
0057     if (m_note == note) {
0058         return;
0059     }
0060     m_note = note;
0061     Q_EMIT noteChanged();
0062 }
0063 
0064 bool ProfileEditorBackend::sensitive() const
0065 {
0066     return m_sensitive;
0067 }
0068 
0069 void ProfileEditorBackend::setSensitive(bool sensitive)
0070 {
0071     if (m_sensitive == sensitive) {
0072         return;
0073     }
0074     m_sensitive = sensitive;
0075     Q_EMIT sensitiveChanged();
0076 }
0077 
0078 QString ProfileEditorBackend::privacy() const
0079 {
0080     return m_privacy;
0081 }
0082 
0083 void ProfileEditorBackend::setPrivacy(const QString &privacy)
0084 {
0085     if (privacy == m_privacy) {
0086         return;
0087     }
0088     m_privacy = privacy;
0089     Q_EMIT privacyChanged();
0090 }
0091 
0092 bool ProfileEditorBackend::bot() const
0093 {
0094     return m_bot;
0095 }
0096 
0097 void ProfileEditorBackend::setBot(bool bot)
0098 {
0099     if (m_bot == bot) {
0100         return;
0101     }
0102     m_bot = bot;
0103     Q_EMIT botChanged();
0104 }
0105 
0106 bool ProfileEditorBackend::discoverable() const
0107 {
0108     return m_discoverable;
0109 }
0110 
0111 void ProfileEditorBackend::setDiscoverable(bool discoverable)
0112 {
0113     if (m_discoverable == discoverable) {
0114         return;
0115     }
0116     m_discoverable = discoverable;
0117     Q_EMIT discoverableChanged();
0118 }
0119 
0120 bool ProfileEditorBackend::locked() const
0121 {
0122     return m_locked;
0123 }
0124 
0125 void ProfileEditorBackend::setLocked(bool locked)
0126 {
0127     if (m_locked == locked) {
0128         return;
0129     }
0130     m_locked = locked;
0131     Q_EMIT lockedChanged();
0132 }
0133 
0134 QString ProfileEditorBackend::language() const
0135 {
0136     return m_language;
0137 }
0138 
0139 void ProfileEditorBackend::setLanguage(const QString &language)
0140 {
0141     if (language == m_language) {
0142         return;
0143     }
0144     m_language = language;
0145     Q_EMIT languageChanged();
0146 }
0147 
0148 QUrl ProfileEditorBackend::avatarUrl() const
0149 {
0150     return m_avatarUrl;
0151 }
0152 
0153 void ProfileEditorBackend::setAvatarUrl(const QUrl &avatarUrl)
0154 {
0155     if (avatarUrl == m_avatarUrl) {
0156         return;
0157     }
0158     m_avatarUrl = avatarUrl;
0159     Q_EMIT avatarUrlChanged();
0160 }
0161 
0162 QUrl ProfileEditorBackend::backgroundUrl() const
0163 {
0164     return m_backgroundUrl;
0165 }
0166 
0167 namespace
0168 {
0169 
0170 auto operator""_MiB(unsigned long long const x) -> long
0171 {
0172     return 1024L * 1024L * x;
0173 }
0174 
0175 QString checkImage(const QUrl &url)
0176 {
0177     if (!url.isLocalFile() || url.isEmpty()) {
0178         return {};
0179     }
0180 
0181     QFileInfo fileInfo(url.toLocalFile());
0182     if (fileInfo.size() > 2_MiB) {
0183         return i18n("Image is too big");
0184     }
0185 
0186     QMimeDatabase mimeDatabase;
0187     const auto mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
0188 
0189     static const QSet<QString> allowedMimeType = {
0190         QStringLiteral("image/png"),
0191         QStringLiteral("image/jpeg"),
0192         QStringLiteral("image/gif"),
0193     };
0194 
0195     if (!allowedMimeType.contains(mimeType.name())) {
0196         return i18n("Unsupported image file. Only jpeg, png and gif are supported.");
0197     }
0198 
0199     return {};
0200 }
0201 }
0202 
0203 QString ProfileEditorBackend::backgroundUrlError() const
0204 {
0205     return checkImage(m_backgroundUrl);
0206 }
0207 
0208 QString ProfileEditorBackend::avatarUrlError() const
0209 {
0210     return checkImage(m_avatarUrl);
0211 }
0212 
0213 void ProfileEditorBackend::setBackgroundUrl(const QUrl &backgroundUrl)
0214 {
0215     if (backgroundUrl == m_backgroundUrl) {
0216         return;
0217     }
0218     m_backgroundUrl = backgroundUrl;
0219     Q_EMIT backgroundUrlChanged();
0220 }
0221 
0222 void ProfileEditorBackend::fetchAccountInfo()
0223 {
0224     Q_ASSERT_X(m_account, Q_FUNC_INFO, "Fetch account called without account");
0225 
0226     m_account->get(m_account->apiUrl(QStringLiteral("/api/v1/accounts/verify_credentials")), true, this, [this](QNetworkReply *reply) {
0227         const auto json = QJsonDocument::fromJson(reply->readAll());
0228         Q_ASSERT(json.isObject());
0229         const auto obj = json.object();
0230         setDisplayName(obj["display_name"].toString());
0231         const auto source = obj["source"].toObject();
0232         setSensitive(source["sensitive"].toBool());
0233         setPrivacy(source["privacy"].toString());
0234         setNote(source["note"].toString());
0235         setLanguage(source["language"].toString());
0236         setBot(obj["bot"].toBool());
0237         setBackgroundUrl(obj["header_static"].toString());
0238         setAvatarUrl(obj["avatar_static"].toString());
0239         setLocked(obj["locked"].toBool());
0240         setDiscoverable(obj["discoverable"].toBool());
0241     });
0242 }
0243 
0244 void ProfileEditorBackend::save()
0245 {
0246     const QUrl url = m_account->apiUrl("/api/v1/accounts/update_credentials");
0247 
0248     auto multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
0249 
0250     QHttpPart displayNamePart;
0251     displayNamePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"display_name\""));
0252     displayNamePart.setBody(displayName().toUtf8());
0253     multiPart->append(displayNamePart);
0254 
0255     QHttpPart notePart;
0256     notePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"note\""));
0257     notePart.setBody(note().toUtf8());
0258     multiPart->append(notePart);
0259 
0260     QHttpPart lockedPart;
0261     lockedPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"locked\""));
0262     lockedPart.setBody(locked() ? "1" : "0");
0263     multiPart->append(lockedPart);
0264 
0265     QHttpPart botPart;
0266     botPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"bot\""));
0267     botPart.setBody(bot() ? "1" : "0");
0268     multiPart->append(botPart);
0269 
0270     QHttpPart discoverablePart;
0271     discoverablePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"discoverable\""));
0272     discoverablePart.setBody(discoverable() ? "1" : "0");
0273     multiPart->append(discoverablePart);
0274 
0275     QHttpPart privacyPart;
0276     privacyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"source[privacy]\""));
0277     privacyPart.setBody(privacy().toUtf8());
0278     multiPart->append(privacyPart);
0279 
0280     QHttpPart sensitivityPart;
0281     sensitivityPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"source[sensitive]\""));
0282     sensitivityPart.setBody(sensitive() ? "1" : "0");
0283     multiPart->append(sensitivityPart);
0284 
0285     QMimeDatabase mimeDatabase;
0286 
0287     if (backgroundUrl().isLocalFile()) {
0288         auto file = new QFile(backgroundUrl().toLocalFile());
0289         const auto mime = mimeDatabase.mimeTypeForUrl(backgroundUrl());
0290         if (file->open(QIODevice::ReadOnly)) {
0291             QHttpPart headerPart;
0292             headerPart.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
0293             headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"header\""));
0294             headerPart.setBodyDevice(file);
0295             file->setParent(multiPart);
0296             multiPart->append(headerPart);
0297         }
0298     } else if (backgroundUrl().isEmpty()) {
0299         QHttpPart headerPart;
0300         headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"header\""));
0301         multiPart->append(headerPart);
0302     }
0303 
0304     if (avatarUrl().isLocalFile()) {
0305         auto file = new QFile(avatarUrl().toLocalFile());
0306         const auto mime = mimeDatabase.mimeTypeForUrl(avatarUrl());
0307         if (file->open(QIODevice::ReadOnly)) {
0308             QHttpPart headerPart;
0309             headerPart.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
0310             headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"avatar\""));
0311             headerPart.setBodyDevice(file);
0312             file->setParent(multiPart);
0313             multiPart->append(headerPart);
0314         }
0315     } else if (avatarUrl().isEmpty()) {
0316         QHttpPart headerPart;
0317         headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"avatar\""));
0318         multiPart->append(headerPart);
0319     }
0320 
0321     m_account->patch(url, multiPart, true, this, [=](QNetworkReply *reply) {
0322         multiPart->setParent(reply);
0323         Q_EMIT sendNotification(i18n("Account details saved"));
0324         fetchAccountInfo();
0325     });
0326 }