LCOV - code coverage report
Current view: top level - libgnucash/engine - qofbook.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 81.2 % 645 524
Test Date: 2026-11-02 18:19:43 Functions: 86.5 % 89 77
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1