tests: Convert ExpressionMatchTests into a GTest-based test case
[quassel.git] / src / common / settings.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QStringList>
22
23 #include "settings.h"
24
25 const int VERSION = 1;              /// Settings version for backwords/forwards incompatible changes
26
27 // This is used if no VersionMinor key exists, e.g. upgrading from a Quassel version before this
28 // change.  This shouldn't be increased from 1; instead, change the logic in Core::Core() and
29 // QtUiApplication::init() to handle upgrading and downgrading.
30 const int VERSION_MINOR_INITIAL = 1; /// Initial settings version for compatible changes
31
32 QHash<QString, QVariant> Settings::_settingsCache;
33 QHash<QString, bool> Settings::_settingsKeyPersistedCache;
34 QHash<QString, std::shared_ptr<SettingsChangeNotifier>> Settings::_settingsChangeNotifier;
35
36 #ifdef Q_OS_MAC
37 #  define create_qsettings QSettings s(QCoreApplication::organizationDomain(), _appName)
38 #else
39 #  define create_qsettings QSettings s(fileName(), format())
40 #endif
41
42 Settings::Settings(QString group, QString appName)
43     : _group(std::move(group)), _appName(std::move(appName))
44 {}
45
46
47 void Settings::setGroup(QString group) {
48     _group = std::move(group);
49 }
50
51
52 QString Settings::keyForNotify(const QString &key) const
53 {
54     return key;
55 }
56
57
58 uint Settings::version() const
59 {
60     // we don't cache this value, and we ignore the group
61     create_qsettings;
62     uint ver = s.value("Config/Version", 0).toUInt();
63     if (!ver) {
64         // No version, so create one
65         s.setValue("Config/Version", VERSION);
66         return VERSION;
67     }
68     return ver;
69 }
70
71
72 uint Settings::versionMinor() const
73 {
74     // Don't cache this value; ignore the group
75     create_qsettings;
76     // '0' means new configuration, anything else indicates an existing configuration.  Application
77     // initialization should check this value and manage upgrades/downgrades, e.g. in Core::Core()
78     // and QtUiApplication::init().
79     uint verMinor = s.value("Config/VersionMinor", 0).toUInt();
80
81     // As previous Quassel versions didn't implement this, we need to check if any settings other
82     // than Config/Version exist.  If so, assume it's version 1.
83     if (verMinor == 0 && s.allKeys().count() > 1) {
84         // More than 1 key exists, but version's never been set.  Assume and set version 1.
85         // const_cast is ok, because setVersionMinor() doesn't actually change this instance
86         const_cast<Settings*>(this)->setVersionMinor(VERSION_MINOR_INITIAL);
87         return VERSION_MINOR_INITIAL;
88     } else {
89         return verMinor;
90     }
91 }
92
93
94 void Settings::setVersionMinor(const uint versionMinor)
95 {
96     // Don't cache this value; ignore the group
97     create_qsettings;
98     // Set the value directly.
99     s.setValue("Config/VersionMinor", versionMinor);
100 }
101
102
103 QSettings::Format Settings::format() const
104 {
105 #ifdef Q_OS_WIN
106     return QSettings::IniFormat;
107 #else
108     return QSettings::NativeFormat;
109 #endif
110 }
111
112
113 bool Settings::sync()
114 {
115     create_qsettings;
116     s.sync();
117     switch (s.status()) {
118         case QSettings::NoError:
119             return true;
120         default:
121             return false;
122     }
123 }
124
125
126 bool Settings::isWritable() const
127 {
128     create_qsettings;
129     return s.isWritable();
130 }
131
132
133 QStringList Settings::allLocalKeys() const
134 {
135     create_qsettings;
136     s.beginGroup(_group);
137     QStringList res = s.allKeys();
138     s.endGroup();
139     return res;
140 }
141
142
143 QStringList Settings::localChildKeys(const QString &rootkey) const
144 {
145     QString g;
146     if (rootkey.isEmpty())
147         g = _group;
148     else
149         g = QString("%1/%2").arg(_group, rootkey);
150
151     create_qsettings;
152     s.beginGroup(g);
153     QStringList res = s.childKeys();
154     s.endGroup();
155     return res;
156 }
157
158
159 QStringList Settings::localChildGroups(const QString &rootkey) const
160 {
161     QString g;
162     if (rootkey.isEmpty())
163         g = _group;
164     else
165         g = QString("%1/%2").arg(_group, rootkey);
166
167     create_qsettings;
168     s.beginGroup(g);
169     QStringList res = s.childGroups();
170     s.endGroup();
171     return res;
172 }
173
174
175 void Settings::setLocalValue(const QString &key, const QVariant &data)
176 {
177     QString normKey = normalizedKey(_group, key);
178     create_qsettings;
179     s.setValue(normKey, data);
180     setCacheKeyPersisted(normKey, true);
181     setCacheValue(normKey, data);
182     if (hasNotifier(normKey)) {
183         emit notifier(normKey)->valueChanged(data);
184     }
185 }
186
187
188 QVariant Settings::localValue(const QString &key, const QVariant &def) const
189 {
190     QString normKey = normalizedKey(_group, key);
191     if (!isCached(normKey)) {
192         create_qsettings;
193         // Since we're loading from settings anyways, cache whether or not the key exists on disk
194         setCacheKeyPersisted(normKey, s.contains(normKey));
195         // Cache key value
196         setCacheValue(normKey, s.value(normKey, def));
197     }
198     if (cacheKeyPersisted(normKey)) {
199         return cacheValue(normKey);
200     }
201     // Don't return possibly wrong cached values
202     // A key gets cached with the first default value requested and never changes afterwards
203     return def;
204 }
205
206
207 bool Settings::localKeyExists(const QString &key) const
208 {
209     QString normKey = normalizedKey(_group, key);
210     if (!isKeyPersistedCached(normKey)) {
211         create_qsettings;
212         // Cache whether or not key exists on disk
213         // We can't cache key value as we don't know the default
214         setCacheKeyPersisted(normKey, s.contains(normKey));
215     }
216
217     return cacheKeyPersisted(normKey);
218 }
219
220
221 void Settings::removeLocalKey(const QString &key)
222 {
223     create_qsettings;
224     s.beginGroup(_group);
225     s.remove(key);
226     s.endGroup();
227     QString normKey = normalizedKey(_group, key);
228     if (isCached(normKey)) {
229         _settingsCache.remove(normKey);
230     }
231     if (isKeyPersistedCached(normKey)) {
232         _settingsKeyPersistedCache.remove(normKey);
233     }
234     if (hasNotifier(normKey)) {
235         emit notifier(normKey)->valueChanged({});
236     }
237 }
238
239
240 QString Settings::fileName() const
241 {
242     return Quassel::configDirPath() + _appName
243            + ((format() == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
244 }
245
246
247 QString Settings::normalizedKey(const QString &group, const QString &key) const
248 {
249     if (group.isEmpty())
250         return key;
251     return group + '/' + key;
252 }
253
254
255 void Settings::setCacheKeyPersisted(const QString &normKey, bool exists) const
256 {
257     _settingsKeyPersistedCache[normKey] = exists;
258 }
259
260
261 bool Settings::cacheKeyPersisted(const QString &normKey) const
262 {
263     return _settingsKeyPersistedCache[normKey];
264 }
265
266
267 bool Settings::isKeyPersistedCached(const QString &normKey) const
268 {
269     return _settingsKeyPersistedCache.contains(normKey);
270 }
271
272
273 void Settings::setCacheValue(const QString &normKey, const QVariant &data) const
274 {
275     _settingsCache[normKey] = data;
276 }
277
278
279 QVariant Settings::cacheValue(const QString &normKey) const
280 {
281     return _settingsCache[normKey];
282 }
283
284
285 bool Settings::isCached(const QString &normKey) const
286 {
287     return _settingsCache.contains(normKey);
288 }
289
290
291 SettingsChangeNotifier *Settings::notifier(const QString &normKey) const
292 {
293     if (!hasNotifier(normKey))
294         _settingsChangeNotifier[normKey] = std::make_shared<SettingsChangeNotifier>();
295     return _settingsChangeNotifier[normKey].get();
296 }
297
298
299 bool Settings::hasNotifier(const QString &normKey) const
300 {
301     return _settingsChangeNotifier.contains(normKey);
302 }