From 54bc3f265a2309dcb6fc363c222fa92df2a3b19a Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Sat, 5 Sep 2015 16:43:30 +0100 Subject: [PATCH] refs #450 Added disk file saving and loading of CValueCache. --- src/blackmisc/dictionary.h | 3 ++ src/blackmisc/valuecache.cpp | 59 ++++++++++++++++++++++++++++++ src/blackmisc/valuecache.h | 10 +++++ tests/blackmisc/testvaluecache.cpp | 35 ++++++++++++++++++ tests/blackmisc/testvaluecache.h | 3 ++ 5 files changed, 110 insertions(+) diff --git a/src/blackmisc/dictionary.h b/src/blackmisc/dictionary.h index 8fee9daf5..2eadd9e98 100644 --- a/src/blackmisc/dictionary.h +++ b/src/blackmisc/dictionary.h @@ -332,6 +332,9 @@ namespace BlackMisc //! Insert new item with key and value iterator insert(const Key &key, const Value &value) { return m_impl.insert(key, value); } + //! Insert all items of other dictionary into this dictionary + void insert(const CDictionary &other) { for (auto i = other.cbegin(); i != other.cend(); ++i) { insert(i.key(), i.value()); } } + //! Returns true if dictionary is empty bool isEmpty() const { return m_impl.isEmpty(); } diff --git a/src/blackmisc/valuecache.cpp b/src/blackmisc/valuecache.cpp index c61289c97..154c1405d 100644 --- a/src/blackmisc/valuecache.cpp +++ b/src/blackmisc/valuecache.cpp @@ -144,6 +144,65 @@ namespace BlackMisc insertValues(map); } + CStatusMessage CValueCache::saveToFiles(const QString &dir, const QString &keyPrefix) const + { + auto values = getAllValues(keyPrefix); + QMap namespaces; + for (auto it = values.cbegin(); it != values.cend(); ++it) + { + namespaces[it.key().section('/', 0, 0)].insert(it.key(), it.value()); + } + if (! QDir::root().mkpath(dir)) + { + return CLogMessage(this).error("Failed to create directory %1") << dir; + } + for (auto it = namespaces.cbegin(); it != namespaces.cend(); ++it) + { + QFile file(dir + "/" + it.key() + ".json"); + if (! file.open(QFile::ReadWrite | QFile::Text)) + { + return CLogMessage(this).error("Failed to open %1: %2") << file.fileName() << file.errorString(); + } + auto json = QJsonDocument::fromJson(file.readAll()); + if (json.isArray() || (json.isNull() && ! json.isEmpty())) + { + return CLogMessage(this).error("Invalid JSON format in %1") << file.fileName(); + } + CVariantMap storedValues; + storedValues.convertFromJson(json.object()); + storedValues.insert(*it); + json.setObject(storedValues.toJson()); + if (! (file.seek(0) && file.resize(0) && file.write(json.toJson()) > 0)) + { + return CLogMessage(this).error("Failed to write to %1: %2") << file.fileName() << file.errorString(); + } + } + return {}; + } + + CStatusMessage CValueCache::loadFromFiles(const QString &dir) + { + if (! QDir(dir).isReadable()) + { + return CLogMessage(this).error("Failed to read directory %1") << dir; + } + for (const auto &filename : QDir(dir).entryList({ "*.json" }, QDir::Files)) + { + QFile file(dir + "/" + filename); + if (! file.open(QFile::ReadOnly | QFile::Text)) + { + return CLogMessage(this).error("Failed to open %1 : %2") << file.fileName() << file.errorString(); + } + auto json = QJsonDocument::fromJson(file.readAll()); + if (json.isArray() || (json.isNull() && ! json.isEmpty())) + { + return CLogMessage(this).error("Invalid JSON format in %1") << file.fileName(); + } + loadFromJson(json.object()); + } + return {}; + } + CValueCache::BatchGuard CValueCache::batchChanges(QObject *owner) { Q_ASSERT(QThread::currentThread() == owner->thread()); diff --git a/src/blackmisc/valuecache.h b/src/blackmisc/valuecache.h index 38eee4aed..4b9317d31 100644 --- a/src/blackmisc/valuecache.h +++ b/src/blackmisc/valuecache.h @@ -59,6 +59,16 @@ namespace BlackMisc //! \threadsafe void loadFromJson(const QJsonObject &json); + //! Save values to Json files in a given directory. + //! If prefix is provided then only those values whose keys start with that prefix. + //! \threadsafe + CStatusMessage saveToFiles(const QString &directory, const QString &keyPrefix = {}) const; + + //! Load all values from Json files in a given directory. + //! Values already in the cache will remain in the cache unless they are overwritten. + //! \threadsafe + CStatusMessage loadFromFiles(const QString &directory); + //! Begins a batch of changes to be made through CCached instances owned by owner. //! \details All changes made through those CCached instances will be deferred until the returned RAII object is //! destroyed. If the destruction happens during stack unwinding due to an exception being thrown, the changes are diff --git a/tests/blackmisc/testvaluecache.cpp b/tests/blackmisc/testvaluecache.cpp index 20b083024..6ef1db450 100644 --- a/tests/blackmisc/testvaluecache.cpp +++ b/tests/blackmisc/testvaluecache.cpp @@ -10,12 +10,15 @@ #include "testvaluecache.h" #include "blackmisc/worker.h" #include "blackmisc/identifier.h" +#include "blackmisc/aviation/aircraftlist.h" +#include "blackmisc/aviation/atcstationlist.h" #include namespace BlackMiscTest { using namespace BlackMisc; + using namespace BlackMisc::Aviation; CTestValueCache::CTestValueCache(QObject *parent) : QObject(parent) { @@ -195,6 +198,38 @@ namespace BlackMiscTest QVERIFY(cache.saveToJson() == testJson); } + void CTestValueCache::saveAndLoad() + { + CAircraftList aircraft({ CAircraft("BAW001", {}, {}) }); + CAtcStationList atcStations({ CAtcStation("EGLL_TWR" ) }); + CVariantMap testData + { + { "namespace1/value1", CVariant::from(1) }, + { "namespace1/value2", CVariant::from(2) }, + { "namespace1/value3", CVariant::from(3) }, + { "namespace2/aircraft", CVariant::from(aircraft) }, + { "namespace2/atcstations", CVariant::from(atcStations) } + }; + CValueCache cache(CValueCache::LocalOnly); + cache.insertValues(testData); + + QDir dir(QDir::currentPath() + "/testcache"); + if (dir.exists()) { dir.removeRecursively(); } + + auto status = cache.saveToFiles(dir.absolutePath()); + QVERIFY(status.isEmpty()); + + auto files = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name); + QCOMPARE(files.size(), 2); + QCOMPARE(files[0].fileName(), QString("namespace1.json")); + QCOMPARE(files[1].fileName(), QString("namespace2.json")); + + CValueCache cache2(CValueCache::LocalOnly); + status = cache2.loadFromFiles(dir.absolutePath()); + QVERIFY(status.isEmpty()); + QCOMPARE(cache2.getAllValues(), testData); + } + bool validator(int value) { return value >= 0 && value <= 100; diff --git a/tests/blackmisc/testvaluecache.h b/tests/blackmisc/testvaluecache.h index 1f80ee18c..7407f81a3 100644 --- a/tests/blackmisc/testvaluecache.h +++ b/tests/blackmisc/testvaluecache.h @@ -48,6 +48,9 @@ namespace BlackMiscTest //! Test Json serialization. void json(); + + //! Test saving to and loading from files. + void saveAndLoad(); }; /*!