Utils: Add PersistentCacheStore

Change-Id: I952e0271afcc0fd4b03ef75fa5acb219be153290
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-09-05 10:12:33 +02:00
parent 7adeaafd93
commit 1c5aa22257
8 changed files with 172 additions and 0 deletions

View File

@@ -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

View File

@@ -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]);

View File

@@ -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;

View File

@@ -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 <QMap>
#include <QMutex>
#include <QStandardPaths>
namespace Utils {
class PrivateGlobal
{
public:
QMutex mutex;
QMap<Key, Store> caches;
};
static expected_str<FilePath> cacheFolder()
{
static const FilePath folder = FilePath::fromUserInput(QStandardPaths::writableLocation(
QStandardPaths::CacheLocation))
/ "CachedStores";
static expected_str<void> created = folder.ensureWritableDir();
static expected_str<FilePath> result = created ? folder
: expected_str<FilePath>(
make_unexpected(created.error()));
QTC_ASSERT_EXPECTED(result, return result);
return result;
}
static PrivateGlobal &globals()
{
static PrivateGlobal global;
return global;
}
static expected_str<FilePath> filePathFromKey(const Key &cacheKey)
{
static const expected_str<FilePath> folder = cacheFolder();
if (!folder)
return folder;
return (*folder / FileUtils::fileSystemFriendlyName(stringFromKey(cacheKey))).withSuffix(".json");
}
expected_str<Store> PersistentCacheStore::byKey(const Key &cacheKey)
{
const expected_str<FilePath> 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<QByteArray> 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<void> PersistentCacheStore::write(const Key &cacheKey, const Store &store)
{
const expected_str<FilePath> 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<qint64> result = path->writeFileContents(json);
if (!result)
return make_unexpected(result.error());
return {};
}
expected_str<void> PersistentCacheStore::clear(const Key &cacheKey)
{
const expected_str<FilePath> 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

View File

@@ -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<Store> byKey(const Key &cacheKey);
static expected_str<void> write(const Key &cacheKey, const Store &store);
static expected_str<void> clear(const Key &cacheKey);
};
} // namespace Utils

View File

@@ -6,6 +6,9 @@
#include "algorithm.h"
#include "qtcassert.h"
#include <QJsonDocument>
#include <QJsonParseError>
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<Store> 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

View File

@@ -3,6 +3,7 @@
#pragma once
#include "expected.h"
#include "storekey.h"
#include <QMap>
@@ -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<Store> storeFromJson(const QByteArray &json);
QTCREATOR_UTILS_EXPORT QByteArray jsonFromStore(const Store &store);
} // Utils
Q_DECLARE_METATYPE(Utils::Store)

View File

@@ -226,6 +226,8 @@ Project {
"pathchooser.h",
"pathlisteditor.cpp",
"pathlisteditor.h",
"persistentcachestore.cpp",
"persistentcachestore.h",
"persistentsettings.cpp",
"persistentsettings.h",
"pointeralgorithm.h",