refs #559, moved cache/settings to blackmisc

This commit is contained in:
Klaus Basan
2015-12-18 01:28:51 +01:00
parent 16a6544017
commit 070ba8f158
4 changed files with 18 additions and 28 deletions

170
src/blackmisc/datacache.cpp Normal file
View File

@@ -0,0 +1,170 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "datacache.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/identifier.h"
#include <QStandardPaths>
#include <QLockFile>
namespace BlackMisc
{
CDataCache::CDataCache() :
CValueCache(CValueCache::LocalOnly) // for signal loopback
{
if (! QDir::root().mkpath(persistentStore()))
{
CLogMessage(this).error("Failed to create directory %1") << persistentStore();
}
connect(this, &CValueCache::valuesChangedByLocal, this, &CDataCache::saveToStoreAsync);
connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &CDataCache::loadFromStoreAsync);
connect(&m_serializer, &CDataCacheSerializer::valuesLoadedFromStore, this, &CDataCache::changeValuesFromRemote);
if (! QFile::exists(m_revisionFileName)) { QFile(m_revisionFileName).open(QFile::WriteOnly); }
m_watcher.addPath(m_revisionFileName);
m_serializer.start();
loadFromStoreAsync();
}
CDataCache *CDataCache::instance()
{
static CDataCache cache;
return &cache;
}
const QString &CDataCache::persistentStore()
{
static const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/org.swift-project/data/cache/core";
return dir;
}
QString CDataCache::filenameForKey(const QString &key)
{
return persistentStore() + "/" + CValueCache::filenameForKey(key);
}
QStringList CDataCache::enumerateStore() const
{
return enumerateFiles(persistentStore());
}
QString lockFileError(const QLockFile &lock)
{
switch (lock.error())
{
case QLockFile::NoError: return "No error";
case QLockFile::PermissionError: return "Insufficient permission";
case QLockFile::UnknownError: return "Unknown filesystem error";
case QLockFile::LockFailedError:
{
QString hostname, appname;
qint64 pid = 0;
lock.getLockInfo(&pid, &hostname, &appname);
return QString("Lock open in another process (%1 %2 on %3)").arg(hostname, QString::number(pid), appname);
}
default: return "Bad error number";
}
}
void CDataCache::saveToStoreAsync(const BlackMisc::CValueCachePacket &values)
{
auto baseline = getAllValues();
QTimer::singleShot(0, &m_serializer, [this, baseline, values]
{
m_serializer.saveToStore(values.toVariantMap(), baseline);
});
}
void CDataCache::loadFromStoreAsync()
{
auto baseline = getAllValues();
QTimer::singleShot(0, &m_serializer, [this, baseline]
{
m_serializer.loadFromStore(baseline);
});
}
CDataCacheSerializer::CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName) :
CContinuousWorker(owner),
m_cache(owner),
m_revisionFileName(revisionFileName)
{}
const QString &CDataCacheSerializer::persistentStore() const
{
return m_cache->persistentStore();
}
void CDataCacheSerializer::saveToStore(const BlackMisc::CVariantMap &values, const BlackMisc::CVariantMap &baseline)
{
QLockFile revisionFileLock(m_revisionFileName + ".lock");
if (! revisionFileLock.lock())
{
CLogMessage(this).error("Failed to lock %1: %2") << m_revisionFileName << lockFileError(revisionFileLock);
return;
}
loadFromStore(baseline, false, true); // last-minute check for remote changes before clobbering the revision file
for (const auto &key : values.keys()) { m_deferredChanges.remove(key); } // ignore changes that we are about to overwrite
QFile revisionFile(m_revisionFileName);
if (! revisionFile.open(QFile::WriteOnly))
{
CLogMessage(this).error("Failed to open %1: %2") << m_revisionFileName << revisionFile.errorString();
return;
}
m_revision = CIdentifier().toUuid();
revisionFile.write(m_revision.toByteArray());
m_cache->saveToFiles(persistentStore(), values);
if (! m_deferredChanges.isEmpty()) // apply changes which we grabbed at the last minute above
{
emit valuesLoadedFromStore(m_deferredChanges, CIdentifier::anonymous());
m_deferredChanges.clear();
}
}
void CDataCacheSerializer::loadFromStore(const BlackMisc::CVariantMap &baseline, bool revLock, bool defer)
{
QLockFile revisionFileLock(m_revisionFileName + ".lock");
if (revLock && ! revisionFileLock.lock())
{
CLogMessage(this).error("Failed to lock %1: %2") << m_revisionFileName << lockFileError(revisionFileLock);
return;
}
QFile revisionFile(m_revisionFileName);
if (! revisionFile.exists())
{
return;
}
if (! revisionFile.open(QFile::ReadOnly))
{
CLogMessage(this).error("Failed to open %1: %2") << m_revisionFileName << revisionFile.errorString();
return;
}
QUuid newRevision(revisionFile.readAll());
if (m_revision != newRevision)
{
m_revision = newRevision;
CValueCachePacket newValues;
m_cache->loadFromFiles(persistentStore(), baseline, newValues);
m_deferredChanges.insert(newValues);
}
if (! (m_deferredChanges.isEmpty() || defer))
{
emit valuesLoadedFromStore(m_deferredChanges, CIdentifier::anonymous());
m_deferredChanges.clear();
}
}
}

