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 ¬e) 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 }