Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 91 additions & 16 deletions src/dconfigfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
#include <QDebug>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>

Check warning on line 20 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDirIterator> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QCollator>

Check warning on line 21 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCollator> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 22 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QRegularExpression>

Check warning on line 23 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QRegularExpression> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <unistd.h>

Check warning on line 25 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <unistd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <pwd.h>

Check warning on line 26 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <pwd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

// https://gitlabwh.uniontech.com/wuhan/se/deepin-specifications/-/issues/3

Expand All @@ -36,6 +37,50 @@

#define FILE_SUFFIX QLatin1String(".json")

// QJson treats all numbers as double, and QJsonValue::toVariant() degrades a
// float literal with no fractional part (e.g. 1.0) to an integer type
// (qlonglong). To keep the original float intent, we match the raw JSON text
// to detect whether the "value" literal contained '.' or 'e'/'E'.
// This mirrors the approach used by tools/dconfig2cpp/main.cpp:isOriginalValueFloat.
struct JsonParseResult {
QJsonDocument doc;
QByteArray raw; // raw bytes, kept for float-literal detection
bool isValid() const { return !doc.isNull(); }
};

// Check whether the number literal under "propertyName" -> {"value": ...}
// in the raw JSON is a floating-point literal (contains '.' or 'e'/'E').
static bool isOriginalValueFloat(const QByteArray &raw, const QString &propertyName, const QJsonValue &value)
{
if (!value.isDouble()) {
return false;
}
const QString searchPattern = QString("\"%1\"\\s*:\\s*\\{[^}]*\"value\"\\s*:\\s*([0-9.eE+-]+)").arg(propertyName);
const QRegularExpression regex(searchPattern);
const QRegularExpressionMatch match = regex.match(QString::fromUtf8(raw));
if (match.hasMatch()) {
const QString numberStr = match.captured(1);
return numberStr.contains(QLatin1Char('.')) || numberStr.contains(QLatin1Char('e'), Qt::CaseInsensitive);
}
return false;
}

// Convert a QJsonValue to QVariant, forcing double when the raw literal was float.
inline static QVariant jsonValueToVariant(const QJsonValue &value, bool isFloat)
{
QVariant var = value.toVariant();
if (isFloat) {
QVariant copy = var;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (copy.convert(QMetaType(QMetaType::Double)))
#else
if (copy.convert(QVariant::Double))
#endif
var = copy;
}
return var;
}

// subpath must be a subdirectory of the dir.
inline static bool subpathIsValid(const QString &subpath, const QDir &dir)
{
Expand Down Expand Up @@ -111,26 +156,29 @@
return nullptr;
}

static QJsonDocument loadJsonFile(QIODevice *data)
static JsonParseResult loadJsonFile(QIODevice *data)
{
if (!data->open(QIODevice::ReadOnly)) {
if (auto file = qobject_cast<QFile*>(data)) {
qCDebug(cfLog, "Falied on open file: \"%s\", error message: \"%s\"",
qPrintable(file->fileName()), qPrintable(file->errorString()));
}
return QJsonDocument();
return JsonParseResult();
}

QJsonParseError error;
auto document = QJsonDocument::fromJson(data->readAll(), &error);
JsonParseResult result;
result.raw = data->readAll();
data->close();

QJsonParseError error;
result.doc = QJsonDocument::fromJson(result.raw, &error);

if (error.error != QJsonParseError::NoError) {
qCWarning(cfLog, "%s", qPrintable(error.errorString()));
return QJsonDocument();
return JsonParseResult();
}

return document;
return result;
}

static DConfigFile::Version parseVersion(const QJsonObject &obj) {
Expand Down Expand Up @@ -443,9 +491,9 @@
return true;
}

inline bool updateValue(const QString &key, const QJsonValue &value)
inline bool updateValue(const QString &key, const QJsonValue &value, const QByteArray &raw)
{
return overrideValue(key, "value", value);
return overrideValue(key, "value", value, raw);
}