154
src/blackmisc/datacache.h Normal file
View File

@@ -0,0 +1,154 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKMISC_DATACACHE_H
#define BLACKMISC_DATACACHE_H
#include "blackmisc/blackmiscexport.h"
#include "blackmisc/valuecache.h"
#include "blackmisc/worker.h"
#include <QUuid>
#include <QFileSystemWatcher>
namespace BlackMisc
{
class CDataCache;
/*!
* Worker which performs (de)serialization on behalf of CDataCache, in a separate thread
* so that the main thread is not blocked by (de)serialization of large objects.
*/
class BLACKMISC_EXPORT CDataCacheSerializer : public BlackMisc::CContinuousWorker
{
Q_OBJECT
public:
//! Constructor.
CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName);
//! Save values to persistent store. Called whenever a value is changed locally.
void saveToStore(const BlackMisc::CVariantMap &values, const BlackMisc::CVariantMap &baseline);
//! Load values from persistent store. Called once per second.
//! Also called by saveToStore, to ensure that remote changes to unrelated values are not lost.
//! \param baseline A snapshot of the currently loaded values, taken when the load is queued.
//! \param lock Whether to acquire the revision file lock. Used when called by saveToStore.
//! \param defer Whether to defer applying the changes. Used when called by saveToStore.
void loadFromStore(const BlackMisc::CVariantMap &baseline, bool lock = true, bool defer = false);
signals:
//! Signal back to the cache when values have been loaded.
void valuesLoadedFromStore(const BlackMisc::CValueCachePacket &values, const BlackMisc::CIdentifier &originator);
private:
const QString &persistentStore() const;
const CDataCache *const m_cache = nullptr;
QUuid m_revision;
const QString m_revisionFileName;
BlackMisc::CValueCachePacket m_deferredChanges;
};
/*!
* Singleton derived class of CValueCache, for core dynamic data.
*
* File-based distribution between processes is built-in to the class.
*/
class BLACKMISC_EXPORT CDataCache : public BlackMisc::CValueCache
{
Q_OBJECT
public:
//! Return the singleton instance.
static CDataCache *instance();
//! The directory where core data are stored.
static const QString &persistentStore();
//! Return the filename where the value with the given key may be stored.
static QString filenameForKey(const QString &key);
//! Return all files where data may be stored.
QStringList enumerateStore() const;
private:
CDataCache();
void saveToStoreAsync(const BlackMisc::CValueCachePacket &values);
void loadFromStoreAsync();
QFileSystemWatcher m_watcher;
const QString m_revisionFileName { persistentStore() + "/.rev" };
CDataCacheSerializer m_serializer { this, m_revisionFileName };
friend class CDataCacheSerializer; // to access protected members of CValueCache
};
/*!
* Class template for accessing a specific value in the CDataCache.
* \tparam Trait A subclass of BlackMisc::CDataTrait that identifies the value's key and other metadata.
*/
template <typename Trait>
class CData : public BlackMisc::CCached<typename Trait::type>
{
public:
//! \copydoc BlackMisc::CCached::NotifySlot
template <typename T>
using NotifySlot = typename BlackMisc::CCached<typename Trait::type>::template NotifySlot<T>;
//! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value.
//! \param slot Slot to call when the value is modified by another object.
//! Must be a void, non-const member function of the owner.
template <typename T>
CData(T *owner, NotifySlot<T> slot = nullptr) :
CData::CCached(CDataCache::instance(), Trait::key(), Trait::isValid, Trait::defaultValue(), owner, slot)
{}
//! Reset the data to its default value.
void setDefault() { this->set(Trait::defaultValue()); }
//! Return the file that is used for persistence for this value.
QString getFilename() const { return CDataCache::filenameForKey(this->getKey()); }
};
/*!
* Base class for traits to be used as template argument to BlackMisc::CData.
*/
template <typename T>
struct CDataTrait
{
//! Data type of the value.
using type = T;
//! Key string of the value. Reimplemented in derived class.
static const char *key() { qFatal("Not implemented"); return ""; }
//! Validator function. Return true if the argument is valid, false otherwise. Default
//! implementation just returns true. Reimplemented in derived class to support validation of the value.
static bool isValid(const T &) { return true; }
//! Return the value to use in case the supplied value does not satisfy the validator.
//! Default implementation returns a default-constructed value.
static const T &defaultValue() { static const T def {}; return def; }
//! Deleted default constructor.
CDataTrait() = delete;
//! Deleted copy constructor.
CDataTrait(const CDataTrait &) = delete;
//! Deleted copy assignment operator.
CDataTrait &operator =(const CDataTrait &) = delete;
};
}
#endif

