GnuCash  5.6-150-g038405b370+
gnc-gsettings.cpp
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 
50 {
51  void operator()(GSettings* gsp)
52  {
53  g_object_unref(gsp);
54  }
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 static bool gnc_gsettings_is_valid_key(GSettings *settings, const gchar *key)
70 {
71  // Check if the key is valid key within settings
72  if (!G_IS_SETTINGS(settings))
73  return false;
74 
75  GSettingsSchema *schema;
76  g_object_get (settings, "settings-schema", &schema, nullptr);
77  if (!schema)
78  return false;
79 
80  auto keys = g_settings_schema_list_keys (schema);
81  auto found = (keys && g_strv_contains(keys, key));
82  g_strfreev (keys);
83  g_settings_schema_unref (schema);
84 
85  return found;
86 }
87 
88 static std::string
89 normalize_schema_name (const gchar *name)
90 {
91  if (!name)
92  return GSET_SCHEMA_PREFIX;
93 
94  if (g_str_has_prefix (name, GSET_SCHEMA_PREFIX) ||
95  (g_str_has_prefix (name, GSET_SCHEMA_OLD_PREFIX)))
96  return name;
97 
98  return std::string{GSET_SCHEMA_PREFIX} + '.' + name;
99 }
100 
101 static GSettings * gnc_gsettings_get_settings_obj (const gchar *schema_str)
102 {
103  ENTER("");
104 
105  auto full_name_str = normalize_schema_name (schema_str);
106  auto full_name = full_name_str.c_str();
107  auto schema_source {g_settings_schema_source_get_default()};
108  g_return_val_if_fail(schema_source, nullptr);
109 
110  auto schema {g_settings_schema_source_lookup(schema_source, full_name, true)};
111  g_return_val_if_fail(schema, nullptr);
112 
113  auto gset = g_settings_new_full (schema, nullptr, nullptr);
114  DEBUG ("Created gsettings object %p for schema %s", gset, full_name);
115 
116  if (!G_IS_SETTINGS(gset))
117  PWARN ("Ignoring attempt to access unknown gsettings schema %s", full_name);
118 
119  LEAVE("");
120  g_settings_schema_unref (schema);
121  return gset;
122 }
123 
124 static GSettings*
125 schema_to_gsettings (const char *schema, bool can_retrieve)
126 {
127  auto full_name = normalize_schema_name (schema);
128  auto iter = schema_hash.find (full_name);
129  if (iter != schema_hash.end())
130  return iter->second.get();
131 
132  if (!can_retrieve)
133  return nullptr;
134 
135  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
136  if (!G_IS_SETTINGS (gs_obj))
137  {
138  PWARN ("Ignoring attempt to access unknown gsettings schema %s", full_name.c_str());
139  return nullptr;
140  }
141 
142  schema_hash[full_name] = GSettingsPtr (gs_obj, g_settings_deleter);
143  return gs_obj;
144 }
145 
146 /************************************************************/
147 /* GSettings Utilities */
148 /************************************************************/
149 
150 const gchar *
152 {
153  return GSET_SCHEMA_PREFIX;
154 }
155 
156 /************************************************************/
157 /* Change notification */
158 /************************************************************/
159 
160 gulong
161 gnc_gsettings_register_cb (const gchar *schema, const gchar *key,
162  gpointer func,
163  gpointer user_data)
164 {
165  ENTER("");
166  g_return_val_if_fail (func, 0);
167 
168  auto gs_obj = schema_to_gsettings (schema, true);
169  g_return_val_if_fail (G_IS_SETTINGS (gs_obj), 0);
170 
171  auto signal = static_cast<char *> (nullptr);
172  if (!(key && *key))
173  signal = g_strdup ("changed");
174  else if (gnc_gsettings_is_valid_key(gs_obj, key))
175  signal = g_strconcat ("changed::", key, nullptr);
176 
177  auto handlerid = g_signal_connect (gs_obj, signal, G_CALLBACK (func), user_data);
178  if (handlerid)
179  {
180  g_object_ref (gs_obj);
181 
182  PINFO("schema: %s, key: %s, gs_obj: %p, handler_id: %ld",
183  schema, key, gs_obj, handlerid);
184  }
185  g_free (signal);
186 
187  LEAVE("");
188  return handlerid;
189 }
190 
191 
192 static void
193 gnc_gsettings_remove_cb_by_id_internal (GSettings *gs_obj, guint handlerid)
194 {
195  ENTER ();
196  g_return_if_fail (G_IS_SETTINGS (gs_obj));
197 
198  g_signal_handler_disconnect (gs_obj, handlerid);
199  g_object_unref (gs_obj);
200 
201  LEAVE ("Schema: %p, handlerid: %d - removed for handler",
202  gs_obj, handlerid);
203 }
204 
205 
206 void
207 gnc_gsettings_remove_cb_by_func (const gchar *schema, const gchar *key,
208  gpointer func, gpointer user_data)
209 {
210  ENTER ();
211  g_return_if_fail (func);
212 
213  auto gs_obj = schema_to_gsettings (schema, false);
214 
215  if (!G_IS_SETTINGS (gs_obj))
216  {
217  LEAVE ("No valid GSettings object retrieved from hash table");
218  return;
219  }
220 
221  auto match_type = static_cast<GSignalMatchType> (G_SIGNAL_MATCH_DETAIL | G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA);
222  auto changed_signal = g_signal_lookup ("changed", G_TYPE_SETTINGS); /* signal_id */
223  auto quark = g_quark_from_string (key); /* signal_detail */
224 
225  auto matched = 0;
226  guint handler_id = 0;
227  do
228  {
229  handler_id = g_signal_handler_find (gs_obj, match_type,
230  changed_signal, quark, nullptr,
231  func, user_data);
232  if (handler_id)
233  {
234  matched ++;
235  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  if (!G_IS_SETTINGS (gs_obj))
239  handler_id = 0;
240  }
241  } while (handler_id);
242 
243  LEAVE ("Schema: %s, key: %s - removed %d handlers for 'changed' signal",
244  schema, key, matched);
245 }
246 
247 
248 void
249 gnc_gsettings_remove_cb_by_id (const gchar *schema, guint handlerid)
250 {
251  ENTER ();
252 
253  auto gs_obj = schema_to_gsettings (schema, false);
254 
255  if (!G_IS_SETTINGS (gs_obj))
256  {
257  LEAVE ("No valid GSettings object retrieved from hash table");
258  return;
259  }
260 
261  gnc_gsettings_remove_cb_by_id_internal (gs_obj, handlerid);
262 
263  LEAVE ("Schema: %p, handlerid: %d - removed for handler",
264  gs_obj, handlerid);
265 }
266 
267 
268 guint
269 gnc_gsettings_register_any_cb (const gchar *schema,
270  gpointer func,
271  gpointer user_data)
272 {
273  return gnc_gsettings_register_cb (schema, nullptr, func, user_data);
274 }
275 
276 
277 void
279  gpointer func,
280  gpointer user_data)
281 {
282  gnc_gsettings_remove_cb_by_func (schema, nullptr, func, user_data);
283 }
284 
285 
286 static gboolean gnc_gsettings_enum_bool_mapping_get (GValue *value,
287  GVariant *variant,
288  gpointer user_data)
289 {
290  g_value_set_boolean (value,
291  !g_strcmp0 ((const gchar *)user_data,
292  g_variant_get_string (variant, nullptr)));
293 
294  return true;
295 }
296 
297 static GVariant* gnc_gsettings_enum_bool_mapping_set (const GValue *value,
298  const GVariantType *expected_type,
299  gpointer user_data)
300 {
301  if (g_value_get_boolean (value))
302  {
303  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  return nullptr;
310  }
311 }
312 
313 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  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
320  g_return_if_fail (G_IS_SETTINGS (gs_obj));
321 
322  if (gnc_gsettings_is_valid_key (gs_obj, key))
323  {
324  if (value)
325  {
326  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  g_strdup (value), g_free);
331  }
332  else
333  {
334  g_settings_bind (gs_obj, key, object, property, G_SETTINGS_BIND_DEFAULT);
335  }
336  }
337  else
338  {
339  PERR ("Invalid key %s for schema %s", key, schema);
340  }
341 }
342 
343 
344 static void
345 gs_obj_block_handlers ([[maybe_unused]] gpointer key, gpointer gs_obj,
346  [[maybe_unused]] gpointer pointer)
347 {
348  g_signal_handlers_block_matched (gs_obj, G_SIGNAL_MATCH_CLOSURE, 0, 0, nullptr, nullptr, nullptr);
349  PINFO("Block all handlers for GSettings object %p", gs_obj);
350 }
351 
352 static void
353 gs_obj_unblock_handlers ([[maybe_unused]] gpointer key, gpointer gs_obj,
354  [[maybe_unused]] gpointer pointer)
355 {
356  g_signal_handlers_unblock_matched (gs_obj, G_SIGNAL_MATCH_CLOSURE, 0, 0, nullptr, nullptr, nullptr);
357  PINFO("Unblock all handlers for GSettings object %p", gs_obj);
358 }
359 
361 {
362  ENTER ();
363  for (const auto& it : schema_hash)
364  gs_obj_block_handlers (nullptr, it.second.get(), nullptr);
365  LEAVE();
366 }
367 
368 
370 {
371  ENTER ();
372  for (const auto& it : schema_hash)
373  gs_obj_unblock_handlers (nullptr, it.second.get(), nullptr);
374  LEAVE();
375 }
376 
377 
378 /************************************************************/
379 /* Getters */
380 /************************************************************/
381 template<typename T>
382 T gnc_gsettings_get(const char *schema, const char *key,
383  auto getter(GSettings*, const char *)->T, T default_val)
384 {
385  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
386  g_return_val_if_fail (G_IS_SETTINGS (gs_obj), default_val);
387 
388  T val = default_val;
389  if (gnc_gsettings_is_valid_key (gs_obj, key))
390  val = getter (gs_obj, key);
391  else
392  PERR ("Invalid key %s for schema %s", key, schema);
393 
394  g_object_unref (gs_obj);
395  return val;
396 }
397 
398 gboolean
399 gnc_gsettings_get_bool (const gchar *schema, const gchar *key)
400 {
401  return gnc_gsettings_get (schema, key, g_settings_get_boolean,
402  static_cast<gboolean>(false));
403 }
404 
405 gint
406 gnc_gsettings_get_int (const gchar *schema, const gchar *key)
407 {
408  return gnc_gsettings_get (schema, key, g_settings_get_int, 0);
409 }
410 
411 gdouble
412 gnc_gsettings_get_float (const gchar *schema, const gchar *key)
413 {
414  return gnc_gsettings_get (schema, key, g_settings_get_double, 0.0);
415 }
416 
417 gchar *
418 gnc_gsettings_get_string (const gchar *schema, const gchar *key)
419 {
420  return gnc_gsettings_get (schema, key, g_settings_get_string,
421  static_cast<gchar *> (nullptr));
422 }
423 
424 gint
425 gnc_gsettings_get_enum (const gchar *schema, const gchar *key)
426 {
427  return gnc_gsettings_get (schema, key, g_settings_get_enum, 0);
428 }
429 
430 GVariant *
431 gnc_gsettings_get_value (const gchar *schema, const gchar *key)
432 {
433  return gnc_gsettings_get (schema, key, g_settings_get_value,
434  static_cast<GVariant *> (nullptr));
435 }
436 
437 /************************************************************/
438 /* Setters */
439 /************************************************************/
440 template<typename T> gboolean
441 gnc_gsettings_set (const gchar *schema,
442  const gchar *key,
443  T value,
444  gboolean setter(GSettings*, const char *, T))
445 {
446  ENTER("schema: %s, key: %s", schema, key);
447 
448  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
449  g_return_val_if_fail (G_IS_SETTINGS (gs_obj), false);
450 
451  auto result = false;
452  if (gnc_gsettings_is_valid_key (gs_obj, key))
453  {
454  result = setter (gs_obj, key, value);
455  if (!result)
456  PERR ("Unable to set value for key %s in schema %s", key, schema);
457  }
458  else
459  PERR ("Invalid key %s for schema %s", key, schema);
460 
461  g_object_unref (gs_obj);
462  LEAVE("result %i", result);
463  return result;
464 }
465 
466 gboolean
467 gnc_gsettings_set_bool (const gchar *schema, const gchar *key, gboolean value)
468 {
469  return gnc_gsettings_set (schema, key, value, g_settings_set_boolean);
470 }
471 
472 gboolean
473 gnc_gsettings_set_int (const gchar *schema, const gchar *key, gint value)
474 {
475  return gnc_gsettings_set (schema, key, value, g_settings_set_int);
476 }
477 
478 gboolean
479 gnc_gsettings_set_float (const gchar *schema, const gchar *key, gdouble value)
480 {
481  return gnc_gsettings_set (schema, key, value, g_settings_set_double);
482 }
483 
484 gboolean
485 gnc_gsettings_set_string (const gchar *schema, const gchar *key, const gchar *value)
486 {
487  return gnc_gsettings_set (schema, key, value, g_settings_set_string);
488 }
489 
490 gboolean
491 gnc_gsettings_set_enum (const gchar *schema, const gchar *key, gint value)
492 {
493  return gnc_gsettings_set (schema, key, value, g_settings_set_enum);
494 }
495 
496 gboolean
497 gnc_gsettings_set_value (const gchar *schema, const gchar *key, GVariant *value)
498 {
499  return gnc_gsettings_set (schema, key, value, g_settings_set_value);
500 }
501 
502 void
503 gnc_gsettings_reset (const gchar *schema,
504  const gchar *key)
505 {
506  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
507  g_return_if_fail (G_IS_SETTINGS (gs_obj));
508 
509  if (gnc_gsettings_is_valid_key (gs_obj, key))
510  g_settings_reset (gs_obj, key);
511  else
512  PERR ("Invalid key %s for schema %s", key, schema);
513 
514  g_object_unref (gs_obj);
515 }
516 
517 void
518 gnc_gsettings_reset_schema (const gchar *schema_str)
519 {
520  auto gs_obj = gnc_gsettings_get_settings_obj (schema_str);
521 
522  if (!gs_obj)
523  return;
524 
525  GSettingsSchema *schema;
526  g_object_get (gs_obj, "settings-schema", &schema, nullptr);
527  if (!schema)
528  {
529  g_object_unref (gs_obj);
530  return;
531  }
532 
533  auto keys = g_settings_schema_list_keys (schema);
534  if (keys)
535  {
536  auto fkeys = keys;
537  for (auto key = *fkeys; key; key = *++fkeys)
538  gnc_gsettings_reset (schema_str, key);
539  }
540 
541  g_object_unref (gs_obj);
542  g_settings_schema_unref (schema);
543  g_strfreev (keys);
544 }
545 
546 static void
547 gnc_settings_dump_schema_paths (void)
548 {
549  gchar **non_relocatable;
550 
551  auto schema_source {g_settings_schema_source_get_default()};
552  g_return_if_fail(schema_source);
553 
554  g_settings_schema_source_list_schemas (schema_source, true,
555  &non_relocatable, nullptr);
556 
557  for (gint i = 0; non_relocatable[i] != nullptr; i++)
558  PINFO("Schema entry %d is '%s'", i, non_relocatable[i]);
559 
560  g_strfreev (non_relocatable);
561 }
562 
564 {
565  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  if (g_strcmp0 (g_getenv ("GNC_UNINSTALLED"), "1") == 0)
572  return;
573 
574  g_free (prefsbackend);
575  prefsbackend = g_new0 (PrefsBackend, 1);
576 
577  prefsbackend->register_cb = gnc_gsettings_register_cb;
578  prefsbackend->remove_cb_by_func = gnc_gsettings_remove_cb_by_func;
579  prefsbackend->remove_cb_by_id = gnc_gsettings_remove_cb_by_id;
580  prefsbackend->register_group_cb = gnc_gsettings_register_any_cb;
581  prefsbackend->remove_group_cb_by_func = gnc_gsettings_remove_any_cb_by_func;
582  prefsbackend->bind = gnc_gsettings_bind;
583  prefsbackend->get_bool = gnc_gsettings_get_bool;
584  prefsbackend->get_int = gnc_gsettings_get_int;
585  prefsbackend->get_float = gnc_gsettings_get_float;
586  prefsbackend->get_string = gnc_gsettings_get_string;
587  prefsbackend->get_enum = gnc_gsettings_get_enum;
588  prefsbackend->get_value = gnc_gsettings_get_value;
589  prefsbackend->set_bool = gnc_gsettings_set_bool;
590  prefsbackend->set_int = gnc_gsettings_set_int;
591  prefsbackend->set_float = gnc_gsettings_set_float;
592  prefsbackend->set_string = gnc_gsettings_set_string;
593  prefsbackend->set_enum = gnc_gsettings_set_enum;
594  prefsbackend->set_value = gnc_gsettings_set_value;
595  prefsbackend->reset = gnc_gsettings_reset;
596  prefsbackend->reset_group = gnc_gsettings_reset_schema;
597  prefsbackend->block_all = gnc_gsettings_block_all;
598  prefsbackend->unblock_all = gnc_gsettings_unblock_all;
599 
600  if (qof_log_check (log_module, QOF_LOG_DEBUG))
601  gnc_settings_dump_schema_paths ();
602 
603  /* Run any data model changes for the backend before it's used
604  * by anyone */
606 
607  LEAVE("Prefsbackend bind = %p", prefsbackend->bind);
608 }
609 
610 void
612 {
613  schema_hash.clear();
614  g_free (prefsbackend);
615 }
616 
617 
618 static GVariant *
619 gnc_gsettings_get_user_value (const gchar *schema,
620  const gchar *key)
621 {
622  auto gs_obj = gnc_gsettings_get_settings_obj (schema);
623  g_return_val_if_fail (G_IS_SETTINGS (gs_obj), nullptr);
624 
625  auto val = static_cast<GVariant *> (nullptr);
626  if (gnc_gsettings_is_valid_key (gs_obj, key))
627  val = g_settings_get_user_value (gs_obj, key);
628  else
629  PERR ("Invalid key %s for schema %s", key, schema);
630 
631  g_object_unref (gs_obj);
632  return val;
633 }
634 
635 using opt_str_vec = boost::optional<std::string>;
636 
637 static void
638 deprecate_one_key (const opt_str_vec &oldpath, const opt_str_vec &oldkey)
639 {
640  if (!oldpath || !oldkey )
641  {
642  DEBUG ("Skipping <deprecate> node - missing attribute (old-path or old-key)");
643  return;
644  }
645 
646  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 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  if (!oldpath || !oldkey || !newpath || !newkey)
656  {
657  DEBUG ("Skipping <migrate> node - missing attribute (old-path, old-key, new-path or new-key)");
658  return;
659  }
660 
661  PINFO ("Migrating '%s:%s' to '%s:%s'", oldpath->c_str(), oldkey->c_str(),
662  newpath->c_str(), newkey->c_str());
663 
664  auto user_value = gnc_gsettings_get_user_value (oldpath->c_str(), oldkey->c_str());
665  if (user_value)
666  gnc_gsettings_set_value (newpath->c_str(), newkey->c_str(), user_value);
667 }
668 
669 static void
670 obsolete_one_key (const opt_str_vec &oldpath, const opt_str_vec &oldkey)
671 {
672  if (!oldpath || !oldkey )
673  {
674  DEBUG ("Skipping <obsolete> node - missing attribute (old-path or old-key)");
675  return;
676  }
677 
678  PINFO ("Resetting obsolete '%s:%s'", oldpath->c_str(), oldkey->c_str());
679  gnc_gsettings_reset (oldpath->c_str(), oldkey->c_str());
680 }
681 
682 static void
683 parse_one_release_node (bpt::ptree &pt)
684 {
685  /* loop over top-level property tree */
686  std::for_each (pt.begin(), pt.end(),
687  [] (std::pair<bpt::ptree::key_type, bpt::ptree> node)
688  {
689  if (node.first == "<xmlattr>")
690  return;
691  else if (node.first == "deprecate")
692  deprecate_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
693  node.second.get_optional<std::string> ("<xmlattr>.old-key"));
694  else if (node.first == "migrate")
695  migrate_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
696  node.second.get_optional<std::string> ("<xmlattr>.old-key"),
697  node.second.get_optional<std::string> ("<xmlattr>.new-path"),
698  node.second.get_optional<std::string> ("<xmlattr>.new-key"));
699  else if (node.first == "obsolete")
700  obsolete_one_key (node.second.get_optional<std::string> ("<xmlattr>.old-path"),
701  node.second.get_optional<std::string> ("<xmlattr>.old-key"));
702  else
703  {
704  DEBUG ("Skipping unknown node <%s>", node.first.c_str());
705  return;
706  }
707  });
708 }
709 
710 static void
711 transform_settings (int old_maj_min, int cur_maj_min)
712 {
713  bpt::ptree pt;
714 
715  auto pkg_data_dir = gnc_path_get_pkgdatadir();
716  auto transform_file = std::string (pkg_data_dir) + "/pref_transformations.xml";
717  g_free (pkg_data_dir);
718 
719  std::ifstream transform_stream {transform_file};
720  if (!transform_stream.is_open())
721  {
722  PWARN("Failed to load preferences transformation file '%s'", transform_file.c_str());
723  return;
724  }
725 
726  try
727  {
728  bpt::read_xml (transform_stream, pt);
729  }
730  catch (bpt::xml_parser_error &e) {
731  PWARN ("Failed to parse GnuCash preferences transformation file.\n");
732  PWARN ("Error message:\n");
733  PWARN ("%s\n", e.what());
734  return;
735  }
736  catch (...) {
737  PWARN ("Unknown error while parsing GnuCash preferences transformation file.\n");
738  return;
739  }
740 
741  /* loop over top-level property tree */
742  std::for_each (pt.begin(), pt.end(),
743  [&old_maj_min, &cur_maj_min] (std::pair<bpt::ptree::key_type, bpt::ptree> node)
744  {
745  if (node.first != "release")
746  {
747  DEBUG ("Skipping non-<release> node <%s>", node.first.c_str());
748  return;
749  }
750  auto version = node.second.get_optional<int> ("<xmlattr>.version");
751  if (!version)
752  {
753  DEBUG ("Skipping <release> node - no version attribute found");
754  return;
755  }
756  if (*version <= old_maj_min)
757  {
758  DEBUG ("Skipping <release> node - version %i is less than current compatibility level %i", *version, old_maj_min);
759  return;
760  }
761  if (*version > cur_maj_min)
762  {
763  DEBUG ("Skipping <release> node - version %i is greater than current version level %i", *version, cur_maj_min);
764  return;
765  }
766  DEBUG ("Retrieved version value '%i'", *version);
767 
768  parse_one_release_node (node.second);
769  });
770 }
771 
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  ENTER("Start of settings transform routine.");
798 
799  auto ogG_maj_min = gnc_gsettings_get_user_value (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
800  auto og_maj_min = gnc_gsettings_get_user_value (GSET_SCHEMA_OLD_PREFIX "." GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
801 
802  auto cur_maj_min = PROJECT_VERSION_MAJOR * 1000 + PROJECT_VERSION_MINOR;
803 
804  if (!ogG_maj_min && !og_maj_min) // new install
805  {
806  gnc_gsettings_set_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION, cur_maj_min);
807  LEAVE ("Setting Previous compatibility level to current version: %i", cur_maj_min);
808  return;
809  }
810 
811  auto old_maj_min = 0;
812  if (!ogG_maj_min) // old preference location
813  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  g_variant_unref (ogG_maj_min);
817  old_maj_min = gnc_gsettings_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION);
818  }
819  if (og_maj_min)
820  g_variant_unref (og_maj_min);
821 
822  PINFO ("Previous setting compatibility level: %i, Current version: %i", old_maj_min, cur_maj_min);
823 
824  transform_settings (old_maj_min, cur_maj_min);
825 
826  /* Only write current version if it's more recent than what was set */
827  if (cur_maj_min > old_maj_min)
828  gnc_gsettings_set_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_VERSION, cur_maj_min);
829 
830  LEAVE("");
831 }
gboolean gnc_gsettings_set_int(const gchar *schema, const gchar *key, gint value)
Store an integer value into GSettings.
gboolean gnc_gsettings_set_float(const gchar *schema, const gchar *key, gdouble value)
Store a float value into GSettings.
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
gint gnc_gsettings_get_int(const gchar *schema, const gchar *key)
Get an integer value from GSettings.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void gnc_gsettings_remove_any_cb_by_func(const gchar *schema, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when any key in the given settings schema change...
gboolean gnc_gsettings_set_enum(const gchar *schema, const gchar *key, gint value)
Store an enum value into GSettings.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
gboolean qof_log_check(QofLogModule domain, QofLogLevel level)
Check to see if the given log_module is configured to log at the given log_level. ...
Definition: qoflog.cpp:330
GVariant * gnc_gsettings_get_value(const gchar *schema, const gchar *key)
Get an arbitrary combination of values from GSettings.
guint gnc_gsettings_register_any_cb(const gchar *schema, gpointer func, gpointer user_data)
Register a callback for when any key in the settings schema is changed.
void gnc_gsettings_unblock_all(void)
UnBlock all prefs callbacks, used while preference dialog is loaded.
void gnc_gsettings_reset_schema(const gchar *schema_str)
Reset all keys in a schema to their default values in GSettings.
void gnc_gsettings_shutdown(void)
Free the GSettings resources.
gdouble gnc_gsettings_get_float(const gchar *schema, const gchar *key)
Get an float value from GSettings.
void gnc_gsettings_reset(const gchar *schema, const gchar *key)
Reset a key to its default value in GSettings.
gchar * gnc_gsettings_get_string(const gchar *schema, const gchar *key)
Get a string value from GSettings.
void gnc_gsettings_bind(const gchar *schema, const gchar *key, const gchar *value, gpointer object, const gchar *property)
Bind a setting to a g_object property.
gint gnc_gsettings_get_enum(const gchar *schema, const gchar *key)
Get an enum value from GSettings.
gboolean gnc_gsettings_set_bool(const gchar *schema, const gchar *key, gboolean value)
Store a boolean value into GSettings.
void gnc_gsettings_load_backend(void)
Configure gsettings as the backend for the gnucash preferences api.
void gnc_gsettings_version_upgrade(void)
Check whether we need to adjust the user settings to a newer version.
void gnc_gsettings_block_all(void)
Block all prefs callbacks, used while preference dialog is loaded.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
gboolean gnc_gsettings_get_bool(const gchar *schema, const gchar *key)
Get a boolean value from GSettings.
const gchar * gnc_gsettings_get_prefix(void)
Get the default gsettings schema prefix.
void gnc_gsettings_remove_cb_by_id(const gchar *schema, guint handlerid)
Remove a function that was registered for a callback when a specific key in the settings schema chang...
gboolean gnc_gsettings_set_value(const gchar *schema, const gchar *key, GVariant *value)
Store an arbitrary combination of values into GSettings.
gulong gnc_gsettings_register_cb(const char *schema, const gchar *key, gpointer func, gpointer user_data)
Register a callback for when a specific key in the settings schema is changed.
GSettings helper routines.
gboolean gnc_gsettings_set_string(const gchar *schema, const gchar *key, const gchar *value)
Store a string into GSettings.
void gnc_gsettings_remove_cb_by_func(const gchar *schema, const gchar *key, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when a specific key in the settings schema chang...