1 /********************************************************************\
2  * qofbook.c -- dataset access (set of books of entities)           *
3  *                                                                  *
4  * This program is free software; you can redistribute it and/or    *
5  * modify it under the terms of the GNU General Public License as   *
6  * published by the Free Software Foundation; either version 2 of   *
7  * the License, or (at your option) any later version.              *
8  *                                                                  *
9  * This program is distributed in the hope that it will be useful,  *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
12  * GNU General Public License for more details.                     *
13  *                                                                  *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact:                        *
16  *                                                                  *
17  * Free Software Foundation           Voice:  +1-617-542-5942       *
18  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
19  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
20 \********************************************************************/
21 
22 /*
23  * FILE:
24  * qofbook.cpp
25  *
26  * FUNCTION:
27  * Encapsulate all the information about a QOF dataset.
28  *
29  * HISTORY:
30  * Created by Linas Vepstas December 1998
31  * Copyright (c) 1998-2001,2003 Linas Vepstas <linas@linas.org>
32  * Copyright (c) 2000 Dave Peticolas
33  * Copyright (c) 2007 David Hampton <hampton@employees.org>
34  */
35 #include <glib.h>
36 
37 extern "C"
38 {
39 
40 #include <config.h>
41 
42 #include <stdlib.h>
43 #include <string.h>
44 
45 #ifdef GNC_PLATFORM_WINDOWS
46   /* Mingw disables the standard type macros for C++ without this override. */
47 #define __STDC_FORMAT_MACROS = 1
48 #endif
49 #include <inttypes.h>
50 
51 }
52 
53 #include "qof.h"
54 #include "qofevent-p.h"
55 #include "qofbackend.h"
56 #include "qofbook-p.h"
57 #include "qofid-p.h"
58 #include "qofobject-p.h"
59 #include "qofbookslots.h"
60 #include "kvp-frame.hpp"
61 // For GNC_ID_ROOT_ACCOUNT:
62 #include "AccountP.h"
63 
64 static QofLogModule log_module = QOF_MOD_ENGINE;
65 #define AB_KEY "hbci"
66 #define AB_TEMPLATES "template-list"
67 
68 enum
69 {
70     PROP_0,
71 //  PROP_ROOT_ACCOUNT,                        /* Table */
72 //  PROP_ROOT_TEMPLATE,                       /* Table */
73 /*   keep trading accounts property, while adding book-currency, default gains
74      policy and default gains account properties, so that files prior to 2.7 can
75      be read/processed; GUI changed to use all four properties as of 2.7.
76      Trading accounts, on the one hand, and book-currency plus default-gains-
77      policy, and optionally, default gains account, on the other, are mutually
78      exclusive */
79     PROP_OPT_TRADING_ACCOUNTS,              /* KVP */
80 /*   Book currency and default gains policy properties only apply if currency
81      accounting method selected in GUI is 'book-currency'; both required and
82      both are exclusive with trading accounts */
83     PROP_OPT_BOOK_CURRENCY,                 /* KVP */
84     PROP_OPT_DEFAULT_GAINS_POLICY,          /* KVP */
85 /*   Default gains account property only applies if currency accounting method
86      selected in GUI is 'book-currency'; its use is optional but exclusive with
87      trading accounts */
88     PROP_OPT_DEFAULT_GAINS_ACCOUNT_GUID,    /* KVP */
89     PROP_OPT_AUTO_READONLY_DAYS,            /* KVP */
90     PROP_OPT_NUM_FIELD_SOURCE,              /* KVP */
91     PROP_OPT_DEFAULT_BUDGET,                /* KVP */
92     PROP_OPT_FY_END,                        /* KVP */
93     PROP_AB_TEMPLATES,                      /* KVP */
94     N_PROPERTIES                            /* Just a counter */
95 };
96 
97 static void
98 qof_book_option_num_field_source_changed_cb (GObject *gobject,
99                                              GParamSpec *pspec,
100                                              gpointer    user_data);
101 static void
102 qof_book_option_num_autoreadonly_changed_cb (GObject *gobject,
103                                              GParamSpec *pspec,
104                                              gpointer    user_data);
105 
106 // Use a #define for the GParam name to avoid typos
107 #define PARAM_NAME_NUM_FIELD_SOURCE "split-action-num-field"
108 #define PARAM_NAME_NUM_AUTOREAD_ONLY "autoreadonly-days"
109 
110 G_DEFINE_TYPE(QofBook, qof_book, QOF_TYPE_INSTANCE);
111 QOF_GOBJECT_DISPOSE(qof_book);
112 QOF_GOBJECT_FINALIZE(qof_book);
113 
114 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
115 #undef G_PARAM_READWRITE
116 #define G_PARAM_READWRITE static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_WRITABLE)
117 /* ====================================================================== */
118 /* constructor / destructor */
119 
coll_destroy(gpointer col)120 static void coll_destroy(gpointer col)
121 {
122     qof_collection_destroy((QofCollection *) col);
123 }
124 
125 static void
qof_book_init(QofBook * book)126 qof_book_init (QofBook *book)
127 {
128     if (!book) return;
129 
130     book->hash_of_collections = g_hash_table_new_full(
131                                     g_str_hash, g_str_equal,
132                                     (GDestroyNotify)qof_string_cache_remove,  /* key_destroy_func   */
133                                     coll_destroy);                            /* value_destroy_func */
134 
135     qof_instance_init_data (&book->inst, QOF_ID_BOOK, book);
136 
137     book->data_tables = g_hash_table_new (g_str_hash, g_str_equal);
138     book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
139 
140     book->book_open = 'y';
141     book->read_only = FALSE;
142     book->session_dirty = FALSE;
143     book->version = 0;
144     book->cached_num_field_source_isvalid = FALSE;
145     book->cached_num_days_autoreadonly_isvalid = FALSE;
146 
147     // Register a callback on this NUM_FIELD_SOURCE property of that object
148     // because it gets called quite a lot, so that its value must be stored in
149     // a bool member variable instead of a KVP lookup on each getter call.
150     g_signal_connect (G_OBJECT(book),
151                       "notify::" PARAM_NAME_NUM_FIELD_SOURCE,
152                       G_CALLBACK (qof_book_option_num_field_source_changed_cb),
153                       book);
154 
155     // Register a callback on this NUM_AUTOREAD_ONLY property of that object
156     // because it gets called quite a lot, so that its value must be stored in
157     // a bool member variable instead of a KVP lookup on each getter call.
158     g_signal_connect (G_OBJECT(book),
159                       "notify::" PARAM_NAME_NUM_AUTOREAD_ONLY,
160                       G_CALLBACK (qof_book_option_num_autoreadonly_changed_cb),
161                       book);
162 }
163 
164 static const std::string str_KVP_OPTION_PATH(KVP_OPTION_PATH);
165 static const std::string str_OPTION_SECTION_ACCOUNTS(OPTION_SECTION_ACCOUNTS);
166 static const std::string str_OPTION_SECTION_BUDGETING(OPTION_SECTION_BUDGETING);
167 static const std::string str_OPTION_NAME_DEFAULT_BUDGET(OPTION_NAME_DEFAULT_BUDGET);
168 static const std::string str_OPTION_NAME_TRADING_ACCOUNTS(OPTION_NAME_TRADING_ACCOUNTS);
169 static const std::string str_OPTION_NAME_AUTO_READONLY_DAYS(OPTION_NAME_AUTO_READONLY_DAYS);
170 static const std::string str_OPTION_NAME_NUM_FIELD_SOURCE(OPTION_NAME_NUM_FIELD_SOURCE);
171 
172 static void
qof_book_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)173 qof_book_get_property (GObject* object,
174                guint prop_id,
175                GValue* value,
176                GParamSpec* pspec)
177 {
178     QofBook *book;
179     gchar *key;
180 
181     g_return_if_fail (QOF_IS_BOOK (object));
182     book = QOF_BOOK (object);
183     switch (prop_id)
184     {
185     case PROP_OPT_TRADING_ACCOUNTS:
186         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
187                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_TRADING_ACCOUNTS});
188         break;
189     case PROP_OPT_BOOK_CURRENCY:
190         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
191                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_BOOK_CURRENCY});
192         break;
193     case PROP_OPT_DEFAULT_GAINS_POLICY:
194         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
195                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_GAINS_POLICY});
196         break;
197     case PROP_OPT_DEFAULT_GAINS_ACCOUNT_GUID:
198         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
199                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_GAINS_LOSS_ACCT_GUID});
200         break;
201     case PROP_OPT_AUTO_READONLY_DAYS:
202         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
203                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_AUTO_READONLY_DAYS});
204         break;
205     case PROP_OPT_NUM_FIELD_SOURCE:
206         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
207                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_NUM_FIELD_SOURCE});
208         break;
209     case PROP_OPT_DEFAULT_BUDGET:
210         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
211                 str_OPTION_SECTION_BUDGETING, str_OPTION_NAME_DEFAULT_BUDGET});
212         break;
213     case PROP_OPT_FY_END:
214         qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {"fy_end"});
215         break;
216     case PROP_AB_TEMPLATES:
217           qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {"AB_KEY", "AB_TEMPLATES"});
218         break;
219     default:
220         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
221         break;
222     }
223 }
224 
225 static void
qof_book_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)226 qof_book_set_property (GObject      *object,
227                guint         prop_id,
228                const GValue *value,
229                GParamSpec   *pspec)
230 {
231     QofBook *book;
232     gchar *key;
233 
234     g_return_if_fail (QOF_IS_BOOK (object));
235     book = QOF_BOOK (object);
236     g_assert (qof_instance_get_editlevel(book));
237 
238     switch (prop_id)
239     {
240     case PROP_OPT_TRADING_ACCOUNTS:
241         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
242                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_TRADING_ACCOUNTS});
243         break;
244     case PROP_OPT_BOOK_CURRENCY:
245         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
246                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_BOOK_CURRENCY});
247         break;
248     case PROP_OPT_DEFAULT_GAINS_POLICY:
249         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
250                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_GAINS_POLICY});
251         break;
252     case PROP_OPT_DEFAULT_GAINS_ACCOUNT_GUID:
253         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
254                 str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_GAINS_LOSS_ACCT_GUID});
255         break;
256     case PROP_OPT_AUTO_READONLY_DAYS:
257         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
258                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_AUTO_READONLY_DAYS});
259         break;
260     case PROP_OPT_NUM_FIELD_SOURCE:
261         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
262                 str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_NUM_FIELD_SOURCE});
263         break;
264     case PROP_OPT_DEFAULT_BUDGET:
265         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
266                 str_OPTION_SECTION_BUDGETING, OPTION_NAME_DEFAULT_BUDGET});
267         break;
268     case PROP_OPT_FY_END:
269         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {"fy_end"});
270         break;
271     case PROP_AB_TEMPLATES:
272         qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {AB_KEY, AB_TEMPLATES});
273         break;
274     default:
275         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
276         break;
277     }
278 }
279 
280 static void
qof_book_class_init(QofBookClass * klass)281 qof_book_class_init (QofBookClass *klass)
282 {
283     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
284     gobject_class->dispose = qof_book_dispose;
285     gobject_class->finalize = qof_book_finalize;
286     gobject_class->get_property = qof_book_get_property;
287     gobject_class->set_property = qof_book_set_property;
288 
289     g_object_class_install_property
290     (gobject_class,
291      PROP_OPT_TRADING_ACCOUNTS,
292      g_param_spec_string("trading-accts",
293                          "Use Trading Accounts",
294                          "Scheme true ('t') or NULL. If 't', then the book "
295                          "uses trading accounts for managing multiple-currency "
296                          "transactions.",
297                          NULL,
298                          G_PARAM_READWRITE));
299 
300     g_object_class_install_property
301     (gobject_class,
302      PROP_OPT_BOOK_CURRENCY,
303      g_param_spec_string("book-currency",
304                          "Select Book Currency",
305                          "The reference currency used to manage multiple-currency "
306                          "transactions when 'book-currency' currency accounting method "
307                          "selected; requires valid default gains/loss policy.",
308                          NULL,
309                          G_PARAM_READWRITE));
310 
311     g_object_class_install_property
312     (gobject_class,
313      PROP_OPT_DEFAULT_GAINS_POLICY,
314      g_param_spec_string("default-gains-policy",
315                          "Select Default Gains Policy",
316                          "The default policy to be used to calculate gains/losses on "
317                          "dispositions of currencies/commodities other than "
318                          "'book-currency' when 'book-currency' currency accounting "
319                          "method selected; requires valid book-currency.",
320                          NULL,
321                          G_PARAM_READWRITE));
322 
323     g_object_class_install_property
324     (gobject_class,
325      PROP_OPT_DEFAULT_GAINS_ACCOUNT_GUID,
326      g_param_spec_boxed("default-gain-loss-account-guid",
327                         "Select Default Gain/Loss Account",
328                         "The default account to be used for calculated gains/losses on "
329                         "dispositions of currencies/commodities other than "
330                         "'book-currency' when 'book-currency' currency accounting "
331                         "method selected; requires valid book-currency.",
332                          GNC_TYPE_GUID,
333                          G_PARAM_READWRITE));
334 
335     g_object_class_install_property
336     (gobject_class,
337      PROP_OPT_NUM_FIELD_SOURCE,
338      g_param_spec_string(PARAM_NAME_NUM_FIELD_SOURCE,
339                          "Use Split-Action in the Num Field",
340                          "Scheme true ('t') or NULL. If 't', then the book "
341                          "will put the split action value in the Num field.",
342                          NULL,
343                          G_PARAM_READWRITE));
344 
345     g_object_class_install_property
346     (gobject_class,
347      PROP_OPT_AUTO_READONLY_DAYS,
348      g_param_spec_double("autoreadonly-days",
349                          "Transaction Auto-read-only Days",
350                          "Prevent editing of transactions posted more than "
351                          "this many days ago.",
352                          0,
353                          G_MAXDOUBLE,
354                          0,
355                          G_PARAM_READWRITE));
356 
357     g_object_class_install_property
358     (gobject_class,
359      PROP_OPT_DEFAULT_BUDGET,
360      g_param_spec_boxed("default-budget",
361                         "Book Default Budget",
362                         "The default Budget for this book.",
363                         GNC_TYPE_GUID,
364                         G_PARAM_READWRITE));
365     g_object_class_install_property
366     (gobject_class,
367      PROP_OPT_FY_END,
368      g_param_spec_boxed("fy-end",
369                         "Book Fiscal Year End",
370                         "A GDate with a bogus year having the last Month and "
371                         "Day of the Fiscal year for the book.",
372                         G_TYPE_DATE,
373                         G_PARAM_READWRITE));
374     g_object_class_install_property
375     (gobject_class,
376      PROP_AB_TEMPLATES,
377      g_param_spec_boxed("ab-templates",
378                         "AQBanking Template List",
379                         "A GList of AQBanking Templates",
380                         GNC_TYPE_VALUE_LIST,
381                         G_PARAM_READWRITE));
382 }
383 
384 QofBook *
qof_book_new(void)385 qof_book_new (void)
386 {
387     QofBook *book;
388 
389     ENTER (" ");
390     book = static_cast<QofBook*>(g_object_new(QOF_TYPE_BOOK, NULL));
391     qof_object_book_begin (book);
392 
393     qof_event_gen (&book->inst, QOF_EVENT_CREATE, NULL);
394     LEAVE ("book=%p", book);
395     return book;
396 }
397 
398 static void
book_final(gpointer key,gpointer value,gpointer booq)399 book_final (gpointer key, gpointer value, gpointer booq)
400 {
401     QofBookFinalCB cb = reinterpret_cast<QofBookFinalCB>(value);
402     QofBook *book = static_cast<QofBook*>(booq);
403 
404     gpointer user_data = g_hash_table_lookup (book->data_tables, key);
405     (*cb) (book, key, user_data);
406 }
407 
408 static void
qof_book_dispose_real(G_GNUC_UNUSED GObject * bookp)409 qof_book_dispose_real (G_GNUC_UNUSED GObject *bookp)
410 {
411 }
412 
413 static void
qof_book_finalize_real(G_GNUC_UNUSED GObject * bookp)414 qof_book_finalize_real (G_GNUC_UNUSED GObject *bookp)
415 {
416 }
417 
418 void
qof_book_destroy(QofBook * book)419 qof_book_destroy (QofBook *book)
420 {
421     GHashTable* cols;
422 
423     if (!book) return;
424     ENTER ("book=%p", book);
425 
426     book->shutting_down = TRUE;
427     qof_event_force (&book->inst, QOF_EVENT_DESTROY, NULL);
428 
429     /* Call the list of finalizers, let them do their thing.
430      * Do this before tearing into the rest of the book.
431      */
432     g_hash_table_foreach (book->data_table_finalizers, book_final, book);
433 
434     qof_object_book_end (book);
435 
436     g_hash_table_destroy (book->data_table_finalizers);
437     book->data_table_finalizers = NULL;
438     g_hash_table_destroy (book->data_tables);
439     book->data_tables = NULL;
440 
441     /* qof_instance_release (&book->inst); */
442 
443     /* Note: we need to save this hashtable until after we remove ourself
444      * from it, otherwise we'll crash in our dispose() function when we
445      * DO remove ourself from the collection but the collection had already
446      * been destroyed.
447      */
448     cols = book->hash_of_collections;
449     g_object_unref (book);
450     g_hash_table_destroy (cols);
451     /*book->hash_of_collections = NULL;*/
452 
453     LEAVE ("book=%p", book);
454 }
455 
456 /* ====================================================================== */
457 
458 gboolean
qof_book_session_not_saved(const QofBook * book)459 qof_book_session_not_saved (const QofBook *book)
460 {
461     if (!book) return FALSE;
462     return !qof_book_empty(book) && book->session_dirty;
463 
464 }
465 
466 void
qof_book_mark_session_saved(QofBook * book)467 qof_book_mark_session_saved (QofBook *book)
468 {
469     if (!book) return;
470 
471     book->dirty_time = 0;
472     if (book->session_dirty)
473     {
474         /* Set the session clean upfront, because the callback will check. */
475         book->session_dirty = FALSE;
476         if (book->dirty_cb)
477             book->dirty_cb(book, FALSE, book->dirty_data);
478     }
479 }
480 
qof_book_mark_session_dirty(QofBook * book)481 void qof_book_mark_session_dirty (QofBook *book)
482 {
483     if (!book) return;
484     if (!book->session_dirty)
485     {
486         /* Set the session dirty upfront, because the callback will check. */
487         book->session_dirty = TRUE;
488         book->dirty_time = gnc_time (NULL);
489         if (book->dirty_cb)
490             book->dirty_cb(book, TRUE, book->dirty_data);
491     }
492 }
493 
494 void
qof_book_print_dirty(const QofBook * book)495 qof_book_print_dirty (const QofBook *book)
496 {
497     if (qof_book_session_not_saved(book))
498         PINFO("book is dirty.");
499     qof_book_foreach_collection
500     (book, (QofCollectionForeachCB)qof_collection_print_dirty, NULL);
501 }
502 
503 time64
qof_book_get_session_dirty_time(const QofBook * book)504 qof_book_get_session_dirty_time (const QofBook *book)
505 {
506     return book->dirty_time;
507 }
508 
509 void
qof_book_set_dirty_cb(QofBook * book,QofBookDirtyCB cb,gpointer user_data)510 qof_book_set_dirty_cb(QofBook *book, QofBookDirtyCB cb, gpointer user_data)
511 {
512     g_return_if_fail(book);
513     if (book->dirty_cb)
514         PWARN("Already existing callback %p, will be overwritten by %p\n",
515                   book->dirty_cb, cb);
516     book->dirty_data = user_data;
517     book->dirty_cb = cb;
518 }
519 
520 /* ====================================================================== */
521 /* getters */
522 
523 QofBackend *
qof_book_get_backend(const QofBook * book)524 qof_book_get_backend (const QofBook *book)
525 {
526     if (!book) return NULL;
527     return book->backend;
528 }
529 
530 gboolean
qof_book_shutting_down(const QofBook * book)531 qof_book_shutting_down (const QofBook *book)
532 {
533     if (!book) return FALSE;
534     return book->shutting_down;
535 }
536 
537 /* ====================================================================== */
538 /* setters */
539 
540 void
qof_book_set_backend(QofBook * book,QofBackend * be)541 qof_book_set_backend (QofBook *book, QofBackend *be)
542 {
543     if (!book) return;
544     ENTER ("book=%p be=%p", book, be);
545     book->backend = be;
546     LEAVE (" ");
547 }
548 
549 /* ====================================================================== */
550 /* Store arbitrary pointers in the QofBook for data storage extensibility */
551 /* XXX if data is NULL, we should remove the key from the hash table!
552  */
553 void
qof_book_set_data(QofBook * book,const char * key,gpointer data)554 qof_book_set_data (QofBook *book, const char *key, gpointer data)
555 {
556     if (!book || !key) return;
557     g_hash_table_insert (book->data_tables, (gpointer)key, data);
558 }
559 
560 void
qof_book_set_data_fin(QofBook * book,const char * key,gpointer data,QofBookFinalCB cb)561 qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB cb)
562 {
563     if (!book || !key) return;
564     g_hash_table_insert (book->data_tables, (gpointer)key, data);
565 
566     if (!cb) return;
567     g_hash_table_insert (book->data_table_finalizers, (gpointer)key,
568              reinterpret_cast<void*>(cb));
569 }
570 
571 gpointer
qof_book_get_data(const QofBook * book,const char * key)572 qof_book_get_data (const QofBook *book, const char *key)
573 {
574     if (!book || !key) return NULL;
575     return g_hash_table_lookup (book->data_tables, (gpointer)key);
576 }
577 
578 /* ====================================================================== */
579 gboolean
qof_book_is_readonly(const QofBook * book)580 qof_book_is_readonly(const QofBook *book)
581 {
582     g_return_val_if_fail( book != NULL, TRUE );
583     return book->read_only;
584 }
585 
586 void
qof_book_mark_readonly(QofBook * book)587 qof_book_mark_readonly(QofBook *book)
588 {
589     g_return_if_fail( book != NULL );
590     book->read_only = TRUE;
591 }
592 
593 gboolean
qof_book_empty(const QofBook * book)594 qof_book_empty(const QofBook *book)
595 {
596     if (!book) return TRUE;
597     auto root_acct_col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
598     return qof_collection_get_data(root_acct_col) == nullptr;
599 }
600 
601 /* ====================================================================== */
602 
603 QofCollection *
qof_book_get_collection(const QofBook * book,QofIdType entity_type)604 qof_book_get_collection (const QofBook *book, QofIdType entity_type)
605 {
606     QofCollection *col;
607 
608     if (!book || !entity_type) return NULL;
609 
610     col = static_cast<QofCollection*>(g_hash_table_lookup (book->hash_of_collections, entity_type));
611     if (!col)
612     {
613         col = qof_collection_new (entity_type);
614         g_hash_table_insert(
615             book->hash_of_collections,
616             (gpointer)qof_string_cache_insert(entity_type), col);
617     }
618     return col;
619 }
620 
621 struct _iterate
622 {
623     QofCollectionForeachCB  fn;
624     gpointer                data;
625 };
626 
627 static void
foreach_cb(G_GNUC_UNUSED gpointer key,gpointer item,gpointer arg)628 foreach_cb (G_GNUC_UNUSED gpointer key, gpointer item, gpointer arg)
629 {
630     struct _iterate *iter = static_cast<_iterate*>(arg);
631     QofCollection *col = static_cast<QofCollection*>(item);
632 
633     iter->fn (col, iter->data);
634 }
635 
636 void
qof_book_foreach_collection(const QofBook * book,QofCollectionForeachCB cb,gpointer user_data)637 qof_book_foreach_collection (const QofBook *book,
638                              QofCollectionForeachCB cb, gpointer user_data)
639 {
640     struct _iterate iter;
641 
642     g_return_if_fail (book);
643     g_return_if_fail (cb);
644 
645     iter.fn = cb;
646     iter.data = user_data;
647 
648     g_hash_table_foreach (book->hash_of_collections, foreach_cb, &iter);
649 }
650 
651 /* ====================================================================== */
652 
qof_book_mark_closed(QofBook * book)653 void qof_book_mark_closed (QofBook *book)
654 {
655     if (!book)
656     {
657         return;
658     }
659     book->book_open = 'n';
660 }
661 
662 gint64
qof_book_get_counter(QofBook * book,const char * counter_name)663 qof_book_get_counter (QofBook *book, const char *counter_name)
664 {
665     KvpFrame *kvp;
666     KvpValue *value;
667 
668     if (!book)
669     {
670         PWARN ("No book!!!");
671         return -1;
672     }
673 
674     if (!counter_name || *counter_name == '\0')
675     {
676         PWARN ("Invalid counter name.");
677         return -1;
678     }
679 
680     /* Use the KVP in the book */
681     kvp = qof_instance_get_slots (QOF_INSTANCE (book));
682 
683     if (!kvp)
684     {
685         PWARN ("Book has no KVP_Frame");
686         return -1;
687     }
688 
689     value = kvp->get_slot({"counters", counter_name});
690     if (value)
691     {
692         /* found it */
693         return value->get<int64_t>();
694     }
695     else
696     {
697         /* New counter */
698         return 0;
699     }
700 }
701 
702 gchar *
qof_book_increment_and_format_counter(QofBook * book,const char * counter_name)703 qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
704 {
705     KvpFrame *kvp;
706     KvpValue *value;
707     gint64 counter;
708     gchar* format;
709     gchar* result;
710 
711     if (!book)
712     {
713         PWARN ("No book!!!");
714         return NULL;
715     }
716 
717     if (!counter_name || *counter_name == '\0')
718     {
719         PWARN ("Invalid counter name.");
720         return NULL;
721     }
722 
723     /* Get the current counter value from the KVP in the book. */
724     counter = qof_book_get_counter(book, counter_name);
725 
726     /* Check if an error occurred */
727     if (counter < 0)
728         return NULL;
729 
730     /* Increment the counter */
731     counter++;
732 
733     /* Get the KVP from the current book */
734     kvp = qof_instance_get_slots (QOF_INSTANCE (book));
735 
736     if (!kvp)
737     {
738         PWARN ("Book has no KVP_Frame");
739         return NULL;
740     }
741 
742     /* Save off the new counter */
743     qof_book_begin_edit(book);
744     value = new KvpValue(counter);
745     delete kvp->set_path({"counters", counter_name}, value);
746     qof_instance_set_dirty (QOF_INSTANCE (book));
747     qof_book_commit_edit(book);
748 
749     format = qof_book_get_counter_format(book, counter_name);
750 
751     if (!format)
752     {
753         PWARN("Cannot get format for counter");
754         return NULL;
755     }
756 
757     /* Generate a string version of the counter */
758     result = g_strdup_printf(format, counter);
759     g_free (format);
760     return result;
761 }
762 
763 char *
qof_book_get_counter_format(const QofBook * book,const char * counter_name)764 qof_book_get_counter_format(const QofBook *book, const char *counter_name)
765 {
766     KvpFrame *kvp;
767     const char *user_format = NULL;
768     gchar *norm_format = NULL;
769     KvpValue *value;
770     gchar *error = NULL;
771 
772     if (!book)
773     {
774         PWARN ("No book!!!");
775         return NULL;
776     }
777 
778     if (!counter_name || *counter_name == '\0')
779     {
780         PWARN ("Invalid counter name.");
781         return NULL;
782     }
783 
784     /* Get the KVP from the current book */
785     kvp = qof_instance_get_slots (QOF_INSTANCE (book));
786 
787     if (!kvp)
788     {
789         PWARN ("Book has no KVP_Frame");
790         return NULL;
791     }
792 
793     /* Get the format string */
794     value = kvp->get_slot({"counter_formats", counter_name});
795     if (value)
796     {
797         user_format = value->get<const char*>();
798         norm_format = qof_book_normalize_counter_format(user_format, &error);
799         if (!norm_format)
800         {
801             PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", user_format, counter_name, error);
802             /* Invalid format string */
803             user_format = NULL;
804             g_free(error);
805         }
806     }
807 
808     /* If no (valid) format string was found, use the default format
809      * string */
810     if (!norm_format)
811     {
812         /* Use the default format */
813         norm_format = g_strdup ("%.6" PRIi64);
814     }
815     return norm_format;
816 }
817 
818 gchar *
qof_book_normalize_counter_format(const gchar * p,gchar ** err_msg)819 qof_book_normalize_counter_format(const gchar *p, gchar **err_msg)
820 {
821     const gchar *valid_formats [] = {
822             G_GINT64_FORMAT,
823             "lli",
824             "I64i",
825             PRIi64,
826             "li",
827             NULL,
828     };
829     int i = 0;
830     gchar *normalized_spec = NULL;
831 
832     while (valid_formats[i])
833     {
834 
835         if (err_msg && *err_msg)
836         {
837             g_free (*err_msg);
838             *err_msg = NULL;
839         }
840 
841         normalized_spec = qof_book_normalize_counter_format_internal(p, valid_formats[i], err_msg);
842         if (normalized_spec)
843             return normalized_spec;  /* Found a valid format specifier, return */
844         i++;
845     }
846 
847     return NULL;
848 }
849 
850 gchar *
qof_book_normalize_counter_format_internal(const gchar * p,const gchar * gint64_format,gchar ** err_msg)851 qof_book_normalize_counter_format_internal(const gchar *p,
852         const gchar *gint64_format, gchar **err_msg)
853 {
854     const gchar *conv_start, *base, *tmp = NULL;
855     gchar *normalized_str = NULL, *aux_str = NULL;
856 
857     /* Validate a counter format. This is a very simple "parser" that
858      * simply checks for a single gint64 conversion specification,
859      * allowing all modifiers and flags that printf(3) specifies (except
860      * for the * width and precision, which need an extra argument). */
861     base = p;
862 
863     /* Skip a prefix of any character except % */
864     while (*p)
865     {
866         /* Skip two adjacent percent marks, which are literal percent
867          * marks */
868         if (p[0] == '%' && p[1] == '%')
869         {
870             p += 2;
871             continue;
872         }
873         /* Break on a single percent mark, which is the start of the
874          * conversion specification */
875         if (*p == '%')
876             break;
877         /* Skip all other characters */
878         p++;
879     }
880 
881     if (!*p)
882     {
883         if (err_msg)
884             *err_msg = g_strdup("Format string ended without any conversion specification");
885         return NULL;
886     }
887 
888     /* Store the start of the conversion for error messages */
889     conv_start = p;
890 
891     /* Skip the % */
892     p++;
893 
894     /* See whether we have already reached the correct format
895      * specification (e.g. "li" on Unix, "I64i" on Windows). */
896     tmp = strstr(p, gint64_format);
897 
898     if (!tmp)
899     {
900         if (err_msg)
901             *err_msg = g_strdup_printf("Format string doesn't contain requested format specifier: %s", gint64_format);
902         return NULL;
903     }
904 
905     /* Skip any number of flag characters */
906     while (*p && (tmp != p) && strchr("#0- +'I", *p))
907     {
908         p++;
909         tmp = strstr(p, gint64_format);
910     }
911 
912     /* Skip any number of field width digits,
913      * and precision specifier digits (including the leading dot) */
914     while (*p && (tmp != p) && strchr("0123456789.", *p))
915     {
916         p++;
917         tmp = strstr(p, gint64_format);
918     }
919 
920     if (!*p)
921     {
922         if (err_msg)
923             *err_msg = g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
924         return NULL;
925     }
926 
927     /* See if the format string starts with the correct format
928      * specification. */
929     tmp = strstr(p, gint64_format);
930     if (tmp == NULL)
931     {
932         if (err_msg)
933             *err_msg = g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
934         return NULL;
935     }
936     else if (tmp != p)
937     {
938         if (err_msg)
939             *err_msg = g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
940         return NULL;
941     }
942 
943     /* Copy the string we have so far and add normalized format specifier for long int */
944     aux_str = g_strndup (base, p - base);
945     normalized_str = g_strconcat (aux_str, PRIi64, nullptr);
946     g_free (aux_str);
947 
948     /* Skip length modifier / conversion specifier */
949     p += strlen(gint64_format);
950     tmp = p;
951 
952     /* Skip a suffix of any character except % */
953     while (*p)
954     {
955         /* Skip two adjacent percent marks, which are literal percent
956          * marks */
957         if (p[0] == '%' && p[1] == '%')
958         {
959             p += 2;
960             continue;
961         }
962         /* Break on a single percent mark, which is the start of the
963          * conversion specification */
964         if (*p == '%')
965         {
966             if (err_msg)
967                 *err_msg = g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
968             g_free (normalized_str);
969             return NULL;
970         }
971         /* Skip all other characters */
972         p++;
973     }
974 
975     /* Add the suffix to our normalized string */
976     aux_str = normalized_str;
977     normalized_str = g_strconcat (aux_str, tmp, nullptr);
978     g_free (aux_str);
979 
980     /* If we end up here, the string was valid, so return no error
981      * message */
982     return normalized_str;
983 }
984 
985 /** Returns pointer to book-currency name for book, if one exists in the
986   * KVP, or NULL; does not validate contents nor determine if there is a valid
987   * default gain/loss policy, both of which are required, for the
988   * 'book-currency' currency accounting method to apply. Use instead
989   * 'gnc_book_get_book_currency_name' which does these validations. */
990 const gchar *
qof_book_get_book_currency_name(QofBook * book)991 qof_book_get_book_currency_name (QofBook *book)
992 {
993     const gchar *opt = NULL;
994     qof_instance_get (QOF_INSTANCE (book),
995               "book-currency", &opt,
996               NULL);
997     return opt;
998 }
999 
1000 /** Returns pointer to default gain/loss policy for book, if one exists in the
1001   * KVP, or NULL; does not validate contents nor determine if there is a valid
1002   * book-currency, both of which are required, for the 'book-currency'
1003   * currency accounting method to apply. Use instead
1004   * 'gnc_book_get_default_gains_policy' which does these validations. */
1005 const gchar *
qof_book_get_default_gains_policy(QofBook * book)1006 qof_book_get_default_gains_policy (QofBook *book)
1007 {
1008     const gchar *opt = NULL;
1009     qof_instance_get (QOF_INSTANCE (book),
1010               "default-gains-policy", &opt,
1011               NULL);
1012     return opt;
1013 }
1014 
1015 /** Returns pointer to default gain/loss account GUID for book, if one exists in
1016   * the KVP, or NULL; does not validate contents nor determine if there is a
1017   * valid book-currency, both of which are required, for the 'book-currency'
1018   * currency accounting method to apply. Use instead
1019   * 'gnc_book_get_default_gain_loss_acct' which does these validations. */
1020 GncGUID *
qof_book_get_default_gain_loss_acct_guid(QofBook * book)1021 qof_book_get_default_gain_loss_acct_guid (QofBook *book)
1022 {
1023     GncGUID *guid = NULL;
1024     qof_instance_get (QOF_INSTANCE (book),
1025               "default-gain-loss-account-guid", &guid,
1026               NULL);
1027     return guid;
1028 
1029 }
1030 
1031 /* Determine whether this book uses trading accounts */
1032 gboolean
qof_book_use_trading_accounts(const QofBook * book)1033 qof_book_use_trading_accounts (const QofBook *book)
1034 {
1035     char *opt = nullptr;
1036     qof_instance_get (QOF_INSTANCE (book), "trading-accts", &opt, nullptr);
1037     auto retval = (opt && opt[0] == 't' && opt[1] == 0);
1038     g_free (opt);
1039     return retval;
1040 }
1041 
1042 /* Returns TRUE if this book uses split action field as the 'Num' field, FALSE
1043  * if it uses transaction number field */
1044 gboolean
qof_book_use_split_action_for_num_field(const QofBook * book)1045 qof_book_use_split_action_for_num_field (const QofBook *book)
1046 {
1047     g_return_val_if_fail (book, FALSE);
1048     if (!book->cached_num_field_source_isvalid)
1049     {
1050         // No cached value? Then do the expensive KVP lookup
1051         gboolean result;
1052         char *opt = NULL;
1053         qof_instance_get (QOF_INSTANCE (book),
1054                           PARAM_NAME_NUM_FIELD_SOURCE, &opt,
1055                           NULL);
1056 
1057         if (opt && opt[0] == 't' && opt[1] == 0)
1058             result = TRUE;
1059         else
1060             result = FALSE;
1061         g_free (opt);
1062 
1063         // We need to const_cast the "book" argument into a non-const pointer,
1064         // but as we are dealing only with cache variables, I think this is
1065         // understandable enough.
1066         const_cast<QofBook*>(book)->cached_num_field_source = result;
1067         const_cast<QofBook*>(book)->cached_num_field_source_isvalid = TRUE;
1068     }
1069     // Value is cached now. Use the cheap variable returning.
1070     return book->cached_num_field_source;
1071 }
1072 
1073 // The callback that is called when the KVP option value of
1074 // "split-action-num-field" changes, so that we mark the cached value as
1075 // invalid.
1076 static void
qof_book_option_num_field_source_changed_cb(GObject * gobject,GParamSpec * pspec,gpointer user_data)1077 qof_book_option_num_field_source_changed_cb (GObject *gobject,
1078                                              GParamSpec *pspec,
1079                                              gpointer    user_data)
1080 {
1081     QofBook *book = reinterpret_cast<QofBook*>(user_data);
1082     g_return_if_fail(QOF_IS_BOOK(book));
1083     book->cached_num_field_source_isvalid = FALSE;
1084 }
1085 
qof_book_uses_autoreadonly(const QofBook * book)1086 gboolean qof_book_uses_autoreadonly (const QofBook *book)
1087 {
1088     g_assert(book);
1089     return (qof_book_get_num_days_autoreadonly(book) != 0);
1090 }
1091 
qof_book_get_num_days_autoreadonly(const QofBook * book)1092 gint qof_book_get_num_days_autoreadonly (const QofBook *book)
1093 {
1094     g_assert(book);
1095 
1096     if (!book->cached_num_days_autoreadonly_isvalid)
1097     {
1098         double tmp;
1099 
1100         // No cached value? Then do the expensive KVP lookup
1101         qof_instance_get (QOF_INSTANCE (book),
1102               PARAM_NAME_NUM_AUTOREAD_ONLY, &tmp,
1103               NULL);
1104 
1105         const_cast<QofBook*>(book)->cached_num_days_autoreadonly = tmp;
1106         const_cast<QofBook*>(book)->cached_num_days_autoreadonly_isvalid = TRUE;
1107     }
1108     // Value is cached now. Use the cheap variable returning.
1109     return (gint) book->cached_num_days_autoreadonly;
1110 }
1111 
qof_book_get_autoreadonly_gdate(const QofBook * book)1112 GDate* qof_book_get_autoreadonly_gdate (const QofBook *book)
1113 {
1114     gint num_days;
1115     GDate* result = NULL;
1116 
1117     g_assert(book);
1118     num_days = qof_book_get_num_days_autoreadonly(book);
1119     if (num_days > 0)
1120     {
1121         result = gnc_g_date_new_today();
1122         g_date_subtract_days(result, num_days);
1123     }
1124     return result;
1125 }
1126 
1127 // The callback that is called when the KVP option value of
1128 // "autoreadonly-days" changes, so that we mark the cached value as
1129 // invalid.
1130 static void
qof_book_option_num_autoreadonly_changed_cb(GObject * gobject,GParamSpec * pspec,gpointer user_data)1131 qof_book_option_num_autoreadonly_changed_cb (GObject *gobject,
1132                                              GParamSpec *pspec,
1133                                              gpointer    user_data)
1134 {
1135     QofBook *book = reinterpret_cast<QofBook*>(user_data);
1136     g_return_if_fail(QOF_IS_BOOK(book));
1137     book->cached_num_days_autoreadonly_isvalid = FALSE;
1138 }
1139 
1140 /* Note: this will fail if the book slots we're looking for here are flattened at some point !
1141  * When that happens, this function can be removed. */
opt_name_to_path(const char * opt_name)1142 static Path opt_name_to_path (const char* opt_name)
1143 {
1144     Path result;
1145     g_return_val_if_fail (opt_name, result);
1146     auto opt_name_list = g_strsplit(opt_name, "/", -1);
1147     for (int i=0; opt_name_list[i]; i++)
1148         result.push_back (opt_name_list[i]);
1149     g_strfreev (opt_name_list);
1150     return result;
1151 }
1152 
1153 const char*
qof_book_get_string_option(const QofBook * book,const char * opt_name)1154 qof_book_get_string_option(const QofBook* book, const char* opt_name)
1155 {
1156     auto slot = qof_instance_get_slots(QOF_INSTANCE (book))->get_slot(opt_name_to_path(opt_name));
1157     if (slot == nullptr)
1158         return nullptr;
1159     return slot->get<const char*>();
1160 }
1161 
1162 void
qof_book_set_string_option(QofBook * book,const char * opt_name,const char * opt_val)1163 qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val)
1164 {
1165     qof_book_begin_edit(book);
1166     auto frame = qof_instance_get_slots(QOF_INSTANCE(book));
1167     auto opt_path = opt_name_to_path(opt_name);
1168     if (opt_val && (*opt_val != '\0'))
1169         delete frame->set_path(opt_path, new KvpValue(g_strdup(opt_val)));
1170     else
1171         delete frame->set_path(opt_path, nullptr);
1172     qof_instance_set_dirty (QOF_INSTANCE (book));
1173     qof_book_commit_edit(book);
1174 }
1175 
1176 const GncGUID*
qof_book_get_guid_option(QofBook * book,GSList * path)1177 qof_book_get_guid_option(QofBook* book, GSList* path)
1178 {
1179     g_return_val_if_fail(book != nullptr, nullptr);
1180     g_return_val_if_fail(path != nullptr, nullptr);
1181 
1182     auto table_value = qof_book_get_option(book, path);
1183     if (!table_value)
1184         return nullptr;
1185     return table_value->get<GncGUID*>();
1186 }
1187 
1188 void
qof_book_option_frame_delete(QofBook * book,const char * opt_name)1189 qof_book_option_frame_delete (QofBook *book, const char* opt_name)
1190 {
1191     if (opt_name && (*opt_name != '\0'))
1192     {
1193         qof_book_begin_edit(book);
1194         auto frame = qof_instance_get_slots(QOF_INSTANCE(book));
1195         auto opt_path = opt_name_to_path(opt_name);
1196         delete frame->set_path(opt_path, nullptr);
1197         qof_instance_set_dirty (QOF_INSTANCE (book));
1198         qof_book_commit_edit(book);
1199     }
1200 }
1201 
1202 void
qof_book_begin_edit(QofBook * book)1203 qof_book_begin_edit (QofBook *book)
1204 {
1205     qof_begin_edit(&book->inst);
1206 }
1207 
commit_err(G_GNUC_UNUSED QofInstance * inst,QofBackendError errcode)1208 static void commit_err (G_GNUC_UNUSED QofInstance *inst, QofBackendError errcode)
1209 {
1210     PERR ("Failed to commit: %d", errcode);
1211 //  gnc_engine_signal_commit_error( errcode );
1212 }
1213 
1214 #define GNC_FEATURES "features"
1215 static void
add_feature_to_hash(const gchar * key,KvpValue * value,GHashTable * user_data)1216 add_feature_to_hash (const gchar *key, KvpValue *value, GHashTable * user_data)
1217 {
1218     gchar *descr = g_strdup(value->get<const char*>());
1219     g_hash_table_insert (user_data, (gchar*)key, descr);
1220 }
1221 
1222 GHashTable *
qof_book_get_features(QofBook * book)1223 qof_book_get_features (QofBook *book)
1224 {
1225     KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1226     GHashTable *features = g_hash_table_new_full (g_str_hash, g_str_equal,
1227                                                   NULL, g_free);
1228 
1229     auto slot = frame->get_slot({GNC_FEATURES});
1230     if (slot != nullptr)
1231     {
1232         frame = slot->get<KvpFrame*>();
1233         frame->for_each_slot_temp(&add_feature_to_hash, features);
1234     }
1235     return features;
1236 }
1237 
1238 void
qof_book_set_feature(QofBook * book,const gchar * key,const gchar * descr)1239 qof_book_set_feature (QofBook *book, const gchar *key, const gchar *descr)
1240 {
1241     KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1242     KvpValue* feature = nullptr;
1243     auto feature_slot = frame->get_slot({GNC_FEATURES});
1244     if (feature_slot)
1245     {
1246         auto feature_frame = feature_slot->get<KvpFrame*>();
1247         feature = feature_frame->get_slot({key});
1248     }
1249     if (feature == nullptr || g_strcmp0 (feature->get<const char*>(), descr))
1250     {
1251         qof_book_begin_edit (book);
1252         delete frame->set_path({GNC_FEATURES, key}, new KvpValue(g_strdup (descr)));
1253         qof_instance_set_dirty (QOF_INSTANCE (book));
1254         qof_book_commit_edit (book);
1255     }
1256 }
1257 
1258 void
qof_book_load_options(QofBook * book,GNCOptionLoad load_cb,GNCOptionDB * odb)1259 qof_book_load_options (QofBook *book, GNCOptionLoad load_cb, GNCOptionDB *odb)
1260 {
1261     load_cb (odb, book);
1262 }
1263 
1264 void
qof_book_save_options(QofBook * book,GNCOptionSave save_cb,GNCOptionDB * odb,gboolean clear)1265 qof_book_save_options (QofBook *book, GNCOptionSave save_cb,
1266                GNCOptionDB* odb, gboolean clear)
1267 {
1268     /* Wrap this in begin/commit so that it commits only once instead of doing
1269      * so for every option. Qof_book_set_option will take care of dirtying the
1270      * book.
1271      */
1272     qof_book_begin_edit (book);
1273     save_cb (odb, book, clear);
1274     qof_book_commit_edit (book);
1275 }
1276 
noop(QofInstance * inst)1277 static void noop (QofInstance *inst) {}
1278 
1279 void
qof_book_commit_edit(QofBook * book)1280 qof_book_commit_edit(QofBook *book)
1281 {
1282     if (!qof_commit_edit (QOF_INSTANCE(book))) return;
1283     qof_commit_edit_part2 (&book->inst, commit_err, noop, noop/*lot_free*/);
1284 }
1285 
1286 /* Deal with the fact that some options are not in the "options" tree but rather
1287  * in the "counters" tree */
gslist_to_option_path(GSList * gspath)1288 static Path gslist_to_option_path (GSList *gspath)
1289 {
1290     Path tmp_path;
1291     if (!gspath) return tmp_path;
1292 
1293     Path path_v {str_KVP_OPTION_PATH};
1294     for (auto item = gspath; item != nullptr; item = g_slist_next(item))
1295         tmp_path.push_back(static_cast<const char*>(item->data));
1296     if (tmp_path.front() == "counters")
1297         return tmp_path;
1298 
1299     path_v.insert(path_v.end(), tmp_path.begin(), tmp_path.end());
1300     return path_v;
1301 }
1302 
1303 void
qof_book_set_option(QofBook * book,KvpValue * value,GSList * path)1304 qof_book_set_option (QofBook *book, KvpValue *value, GSList *path)
1305 {
1306     KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE (book));
1307     qof_book_begin_edit (book);
1308     delete root->set_path(gslist_to_option_path(path), value);
1309     qof_instance_set_dirty (QOF_INSTANCE (book));
1310     qof_book_commit_edit (book);
1311 
1312     // Also, mark any cached value as invalid
1313     book->cached_num_field_source_isvalid = FALSE;
1314 }
1315 
1316 KvpValue*
qof_book_get_option(QofBook * book,GSList * path)1317 qof_book_get_option (QofBook *book, GSList *path)
1318 {
1319     KvpFrame *root = qof_instance_get_slots(QOF_INSTANCE (book));
1320     return root->get_slot(gslist_to_option_path(path));
1321 }
1322 
1323 void
qof_book_options_delete(QofBook * book,GSList * path)1324 qof_book_options_delete (QofBook *book, GSList *path)
1325 {
1326     KvpFrame *root = qof_instance_get_slots(QOF_INSTANCE (book));
1327     if (path != nullptr)
1328     {
1329         Path path_v {str_KVP_OPTION_PATH};
1330         Path tmp_path;
1331         for (auto item = path; item != nullptr; item = g_slist_next(item))
1332             tmp_path.push_back(static_cast<const char*>(item->data));
1333         delete root->set_path(gslist_to_option_path(path), nullptr);
1334     }
1335     else
1336         delete root->set_path({str_KVP_OPTION_PATH}, nullptr);
1337 }
1338 
1339 /* QofObject function implementation and registration */
qof_book_register(void)1340 gboolean qof_book_register (void)
1341 {
1342     static QofParam params[] =
1343     {
1344         { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_entity_get_guid, NULL },
1345         { QOF_PARAM_KVP,  QOF_TYPE_KVP,  (QofAccessFunc)qof_instance_get_slots, NULL },
1346         { NULL },
1347     };
1348 
1349     qof_class_register (QOF_ID_BOOK, NULL, params);
1350 
1351     return TRUE;
1352 }
1353 
1354 /* ========================== END OF FILE =============================== */
1355