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 =============================== */
|