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