[plasma/union] src: Convert StyleCache to base cache files on installed path

Arjen Hiemstra <[email protected]> Wed, 5 Aug 2026 10:33:23 +0000 (UTC)
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 31ae510fcbc62a5eb86ce6fc6456cea05618b9b8 by Arjen Hiemstra.
Committed on 05/08/2026 at 10:24.
Pushed by ahiemstra into branch 'master'.

Convert StyleCache to base cache files on installed path

Instead of using a combination of plugin + style ID, use the path of the
style (package) as identifier. This separates caches for different
installs of the same style, removing conflicts when one or the other is
a different version.

M  +31   -48   src/StyleCache.cpp
M  +5    -7    src/StyleCache_p.h
M  +18   -49   src/StyleRegistry.cpp

https://invent.kde.org/plasma/union/-/commit/31ae510fcbc62a5eb86ce6fc6456cea05618b9b8

diff --git a/src/StyleCache.cpp b/src/StyleCache.cpp
index 4a5f48b0..16ec5cec 100644
--- a/src/StyleCache.cpp
+++ b/src/StyleCache.cpp
@@ -3,6 +3,7 @@
 
 #include "StyleCache_p.h"
 
+#include <QCryptographicHash>
 #include <QFile>
 #include <QSaveFile>
 #include <QStandardPaths>
@@ -27,38 +28,21 @@ static constexpr uint32_t CacheVersion = 6;
 class StyleCache::Private
 {
 public:
-    QHash<StyleId, fs::path> stylePaths;
+    fs::path cachePath(const fs::path &stylePath)
+    {
+        auto pathHash = QCryptographicHash::hash(stylePath.string(), QCryptographicHash::Sha256)
+                            .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
+                            .toStdString();
+        return storagePath / pathHash;
+    }
+
+    fs::path storagePath;
 };
 
 StyleCache::StyleCache()
     : d(std::make_unique<Private>())
 {
-    auto cachePath = fs::path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).toStdString()) / "union";
-    if (!enabled() || !fs::exists(cachePath)) {
-        return;
-    }
-
-    for (const auto &pluginEntry : fs::directory_iterator(cachePath)) {
-        if (!pluginEntry.is_directory()) {
-            continue;
-        }
-
-        auto pluginName = pluginEntry.path().filename();
-        for (const auto &styleEntry : fs::directory_iterator(pluginEntry.path())) {
-            if (!styleEntry.is_regular_file()) {
-                continue;
-            }
-
-            if (styleEntry.file_size() == 0) {
-                continue;
-            }
-
-            auto styleName = styleEntry.path().stem();
-
-            auto styleId = std::make_pair(pluginName.string(), styleName.string());
-            d->stylePaths.insert(styleId, styleEntry.path());
-        }
-    }
+    d->storagePath = fs::path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).toStdString()) / "union";
 }
 
 StyleCache::~StyleCache() = default;
@@ -69,21 +53,21 @@ bool Union::StyleCache::enabled() const
     return enabled;
 }
 
-bool StyleCache::hasEntry(const StyleId &styleId) const
+bool StyleCache::hasEntry(const fs::path &path) const
 {
-    return d->stylePaths.contains(styleId);
+    return fs::exists(d->cachePath(path));
 }
 
-std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
+std::unique_ptr<StylePrivate> StyleCache::load(const fs::path &path) const
 {
     if (!enabled()) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << styleId.second << "from plugin" << styleId.first << "because caching has been disabled";
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "because caching has been disabled";
         return nullptr;
     }
 
-    auto path = d->stylePaths.value(styleId);
-    if (path.empty()) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << styleId.second << "from plugin" << styleId.first << "because no cache file could be found";
+    auto cachePath = d->cachePath(path);
+    if (!fs::exists(cachePath)) {
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "because no cache file could be found";
         return nullptr;
     }
 
@@ -92,7 +76,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
         return nullptr;
     }
 
-    QFile cacheFile(path);
+    QFile cacheFile(cachePath);
     if (!cacheFile.open(QIODevice::ReadOnly)) {
         return nullptr;
     }