inline void updateSerial(const QString &key, const QJsonValue &value)
Expand All @@ -467,14 +515,20 @@
return contents;
}
private:
bool overrideValue(const QString &key, const QString &subkey, const QJsonValue &from) {
bool overrideValue(const QString &key, const QString &subkey, const QJsonValue &from, const QByteArray &raw = QByteArray()) {
const QJsonValue &v = from[subkey];

if (v.isUndefined()) {
return false;
}

values[key][subkey] = v.toVariant();
if (subkey == QLatin1String("value")) {
// QJsonValue::toVariant() degrades 1.0 to qlonglong; restore double
// when the raw literal was a float.
values[key][subkey] = jsonValueToVariant(v, isOriginalValueFloat(raw, key, v));
} else {
values[key][subkey] = v.toVariant();
}
return true;
}

Expand Down Expand Up @@ -732,7 +786,8 @@
bool load(QIODevice *meta, const QList<QIODevice*> &overrides) override
{
{
const QJsonDocument &doc = loadJsonFile(meta);
const JsonParseResult pr = loadJsonFile(meta);
const QJsonDocument &doc = pr.doc;
if (!doc.isObject())
return false;

Expand All @@ -759,15 +814,25 @@

// 初始化原始值
for (; i != contents.constEnd(); ++i) {
if (!values.update(i.key(), i.value().toObject().toVariantHash())) {
const QJsonObject itemObj = i.value().toObject();
QVariantHash vh = itemObj.toVariantHash();
// QJsonValue::toVariant() degrades 1.0 to qlonglong; restore double
// when the raw literal was a float.
const QJsonValue valueField = itemObj.value(QLatin1String("value"));
if (valueField.isDouble()) {
vh.insert(QLatin1String("value"),
jsonValueToVariant(valueField, isOriginalValueFloat(pr.raw, i.key(), valueField)));
}
if (!values.update(i.key(), vh)) {
qWarning() << "key:" << i.key() << "has no value";
return false;
}
}
}
// for override
Q_FOREACH(auto override, overrides) {
const QJsonDocument &doc = loadJsonFile(override);
const JsonParseResult ovr = loadJsonFile(override);
const QJsonDocument &doc = ovr.doc;
if (doc.isObject()) {
const QJsonObject &root = doc.object();
if (!checkMagic(root, MAGIC_OVERRIDE)) {
Expand Down Expand Up @@ -800,7 +865,7 @@
if (values.flags(i.key()) & DConfigFile::NoOverride)
continue;

if (!values.updateValue(i.key(), i.value())) {
if (!values.updateValue(i.key(), i.value(), ovr.raw)) {
qWarning() << "key (override):" << i.key() << "has no value";
return false;
}
Expand Down Expand Up @@ -1183,7 +1248,8 @@
return true;
}

const QJsonDocument &doc = loadJsonFile(cache.data());
const JsonParseResult pr = loadJsonFile(cache.data());
const QJsonDocument &doc = pr.doc;

if (doc.isObject()) {
const QJsonObject &root = doc.object();
Expand All @@ -1197,7 +1263,16 @@

// 原样保存原始数据
for (; i != contents.constEnd(); ++i) {
values.update(i.key(), i.value().toObject().toVariantHash());
const QJsonObject itemObj = i.value().toObject();
QVariantHash vh = itemObj.toVariantHash();
// QJsonValue::toVariant() degrades 1.0 to qlonglong; restore double
// when the raw literal was a float.
const QJsonValue valueField = itemObj.value(QLatin1String("value"));
if (valueField.isDouble()) {
vh.insert(QLatin1String("value"),
jsonValueToVariant(valueField, isOriginalValueFloat(pr.raw, i.key(), valueField)));
}
values.update(i.key(), vh);
}
}
return true;
Expand Down
20 changes: 17 additions & 3 deletions tests/ut_dconfigfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,23 @@ TEST_F(ut_DConfigFile, setValueTypeCheck) {
ASSERT_EQ(config.value("number", userCache.get()).type(), type);
}
{
const auto type = config.value("numberDouble", userCache.get()).type();
ASSERT_TRUE(config.setValue("numberDouble", 1.2, "test", userCache.get()));
ASSERT_EQ(config.value("numberDouble", userCache.get()), 1.2);
const auto type = config.value("numberDouble", userCache.get()).type();
ASSERT_TRUE(config.setValue("numberDouble", 1.2, "test", userCache.get()));
ASSERT_EQ(config.value("numberDouble", userCache.get()), 1.2);
}
// The default literal in meta is "1.0" (a float literal); toVariant()
// would normally degrade it to qlonglong, losing the float intent. With
// the raw-text float detection fix, the default must stay double so that
// a later `dde-dconfig set -v 1.3` takes the Double branch.
{
const auto defaultValue = config.value("numberDouble", userCache.get());
ASSERT_EQ(defaultValue.userType(), static_cast<int>(QMetaType::Double));
}
// Conversely, "number" has default literal "1" (no fractional part), so
// it must remain an integer.
{
const auto defaultValue = config.value("number", userCache.get());
ASSERT_EQ(defaultValue.userType(), static_cast<int>(QMetaType::LongLong));
}
{
const auto type = config.value("array", userCache.get()).type();
Expand Down
Loading