diff options
Diffstat (limited to 'settings/accounts-translations-plugin')
| -rw-r--r-- | settings/accounts-translations-plugin/accounts-translations-plugin.pro | 6 | ||||
| -rw-r--r-- | settings/accounts-translations-plugin/plugin.cpp | 182 | ||||
| -rw-r--r-- | settings/accounts-translations-plugin/qmldir | 4 |
3 files changed, 180 insertions, 12 deletions
diff --git a/settings/accounts-translations-plugin/accounts-translations-plugin.pro b/settings/accounts-translations-plugin/accounts-translations-plugin.pro index aad978f..faa5ae5 100644 --- a/settings/accounts-translations-plugin/accounts-translations-plugin.pro +++ b/settings/accounts-translations-plugin/accounts-translations-plugin.pro @@ -3,13 +3,13 @@ # SPDX-License-Identifier: BSD-3-Clause TEMPLATE = lib -TARGET = mastodonaccountstranslationsplugin +TARGET = fediverseaccountstranslationsplugin TARGET = $$qtLibraryTarget($$TARGET) -MODULENAME = com/jolla/settings/accounts/mastodon +MODULENAME = com/jolla/settings/accounts/fediverse TARGETPATH = $$[QT_INSTALL_QML]/$$MODULENAME -QT += qml +QT += qml network CONFIG += plugin SOURCES += plugin.cpp diff --git a/settings/accounts-translations-plugin/plugin.cpp b/settings/accounts-translations-plugin/plugin.cpp index 4a1c651..e377b6c 100644 --- a/settings/accounts-translations-plugin/plugin.cpp +++ b/settings/accounts-translations-plugin/plugin.cpp @@ -5,10 +5,20 @@ */ #include <QCoreApplication> +#include <QCryptographicHash> +#include <QDir> +#include <QFile> +#include <QFileInfo> #include <QLocale> +#include <QNetworkAccessManager> +#include <QNetworkReply> #include <QQmlEngine> #include <QQmlExtensionPlugin> +#include <QSaveFile> +#include <QSet> +#include <QStandardPaths> #include <QTranslator> +#include <QUrl> #include <QtQml> class AppTranslator : public QTranslator @@ -27,10 +37,167 @@ public: } }; -class MastodonAccountsTranslationsPlugin : public QQmlExtensionPlugin +class FediverseInstanceIconCache : public QObject { Q_OBJECT - Q_PLUGIN_METADATA(IID "com.jolla.settings.accounts.mastodon") +public: + explicit FediverseInstanceIconCache(QObject *parent = nullptr) + : QObject(parent) + , m_networkAccessManager(new QNetworkAccessManager(this)) + { + } + + Q_INVOKABLE QString cachedIconPath(const QString &apiHost) const + { + const QString normalizedHost = normalizeApiHost(apiHost); + if (normalizedHost.isEmpty()) { + return QString(); + } + + const QString prefix = iconPrefix(normalizedHost); + const QDir dir(cacheDirectory()); + const QStringList matches = dir.entryList(QStringList() << (prefix + QStringLiteral(".*")), QDir::Files); + if (matches.isEmpty()) { + return QString(); + } + + return dir.absoluteFilePath(matches.first()); + } + + Q_INVOKABLE void cacheIcon(const QString &apiHost, const QString &iconUrl) + { + const QString normalizedHost = normalizeApiHost(apiHost); + const QUrl normalizedUrl = QUrl::fromUserInput(iconUrl); + if (normalizedHost.isEmpty() || !normalizedUrl.isValid() || normalizedUrl.scheme().isEmpty()) { + emit iconError(apiHost); + return; + } + + const QString existingPath = cachedIconPath(normalizedHost); + if (!existingPath.isEmpty()) { + emit iconReady(normalizedHost, existingPath); + return; + } + + if (m_hostsInFlight.contains(normalizedHost)) { + return; + } + + QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(normalizedUrl)); + reply->setProperty("apiHost", normalizedHost); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { handleReply(reply); }); + m_hostsInFlight.insert(normalizedHost); + } + +Q_SIGNALS: + void iconReady(const QString &apiHost, const QString &iconPath); + void iconError(const QString &apiHost); + +private: + static QString normalizeApiHost(const QString &rawHost) + { + QString host = rawHost.trimmed(); + if (host.isEmpty()) { + return QString(); + } + if (!host.startsWith(QLatin1String("https://")) && !host.startsWith(QLatin1String("http://"))) { + host.prepend(QStringLiteral("https://")); + } + + const QUrl url(host); + if (!url.isValid() || url.host().isEmpty()) { + return QString(); + } + + QString normalized = QString::fromLatin1(url.toEncoded(QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment)); + if (normalized.endsWith(QLatin1Char('/'))) { + normalized.chop(1); + } + return normalized; + } + + static QString cacheDirectory() + { + const QString base = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); + return base + QStringLiteral("/sailfish-account-fediverse/icons"); + } + + static QString iconPrefix(const QString &apiHost) + { + return QString::fromLatin1(QCryptographicHash::hash(apiHost.toUtf8(), QCryptographicHash::Sha1).toHex()); + } + + static QString extensionForReply(QNetworkReply *reply) + { + const QString suffix = QFileInfo(reply->url().path()).suffix().trimmed().toLower(); + if (!suffix.isEmpty()) { + return suffix; + } + + const QString contentType = QString::fromLatin1(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray()).toLower(); + if (contentType.contains(QLatin1String("png"))) { + return QStringLiteral("png"); + } + if (contentType.contains(QLatin1String("jpeg")) || contentType.contains(QLatin1String("jpg"))) { + return QStringLiteral("jpg"); + } + if (contentType.contains(QLatin1String("webp"))) { + return QStringLiteral("webp"); + } + if (contentType.contains(QLatin1String("svg"))) { + return QStringLiteral("svg"); + } + return QStringLiteral("img"); + } + + void handleReply(QNetworkReply *reply) + { + const QString apiHost = reply->property("apiHost").toString(); + m_hostsInFlight.remove(apiHost); + + const int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + const bool success = reply->error() == QNetworkReply::NoError && httpCode >= 200 && httpCode < 300; + const QString extension = extensionForReply(reply); + const QByteArray data = reply->readAll(); + reply->deleteLater(); + + if (!success || data.isEmpty()) { + emit iconError(apiHost); + return; + } + + const QString dirPath = cacheDirectory(); + QDir dir; + if (!dir.mkpath(dirPath)) { + emit iconError(apiHost); + return; + } + + const QString prefix = iconPrefix(apiHost); + const QDir cacheDir(dirPath); + const QStringList matches = cacheDir.entryList(QStringList() << (prefix + QStringLiteral(".*")), QDir::Files); + for (const QString &match : matches) { + QFile::remove(cacheDir.absoluteFilePath(match)); + } + + const QString filePath = cacheDir.absoluteFilePath(prefix + QStringLiteral(".") + extension); + QSaveFile file(filePath); + if (!file.open(QIODevice::WriteOnly) || file.write(data) != data.size() || !file.commit()) { + emit iconError(apiHost); + return; + } + + emit iconReady(apiHost, filePath); + } + + QNetworkAccessManager *m_networkAccessManager; + QSet<QString> m_hostsInFlight; +}; + +class FediverseAccountsTranslationsPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "com.jolla.settings.accounts.fediverse") public: void initializeEngine(QQmlEngine *engine, const char *uri) override @@ -38,18 +205,19 @@ public: Q_UNUSED(uri) AppTranslator *engineeringEnglish = new AppTranslator(engine); - engineeringEnglish->load("settings-accounts-mastodon_eng_en", "/usr/share/translations"); + engineeringEnglish->load("settings-accounts-fediverse_eng_en", "/usr/share/translations"); AppTranslator *translator = new AppTranslator(engine); - translator->load(QLocale(), "settings-accounts-mastodon", "-", "/usr/share/translations"); + translator->load(QLocale(), "settings-accounts-fediverse", "-", "/usr/share/translations"); } void registerTypes(const char *uri) override { - Q_ASSERT(QLatin1String(uri) == QLatin1String("com.jolla.settings.accounts.mastodon")); - qmlRegisterUncreatableType<MastodonAccountsTranslationsPlugin>(uri, 1, 0, - "MastodonTranslationPlugin", + Q_ASSERT(QLatin1String(uri) == QLatin1String("com.jolla.settings.accounts.fediverse")); + qmlRegisterUncreatableType<FediverseAccountsTranslationsPlugin>(uri, 1, 0, + "FediverseTranslationPlugin", QString()); + qmlRegisterType<FediverseInstanceIconCache>(uri, 1, 0, "FediverseInstanceIconCache"); } }; diff --git a/settings/accounts-translations-plugin/qmldir b/settings/accounts-translations-plugin/qmldir index cf96622..69f186b 100644 --- a/settings/accounts-translations-plugin/qmldir +++ b/settings/accounts-translations-plugin/qmldir @@ -1,4 +1,4 @@ # Copyright (C) 2013-2026 Jolla Ltd. -module com.jolla.settings.accounts.mastodon -plugin mastodonaccountstranslationsplugin +module com.jolla.settings.accounts.fediverse +plugin fediverseaccountstranslationsplugin |