@@ -104,7 +88,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
     reader >> magic;
 
     if (magic != CacheMagic) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "invalid magic value";
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "invalid magic value";
         return nullptr;
     }
 
@@ -112,15 +96,15 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
     reader >> version;
 
     if (version != CacheVersion) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "version mismatch";
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "version mismatch";
         return nullptr;
     }
 
     auto result = std::make_unique<StylePrivate>();
     reader >> result->path;
 
-    if (result->pluginName.toStdString() != styleId.first || result->styleName.toStdString() != styleId.second) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "plugin/style name mismatch";
+    if (result->path != path) {
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "style path mismatch";
         return nullptr;
     }
 
@@ -128,7 +112,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
     reader >> result->modificationTimes;
 
     if (result->cachePaths.size() != result->modificationTimes.size()) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "mismatch between cache paths and modification times";
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "mismatch between cache paths and modification times";
         return nullptr;
     }
 
@@ -136,7 +120,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
         auto path = result->cachePaths.at(i);
 
         if (!fs::exists(path)) {
-            qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "original file no longer exists";
+            qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "original file no longer exists";
             return nullptr;
         }
 
@@ -144,7 +128,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
         auto currentModificationTime = fs::last_write_time(path);
 
         if (cachedModificationTime != currentModificationTime) {
-            qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "file modification time mismatch";
+            qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "file modification time mismatch";
             return nullptr;
         }
     }
@@ -159,7 +143,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
     }
 
     if (reader.status() != QDataStream::Status::Ok) {
-        qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "restoring cached data failed";
+        qCDebug(UNION_GENERAL) << "Ignoring cache for style" << path.string() << "restoring cached data failed";
         return nullptr;
     }
 
@@ -176,15 +160,14 @@ bool Union::StyleCache::save(const StylePrivate *style) const
         return false;
     }
 
