mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-26 02:35:38 +08:00
refs #460 CDataCache for dynamic (downloaded/generated) data with file-based distribution among processes.
This commit is contained in:
129
src/blackcore/datacache.cpp
Normal file
129
src/blackcore/datacache.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/* 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>
|
||||
|
||||
using namespace BlackMisc;
|
||||
|
||||
namespace BlackCore
|
||||
{
|
||||
|
||||
CDataCache::CDataCache() :
|
||||
CValueCache(CValueCache::Distributed)
|
||||
{
|
||||
if (! QDir::root().mkpath(persistentStore()))
|
||||
{
|
||||
CLogMessage(this).error("Failed to create directory %1") << persistentStore();
|
||||
}
|
||||
|
||||
connect(this, &CValueCache::valuesChangedByLocal, this, &CDataCache::saveToStore);
|
||||
|
||||
connect(&m_reloadTimer, &QTimer::timeout, this, [this]() { loadFromStore(); });
|
||||
m_reloadTimer.start(1000);
|
||||
loadFromStore();
|
||||
}
|
||||
|
||||
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 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::saveToStore(const BlackMisc::CVariantMap &values)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
QLockFile revisionFileLock(m_revisionFileName);
|
||||
if (! revisionFileLock.lock())
|
||||
{
|
||||
CLogMessage(this).error("Failed to lock %1: %2") << m_revisionFileName << lockFileError(revisionFileLock);
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromStore(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());
|
||||
|
||||
saveToFiles(persistentStore(), values);
|
||||
}
|
||||
|
||||
void CDataCache::loadFromStore(bool revLock, bool defer)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
QLockFile revisionFileLock(m_revisionFileName);
|
||||
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;
|
||||
CVariantMap newValues;
|
||||
loadFromFiles(persistentStore(), newValues);
|
||||
m_deferredChanges.insert(newValues);
|
||||
}
|
||||
|
||||
if (! (m_deferredChanges.isEmpty() || defer))
|
||||
{
|
||||
changeValuesFromRemote(m_deferredChanges, CIdentifier::anonymous());
|
||||
m_deferredChanges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
110
src/blackcore/datacache.h
Normal file
110
src/blackcore/datacache.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* 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 BLACKCORE_DATACACHE_H
|
||||
#define BLACKCORE_DATACACHE_H
|
||||
|
||||
#include "blackcore/blackcoreexport.h"
|
||||
#include "blackmisc/valuecache.h"
|
||||
|
||||
namespace BlackCore
|
||||
{
|
||||
|
||||
/*!
|
||||
* Singleton derived class of CValueCache, for core dynamic data.
|
||||
*
|
||||
* File-based distribution between processes is built-in to the class.
|
||||
*/
|
||||
class BLACKCORE_EXPORT CDataCache : public BlackMisc::CValueCache
|
||||
{
|
||||
public:
|
||||
//! Return the singleton instance.
|
||||
static CDataCache *instance();
|
||||
|
||||
//! The directory where core data are stored.
|
||||
static const QString &persistentStore();
|
||||
|
||||
private:
|
||||
CDataCache();
|
||||
|
||||
//! Save values to persistent store. Called whenever a value is changed locally.
|
||||
void saveToStore(const BlackMisc::CVariantMap &values);
|
||||
|
||||
//! 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 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(bool lock = true, bool defer = false);
|
||||
|
||||
QTimer m_reloadTimer;
|
||||
QUuid m_revision;
|
||||
const QString m_revisionFileName { persistentStore() + "/.rev" };
|
||||
BlackMisc::CVariantMap m_deferredChanges;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Class template for accessing a specific value in the CDataCache.
|
||||
* \tparam Trait A subclass of BlackCore::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()); }
|
||||
};
|
||||
|
||||
/*!
|
||||
* Base class for traits to be used as template argument to BlackCore::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
|
||||
Reference in New Issue
Block a user