| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com> |
| 4 | SPDX-FileCopyrightText: 1999-2000 Preston Brown <pbrown@kde.org> |
| 5 | SPDX-FileCopyrightText: 1996-2000 Matthias Kalle Dalheimer <kalle@kde.org> |
| 6 | |
| 7 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 8 | */ |
| 9 | |
| 10 | #include "kconfigdata_p.h" |
| 11 | |
| 12 | QDebug operator<<(QDebug dbg, const KEntryKey &key) |
| 13 | { |
| 14 | dbg.nospace() << "[" << key.mGroup << ", " << key.mKey << (key.bLocal ? " localized" : "" ) << (key.bDefault ? " default" : "" ) << (key.bRaw ? " raw" : "" ) |
| 15 | << "]" ; |
| 16 | return dbg.space(); |
| 17 | } |
| 18 | |
| 19 | QDebug operator<<(QDebug dbg, const KEntry &entry) |
| 20 | { |
| 21 | dbg.nospace() << "[" << entry.mValue << (entry.bDirty ? " dirty" : "" ) << (entry.bGlobal ? " global" : "" ) |
| 22 | << (entry.bOverridesGlobal ? " overrides global" : "" ) << (entry.bImmutable ? " immutable" : "" ) << (entry.bDeleted ? " deleted" : "" ) |
| 23 | << (entry.bReverted ? " reverted" : "" ) << (entry.bExpand ? " expand" : "" ) << "]" ; |
| 24 | |
| 25 | return dbg.space(); |
| 26 | } |
| 27 | |
| 28 | KEntryMapIterator KEntryMap::findExactEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) |
| 29 | { |
| 30 | const KEntryKeyView theKey(group, key, bool(flags & SearchLocalized), bool(flags & SearchDefaults)); |
| 31 | return find(x: theKey); |
| 32 | } |
| 33 | |
| 34 | KEntryMapIterator KEntryMap::findEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) |
| 35 | { |
| 36 | KEntryKeyView theKey(group, key, false, bool(flags & SearchDefaults)); |
| 37 | |
| 38 | // try the localized key first |
| 39 | if (flags & SearchLocalized) { |
| 40 | theKey.bLocal = true; |
| 41 | |
| 42 | iterator it = find(x: theKey); |
| 43 | if (it != end()) { |
| 44 | return it; |
| 45 | } |
| 46 | |
| 47 | theKey.bLocal = false; |
| 48 | } |
| 49 | return find(x: theKey); |
| 50 | } |
| 51 | |
| 52 | KEntryMapConstIterator KEntryMap::constFindEntry(const QString &group, QAnyStringView key, SearchFlags flags) const |
| 53 | { |
| 54 | KEntryKeyView theKey(group, key, false, bool(flags & SearchDefaults)); |
| 55 | |
| 56 | // try the localized key first |
| 57 | if (flags & SearchLocalized) { |
| 58 | theKey.bLocal = true; |
| 59 | |
| 60 | auto it = find(x: theKey); |
| 61 | if (it != cend()) { |
| 62 | return it; |
| 63 | } |
| 64 | |
| 65 | theKey.bLocal = false; |
| 66 | } |
| 67 | |
| 68 | return find(x: theKey); |
| 69 | } |
| 70 | |
| 71 | bool KEntryMap::setEntry(const QString &group, const QByteArray &key, const QByteArray &value, KEntryMap::EntryOptions options) |
| 72 | { |
| 73 | KEntryKey k; |
| 74 | KEntry e; |
| 75 | bool newKey = false; |
| 76 | |
| 77 | const iterator it = findExactEntry(group, key, flags: SearchFlags(options >> 16)); |
| 78 | |
| 79 | if (key.isEmpty()) { // inserting a group marker |
| 80 | k.mGroup = group; |
| 81 | e.bImmutable = (options & EntryImmutable); |
| 82 | if (options & EntryDeleted) { |
| 83 | qWarning(msg: "Internal KConfig error: cannot mark groups as deleted" ); |
| 84 | } |
| 85 | if (it == end()) { |
| 86 | insert_or_assign(k: k, obj&: e); |
| 87 | return true; |
| 88 | } else if (it->second == e) { |
| 89 | return false; |
| 90 | } |
| 91 | |
| 92 | it->second = e; |
| 93 | return true; |
| 94 | } |
| 95 | |
| 96 | if (it != end()) { |
| 97 | if (it->second.bImmutable) { |
| 98 | return false; // we cannot change this entry. Inherits group immutability. |
| 99 | } |
| 100 | k = it->first; |
| 101 | e = it->second; |
| 102 | // qDebug() << "found existing entry for key" << k; |
| 103 | // If overridden entry is global and not default. And it's overridden by a non global |
| 104 | if (e.bGlobal && !(options & EntryGlobal) && !k.bDefault) { |
| 105 | e.bOverridesGlobal = true; |
| 106 | } |
| 107 | } else { |
| 108 | // make sure the group marker is in the map |
| 109 | KEntryMap const *that = this; |
| 110 | auto cit = that->constFindEntry(group); |
| 111 | if (cit == cend()) { |
| 112 | insert_or_assign(k: KEntryKey(group), obj: KEntry()); |
| 113 | } else if (cit->second.bImmutable) { |
| 114 | return false; // this group is immutable, so we cannot change this entry. |
| 115 | } |
| 116 | |
| 117 | k = KEntryKey(group, key); |
| 118 | newKey = true; |
| 119 | } |
| 120 | |
| 121 | // set these here, since we may be changing the type of key from the one we found |
| 122 | k.bLocal = (options & EntryLocalized); |
| 123 | k.bDefault = (options & EntryDefault); |
| 124 | k.bRaw = (options & EntryRawKey); |
| 125 | |
| 126 | e.mValue = value; |
| 127 | e.bDirty = e.bDirty || (options & EntryDirty); |
| 128 | e.bNotify = e.bNotify || (options & EntryNotify); |
| 129 | |
| 130 | e.bGlobal = (options & EntryGlobal); // we can't use || here, because changes to entries in |
| 131 | // kdeglobals would be written to kdeglobals instead |
| 132 | // of the local config file, regardless of the globals flag |
| 133 | e.bImmutable = e.bImmutable || (options & EntryImmutable); |
| 134 | if (value.isNull()) { |
| 135 | e.bDeleted = e.bDeleted || (options & EntryDeleted); |
| 136 | } else { |
| 137 | e.bDeleted = false; // setting a value to a previously deleted entry |
| 138 | } |
| 139 | e.bExpand = (options & EntryExpansion); |
| 140 | e.bReverted = false; |
| 141 | if (options & EntryLocalized) { |
| 142 | e.bLocalizedCountry = (options & EntryLocalizedCountry); |
| 143 | } else { |
| 144 | e.bLocalizedCountry = false; |
| 145 | } |
| 146 | |
| 147 | if (newKey) { |
| 148 | // qDebug() << "inserting" << k << "=" << value; |
| 149 | insert_or_assign(k: k, obj&: e); |
| 150 | if (k.bDefault) { |
| 151 | k.bDefault = false; |
| 152 | // qDebug() << "also inserting" << k << "=" << value; |
| 153 | insert_or_assign(k: k, obj&: e); |
| 154 | } |
| 155 | // TODO check for presence of unlocalized key |
| 156 | return true; |
| 157 | } |
| 158 | |
| 159 | // KEntry e2 = it->second; |
| 160 | if (options & EntryLocalized) { |
| 161 | // fast exit checks for cases where the existing entry is more specific |
| 162 | const KEntry &e2 = it->second; |
| 163 | if (e2.bLocalizedCountry && !e.bLocalizedCountry) { |
| 164 | // lang_COUNTRY > lang |
| 165 | return false; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | if (it->second != e) { |
| 170 | // qDebug() << "changing" << k << "from" << it->second.mValue << "to" << value << e; |
| 171 | it->second = e; |
| 172 | if (k.bDefault) { |
| 173 | KEntryKey nonDefaultKey(k); |
| 174 | nonDefaultKey.bDefault = false; |
| 175 | insert_or_assign(k: nonDefaultKey, obj&: e); |
| 176 | } |
| 177 | if (!(options & EntryLocalized)) { |
| 178 | KEntryKey theKey(group, key, true, false); |
| 179 | // qDebug() << "non-localized entry, remove localized one:" << theKey; |
| 180 | erase(x: theKey); |
| 181 | if (k.bDefault) { |
| 182 | theKey.bDefault = true; |
| 183 | erase(x: theKey); |
| 184 | } |
| 185 | } |
| 186 | return true; |
| 187 | } |
| 188 | |
| 189 | // qDebug() << k << "was already set to" << e.mValue; |
| 190 | if (!(options & EntryLocalized)) { |
| 191 | // qDebug() << "unchanged non-localized entry, remove localized one."; |
| 192 | KEntryKey theKey(group, key, true, false); |
| 193 | bool ret = false; |
| 194 | iterator cit = find(x: theKey); |
| 195 | if (cit != end()) { |
| 196 | erase(position: cit); |
| 197 | ret = true; |
| 198 | } |
| 199 | if (k.bDefault) { |
| 200 | theKey.bDefault = true; |
| 201 | iterator cit = find(x: theKey); |
| 202 | if (cit != end()) { |
| 203 | erase(position: cit); |
| 204 | return true; |
| 205 | } |
| 206 | } |
| 207 | return ret; |
| 208 | } |
| 209 | |
| 210 | // qDebug() << "localized entry, unchanged, return false"; |
| 211 | // When we are writing a default, we know that the non- |
| 212 | // default is the same as the default, so we can simply |
| 213 | // use the same branch. |
| 214 | return false; |
| 215 | } |
| 216 | |
| 217 | QString KEntryMap::getEntry(const QString &group, QAnyStringView key, const QString &defaultValue, KEntryMap::SearchFlags flags, bool *expand) const |
| 218 | { |
| 219 | const auto it = constFindEntry(group, key, flags); |
| 220 | QString theValue = defaultValue; |
| 221 | |
| 222 | if (it != cend() && !it->second.bDeleted) { |
| 223 | if (!it->second.mValue.isNull()) { |
| 224 | const QByteArray data = it->second.mValue; |
| 225 | theValue = QString::fromUtf8(utf8: data.constData(), size: data.length()); |
| 226 | if (expand) { |
| 227 | *expand = it->second.bExpand; |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | return theValue; |
| 233 | } |
| 234 | |
| 235 | bool KEntryMap::hasEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const |
| 236 | { |
| 237 | const auto it = constFindEntry(group, key, flags); |
| 238 | if (it == cend()) { |
| 239 | return false; |
| 240 | } |
| 241 | if (it->second.bDeleted) { |
| 242 | return false; |
| 243 | } |
| 244 | if (key.isNull()) { // looking for group marker |
| 245 | return it->second.mValue.isNull(); |
| 246 | } |
| 247 | // if it->bReverted, we'll just return true; the real answer depends on lookup up with SearchDefaults, though. |
| 248 | return true; |
| 249 | } |
| 250 | |
| 251 | bool KEntryMap::getEntryOption(const KEntryMapConstIterator &it, KEntryMap::EntryOption option) const |
| 252 | { |
| 253 | if (it == cend()) { |
| 254 | return false; |
| 255 | } |
| 256 | |
| 257 | switch (option) { |
| 258 | case EntryDirty: |
| 259 | return it->second.bDirty; |
| 260 | case EntryLocalized: |
| 261 | return it->first.bLocal; |
| 262 | case EntryGlobal: |
| 263 | return it->second.bGlobal; |
| 264 | case EntryImmutable: |
| 265 | return it->second.bImmutable; |
| 266 | case EntryDeleted: |
| 267 | return it->second.bDeleted; |
| 268 | case EntryExpansion: |
| 269 | return it->second.bExpand; |
| 270 | case EntryNotify: |
| 271 | return it->second.bNotify; |
| 272 | default: |
| 273 | return false; |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | void KEntryMap::setEntryOption(KEntryMapIterator it, KEntryMap::EntryOption option, bool bf) |
| 278 | { |
| 279 | if (it == end()) { |
| 280 | return; |
| 281 | } |
| 282 | |
| 283 | switch (option) { |
| 284 | case EntryDirty: |
| 285 | it->second.bDirty = bf; |
| 286 | return; |
| 287 | case EntryGlobal: |
| 288 | it->second.bGlobal = bf; |
| 289 | return; |
| 290 | case EntryImmutable: |
| 291 | it->second.bImmutable = bf; |
| 292 | return; |
| 293 | case EntryDeleted: |
| 294 | it->second.bDeleted = bf; |
| 295 | return; |
| 296 | case EntryExpansion: |
| 297 | it->second.bExpand = bf; |
| 298 | return; |
| 299 | case EntryNotify: |
| 300 | it->second.bNotify = bf; |
| 301 | return; |
| 302 | default: |
| 303 | return; // fall through |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | bool KEntryMap::revertEntry(const QString &group, QAnyStringView key, KEntryMap::EntryOptions options, KEntryMap::SearchFlags flags) |
| 308 | { |
| 309 | Q_ASSERT((flags & KEntryMap::SearchDefaults) == 0); |
| 310 | iterator entry = findEntry(group, key, flags); |
| 311 | if (entry == end()) { |
| 312 | return false; |
| 313 | } |
| 314 | |
| 315 | // qDebug() << "reverting" << entry->first << " = " << entry->mValue; |
| 316 | if (entry->second.bReverted) { // already done before |
| 317 | return false; |
| 318 | } |
| 319 | |
| 320 | KEntryKey defaultKey(entry->first); |
| 321 | defaultKey.bDefault = true; |
| 322 | // qDebug() << "looking up default entry with key=" << defaultKey; |
| 323 | const auto defaultEntry = find(x: defaultKey); |
| 324 | if (defaultEntry != cend()) { |
| 325 | Q_ASSERT(defaultEntry->first.bDefault); |
| 326 | // qDebug() << "found, update entry"; |
| 327 | entry->second = defaultEntry->second; // copy default value, for subsequent lookups |
| 328 | } else { |
| 329 | entry->second.mValue = QByteArray(); |
| 330 | } |
| 331 | entry->second.bNotify = entry->second.bNotify || (options & EntryNotify); |
| 332 | entry->second.bDirty = true; |
| 333 | entry->second.bReverted = true; // skip it when writing out to disk |
| 334 | |
| 335 | // qDebug() << "Here's what we have now:" << *this; |
| 336 | return true; |
| 337 | } |
| 338 | |