From 1c5aa2225730a6b325c3c80259c39c20354fc4af Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 5 Sep 2023 10:12:33 +0200 Subject: [PATCH] Utils: Add PersistentCacheStore Change-Id: I952e0271afcc0fd4b03ef75fa5acb219be153290 Reviewed-by: hjk --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/filepath.cpp | 5 ++ src/libs/utils/filepath.h | 1 + src/libs/utils/persistentcachestore.cpp | 115 ++++++++++++++++++++++++ src/libs/utils/persistentcachestore.h | 22 +++++ src/libs/utils/store.cpp | 22 +++++ src/libs/utils/store.h | 4 + src/libs/utils/utils.qbs | 2 + 8 files changed, 172 insertions(+) create mode 100644 src/libs/utils/persistentcachestore.cpp create mode 100644 src/libs/utils/persistentcachestore.h diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 72c18e3346f..9f1569b73b2 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -117,6 +117,7 @@ add_qtc_library(Utils passworddialog.cpp passworddialog.h pathchooser.cpp pathchooser.h pathlisteditor.cpp pathlisteditor.h + persistentcachestore.cpp persistentcachestore.h persistentsettings.cpp persistentsettings.h pointeralgorithm.h port.cpp port.h diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 4ab0f502f74..a5a51b43bbe 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -749,6 +749,11 @@ FilePath FilePath::withExecutableSuffix() const return withNewPath(OsSpecificAspects::withExecutableSuffix(osType(), path())); } +FilePath FilePath::withSuffix(const QString &suffix) const +{ + return withNewPath(path() + suffix); +} + static bool startsWithWindowsDriveLetterAndSlash(QStringView path) { return path.size() > 2 && path[1] == ':' && path[2] == '/' && isWindowsDriveLetter(path[0]); diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index a5605769866..9d7b3ce5862 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -160,6 +160,7 @@ public: [[nodiscard]] FilePath symLinkTarget() const; [[nodiscard]] FilePath resolveSymlinks() const; [[nodiscard]] FilePath withExecutableSuffix() const; + [[nodiscard]] FilePath withSuffix(const QString &suffix) const; [[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const; [[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const; [[nodiscard]] Environment deviceEnvironment() const; diff --git a/src/libs/utils/persistentcachestore.cpp b/src/libs/utils/persistentcachestore.cpp new file mode 100644 index 00000000000..0cac5f83cde --- /dev/null +++ b/src/libs/utils/persistentcachestore.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "persistentcachestore.h" + +#include "filepath.h" +#include "fileutils.h" + +#include +#include +#include + +namespace Utils { + +class PrivateGlobal +{ +public: + QMutex mutex; + QMap caches; +}; + +static expected_str cacheFolder() +{ + static const FilePath folder = FilePath::fromUserInput(QStandardPaths::writableLocation( + QStandardPaths::CacheLocation)) + / "CachedStores"; + static expected_str created = folder.ensureWritableDir(); + static expected_str result = created ? folder + : expected_str( + make_unexpected(created.error())); + + QTC_ASSERT_EXPECTED(result, return result); + return result; +} + +static PrivateGlobal &globals() +{ + static PrivateGlobal global; + return global; +} + +static expected_str filePathFromKey(const Key &cacheKey) +{ + static const expected_str folder = cacheFolder(); + if (!folder) + return folder; + + return (*folder / FileUtils::fileSystemFriendlyName(stringFromKey(cacheKey))).withSuffix(".json"); +} + +expected_str PersistentCacheStore::byKey(const Key &cacheKey) +{ + const expected_str path = filePathFromKey(cacheKey); + if (!path) + return make_unexpected(path.error()); + + QMutexLocker locker(&globals().mutex); + + auto it = globals().caches.find(cacheKey); + if (it != globals().caches.end()) + return it.value(); + + const expected_str contents = path->fileContents(); + if (!contents) + return make_unexpected(contents.error()); + + auto result = storeFromJson(*contents); + if (!result) + return result; + + if (result->value("__cache_key__").toString() != stringFromKey(cacheKey)) { + return make_unexpected(QString("Cache key mismatch: \"%1\" to \"%2\" in \"%3\".") + .arg(stringFromKey(cacheKey)) + .arg(result->value("__cache_key__").toString()) + .arg(path->toUserOutput())); + } + + return result; +} + +expected_str PersistentCacheStore::write(const Key &cacheKey, const Store &store) +{ + const expected_str path = filePathFromKey(cacheKey); + if (!path) + return make_unexpected(path.error()); + + QMutexLocker locker(&globals().mutex); + globals().caches.insert(cacheKey, store); + + // TODO: The writing of the store data could be done in a separate thread in the future. + Store storeCopy = store; + storeCopy.insert("__cache_key__", stringFromKey(cacheKey)); + storeCopy.insert("__last_modified__", QDateTime::currentDateTime().toString(Qt::ISODate)); + QByteArray json = jsonFromStore(storeCopy); + const expected_str result = path->writeFileContents(json); + if (!result) + return make_unexpected(result.error()); + return {}; +} + +expected_str PersistentCacheStore::clear(const Key &cacheKey) +{ + const expected_str path = filePathFromKey(cacheKey); + if (!path) + return make_unexpected(path.error()); + + QMutexLocker locker(&globals().mutex); + globals().caches.remove(cacheKey); + + if (!path->removeFile()) + return make_unexpected(QString("Failed to remove cache file.")); + return {}; +} + +} // namespace Utils diff --git a/src/libs/utils/persistentcachestore.h b/src/libs/utils/persistentcachestore.h new file mode 100644 index 00000000000..0c40061d619 --- /dev/null +++ b/src/libs/utils/persistentcachestore.h @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "expected.h" +#include "store.h" +#include "storekey.h" + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT PersistentCacheStore +{ +public: + static expected_str byKey(const Key &cacheKey); + static expected_str write(const Key &cacheKey, const Store &store); + static expected_str clear(const Key &cacheKey); +}; + +} // namespace Utils diff --git a/src/libs/utils/store.cpp b/src/libs/utils/store.cpp index d48adc2cde4..42a14d6f1a9 100644 --- a/src/libs/utils/store.cpp +++ b/src/libs/utils/store.cpp @@ -6,6 +6,9 @@ #include "algorithm.h" #include "qtcassert.h" +#include +#include + namespace Utils { KeyList keysFromStrings(const QStringList &list) @@ -111,4 +114,23 @@ Key numberedKey(const Key &key, int number) return key + Key::number(number); } +expected_str storeFromJson(const QByteArray &json) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) + return make_unexpected(error.errorString()); + + if (!doc.isObject()) + return make_unexpected(QString("Not a valid JSON object.")); + + return storeFromMap(doc.toVariant().toMap()); +} + +QByteArray jsonFromStore(const Store &store) +{ + QJsonDocument doc = QJsonDocument::fromVariant(mapFromStore(store)); + return doc.toJson(); +} + } // Utils diff --git a/src/libs/utils/store.h b/src/libs/utils/store.h index 3050251bc1c..f09b98f3306 100644 --- a/src/libs/utils/store.h +++ b/src/libs/utils/store.h @@ -3,6 +3,7 @@ #pragma once +#include "expected.h" #include "storekey.h" #include @@ -27,6 +28,9 @@ QTCREATOR_UTILS_EXPORT bool isStore(const QVariant &value); QTCREATOR_UTILS_EXPORT Key numberedKey(const Key &key, int number); +QTCREATOR_UTILS_EXPORT expected_str storeFromJson(const QByteArray &json); +QTCREATOR_UTILS_EXPORT QByteArray jsonFromStore(const Store &store); + } // Utils Q_DECLARE_METATYPE(Utils::Store) diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 021c1d37f26..1110ad11c73 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -226,6 +226,8 @@ Project { "pathchooser.h", "pathlisteditor.cpp", "pathlisteditor.h", + "persistentcachestore.cpp", + "persistentcachestore.h", "persistentsettings.cpp", "persistentsettings.h", "pointeralgorithm.h",