-    auto path = fs::path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).toStdString()) / "union" / style->pluginName.toStdString();
-    if (!fs::exists(path)) {
-        if (!fs::create_directories(path)) {
-            qCWarning(UNION_GENERAL) << "Could not create cache path" << path.string();
+    if (!fs::exists(d->storagePath)) {
+        if (!fs::create_directories(d->storagePath)) {
+            qCWarning(UNION_GENERAL) << "Could not create cache path" << d->storagePath.string();
             return false;
         }
     }
 
-    QSaveFile cacheFile(QString::fromStdString(path / (style->styleName.toStdString() + ".cache"s)));
+    QSaveFile cacheFile(QString::fromStdString(d->cachePath(style->path)));
     if (!cacheFile.open(QIODevice::WriteOnly)) {
         qCWarning(UNION_GENERAL) << "Could not open cache file" << qPrintable(cacheFile.fileName()) << "for writing";
         return false;
diff --git a/src/StyleCache_p.h b/src/StyleCache_p.h
index 48abc7de..3f74888b 100644
--- a/src/StyleCache_p.h
+++ b/src/StyleCache_p.h
@@ -27,8 +27,6 @@ class StylePrivate;
 class StyleCache
 {
 public:
-    using StyleId = std::pair<std::string, std::string>;
-
     StyleCache();
     ~StyleCache();
 
@@ -38,20 +36,20 @@ public:
     // set.
     bool enabled() const;
 
-    // Does a cache entry exist for the given style ID.
+    // Does a cache entry exist for the given style package path.
     //
-    // Note that this is based on whether a cache file exists for the given ID.
+    // Note that this is based on whether a cache file exists for the given path.
     // It does not verify that the cache file actually loads properly, so this
     // may return true while load() still ends up returning nullptr.
-    bool hasEntry(const StyleId &styleId) const;
+    bool hasEntry(const std::filesystem::path &path) const;
 
-    // Load the cached data for the style with the given style ID.
+    // Load the cached data for the style with the given style package path.
     //
     // This will return nullptr when the cached data cannot be loaded. Cached
     // data may not be loaded for a number of reasons, including changes to the
     // underlying style files as well as changes to the code or structure of
     // cache files.
-    std::unique_ptr<StylePrivate> load(const StyleId &styleId) const;
+    std::unique_ptr<StylePrivate> load(const std::filesystem::path &path) const;
 
     // Save the style data to a cache file.
     //
diff --git a/src/StyleRegistry.cpp b/src/StyleRegistry.cpp
index 53ef8d1f..75b52040 100644
--- a/src/StyleRegistry.cpp
+++ b/src/StyleRegistry.cpp
@@ -41,55 +41,6 @@ public:
     {
     }
 
-    Style::Ptr loadStyle(const QString &styleName, const QString &pluginName)
-    {
-        auto styleId = std::make_pair(pluginName.toStdString(), styleName.toStdString());
-        if (styles.contains(styleId)) {
-            return styles.value(styleId);
-        }
-
-        if (!styleCache) {
-            return nullptr;
-        }
-
-        if (styleCache->hasEntry(styleId)) {
-            auto data = styleCache->load(styleId);
-            if (data) {
-                auto style = std::make_shared<Style>(std::move(data));
-                qCDebug(UNION_GENERAL) << "Loaded style" << styleName << "from cached data";
-                styles.insert(styleId, style);
-                return style;
-            }
-        }
-
-        static const bool DisablePlugins = qEnvironmentVariableIsSet("UNION_DISABLE_INPUT_PLUGINS");
-        if (DisablePlugins) {
-            return nullptr;
-        }
-
-        auto plugin = InputPlugin::inputPlugin(pluginName);
-        if (!plugin) {
-            qCWarning(UNION_GENERAL) << "Requested style" << styleName << "from plugin" << pluginName << "but the plugin could not be found!";
-            return nullptr;
-        }
-
-        auto style = plugin->createStyle(styleName);
-        if (!style) {
-            qCWarning(UNION_GENERAL) << "Requested style" << styleName << "from plugin" << pluginName << "but the style could not be found!";
-            return nullptr;
-        }
-
-        if (!style->load()) {
-            qCWarning(UNION_GENERAL) << "Requested style" << styleName << "from plugin" << pluginName << "but it failed to load!";
-            return nullptr;
-        }
-
-        qCDebug(UNION_GENERAL) << "Loaded style" << styleName << "from plugin" << pluginName;
-
-        styles.insert(styleId, style);
-        return style;
-    }
-
     void loadPlatform()
     {
         const auto forcedPlatform = qEnvironmentVariable("UNION_FORCE_PLATFORM", QString{});
@@ -103,6 +54,11 @@ public:
         const auto platformName = QGuiApplication::platformName();
         const auto desktopNames = qEnvironmentVariable("XDG_CURRENT_DESKTOP", QString{}).split(u':');
 
+        if (platformName.isEmpty()) {
+            platform = std::make_shared<FallbackPlatformPlugin>();
+            return;
+        }
+
         const auto plugins = platformRegistry->plugins();
         for (const auto &plugin : plugins) {
             const auto supportedPlatforms = plugin.metaData.value(u"union-supported-platforms").toArray().toVariantList();
@@ -198,12 +154,25 @@ std::shared_ptr<Style> StyleRegistry::style(const QString &styleId)
         return itr.value();
     }
 
+    if (!d->styleCache) {
+        return nullptr;
+    }
+
     auto stylePackage = d->packageHandler->package(styleId);
     if (!stylePackage.isValid()) {
         qCWarning(UNION_GENERAL) << "Could not find style" << styleId;
         return nullptr;
     }
 
+    if (d->styleCache->hasEntry(stylePackage.path())) {
+        if (auto data = d->styleCache->load(stylePackage.path()); data) {
+            auto style = std::make_shared<Style>(std::move(data));
+            qCDebug(UNION_GENERAL) << "Loaded style" << styleId << "from cached data";
+            d->styles.insert(stylePackage.path(), style);
+            return style;
+        }
+    }
+
     auto style = stylePackage.load();
     if (!style) {
         qCWarning(UNION_GENERAL) << "Style" << styleId << "failed to load";