Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-gsettings.c -- utility functions for storing/retrieving *
3 : : * data in the GSettings database for GnuCash *
4 : : * Copyright (C) 2013 Geert Janssens <geert@kobaltwit.be> *
5 : : * *
6 : : * This program is free software; you can redistribute it and/or *
7 : : * modify it under the terms of the GNU General Public License as *
8 : : * published by the Free Software Foundation; either version 2 of *
9 : : * the License, or (at your option) any later version. *
10 : : * *
11 : : * This program is distributed in the hope that it will be useful, *
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 : : * GNU General Public License for more details. *
15 : : * *
16 : : * You should have received a copy of the GNU General Public License*
17 : : * along with this program; if not, contact: *
18 : : * *
19 : : * Free Software Foundation Voice: +1-617-542-5942 *
20 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : : * *
23 : : \********************************************************************/
24 : :
25 : : #include <config.h>
26 : :
27 : :
28 : : #include <gio/gio.h>
29 : : #include <glib.h>
30 : :
31 : : #include <stdio.h>
32 : : #include <string.h>
33 : : #include "gnc-gsettings.h"
34 : : #include "gnc-path.h"
35 : : #include "qof.h"
36 : : #include "gnc-prefs-p.h"
37 : :
38 : : #include <boost/property_tree/ptree.hpp>
39 : : #include <boost/property_tree/xml_parser.hpp>
40 : : #include <fstream>
41 : : #include <iostream>
42 : : #include <unordered_map>
43 : :
44 : : namespace bpt = boost::property_tree;
45 : :
46 : : #define GSET_SCHEMA_PREFIX "org.gnucash.GnuCash"
47 : : #define GSET_SCHEMA_OLD_PREFIX "org.gnucash"
48 : :
49 : : struct GSettingsDeleter
50 : : {
51 : 0 : void operator()(GSettings* gsp)
52 : : {
53 : 0 : g_object_unref(gsp);
54 : 0 : }
55 : : };
56 : :
57 : : static GSettingsDeleter g_settings_deleter;
58 : :
59 : : using GSettingsPtr = std::unique_ptr<GSettings, GSettingsDeleter>;
60 : :
61 : : static std::unordered_map<std::string,GSettingsPtr> schema_hash;
62 : :
63 : : /* This static indicates the debugging module that this .o belongs to. */
64 : : static QofLogModule log_module = "gnc.app-utils.gsettings";
65 : :
66 : : /************************************************************/
67 : : /* Internal helper functions */
68 : : /************************************************************/
69 : 0 : static bool gnc_gsettings_is_valid_key(GSettings *settings, const gchar *key)
70 : : {
71 : : // Check if the key is valid key within settings
72 : 0 : if (!G_IS_SETTINGS(settings))
73 : 0 : return false;
74 : :
75 : : GSettingsSchema *schema;
76 : 0 : g_object_get (settings, "settings-schema", &schema, nullptr);
77 : 0 : if (!schema)
78 : 0 : return false;
79 : :
80 : 0 : auto keys = g_settings_schema_list_keys (schema);
81 : 0 : auto found = (keys && g_strv_contains(keys, key));
82 : 0 : g_strfreev (keys);
83 : 0 : g_settings_schema_unref (schema);
84 : :
85 : 0 : return found;
86 : : }
87 : :
88 : : static std::string
89 : 0 : normalize_schema_name (const gchar *name)
90 : : {
91 : 0 : if (!name)
92 : 0 : return GSET_SCHEMA_PREFIX;
93 : :
94 : 0 : if (g_str_has_prefix (name, GSET_SCHEMA_PREFIX) ||
95 : 0 : (g_str_has_prefix (name, GSET_SCHEMA_OLD_PREFIX)))
96 : 0 : return name;
97 : :
98 : 0 : return std::string{GSET_SCHEMA_PREFIX} + '.' + name;
99 : : }
100 : :
101 : 0 : static GSettings * gnc_gsettings_get_settings_obj (const gchar *schema_str)
102 : : {
103 : 0 : ENTER("");
104 : :
105 : 0 : auto full_name_str = normalize_schema_name (schema_str);
106 : 0 : auto full_name = full_name_str.c_str();
107 : 0 : auto schema_source {g_settings_schema_source_get_default()};
108 : 0 : auto schema {g_settings_schema_source_lookup(schema_source, full_name, true)};
109 : 0 : auto gset = g_settings_new_full (schema, nullptr, nullptr);
110 : 0 : DEBUG ("Created gsettings object %p for schema %s", gset, full_name);
111 : :
112 : 0 : if (!G_IS_SETTINGS(gset))
113 : 0 : PWARN ("Ignoring attempt to access unknown gsettings schema %s", full_name);
114 : :
115 : 0 : LEAVE("");
116 : 0 : g_settings_schema_unref (schema);
117 : 0 : return gset;
118 : 0 : }
119 : :
120 : : static GSettings*
121 : 0 : schema_to_gsettings (const char *schema, bool can_retrieve)
122 : : {
123 : 0 : auto full_name = normalize_schema_name (schema);
124 : 0 : auto iter = schema_hash.find (full_name);
125 : 0 : if (iter != schema_hash.end())
126 : 0 : return iter->second.get();
127 : :
128 : 0 : if (!can_retrieve)
129 : 0 : return nullptr;
130 : :
131 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
132 : 0 : if (!G_IS_SETTINGS (gs_obj))
133 : : {
134 : 0 : PWARN ("Ignoring attempt to access unknown gsettings schema %s", full_name.c_str());
135 : 0 : return nullptr;
136 : : }
137 : :
138 : 0 : schema_hash[full_name] = GSettingsPtr (gs_obj, g_settings_deleter);
139 : 0 : return gs_obj;
140 : 0 : }
141 : :
142 : : /************************************************************/
143 : : /* GSettings Utilities */
144 : : /************************************************************/
145 : :
146 : : const gchar *
147 : 0 : gnc_gsettings_get_prefix (void)
148 : : {
149 : 0 : return GSET_SCHEMA_PREFIX;
150 : : }
151 : :
152 : : /************************************************************/
153 : : /* Change notification */
154 : : /************************************************************/
155 : :
156 : : gulong
157 : 0 : gnc_gsettings_register_cb (const gchar *schema, const gchar *key,
158 : : gpointer func,
159 : : gpointer user_data)
160 : : {
161 : 0 : ENTER("");
162 : 0 : g_return_val_if_fail (func, 0);
163 : :
164 : 0 : auto gs_obj = schema_to_gsettings (schema, true);
165 : 0 : g_return_val_if_fail (G_IS_SETTINGS (gs_obj), 0);
166 : :
167 : 0 : auto signal = static_cast<char *> (nullptr);
168 : 0 : if (!(key && *key))
169 : 0 : signal = g_strdup ("changed");
170 : 0 : else if (gnc_gsettings_is_valid_key(gs_obj, key))
171 : 0 : signal = g_strconcat ("changed::", key, nullptr);
172 : :
173 : 0 : auto handlerid = g_signal_connect (gs_obj, signal, G_CALLBACK (func), user_data);
174 : 0 : if (handlerid)
175 : : {
176 : 0 : g_object_ref (gs_obj);
177 : :
178 : 0 : PINFO("schema: %s, key: %s, gs_obj: %p, handler_id: %ld",
179 : : schema, key, gs_obj, handlerid);
180 : : }
181 : 0 : g_free (signal);
182 : :
183 : 0 : LEAVE("");
184 : 0 : return handlerid;
185 : : }
186 : :
187 : :
188 : : static void
189 : 0 : gnc_gsettings_remove_cb_by_id_internal (GSettings *gs_obj, guint handlerid)
190 : : {
191 : 0 : ENTER ();
192 : 0 : g_return_if_fail (G_IS_SETTINGS (gs_obj));
193 : :
194 : 0 : g_signal_handler_disconnect (gs_obj, handlerid);
195 : 0 : g_object_unref (gs_obj);
196 : :
197 : 0 : LEAVE ("Schema: %p, handlerid: %d - removed for handler",
198 : : gs_obj, handlerid);
199 : : }
200 : :
201 : :
202 : : void
203 : 0 : gnc_gsettings_remove_cb_by_func (const gchar *schema, const gchar *key,
204 : : gpointer func, gpointer user_data)
205 : : {
206 : 0 : ENTER ();
207 : 0 : g_return_if_fail (func);
208 : :
209 : 0 : auto gs_obj = schema_to_gsettings (schema, false);
210 : :
211 : 0 : if (!G_IS_SETTINGS (gs_obj))
212 : : {
213 : 0 : LEAVE ("No valid GSettings object retrieved from hash table");
214 : 0 : return;
215 : : }
216 : :
217 : 0 : auto match_type = static_cast<GSignalMatchType> (G_SIGNAL_MATCH_DETAIL | G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA);
218 : 0 : auto changed_signal = g_signal_lookup ("changed", G_TYPE_SETTINGS); /* signal_id */
219 : 0 : auto quark = g_quark_from_string (key); /* signal_detail */
220 : :
221 : 0 : auto matched = 0;
222 : 0 : guint handler_id = 0;
223 : : do
224 : : {
225 : 0 : handler_id = g_signal_handler_find (gs_obj, match_type,
226 : : changed_signal, quark, nullptr,
227 : : func, user_data);
228 : 0 : if (handler_id)
229 : : {
230 : 0 : matched ++;
231 : 0 : gnc_gsettings_remove_cb_by_id_internal (gs_obj, handler_id);
232 : :
233 : : // Previous function will invalidate object if there is only one handler
234 : 0 : if (!G_IS_SETTINGS (gs_obj))
235 : 0 : handler_id = 0;
236 : : }
237 : 0 : } while (handler_id);
238 : :
239 : 0 : LEAVE ("Schema: %s, key: %s - removed %d handlers for 'changed' signal",
240 : : schema, key, matched);
241 : : }
242 : :
243 : :
244 : : void
245 : 0 : gnc_gsettings_remove_cb_by_id (const gchar *schema, guint handlerid)
246 : : {
247 : 0 : ENTER ();
248 : :
249 : 0 : auto gs_obj = schema_to_gsettings (schema, false);
250 : :
251 : 0 : if (!G_IS_SETTINGS (gs_obj))
252 : : {
253 : 0 : LEAVE ("No valid GSettings object retrieved from hash table");
254 : 0 : return;
255 : : }
256 : :
257 : 0 : gnc_gsettings_remove_cb_by_id_internal (gs_obj, handlerid);
258 : :
259 : 0 : LEAVE ("Schema: %p, handlerid: %d - removed for handler",
260 : : gs_obj, handlerid);
261 : : }
262 : :
263 : :
264 : : guint
265 : 0 : gnc_gsettings_register_any_cb (const gchar *schema,
266 : : gpointer func,
267 : : gpointer user_data)
268 : : {
269 : 0 : return gnc_gsettings_register_cb (schema, nullptr, func, user_data);
270 : : }
271 : :
272 : :
273 : : void
274 : 0 : gnc_gsettings_remove_any_cb_by_func (const gchar *schema,
275 : : gpointer func,
276 : : gpointer user_data)
277 : : {
278 : 0 : gnc_gsettings_remove_cb_by_func (schema, nullptr, func, user_data);
279 : 0 : }
280 : :
281 : :
282 : 0 : static gboolean gnc_gsettings_enum_bool_mapping_get (GValue *value,
283 : : GVariant *variant,
284 : : gpointer user_data)
285 : : {
286 : 0 : g_value_set_boolean (value,
287 : 0 : !g_strcmp0 ((const gchar *)user_data,
288 : : g_variant_get_string (variant, nullptr)));
289 : :
290 : 0 : return true;
291 : : }
292 : :
293 : 0 : static GVariant* gnc_gsettings_enum_bool_mapping_set (const GValue *value,
294 : : const GVariantType *expected_type,
295 : : gpointer user_data)
296 : : {
297 : 0 : if (g_value_get_boolean (value))
298 : : {
299 : 0 : return g_variant_new_string ((const gchar *)user_data);
300 : : }
301 : : else
302 : : {
303 : : /* GtkRadioButtons will set the value to false when another option is
304 : : * selected, just ignore this. */
305 : 0 : return nullptr;
306 : : }
307 : : }
308 : :
309 : 0 : void gnc_gsettings_bind (const gchar *schema,
310 : : /*@ null @*/ const gchar *key,
311 : : /*@ null @*/ const gchar *value,
312 : : gpointer object,
313 : : const gchar *property)
314 : : {
315 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
316 : 0 : g_return_if_fail (G_IS_SETTINGS (gs_obj));
317 : :
318 : 0 : if (gnc_gsettings_is_valid_key (gs_obj, key))
319 : : {
320 : 0 : if (value)
321 : : {
322 : 0 : g_settings_bind_with_mapping (gs_obj, key, object, property,
323 : : G_SETTINGS_BIND_DEFAULT,
324 : : gnc_gsettings_enum_bool_mapping_get,
325 : : gnc_gsettings_enum_bool_mapping_set,
326 : 0 : g_strdup (value), g_free);
327 : : }
328 : : else
329 : : {
330 : 0 : g_settings_bind (gs_obj, key, object, property, G_SETTINGS_BIND_DEFAULT);
331 : : }
332 : : }
333 : : else
334 : : {
335 : 0 : PERR ("Invalid key %s for schema %s", key, schema);
336 : : }
337 : : }
338 : :
339 : :
340 : : static void
341 : 0 : gs_obj_block_handlers ([[maybe_unused]] gpointer key, gpointer gs_obj,
342 : : [[maybe_unused]] gpointer pointer)
343 : : {
344 : 0 : g_signal_handlers_block_matched (gs_obj, G_SIGNAL_MATCH_CLOSURE, 0, 0, nullptr, nullptr, nullptr);
345 : 0 : PINFO("Block all handlers for GSettings object %p", gs_obj);
346 : 0 : }
347 : :
348 : : static void
349 : 0 : gs_obj_unblock_handlers ([[maybe_unused]] gpointer key, gpointer gs_obj,
350 : : [[maybe_unused]] gpointer pointer)
351 : : {
352 : 0 : g_signal_handlers_unblock_matched (gs_obj, G_SIGNAL_MATCH_CLOSURE, 0, 0, nullptr, nullptr, nullptr);
353 : 0 : PINFO("Unblock all handlers for GSettings object %p", gs_obj);
354 : 0 : }
355 : :
356 : 0 : void gnc_gsettings_block_all (void)
357 : : {
358 : 0 : ENTER ();
359 : 0 : for (const auto& it : schema_hash)
360 : 0 : gs_obj_block_handlers (nullptr, it.second.get(), nullptr);
361 : 0 : LEAVE();
362 : 0 : }
363 : :
364 : :
365 : 0 : void gnc_gsettings_unblock_all (void)
366 : : {
367 : 0 : ENTER ();
368 : 0 : for (const auto& it : schema_hash)
369 : 0 : gs_obj_unblock_handlers (nullptr, it.second.get(), nullptr);
370 : 0 : LEAVE();
371 : 0 : }
372 : :
373 : :
374 : : /************************************************************/
375 : : /* Getters */
376 : : /************************************************************/
377 : : template<typename T>
378 : 0 : T gnc_gsettings_get(const char *schema, const char *key,
379 : : auto getter(GSettings*, const char *)->T, T default_val)
380 : : {
381 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
382 : 0 : g_return_val_if_fail (G_IS_SETTINGS (gs_obj), default_val);
383 : :
384 : 0 : T val = default_val;
385 : 0 : if (gnc_gsettings_is_valid_key (gs_obj, key))
386 : 0 : val = getter (gs_obj, key);
387 : : else
388 : 0 : PERR ("Invalid key %s for schema %s", key, schema);
389 : :
390 : 0 : g_object_unref (gs_obj);
391 : 0 : return val;
392 : : }
393 : :
394 : : gboolean
395 : 0 : gnc_gsettings_get_bool (const gchar *schema, const gchar *key)
396 : : {
397 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_boolean,
398 : 0 : static_cast<gboolean>(false));
399 : : }
400 : :
401 : : gint
402 : 0 : gnc_gsettings_get_int (const gchar *schema, const gchar *key)
403 : : {
404 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_int, 0);
405 : : }
406 : :
407 : : gdouble
408 : 0 : gnc_gsettings_get_float (const gchar *schema, const gchar *key)
409 : : {
410 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_double, 0.0);
411 : : }
412 : :
413 : : gchar *
414 : 0 : gnc_gsettings_get_string (const gchar *schema, const gchar *key)
415 : : {
416 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_string,
417 : 0 : static_cast<gchar *> (nullptr));
418 : : }
419 : :
420 : : gint
421 : 0 : gnc_gsettings_get_enum (const gchar *schema, const gchar *key)
422 : : {
423 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_enum, 0);
424 : : }
425 : :
426 : : GVariant *
427 : 0 : gnc_gsettings_get_value (const gchar *schema, const gchar *key)
428 : : {
429 : 0 : return gnc_gsettings_get (schema, key, g_settings_get_value,
430 : 0 : static_cast<GVariant *> (nullptr));
431 : : }
432 : :
433 : : /************************************************************/
434 : : /* Setters */
435 : : /************************************************************/
436 : : template<typename T> gboolean
437 : 0 : gnc_gsettings_set (const gchar *schema,
438 : : const gchar *key,
439 : : T value,
440 : : gboolean setter(GSettings*, const char *, T))
441 : : {
442 : 0 : ENTER("schema: %s, key: %s", schema, key);
443 : :
444 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
445 : 0 : g_return_val_if_fail (G_IS_SETTINGS (gs_obj), false);
446 : :
447 : 0 : auto result = false;
448 : 0 : if (gnc_gsettings_is_valid_key (gs_obj, key))
449 : : {
450 : 0 : result = setter (gs_obj, key, value);
451 : 0 : if (!result)
452 : 0 : PERR ("Unable to set value for key %s in schema %s", key, schema);
453 : : }
454 : : else
455 : 0 : PERR ("Invalid key %s for schema %s", key, schema);
456 : :
457 : 0 : g_object_unref (gs_obj);
458 : 0 : LEAVE("result %i", result);
459 : 0 : return result;
460 : : }
461 : :
462 : : gboolean
463 : 0 : gnc_gsettings_set_bool (const gchar *schema, const gchar *key, gboolean value)
464 : : {
465 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_boolean);
466 : : }
467 : :
468 : : gboolean
469 : 0 : gnc_gsettings_set_int (const gchar *schema, const gchar *key, gint value)
470 : : {
471 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_int);
472 : : }
473 : :
474 : : gboolean
475 : 0 : gnc_gsettings_set_float (const gchar *schema, const gchar *key, gdouble value)
476 : : {
477 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_double);
478 : : }
479 : :
480 : : gboolean
481 : 0 : gnc_gsettings_set_string (const gchar *schema, const gchar *key, const gchar *value)
482 : : {
483 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_string);
484 : : }
485 : :
486 : : gboolean
487 : 0 : gnc_gsettings_set_enum (const gchar *schema, const gchar *key, gint value)
488 : : {
489 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_enum);
490 : : }
491 : :
492 : : gboolean
493 : 0 : gnc_gsettings_set_value (const gchar *schema, const gchar *key, GVariant *value)
494 : : {
495 : 0 : return gnc_gsettings_set (schema, key, value, g_settings_set_value);
496 : : }
497 : :
498 : : void
499 : 0 : gnc_gsettings_reset (const gchar *schema,
500 : : const gchar *key)
501 : : {
502 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
503 : 0 : g_return_if_fail (G_IS_SETTINGS (gs_obj));
504 : :
505 : 0 : if (gnc_gsettings_is_valid_key (gs_obj, key))
506 : 0 : g_settings_reset (gs_obj, key);
507 : : else
508 : 0 : PERR ("Invalid key %s for schema %s", key, schema);
509 : :
510 : 0 : g_object_unref (gs_obj);
511 : : }
512 : :
513 : : void
514 : 0 : gnc_gsettings_reset_schema (const gchar *schema_str)
515 : : {
516 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema_str);
517 : :
518 : 0 : if (!gs_obj)
519 : 0 : return;
520 : :
521 : : GSettingsSchema *schema;
522 : 0 : g_object_get (gs_obj, "settings-schema", &schema, nullptr);
523 : 0 : if (!schema)
524 : : {
525 : 0 : g_object_unref (gs_obj);
526 : 0 : return;
527 : : }
528 : :
529 : 0 : auto keys = g_settings_schema_list_keys (schema);
530 : 0 : if (keys)
531 : : {
532 : 0 : auto fkeys = keys;
533 : 0 : for (auto key = *fkeys; key; key = *++fkeys)
534 : 0 : gnc_gsettings_reset (schema_str, key);
535 : : }
536 : :
537 : 0 : g_object_unref (gs_obj);
538 : 0 : g_settings_schema_unref (schema);
539 : 0 : g_strfreev (keys);
540 : : }
541 : :
542 : : static void
543 : 0 : gnc_settings_dump_schema_paths (void)
544 : : {
545 : : gchar **non_relocatable;
546 : :
547 : 0 : auto schema_source {g_settings_schema_source_get_default()};
548 : 0 : g_settings_schema_source_list_schemas (schema_source, true,
549 : : &non_relocatable, nullptr);
550 : :
551 : 0 : for (gint i = 0; non_relocatable[i] != nullptr; i++)
552 : 0 : PINFO("Schema entry %d is '%s'", i, non_relocatable[i]);
553 : :
554 : 0 : g_strfreev (non_relocatable);
555 : 0 : }
556 : :
557 : 1 : void gnc_gsettings_load_backend (void)
558 : : {
559 : 1 : ENTER("");
560 : :
561 : : /* The gsettings backend only works in an installed environment.
562 : : * When called from the source environment (for testing purposes)
563 : : * simply return.
564 : : */
565 : 1 : if (g_strcmp0 (g_getenv ("GNC_UNINSTALLED"), "1") == 0)
566 : 1 : return;
567 : :
568 : 0 : g_free (prefsbackend);
569 : 0 : prefsbackend = g_new0 (PrefsBackend, 1);
570 : :
571 : 0 : prefsbackend->register_cb = gnc_gsettings_register_cb;
572 : 0 : prefsbackend->remove_cb_by_func = gnc_gsettings_remove_cb_by_func;
573 : 0 : prefsbackend->remove_cb_by_id = gnc_gsettings_remove_cb_by_id;
574 : 0 : prefsbackend->register_group_cb = gnc_gsettings_register_any_cb;
575 : 0 : prefsbackend->remove_group_cb_by_func = gnc_gsettings_remove_any_cb_by_func;
576 : 0 : prefsbackend->bind = gnc_gsettings_bind;
577 : 0 : prefsbackend->get_bool = gnc_gsettings_get_bool;
578 : 0 : prefsbackend->get_int = gnc_gsettings_get_int;
579 : 0 : prefsbackend->get_float = gnc_gsettings_get_float;
580 : 0 : prefsbackend->get_string = gnc_gsettings_get_string;
581 : 0 : prefsbackend->get_enum = gnc_gsettings_get_enum;
582 : 0 : prefsbackend->get_value = gnc_gsettings_get_value;
583 : 0 : prefsbackend->set_bool = gnc_gsettings_set_bool;
584 : 0 : prefsbackend->set_int = gnc_gsettings_set_int;
585 : 0 : prefsbackend->set_float = gnc_gsettings_set_float;
586 : 0 : prefsbackend->set_string = gnc_gsettings_set_string;
587 : 0 : prefsbackend->set_enum = gnc_gsettings_set_enum;
588 : 0 : prefsbackend->set_value = gnc_gsettings_set_value;
589 : 0 : prefsbackend->reset = gnc_gsettings_reset;
590 : 0 : prefsbackend->reset_group = gnc_gsettings_reset_schema;
591 : 0 : prefsbackend->block_all = gnc_gsettings_block_all;
592 : 0 : prefsbackend->unblock_all = gnc_gsettings_unblock_all;
593 : :
594 : 0 : if (qof_log_check (log_module, QOF_LOG_DEBUG))
595 : 0 : gnc_settings_dump_schema_paths ();
596 : :
597 : : /* Run any data model changes for the backend before it's used
598 : : * by anyone */
599 : 0 : gnc_gsettings_version_upgrade();
600 : :
601 : 0 : LEAVE("Prefsbackend bind = %p", prefsbackend->bind);
602 : : }
603 : :
604 : : void
605 : 0 : gnc_gsettings_shutdown (void)
606 : : {
607 : 0 : schema_hash.clear();
608 : 0 : g_free (prefsbackend);
609 : 0 : }
610 : :
611 : :
612 : : static GVariant *
613 : 0 : gnc_gsettings_get_user_value (const gchar *schema,
614 : : const gchar *key)
615 : : {
616 : 0 : auto gs_obj = gnc_gsettings_get_settings_obj (schema);
617 : 0 : g_return_val_if_fail (G_IS_SETTINGS (gs_obj), nullptr);
618 : :
619 : 0 : auto val = static_cast<GVariant *> (nullptr);
620 : 0 : if (gnc_gsettings_is_valid_key (gs_obj, key))
621 : 0 : val = g_settings_get_user_value (gs_obj, key);
622 : : else
623 : 0 : PERR ("Invalid key %s for schema %s", key, schema);
624 : :
625 : 0 : g_object_unref (gs_obj);
626 : 0 : return val;
627 : : }
628 : :
629 : : using opt_str_vec = boost::optional<std::string>;
630 : :
631 : : static void
632 : 0 : deprecate_one_key (const opt_str_vec &oldpath, const opt_str_vec &oldkey)
633 : : {
634 : 0 : if (!oldpath || !oldkey )
635 : : {
636 : 0 : DEBUG ("Skipping <deprecate> node - missing attribute (old-path or old-key)");
637 : 0 : return;
638 : : }
639 : :
640 : 0 : PINFO ("'%s:%s' has been marked deprecated", oldpath->c_str(), oldkey->c_str());
641 : : /* This does nothing really, but is a reminder for future maintainers
642 : : * to mark this pref as obsolete in the next major release. */
643 : : }
644 : :
645 : : static void
646 : 0 : migrate_one_key (const opt_str_vec &oldpath, const opt_str_vec &oldkey,
647 : : const opt_str_vec &newpath, const opt_str_vec &newkey)
648 : : {
649 : 0 : if (!oldpath || !oldkey || !newpath || !newkey)
650 : : {
651 : 0 : DEBUG ("Skipping <migrate> node - missing attribute (old-path, old-key, new-path or new-key)");
652 : 0 : return;
653 : : }
654 : :
655 : 0 : PINFO ("Migrating '%s:%s' to '%s:%s'", oldpath->c_str(), oldkey->c_str(),
656 : : newpath->c_str(), newkey->c_str());
657 : :
658 : 0 : auto user_value = gnc_gsettings_get_user_value (oldpath->c_str(), oldkey->c_str());
659 : 0 : if (user_value)
660 : 0 : gnc_gsettings_set_value (newpath->c_str(), newkey->c_str(), user_value);
661 : : }
662 : :
663 : : static void
664 : 0 : obsolete_one_key (const opt_str_vec &oldpath, const opt_str_vec &oldkey)
665 : : {
666 : 0 : if (!oldpath || !oldkey )
667 : : {
668 : 0 : DEBUG ("Skipping <obsolete> node - missing attribute (old-path or old-key)");
669 : 0 : return;
670 : : }
671 : :
672 : 0 : PINFO ("Resetting obsolete '%s:%s'", oldpath->c_str(), oldkey->c_str());
673 : 0 : gnc_gsettings_reset (oldpath->c_str(), oldkey->c_str());
674 : : }
675 : :
676 : : static void
677 : 0 : parse_one_release_node (bpt::ptree &pt)
678 : : {
679 : : /* loop over top-level property tree */
680 : 0 : std::for_each (pt.begin(), pt.end(),
681 : 0 : [] (std::pair<bpt::ptree::key_type, bpt::ptree> node)
682 : : {
683 : 0 : if (node.first == "<xmlattr>")
684 : 0 : return;
685 : 0 : else if (node.first == "deprecate")
686 : 0 : deprecate_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
687 : 0 : node.second.get_optional<std::string> ("<xmlattr>.old-key"));
688 : 0 : else if (node.first == "migrate")
689 : 0 : migrate_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
690 : 0 : node.second.get_optional<std::string> ("<xmlattr>.old-key"),
691 : 0 : node.second.get_optional<std::string> ("<xmlattr>.new-path"),
692 : 0 : node.second.get_optional<std::string> ("<xmlattr>.new-key"));
693 : 0 : else if (node.first == "obsolete")
694 : 0 : obsolete_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
695 : 0 : node.second.get_optional<std::string> ("<xmlattr>.old-key"));
696 : : else
697 : : {
698 : 0 : DEBUG ("Skipping unknown node <%s>", node.first.c_str());
699 : 0 : return;
700 : : }
701 : : });
702 : 0 : }
703 : :
704 : : static void
705 : 0 : transform_settings (int old_maj_min, int cur_maj_min)
706 : : {
707 : 0 : bpt::ptree pt;
708 : :
709 : 0 : auto pkg_data_dir = gnc_path_get_pkgdatadir();
710 : 0 : auto transform_file = std::string (pkg_data_dir) + "/pref_transformations.xml";
711 : 0 : g_free (pkg_data_dir);
712 : :
713 : 0 : std::ifstream transform_stream {transform_file};
714 : 0 : if (!transform_stream.is_open())
715 : : {
716 : 0 : PWARN("Failed to load preferences transformation file '%s'", transform_file.c_str());
717 : 0 : return;
718 : : }
719 : :
720 : : try
721 : : {
722 : 0 : bpt::read_xml (transform_stream, pt);
723 : : }
724 : 0 : catch (bpt::xml_parser_error &e) {
725 : 0 : PWARN ("Failed to parse GnuCash preferences transformation file.\n");
726 : 0 : PWARN ("Error message:\n");
727 : 0 : PWARN ("%s\n", e.what());
728 : 0 : return;
729 : 0 : }
730 : 0 : catch (...) {
731 : 0 : PWARN ("Unknown error while parsing GnuCash preferences transformation file.\n");
732 : 0 : return;
733 : 0 : }
734 : :
735 : : /* loop over top-level property tree */
736 : 0 : std::for_each (pt.begin(), pt.end(),
737 : 0 : [&old_maj_min, &cur_maj_min] (std::pair<bpt::ptree::key_type, bpt::ptree> node)
738 : : {
739 : 0 : if (node.first != "release")
740 : : {
741 : 0 : DEBUG ("Skipping non-<release> node <%s>", node.first.c_str());
742 : 0 : return;
743 : : }
744 : 0 : auto version = node.second.get_optional<int> ("<xmlattr>.version");
745 : 0 : if (!version)
746 : : {
747 : 0 : DEBUG ("Skipping <release> node - no version attribute found");
748 : 0 : return;
749 : : }
750 : 0 : if (*version <= old_maj_min)
751 : : {
752 : 0 : DEBUG ("Skipping <release> node - version %i is less than current compatibility level %i", *version, old_maj_min);
753 : 0 : return;
754 : : }
755 : 0 : if (*version > cur_maj_min)
756 : : {
757 : 0 : DEBUG ("Skipping <release> node - version %i is greater than current version level %i", *version, cur_maj_min);
758 : 0 : return;
759 : : }
760 : 0 : DEBUG ("Retrieved version value '%i'", *version);
761 : :
762 : 0 : parse_one_release_node (node.second);
763 : : });
764 : 0 : }
765 : :
766 : 0 : void gnc_gsettings_version_upgrade (void)
767 : : {
768 : : /* This routine will conditionally execute conversion rules from
769 : : * prefs_transformations.xml to adapt the user's existing preferences to
770 : : * the current preferences schema. The rules in this file are versioned and
771 : : * only rules still relevant to the user's existing preferences and for
772 : : * this version of GnuCash will be executed.
773 : : *
774 : : * Starting with GnuCash 4.7 the code expects all preferences to be stored
775 : : * under prefix org.gnucash.GnuCash instead of org.gnucash, including our
776 : : * GNC_PREF_VERSION setting.
777 : : * As the logic to determine whether or not settings conversions are needed
778 : : * depends on this preference, we have to test for its value in two
779 : : * locations:
780 : : * - if GNC_PREF_VERSION is not set under old nor new prefix
781 : : * => GnuCash has never run before so no conversion run necessary
782 : : * - if GNC_PREF_VERSION is set under old prefix and not new prefix
783 : : * => user's preferences weren't moved yet from old to new prefix. Use old
784 : : * prefix GNC_PREF_VERSION to determine which conversions may be needed
785 : : * - if GNC_PREF_VERSION is set under both prefixes
786 : : * => ignore old prefix and use new prefix GNC_PREF_VERSION to determine
787 : : * which conversions may be needed.
788 : : * Sometime in the future (GnuCash 6.0) the old prefix will be fully removed
789 : : * and the test will be simplified to only check in the new prefix.
790 : : */
791 : 0 : ENTER("Start of settings transform routine.");
792 : :
793 : 0 : auto ogG_maj_min = gnc_gsettings_get_user_value (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
794 : 0 : auto og_maj_min = gnc_gsettings_get_user_value (GSET_SCHEMA_OLD_PREFIX "." GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
795 : :
796 : 0 : auto cur_maj_min = PROJECT_VERSION_MAJOR * 1000 + PROJECT_VERSION_MINOR;
797 : :
798 : 0 : if (!ogG_maj_min && !og_maj_min) // new install
799 : : {
800 : 0 : gnc_gsettings_set_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION, cur_maj_min);
801 : 0 : LEAVE ("Setting Previous compatibility level to current version: %i", cur_maj_min);
802 : 0 : return;
803 : : }
804 : :
805 : 0 : auto old_maj_min = 0;
806 : 0 : if (!ogG_maj_min) // old preference location
807 : 0 : old_maj_min = gnc_gsettings_get_int (GSET_SCHEMA_OLD_PREFIX "." GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
808 : : else // new preference location
809 : : {
810 : 0 : g_variant_unref (ogG_maj_min);
811 : 0 : old_maj_min = gnc_gsettings_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
812 : : }
813 : 0 : if (og_maj_min)
814 : 0 : g_variant_unref (og_maj_min);
815 : :
816 : 0 : PINFO ("Previous setting compatibility level: %i, Current version: %i", old_maj_min, cur_maj_min);
817 : :
818 : 0 : transform_settings (old_maj_min, cur_maj_min);
819 : :
820 : : /* Only write current version if it's more recent than what was set */
821 : 0 : if (cur_maj_min > old_maj_min)
822 : 0 : gnc_gsettings_set_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION, cur_maj_min);
823 : :
824 : 0 : LEAVE("");
825 : : }
|