/*!
@~chinese
@file include/global/dconfig.h
@ingroup dglobal
@class Dtk::Core::DConfigBackend dconfig.h
@brief 配置后端的抽象接口。
@details 所有DConfig使用的配置后端都继承此类,用户可以继承此类实现自己的配置后端。

@sa FileBackend
@sa DBusBackend
@sa QSettingBackend

@fn Dtk::Core::DConfigBackend::~DConfigBackend()
@brief DConfigBackend析构函数
@sa FileBackend::~FileBackend()
@sa DBusBackend::~DBusBackend()
@sa QSettingBackend::~QSettingBackend()

@fn bool Dtk::Core::DConfigBackend::isValid() const = 0
@brief 判断此后端是否可用
@sa DConfig::isValid()
@sa FileBackend::isValid()
@sa DBusBackend::isValid()
@sa QSettingBackend::isValid()

@fn bool Dtk::Core::DConfigBackend::load(const QString &) = 0
@brief 初始化后端
@details appId 管理的配置信息key值,默认为应用程序名称。
@sa FileBackend::load()
@sa DBusBackend::load()
@sa QSettingBackend::load()

@fn QStringList Dtk::Core::DConfigBackend::keyList() = 0
@brief 获得所有可用的配置项名称
@sa DConfig::keyList()
@sa FileBackend::keyList()
@sa DBusBackend::keyList()
@sa QSettingBackend::keyList()

@fn QVariant Dtk::Core::DConfigBackend::value(const QString &key, const QVariant &fallback = QVariant()) const = 0
@brief 根据配置项名称获得对应值
@param[in] key 配置项名称
@param[in] fallback 没有获取到配置项值后提供的默认值
@sa DConfig::value()
@sa FileBackend::value()
@sa DBusBackend::value()
@sa QSettingBackend::value()

@fn void Dtk::Core::DConfigBackend::setValue(const QString &key, const QVariant &value) = 0
@brief 根据配置项名称设置其值
@param[in] key 配置项名称
@param[in] value 需要更新的值
@sa DConfig::setValue()
@sa FileBackend::setValue()
@sa DBusBackend::setValue()
@sa QSettingBackend::setValue()

@fn void Dtk::Core::DConfigBackend::reset(const QString &key)
@brief 设置其配置项对应的默认值,此值为经过override机制覆盖后的值,不一定为此配置文件中meta中定义的值。
@param[in] key 配置项名称
@sa DConfig::reset()
@sa FileBackend::reset()
@sa DBusBackend::reset()
@sa QSettingBackend::reset()

@fn bool Dtk::Core::DConfigBackend::isReadOnly(const QString &key)
@brief 检测指定配置项是否为只读
@param[in] key 配置项名称
@return 如果配置项为只读返回true，否则返回false
@sa DConfig::isReadOnly()
@sa FileBackend::isReadOnly()
@sa DBusBackend::isReadOnly()

@fn QString Dtk::Core::DConfigBackend::name() const = 0
@brief 后端配置的唯一标识
@sa FileBackend::name()
@sa DBusBackend::name()
@sa QSettingBackend::name()

@fn bool Dtk::Core::DConfigBackend::isDefaultValue(const QString &key)
@brief 检测指定配置项名称对应的值是否为默认值。
@param[in] key 配置项名称
@sa DConfig::isDefaultValue()
@sa FileBackend::isDefaultValue()
@sa DBusBackend::isDefaultValue()
@sa QSettingBackend::isDefaultValue()

@class Dtk::Core::QSettingBackend dconfig.h
@brief QSetting后端,继承自DConfigBackend抽象接口,并实现了虚函数。
@sa DConfigBackend

@fn QSettingBackend Dtk::Core::QSettingBackend::QSettingBackend(DConfigPrivate* o)
@brief QSettingBackend构造函数

@class Dtk::Core::DConfigPrivate dconfig.h
@brief DConfig的私有实现

@fn DConfigPrivate Dtk::Core::DConfigPrivate::DConfigPrivate()
@brief DConfigPrivate构造函数

@fn bool Dtk::Core::DConfigPrivate::invalid()
@brief 判断此后端是否可用

@fn DConfigBackend* Dtk::Core::DConfigPrivate::getOrCreateBackend()
@brief 创建一个配置后端
@details 默认使用的配置后端会优先根据环境变量来选择配置中心的D-Bus接口还是文件配置后端接口。若没有配置此环境变量,则根据是否有配置中心提供D-Bus服务来选择配置中心服务还是文件配置后端接口。

@fn DConfigBackend* Dtk::Core::DConfigPrivate::createBackendByEnv()
@brief 创建一个配置后端
@details 尝试根据环境变量来选择配置中心的D-Bus接口还是文件配置后端接口。

@var QString Dtk::Core::DConfigPrivate::appId
@brief 配置文件所属的应用Id,为空时默认为本应用Id。

@var QString Dtk::Core::DConfigPrivate::name
@brief 配置文件名

@var QString Dtk::Core::DConfigPrivate::subpath
@brief 配置文件对应的子目录

@var QScopedPointer<DConfigBackend> Dtk::Core::DConfigPrivate::backend
@brief 配置策略后端

@class Dtk::Core::DConfig dconfig.h
@brief 配置策略提供的接口类
@details
## 概述

此接口规范定义了开发库所提供的关于配置文件读写的相关接口，如果应用程序所使用的开发库实现了此规范，则程序应当优先使用开发库提供的接口。

项目目录结构如下：
```bash
├── CMakeLists.txt
├── config
│   └── example.json
└── main.cpp
```

## CMakeLists.txt

```cmake
cmake_minimum_required(VERSION 3.1.0)                # 指定cmake最低版本

project(dconfig-example VERSION 1.0.0 LANGUAGES CXX) # 指定项目名称, 版本, 语言 cxx就是c++

set(CMAKE_CXX_STANDARD 11)                           # 指定c++标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)                  # 指定c++标准要求,至少为11以上

set(CMAKE_AUTOMOC ON)                                # 支持qt moc
set(CMAKE_AUTORCC ON)                                # support qt resource file # 支持qt资源文件

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)                # 支持 clangd

if (CMAKE_VERSION VERSION_LESS "3.7.0")              # 如果cmake版本小于3.7.0
    set(CMAKE_INCLUDE_CURRENT_DIR ON)                # 设置包含当前目录
endif()

find_package(Qt5 REQUIRED COMPONENTS Core)           # 寻找Qt5组件Core
find_package(Dtk REQUIRED COMPONENTS Core)           # 寻找Dtk组件Core

add_executable(${PROJECT_NAME}                       # 生成可执行文件
    main.cpp
)

target_link_libraries(${PROJECT_NAME} PRIVATE        # 添加需要链接的共享库
    Qt5::Core
    dtkcore
)

# dtk_add_config_meta_files函数，部署一些"meta"的配置。
# 函数定义在dtkcore的cmake目录下
# APPID 应用的ID
# FILES 需要部署的文件。
dtk_add_config_meta_files(
    APPID ${PROJECT_NAME} 
    FILES ./config/example.json
)
```

## example.json
```json
{
    "magic": "dsg.config.meta",
    "version": "1.0",
    "contents": {
        "canExit": {
            "value": true,
            "serial": 0,
            "flags": ["global"],
            "name": "I am name",
            "name[zh_CN]": "我是名字",
            "description": "I am description",
            "permissions": "readwrite",
            "visibility": "private"
        },
        "key1": {
            "value": "125",
            "serial": 0,
            "flags": ["nooverride"],
            "name": "I am name",
            "name[zh_CN]": "我是名字",
            "description": "I am description",
            "permissions": "readwrite",
            "visibility": "public"
        },
        "number": {
          "value": 1,
          "serial": 0,
          "flags": ["global"],
          "name": "array value type",
          "permissions": "readwrite",
          "visibility": "public"
        },
        "array": {
          "value": ["value1", "value2"],
          "serial": 0,
          "flags": ["global"],
          "name": "array",
          "permissions": "readwrite",
          "visibility": "public"
        },
        "map": {
          "value": {"key1": "value1", "key2": "value2"},
          "serial": 0,
          "flags": ["global"],
          "name": "map",
          "permissions": "readwrite",
          "visibility": "public"
        }
    }
}
```

## main.cpp
```cpp
#include <DConfig>
#include <QDebug>
#include <QCoreApplication>

DCORE_USE_NAMESPACE

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    // 构造DConfig，元数据文件名example，与元数据安装目录的/usr/share/dsg/configs/APPID名(可执行文件名)对应目录/example.json，
    DConfig config("example");

    // 判断是否有效
    if (!config.isValid()) {
        qWarning() << QString("DConfig无效, name:[%1].").
                      arg(config.name());
        return 0;
    }

    // 获取所有配置项的key
    qDebug() << "所有的所有配置项的key:" << config.keyList();

    // 获取指定配置项的值，配置项可以是字符串，数组，map容器，布尔值，整型，详见example.json
    qDebug() << "canExit对应的值:" << config.value("canExit").toBool();

    QVariantMap map;
    for (int i = 0; i < 1; i++) {
        QVariantMap nestItem;
        for (int j = 0; j < 1; j++) {
            nestItem[QString::number(j)] = QString::number(j);
        }
        map[QString::number(i)] = nestItem;
    }

    QScopedPointer<DConfig> heapConfig;
    heapConfig.reset(new DConfig("example"));

    if (!heapConfig->isValid()) {
        qWarning() << QString("DConfig无效, name:[%1].").
                      arg(heapConfig->name());
        return 0;
    }

    // 监听值改变的信号
    const bool &oldValue = heapConfig->value("canExit").toBool();
    QObject::connect(heapConfig.get(), &DConfig::valueChanged, [oldValue,&heapConfig](const QString &key){
        qDebug() << "canExit原来的值:" << oldValue << ", canExit新的值:" << heapConfig->value(key).toBool();
    });
    
    // 重置canExit的值
    heapConfig->setValue("canExit", !oldValue);

    return a.exec();
}
```

## 从源码构建
```bash
mkdir build && cd build
# 修改路径前缀为/usr,GNU标准的默认值为/usr/local
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install
```
结果如下图

![example](/docs/src/dconfig_example1.png)

@fn DConfig Dtk::Core::DConfig(const QString &name, const QString &subpath, QObject *parent)
@brief 构造配置策略提供的对象
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象

@fn DConfig Dtk::Core::DConfig(DConfigBackend *backend, const QString &name, const QString &subpath, QObject *parent)
@brief 使用自定义的配置策略后端构造对象
@param[in] backend 调用者继承于DConfigBackend的配置策略后端
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象

@fn static DConfig* Dtk::Core::DConfig::create(const QString &appId, const QString &name, const QString &subpath, QObject *parent)
@brief 构造配置策略提供的对象,指定配置所属的应用Id，管理某一特定应用的配置。
@param[in] appId 配置文件所属的应用Id,为空时默认为本应用Id
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象
@return 构造的配置策略对象,由调用者释放

@fn static DConfig* Dtk::Core::DConfig::create(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, QObject *parent)
@brief 构造配置策略提供的对象,指定配置所属的应用Id。
@param[in] backend 调用者继承于DConfigBackend的配置策略后端
@param[in] appId 配置文件所属的应用Id,为空时默认为本应用Id
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象
@return 构造的配置策略对象,由调用者释放

@fn static DConfig* Dtk::Core::DConfig::createGeneric(const QString &name, const QString &subpath, QObject *parent)
@brief 构造配置策略提供的对象，用来管理与应用无关的配置。
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象
@return 构造的配置策略对象,由调用者释放
@note 如果我们管理针对某一特定应用的配置，应该使用 Dtk::Core::DConfig::create()，或 Dtk::Core::DConfig()，来指定所属应用的appId。

@fn static DConfig* Dtk::Core::DConfig::createGeneric(DConfigBackend *backend, const QString &name, const QString &subpath, QObject *parent)
@sa Dtk::Core::DConfig::createGeneric()

@fn DConfig Dtk::Core::DConfig(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, QObject *parent)
@brief 使用自定义的配置策略后端构造对象
@param[in] backend 调用者继承于DConfigBackend的配置策略后端
@param[in] appId 配置文件所属的应用Id,为空时默认为本应用Id
@param[in] name 配置文件名
@param[in] subpath 配置文件对应的子目录
@param[in] parent 父对象
@note 调用者只构造backend,由DConfig释放。

@fn static void DConfig::setAppId(const QString &appId)
@brief 显示指定应用Id,不采用DSGApplication::id()作为应用Id
@param[in] appId 配置文件所属的应用Id
@note 需要在QCoreApplication构造前设置。

@fn static void DConfig::globalThread()
@brief 一个服务于 DConfig 的公用线程，一般用于 dconfig2cpp 生成的代码，此线程在构造时会自动调用 QThread::start 以满足 dconfig2cpp 的需求。
@return 此线程默认为 running 状态
@note 请不要析构它，它会在应用程序退出时释放

@fn QString Dtk::Core::DConfig::backendName()
@brief 配置策略后端名称
@return 配置策略后端名称
@note 调用者只能用DConfig访问DConfigBackend对象,所以不返回DConfigBackend对象。

@fn QStringList Dtk::Core::DConfig::keyList()
@brief 获得所有可用的配置项名称
@return 配置项名称集合

@fn bool Dtk::Core::DConfig::isValid()
@brief 判断此后端是否可用

@fn bool Dtk::Core::DConfig::isDefaultValue(const QString &key)
@brief 检测指定配置项名称对应的值是否为默认值。
@param[in] key 配置项名称

@fn QVariant Dtk::Core::DConfig::value(const QString &key, const QVariant &fallback)
@brief 根据配置项名称获得对应值
@param[in] key 配置项名称
@param[in] fallback 没有获取到配置项值后提供的默认值

@fn void Dtk::Core::DConfig::setValue(const QString &key, const QVariant &value);
@brief 根据配置项名称设置其值
@param[in] key 配置项名称
@param[in] value 需要更新的值

@fn void Dtk::Core::DConfig::reset(const QString &key)
@brief 设置其配置项对应的默认值,此值为经过override机制覆盖后的值,不一定为此配置文件中meta中定义的值
@param[in] key 配置项名称

@fn bool Dtk::Core::DConfig::isReadOnly(const QString &key)
@brief 检测指定配置项是否为只读
@param[in] key 配置项名称
@return 如果配置项为只读返回true，否则返回false

@fn QString Dtk::Core::DConfig::name()
@brief 返回配置文件名称

@fn QString Dtk::Core::DConfig::subpath()
@brief 返回配置文件对应的子目录

*/