View File

@@ -0,0 +1,50 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "settingscache.h"
#include <QStandardPaths>
namespace BlackMisc
{
CSettingsCache::CSettingsCache() :
CValueCache(CValueCache::Distributed)
{}
CSettingsCache *CSettingsCache::instance()
{
static CSettingsCache cache;
return &cache;
}
const QString &CSettingsCache::persistentStore()
{
static const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/org.swift-project/settings/core";
return dir;
}
BlackMisc::CStatusMessage CSettingsCache::saveToStore(const QString &keyPrefix) const
{
return saveToFiles(persistentStore(), keyPrefix);
}
BlackMisc::CStatusMessage CSettingsCache::loadFromStore()
{
return loadFromFiles(persistentStore());
}
QString CSettingsCache::filenameForKey(const QString &key)
{
return persistentStore() + "/" + CValueCache::filenameForKey(key);
}
QStringList CSettingsCache::enumerateStore() const
{
return enumerateFiles(persistentStore());
}
}

View File

@@ -0,0 +1,109 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKMISC_SETTINGSCACHE_H
#define BLACKMISC_SETTINGSCACHE_H
#include "blackmisc/blackmiscexport.h"
#include "blackmisc/valuecache.h"
namespace BlackMisc
{
/*!
* Singleton derived class of CValueCache, for core settings.
*/
class BLACKMISC_EXPORT CSettingsCache : public BlackMisc::CValueCache
{
Q_OBJECT
public:
//! Return the singleton instance.
static CSettingsCache *instance();
//! The directory where core settings are stored.
static const QString &persistentStore();
//! Save core settings to disk.
BlackMisc::CStatusMessage saveToStore(const QString &keyPrefix = {}) const;
//! Load core settings from disk.
BlackMisc::CStatusMessage loadFromStore();
//! Return the filename where the value with the given key may be stored.
static QString filenameForKey(const QString &key);
//! Return all files where settings may be stored.
QStringList enumerateStore() const;
private:
CSettingsCache();
};
/*!
* Class template for accessing a specific value in the CSettingsCache.
* \tparam Trait A subclass of BlackMisc::CSettingTrait that identifies the value's key and other metadata.
*/
template <typename Trait>
class CSetting : public BlackMisc::CCached<typename Trait::type>
{
public:
//! \copydoc BlackMisc::CCached::NotifySlot
template <typename T>
using NotifySlot = typename BlackMisc::CCached<typename Trait::type>::template NotifySlot<T>;
//! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value.
//! \param slot Slot to call when the value is modified by another object.
//! Must be a void, non-const member function of the owner.
template <typename T>
CSetting(T *owner, NotifySlot<T> slot = nullptr) :
CSetting::CCached(CSettingsCache::instance(), Trait::key(), Trait::isValid, Trait::defaultValue(), owner, slot)
{}
//! Reset the setting to its default value.
void setDefault() { this->set(Trait::defaultValue()); }
//! Return the file that is used for persistence for this value.
QString getFilename() const { return CSettingsCache::filenameForKey(this->getKey()); }
};
/*!
* Base class for traits to be used as template argument to BlackMisc::CSetting.
*/
template <typename T>
struct CSettingTrait
{
//! Data type of the value.
using type = T;
//! Key string of the value. Reimplemented in derived class.
static const char *key() { qFatal("Not implemented"); return ""; }
//! Validator function. Return true if the argument is valid, false otherwise. Default
//! implementation just returns true. Reimplemented in derived class to support validation of the value.
static bool isValid(const T &) { return true; }
//! Return the value to use in case the supplied value does not satisfy the validator.
//! Default implementation returns a default-constructed value.
static const T &defaultValue() { static const T def {}; return def; }
//! Deleted default constructor.
CSettingTrait() = delete;
//! Deleted copy constructor.
CSettingTrait(const CSettingTrait &) = delete;
//! Deleted copy assignment operator.
CSettingTrait &operator =(const CSettingTrait &) = delete;
};
}
#endif