Branch data 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.h"
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 : 14756 : G_DEFINE_TYPE(QofBook, qof_book, QOF_TYPE_INSTANCE)
91 : 440 : QOF_GOBJECT_DISPOSE(qof_book);
92 : 440 : 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 : 4745 : static void coll_destroy(gpointer col)
100 : : {
101 : 4745 : qof_collection_destroy((QofCollection *) col);
102 : 4745 : }
103 : :
104 : : static void
105 : 570 : qof_book_init (QofBook *book)
106 : : {
107 : 570 : if (!book) return;
108 : :
109 : 570 : 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 : 570 : qof_instance_init_data (&book->inst, QOF_ID_BOOK, book);
115 : :
116 : 570 : book->data_tables = g_hash_table_new_full (g_str_hash, g_str_equal,
117 : : (GDestroyNotify)qof_string_cache_remove, nullptr);
118 : 570 : book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
119 : :
120 : 570 : book->book_open = 'y';
121 : 570 : book->read_only = FALSE;
122 : 570 : book->session_dirty = FALSE;
123 : 570 : book->version = 0;
124 : 570 : book->cached_num_field_source_isvalid = FALSE;
125 : 570 : 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 : 570 : 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 : 570 : 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 : 6819 : qof_book_get_property (GObject* object,
154 : : guint prop_id,
155 : : GValue* value,
156 : : GParamSpec* pspec)
157 : : {
158 : : QofBook *book;
159 : :
160 : 6819 : g_return_if_fail (QOF_IS_BOOK (object));
161 : 6819 : book = QOF_BOOK (object);
162 : 6819 : switch (prop_id)
163 : : {
164 : 6071 : case PROP_OPT_TRADING_ACCOUNTS:
165 : 30355 : qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
166 : : str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_TRADING_ACCOUNTS});
167 : 6071 : 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 : 154 : case PROP_OPT_NUM_FIELD_SOURCE:
173 : 770 : 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 : 154 : break;
176 : 23 : case PROP_OPT_DEFAULT_BUDGET:
177 : 115 : qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
178 : : str_OPTION_SECTION_BUDGETING, str_OPTION_NAME_DEFAULT_BUDGET});
179 : 23 : break;
180 : 567 : case PROP_OPT_FY_END:
181 : 1134 : qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {"fy_end"});
182 : 567 : break;
183 : 0 : default:
184 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 : 0 : break;
186 : : }
187 : 6252 : }
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 : : 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 : 15 : }
227 : :
228 : : static void
229 : 72 : qof_book_class_init (QofBookClass *klass)
230 : : {
231 : 72 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
232 : 72 : gobject_class->dispose = qof_book_dispose;
233 : 72 : gobject_class->finalize = qof_book_finalize;
234 : 72 : gobject_class->get_property = qof_book_get_property;
235 : 72 : gobject_class->set_property = qof_book_set_property;
236 : :
237 : : g_object_class_install_property
238 : 72 : (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 : 72 : (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 : 72 : (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 : 72 : (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 : 72 : (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 : 72 : }
288 : :
289 : : QofBook *
290 : 568 : qof_book_new (void)
291 : : {
292 : : QofBook *book;
293 : :
294 : 568 : ENTER (" ");
295 : 568 : book = static_cast<QofBook*>(g_object_new(QOF_TYPE_BOOK, nullptr));
296 : 568 : qof_object_book_begin (book);
297 : :
298 : 568 : qof_event_gen (&book->inst, QOF_EVENT_CREATE, nullptr);
299 : 568 : LEAVE ("book=%p", book);
300 : 568 : 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 : 440 : qof_book_dispose_real (G_GNUC_UNUSED GObject *bookp)
315 : : {
316 : 440 : }
317 : :
318 : : static void
319 : 440 : qof_book_finalize_real (G_GNUC_UNUSED GObject *bookp)
320 : : {
321 : 440 : }
322 : :
323 : : static void
324 : 101 : destroy_lot(QofInstance *inst, [[maybe_unused]]void* data)
325 : : {
326 : 101 : auto lot{GNC_LOT(inst)};
327 : 101 : gnc_lot_destroy(lot);
328 : 101 : }
329 : :
330 : : void
331 : 465 : qof_book_destroy (QofBook *book)
332 : : {
333 : : GHashTable* cols;
334 : :
335 : 465 : if (!book) return;
336 : 459 : ENTER ("book=%p", book);
337 : :
338 : 459 : book->shutting_down = TRUE;
339 : 459 : 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 : 459 : 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 : 459 : auto lots{qof_book_get_collection(book, GNC_ID_LOT)};
350 : 459 : qof_collection_foreach(lots, destroy_lot, nullptr);
351 : 459 : qof_object_book_end (book);
352 : :
353 : 459 : g_hash_table_destroy (book->data_table_finalizers);
354 : 459 : book->data_table_finalizers = nullptr;
355 : 459 : g_hash_table_destroy (book->data_tables);
356 : 459 : 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 : 459 : cols = book->hash_of_collections;
366 : 459 : g_object_unref (book);
367 : 459 : g_hash_table_destroy (cols);
368 : :
369 : 459 : LEAVE ("book=%p", book);
370 : : }
371 : :
372 : : /* ====================================================================== */
373 : :
374 : : gboolean
375 : 34 : qof_book_session_not_saved (const QofBook *book)
376 : : {
377 : 34 : if (!book) return FALSE;
378 : 33 : return !qof_book_empty(book) && book->session_dirty;
379 : :
380 : : }
381 : :
382 : : void
383 : 68 : qof_book_mark_session_saved (QofBook *book)
384 : : {
385 : 68 : if (!book) return;
386 : :
387 : 68 : book->dirty_time = 0;
388 : 68 : if (book->session_dirty)
389 : : {
390 : : /* Set the session clean upfront, because the callback will check. */
391 : 65 : book->session_dirty = FALSE;
392 : 65 : if (book->dirty_cb)
393 : 2 : book->dirty_cb(book, FALSE, book->dirty_data);
394 : : }
395 : : }
396 : :
397 : 108955 : void qof_book_mark_session_dirty (QofBook *book)
398 : : {
399 : 108955 : if (!book) return;
400 : 108952 : if (!book->session_dirty)
401 : : {
402 : : /* Set the session dirty upfront, because the callback will check. */
403 : 582 : book->session_dirty = TRUE;
404 : 582 : book->dirty_time = gnc_time (nullptr);
405 : 582 : 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 : 306309 : qof_book_get_backend (const QofBook *book)
441 : : {
442 : 306309 : if (!book) return nullptr;
443 : 306261 : return book->backend;
444 : : }
445 : :
446 : : gboolean
447 : 37869 : qof_book_shutting_down (const QofBook *book)
448 : : {
449 : 37869 : if (!book) return FALSE;
450 : 37868 : return book->shutting_down;
451 : : }
452 : :
453 : : /* ====================================================================== */
454 : : /* setters */
455 : :
456 : : void
457 : 286 : qof_book_set_backend (QofBook *book, QofBackend *be)
458 : : {
459 : 286 : if (!book) return;
460 : 271 : ENTER ("book=%p be=%p", book, be);
461 : 271 : book->backend = be;
462 : 271 : LEAVE (" ");
463 : : }
464 : :
465 : : /* ====================================================================== */
466 : : /* Store arbitrary pointers in the QofBook for data storage extensibility */
467 : : void
468 : 1105 : qof_book_set_data (QofBook *book, const char *key, gpointer data)
469 : : {
470 : 1105 : if (!book || !key) return;
471 : 1103 : if (data)
472 : 921 : g_hash_table_insert (book->data_tables, (gpointer)CACHE_INSERT(key), data);
473 : : else
474 : 182 : 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 : 122851 : qof_book_get_data (const QofBook *book, const char *key)
490 : : {
491 : 122851 : if (!book || !key) return nullptr;
492 : 122849 : return g_hash_table_lookup (book->data_tables, (gpointer)key);
493 : : }
494 : :
495 : : /* ====================================================================== */
496 : : gboolean
497 : 9880 : qof_book_is_readonly(const QofBook *book)
498 : : {
499 : 9880 : g_return_val_if_fail( book != nullptr, TRUE );
500 : 9880 : 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 : 62 : qof_book_empty(const QofBook *book)
512 : : {
513 : 62 : if (!book) return TRUE;
514 : 62 : auto root_acct_col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
515 : 62 : return qof_collection_get_data(root_acct_col) == nullptr;
516 : : }
517 : :
518 : : /* ====================================================================== */
519 : :
520 : : QofCollection *
521 : 141295 : qof_book_get_collection (const QofBook *book, QofIdType entity_type)
522 : : {
523 : : QofCollection *col;
524 : :
525 : 141295 : if (!book || !entity_type) return nullptr;
526 : :
527 : 141293 : col = static_cast<QofCollection*>(g_hash_table_lookup (book->hash_of_collections, entity_type));
528 : 141293 : if (!col)
529 : : {
530 : 5871 : col = qof_collection_new (entity_type);
531 : 5871 : g_hash_table_insert(
532 : 5871 : book->hash_of_collections,
533 : 5871 : (gpointer)qof_string_cache_insert(entity_type), col);
534 : : }
535 : 141293 : 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 : : gint64
580 : 10 : qof_book_get_counter (QofBook *book, const char *counter_name)
581 : : {
582 : : KvpFrame *kvp;
583 : : KvpValue *value;
584 : :
585 : 10 : if (!book)
586 : : {
587 : 1 : PWARN ("No book!!!");
588 : 1 : return -1;
589 : : }
590 : :
591 : 9 : if (!counter_name || *counter_name == '\0')
592 : : {
593 : 2 : PWARN ("Invalid counter name.");
594 : 2 : return -1;
595 : : }
596 : :
597 : : /* Use the KVP in the book */
598 : 7 : kvp = qof_instance_get_slots (QOF_INSTANCE (book));
599 : :
600 : 7 : if (!kvp)
601 : : {
602 : 0 : PWARN ("Book has no KVP_Frame");
603 : 0 : return -1;
604 : : }
605 : :
606 : 28 : value = kvp->get_slot({"counters", counter_name});
607 : 7 : if (value)
608 : : {
609 : 4 : auto int_value{value->get<int64_t>()};
610 : : /* Might be a double because of
611 : : * https://bugs.gnucash.org/show_bug.cgi?id=798930
612 : : */
613 : 4 : if (!int_value)
614 : 0 : int_value = static_cast<int64_t>(value->get<double>());
615 : 4 : return int_value;
616 : : }
617 : : else
618 : : {
619 : : /* New counter */
620 : 3 : return 0;
621 : : }
622 : 21 : }
623 : :
624 : : gchar *
625 : 6 : qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
626 : : {
627 : : KvpFrame *kvp;
628 : : KvpValue *value;
629 : : gint64 counter;
630 : : gchar* format;
631 : : gchar* result;
632 : :
633 : 6 : if (!book)
634 : : {
635 : 1 : PWARN ("No book!!!");
636 : 1 : return nullptr;
637 : : }
638 : :
639 : 5 : if (!counter_name || *counter_name == '\0')
640 : : {
641 : 2 : PWARN ("Invalid counter name.");
642 : 2 : return nullptr;
643 : : }
644 : :
645 : : /* Get the current counter value from the KVP in the book. */
646 : 3 : counter = qof_book_get_counter(book, counter_name);
647 : :
648 : : /* Check if an error occurred */
649 : 3 : if (counter < 0)
650 : 0 : return nullptr;
651 : :
652 : : /* Increment the counter */
653 : 3 : counter++;
654 : :
655 : : /* Get the KVP from the current book */
656 : 3 : kvp = qof_instance_get_slots (QOF_INSTANCE (book));
657 : :
658 : 3 : if (!kvp)
659 : : {
660 : 0 : PWARN ("Book has no KVP_Frame");
661 : 0 : return nullptr;
662 : : }
663 : :
664 : : /* Save off the new counter */
665 : 3 : qof_book_begin_edit(book);
666 : 3 : value = new KvpValue(counter);
667 : 12 : delete kvp->set_path({"counters", counter_name}, value);
668 : 3 : qof_instance_set_dirty (QOF_INSTANCE (book));
669 : 3 : qof_book_commit_edit(book);
670 : :
671 : 3 : format = qof_book_get_counter_format(book, counter_name);
672 : :
673 : 3 : if (!format)
674 : : {
675 : 0 : PWARN("Cannot get format for counter");
676 : 0 : return nullptr;
677 : : }
678 : :
679 : : /* Generate a string version of the counter */
680 : 3 : result = g_strdup_printf(format, counter);
681 : 3 : g_free (format);
682 : 3 : return result;
683 : 9 : }
684 : :
685 : : char *
686 : 10 : qof_book_get_counter_format(const QofBook *book, const char *counter_name)
687 : : {
688 : : KvpFrame *kvp;
689 : 10 : const char *user_format = nullptr;
690 : 10 : gchar *norm_format = nullptr;
691 : : KvpValue *value;
692 : 10 : gchar *error = nullptr;
693 : :
694 : 10 : if (!book)
695 : : {
696 : 1 : PWARN ("No book!!!");
697 : 1 : return nullptr;
698 : : }
699 : :
700 : 9 : if (!counter_name || *counter_name == '\0')
701 : : {
702 : 2 : PWARN ("Invalid counter name.");
703 : 2 : return nullptr;
704 : : }
705 : :
706 : : /* Get the KVP from the current book */
707 : 7 : kvp = qof_instance_get_slots (QOF_INSTANCE (book));
708 : :
709 : 7 : if (!kvp)
710 : : {
711 : 0 : PWARN ("Book has no KVP_Frame");
712 : 0 : return nullptr;
713 : : }
714 : :
715 : : /* Get the format string */
716 : 28 : value = kvp->get_slot({"counter_formats", counter_name});
717 : 7 : if (value)
718 : : {
719 : 0 : user_format = value->get<const char*>();
720 : 0 : norm_format = qof_book_normalize_counter_format(user_format, &error);
721 : 0 : if (!norm_format)
722 : : {
723 : 0 : PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", user_format, counter_name, error);
724 : : /* Invalid format string */
725 : 0 : user_format = nullptr;
726 : 0 : g_free(error);
727 : : }
728 : : }
729 : :
730 : : /* If no (valid) format string was found, use the default format
731 : : * string */
732 : 7 : if (!norm_format)
733 : : {
734 : : /* Use the default format */
735 : 7 : norm_format = g_strdup ("%.6" PRIi64);
736 : : }
737 : 7 : return norm_format;
738 : 21 : }
739 : :
740 : : gchar *
741 : 8 : qof_book_normalize_counter_format(const gchar *p, gchar **err_msg)
742 : : {
743 : 8 : const gchar *valid_formats [] = {
744 : : G_GINT64_FORMAT,
745 : : "lli",
746 : : "I64i",
747 : : PRIi64,
748 : : "li",
749 : : nullptr,
750 : : };
751 : 8 : int i = 0;
752 : 8 : gchar *normalized_spec = nullptr;
753 : :
754 : 15 : while (valid_formats[i])
755 : : {
756 : :
757 : 14 : if (err_msg && *err_msg)
758 : : {
759 : 6 : g_free (*err_msg);
760 : 6 : *err_msg = nullptr;
761 : : }
762 : :
763 : 14 : normalized_spec = qof_book_normalize_counter_format_internal(p, valid_formats[i], err_msg);
764 : 14 : if (normalized_spec)
765 : 7 : return normalized_spec; /* Found a valid format specifier, return */
766 : 7 : i++;
767 : : }
768 : :
769 : 1 : return nullptr;
770 : : }
771 : :
772 : : gchar *
773 : 18 : qof_book_normalize_counter_format_internal(const gchar *p,
774 : : const gchar *gint64_format, gchar **err_msg)
775 : : {
776 : 18 : const gchar *conv_start, *base, *tmp = nullptr;
777 : 18 : gchar *normalized_str = nullptr, *aux_str = nullptr;
778 : :
779 : : /* Validate a counter format. This is a very simple "parser" that
780 : : * simply checks for a single gint64 conversion specification,
781 : : * allowing all modifiers and flags that printf(3) specifies (except
782 : : * for the * width and precision, which need an extra argument). */
783 : 18 : base = p;
784 : :
785 : : /* Skip a prefix of any character except % */
786 : 344 : while (*p)
787 : : {
788 : : /* Skip two adjacent percent marks, which are literal percent
789 : : * marks */
790 : 339 : if (p[0] == '%' && p[1] == '%')
791 : : {
792 : 0 : p += 2;
793 : 0 : continue;
794 : : }
795 : : /* Break on a single percent mark, which is the start of the
796 : : * conversion specification */
797 : 339 : if (*p == '%')
798 : 13 : break;
799 : : /* Skip all other characters */
800 : 326 : p++;
801 : : }
802 : :
803 : 18 : if (!*p)
804 : : {
805 : 5 : if (err_msg)
806 : 5 : *err_msg = g_strdup("Format string ended without any conversion specification");
807 : 5 : return nullptr;
808 : : }
809 : :
810 : : /* Store the start of the conversion for error messages */
811 : 13 : conv_start = p;
812 : :
813 : : /* Skip the % */
814 : 13 : p++;
815 : :
816 : : /* See whether we have already reached the correct format
817 : : * specification (e.g. "li" on Unix, "I64i" on Windows). */
818 : 13 : tmp = strstr(p, gint64_format);
819 : :
820 : 13 : if (!tmp)
821 : : {
822 : 4 : if (err_msg)
823 : 4 : *err_msg = g_strdup_printf("Format string doesn't contain requested format specifier: %s", gint64_format);
824 : 4 : return nullptr;
825 : : }
826 : :
827 : : /* Skip any number of flag characters */
828 : 9 : while (*p && (tmp != p) && strchr("#0- +'I", *p))
829 : : {
830 : 0 : p++;
831 : 0 : tmp = strstr(p, gint64_format);
832 : : }
833 : :
834 : : /* Skip any number of field width digits,
835 : : * and precision specifier digits (including the leading dot) */
836 : 15 : while (*p && (tmp != p) && strchr("0123456789.", *p))
837 : : {
838 : 6 : p++;
839 : 6 : tmp = strstr(p, gint64_format);
840 : : }
841 : :
842 : 9 : if (!*p)
843 : : {
844 : 0 : if (err_msg)
845 : 0 : *err_msg = g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
846 : 0 : return nullptr;
847 : : }
848 : :
849 : : /* See if the format string starts with the correct format
850 : : * specification. */
851 : 9 : tmp = strstr(p, gint64_format);
852 : 9 : if (tmp == nullptr)
853 : : {
854 : 0 : if (err_msg)
855 : 0 : *err_msg = g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
856 : 0 : return nullptr;
857 : : }
858 : 9 : else if (tmp != p)
859 : : {
860 : 0 : if (err_msg)
861 : 0 : *err_msg = g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
862 : 0 : return nullptr;
863 : : }
864 : :
865 : : /* Copy the string we have so far and add normalized format specifier for long int */
866 : 9 : aux_str = g_strndup (base, p - base);
867 : 9 : normalized_str = g_strconcat (aux_str, PRIi64, nullptr);
868 : 9 : g_free (aux_str);
869 : :
870 : : /* Skip length modifier / conversion specifier */
871 : 9 : p += strlen(gint64_format);
872 : 9 : tmp = p;
873 : :
874 : : /* Skip a suffix of any character except % */
875 : 9 : while (*p)
876 : : {
877 : : /* Skip two adjacent percent marks, which are literal percent
878 : : * marks */
879 : 0 : if (p[0] == '%' && p[1] == '%')
880 : : {
881 : 0 : p += 2;
882 : 0 : continue;
883 : : }
884 : : /* Break on a single percent mark, which is the start of the
885 : : * conversion specification */
886 : 0 : if (*p == '%')
887 : : {
888 : 0 : if (err_msg)
889 : 0 : *err_msg = g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
890 : 0 : g_free (normalized_str);
891 : 0 : return nullptr;
892 : : }
893 : : /* Skip all other characters */
894 : 0 : p++;
895 : : }
896 : :
897 : : /* Add the suffix to our normalized string */
898 : 9 : aux_str = normalized_str;
899 : 9 : normalized_str = g_strconcat (aux_str, tmp, nullptr);
900 : 9 : g_free (aux_str);
901 : :
902 : : /* If we end up here, the string was valid, so return no error
903 : : * message */
904 : 9 : return normalized_str;
905 : : }
906 : :
907 : : /* Determine whether this book uses trading accounts */
908 : : gboolean
909 : 6071 : qof_book_use_trading_accounts (const QofBook *book)
910 : : {
911 : 6071 : char *opt = nullptr;
912 : 6071 : qof_instance_get (QOF_INSTANCE (book), "trading-accts", &opt, nullptr);
913 : 6071 : auto retval = (opt && opt[0] == 't' && opt[1] == 0);
914 : 6071 : g_free (opt);
915 : 6071 : return retval;
916 : : }
917 : :
918 : : /* Returns TRUE if this book uses split action field as the 'Num' field, FALSE
919 : : * if it uses transaction number field */
920 : : gboolean
921 : 722913 : qof_book_use_split_action_for_num_field (const QofBook *book)
922 : : {
923 : 722913 : g_return_val_if_fail (book, FALSE);
924 : 722913 : if (!book->cached_num_field_source_isvalid)
925 : : {
926 : : // No cached value? Then do the expensive KVP lookup
927 : : gboolean result;
928 : 154 : char *opt = nullptr;
929 : 154 : qof_instance_get (QOF_INSTANCE (book),
930 : : PARAM_NAME_NUM_FIELD_SOURCE, &opt,
931 : : nullptr);
932 : :
933 : 154 : if (opt && opt[0] == 't' && opt[1] == 0)
934 : 2 : result = TRUE;
935 : : else
936 : 152 : result = FALSE;
937 : 154 : g_free (opt);
938 : :
939 : : // We need to const_cast the "book" argument into a non-const pointer,
940 : : // but as we are dealing only with cache variables, I think this is
941 : : // understandable enough.
942 : 154 : const_cast<QofBook*>(book)->cached_num_field_source = result;
943 : 154 : const_cast<QofBook*>(book)->cached_num_field_source_isvalid = TRUE;
944 : : }
945 : : // Value is cached now. Use the cheap variable returning.
946 : 722913 : return book->cached_num_field_source;
947 : : }
948 : :
949 : : // The callback that is called when the KVP option value of
950 : : // "split-action-num-field" changes, so that we mark the cached value as
951 : : // invalid.
952 : : static void
953 : 4 : qof_book_option_num_field_source_changed_cb (GObject *gobject,
954 : : GParamSpec *pspec,
955 : : gpointer user_data)
956 : : {
957 : 4 : QofBook *book = reinterpret_cast<QofBook*>(user_data);
958 : 4 : g_return_if_fail(QOF_IS_BOOK(book));
959 : 4 : book->cached_num_field_source_isvalid = FALSE;
960 : : }
961 : :
962 : 5 : gboolean qof_book_uses_autoreadonly (const QofBook *book)
963 : : {
964 : 5 : g_assert(book);
965 : 5 : return (qof_book_get_num_days_autoreadonly(book) != 0);
966 : : }
967 : :
968 : 10 : gint qof_book_get_num_days_autoreadonly (const QofBook *book)
969 : : {
970 : 10 : g_assert(book);
971 : :
972 : 10 : if (!book->cached_num_days_autoreadonly_isvalid)
973 : : {
974 : : double tmp;
975 : :
976 : : // No cached value? Then do the expensive KVP lookup
977 : 4 : qof_instance_get (QOF_INSTANCE (book),
978 : : PARAM_NAME_NUM_AUTOREAD_ONLY, &tmp,
979 : : nullptr);
980 : :
981 : 4 : const_cast<QofBook*>(book)->cached_num_days_autoreadonly = tmp;
982 : 4 : const_cast<QofBook*>(book)->cached_num_days_autoreadonly_isvalid = TRUE;
983 : : }
984 : : // Value is cached now. Use the cheap variable returning.
985 : 10 : return (gint) book->cached_num_days_autoreadonly;
986 : : }
987 : :
988 : 0 : GDate* qof_book_get_autoreadonly_gdate (const QofBook *book)
989 : : {
990 : : gint num_days;
991 : 0 : GDate* result = nullptr;
992 : :
993 : 0 : g_assert(book);
994 : 0 : num_days = qof_book_get_num_days_autoreadonly(book);
995 : 0 : if (num_days > 0)
996 : : {
997 : 0 : result = gnc_g_date_new_today();
998 : 0 : g_date_subtract_days(result, num_days);
999 : : }
1000 : 0 : return result;
1001 : : }
1002 : :
1003 : : // The callback that is called when the KVP option value of
1004 : : // "autoreadonly-days" changes, so that we mark the cached value as
1005 : : // invalid.
1006 : : static void
1007 : 3 : qof_book_option_num_autoreadonly_changed_cb (GObject *gobject,
1008 : : GParamSpec *pspec,
1009 : : gpointer user_data)
1010 : : {
1011 : 3 : QofBook *book = reinterpret_cast<QofBook*>(user_data);
1012 : 3 : g_return_if_fail(QOF_IS_BOOK(book));
1013 : 3 : book->cached_num_days_autoreadonly_isvalid = FALSE;
1014 : : }
1015 : :
1016 : : static KvpValue*
1017 : 11 : get_option_default_invoice_report_value (QofBook *book)
1018 : : {
1019 : 11 : KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
1020 : 44 : return root->get_slot ({KVP_OPTION_PATH,
1021 : : OPTION_SECTION_BUSINESS,
1022 : 22 : OPTION_NAME_DEFAULT_INVOICE_REPORT});
1023 : : }
1024 : :
1025 : : void
1026 : 5 : qof_book_set_default_invoice_report (QofBook *book, const gchar *guid,
1027 : : const gchar *name)
1028 : : {
1029 : 5 : const gchar *existing_guid_name = nullptr;
1030 : : gchar *new_guid_name;
1031 : :
1032 : 5 : if (!book)
1033 : : {
1034 : 1 : PWARN ("No book!!!");
1035 : 1 : return;
1036 : : }
1037 : :
1038 : 4 : if (!guid)
1039 : : {
1040 : 1 : PWARN ("No guid!!!");
1041 : 1 : return;
1042 : : }
1043 : :
1044 : 3 : if (!name)
1045 : : {
1046 : 1 : PWARN ("No name!!!");
1047 : 1 : return;
1048 : : }
1049 : :
1050 : 2 : KvpValue *value = get_option_default_invoice_report_value (book);
1051 : :
1052 : 2 : if (value)
1053 : 1 : existing_guid_name = {value->get<const char*>()};
1054 : :
1055 : 2 : new_guid_name = g_strconcat (guid, "/", name, nullptr);
1056 : :
1057 : 2 : if (g_strcmp0 (existing_guid_name, new_guid_name) != 0)
1058 : : {
1059 : 4 : auto value = new KvpValue {g_strdup(new_guid_name)};
1060 : 2 : KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
1061 : 2 : qof_book_begin_edit (book);
1062 : 5 : delete root->set_path ({KVP_OPTION_PATH,
1063 : : OPTION_SECTION_BUSINESS,
1064 : 1 : OPTION_NAME_DEFAULT_INVOICE_REPORT}, value);
1065 : 2 : qof_instance_set_dirty (QOF_INSTANCE(book));
1066 : 2 : qof_book_commit_edit (book);
1067 : : }
1068 : 2 : g_free (new_guid_name);
1069 : : }
1070 : :
1071 : : gchar *
1072 : 7 : qof_book_get_default_invoice_report_guid (const QofBook *book)
1073 : : {
1074 : 7 : gchar *report_guid = nullptr;
1075 : :
1076 : 7 : if (!book)
1077 : : {
1078 : 1 : PWARN ("No book!!!");
1079 : 1 : return report_guid;
1080 : : }
1081 : :
1082 : 6 : KvpValue *value = get_option_default_invoice_report_value (const_cast<QofBook*>(book));
1083 : :
1084 : 6 : if (value)
1085 : : {
1086 : 2 : auto str {value->get<const char*>()};
1087 : 2 : auto ptr = strchr (str, '/');
1088 : 2 : if (ptr)
1089 : : {
1090 : 2 : if (ptr - str == GUID_ENCODING_LENGTH)
1091 : : {
1092 : 2 : if (strlen (str) > GUID_ENCODING_LENGTH)
1093 : 2 : report_guid = g_strndup (&str[0], GUID_ENCODING_LENGTH);
1094 : : }
1095 : : }
1096 : : }
1097 : 6 : return report_guid;
1098 : : }
1099 : :
1100 : : gchar *
1101 : 4 : qof_book_get_default_invoice_report_name (const QofBook *book)
1102 : : {
1103 : 4 : gchar *report_name = nullptr;
1104 : :
1105 : 4 : if (!book)
1106 : : {
1107 : 1 : PWARN ("No book!!!");
1108 : 1 : return report_name;
1109 : : }
1110 : :
1111 : 3 : KvpValue *value = get_option_default_invoice_report_value (const_cast<QofBook*>(book));
1112 : :
1113 : 3 : if (value)
1114 : : {
1115 : 2 : auto str {value->get<const char*>()};
1116 : 2 : auto ptr = strchr (str, '/');
1117 : 2 : if (ptr)
1118 : : {
1119 : 2 : if (ptr - str == GUID_ENCODING_LENGTH)
1120 : : {
1121 : 2 : if (strlen (str) > GUID_ENCODING_LENGTH + 1)
1122 : 2 : report_name = g_strdup (&str[GUID_ENCODING_LENGTH + 1]);
1123 : : else
1124 : 1 : report_name = g_strdup ("");
1125 : : }
1126 : : }
1127 : : }
1128 : 3 : return report_name;
1129 : : }
1130 : :
1131 : : gdouble
1132 : 2 : qof_book_get_default_invoice_report_timeout (const QofBook *book)
1133 : : {
1134 : 2 : double ret = 0;
1135 : :
1136 : 2 : if (!book)
1137 : : {
1138 : 1 : PWARN ("No book!!!");
1139 : 1 : return ret;
1140 : : }
1141 : :
1142 : 1 : KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
1143 : 2 : KvpValue *value = root->get_slot ({KVP_OPTION_PATH,
1144 : : OPTION_SECTION_BUSINESS,
1145 : : OPTION_NAME_DEFAULT_INVOICE_REPORT_TIMEOUT});
1146 : :
1147 : 1 : if (value)
1148 : 0 : ret = {value->get<double>()};
1149 : :
1150 : 1 : return ret;
1151 : : }
1152 : :
1153 : : /* Note: this will fail if the book slots we're looking for here are flattened at some point !
1154 : : * When that happens, this function can be removed. */
1155 : 312 : static Path opt_name_to_path (const char* opt_name)
1156 : : {
1157 : 312 : Path result;
1158 : 312 : g_return_val_if_fail (opt_name, result);
1159 : 312 : auto opt_name_list = g_strsplit(opt_name, "/", -1);
1160 : 1086 : for (int i=0; opt_name_list[i]; i++)
1161 : 1548 : result.push_back (opt_name_list[i]);
1162 : 312 : g_strfreev (opt_name_list);
1163 : 312 : return result;
1164 : 0 : }
1165 : :
1166 : : const char*
1167 : 310 : qof_book_get_string_option(const QofBook* book, const char* opt_name)
1168 : : {
1169 : 310 : auto slot = qof_instance_get_slots(QOF_INSTANCE (book))->get_slot(opt_name_to_path(opt_name));
1170 : 310 : if (slot == nullptr)
1171 : 309 : return nullptr;
1172 : 1 : return slot->get<const char*>();
1173 : : }
1174 : :
1175 : : void
1176 : 2 : qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val)
1177 : : {
1178 : 2 : qof_book_begin_edit(book);
1179 : 2 : auto frame = qof_instance_get_slots(QOF_INSTANCE(book));
1180 : 2 : auto opt_path = opt_name_to_path(opt_name);
1181 : 2 : if (opt_val && (*opt_val != '\0'))
1182 : 4 : delete frame->set_path(opt_path, new KvpValue(g_strdup(opt_val)));
1183 : : else
1184 : 0 : delete frame->set_path(opt_path, nullptr);
1185 : 2 : qof_instance_set_dirty (QOF_INSTANCE (book));
1186 : 2 : qof_book_commit_edit(book);
1187 : 2 : }
1188 : :
1189 : : const GncGUID*
1190 : 0 : qof_book_get_guid_option(QofBook* book, GSList* path)
1191 : : {
1192 : 0 : g_return_val_if_fail(book != nullptr, nullptr);
1193 : 0 : g_return_val_if_fail(path != nullptr, nullptr);
1194 : :
1195 : 0 : auto table_value = qof_book_get_option(book, path);
1196 : 0 : if (!table_value)
1197 : 0 : return nullptr;
1198 : 0 : return table_value->get<GncGUID*>();
1199 : : }
1200 : :
1201 : : void
1202 : 0 : qof_book_option_frame_delete (QofBook *book, const char* opt_name)
1203 : : {
1204 : 0 : if (opt_name && (*opt_name != '\0'))
1205 : : {
1206 : 0 : qof_book_begin_edit(book);
1207 : 0 : auto frame = qof_instance_get_slots(QOF_INSTANCE(book));
1208 : 0 : auto opt_path = opt_name_to_path(opt_name);
1209 : 0 : delete frame->set_path(opt_path, nullptr);
1210 : 0 : qof_instance_set_dirty (QOF_INSTANCE (book));
1211 : 0 : qof_book_commit_edit(book);
1212 : 0 : }
1213 : 0 : }
1214 : :
1215 : : void
1216 : 50 : qof_book_begin_edit (QofBook *book)
1217 : : {
1218 : 50 : qof_begin_edit(&book->inst);
1219 : 50 : }
1220 : :
1221 : 0 : static void commit_err (G_GNUC_UNUSED QofInstance *inst, QofBackendError errcode)
1222 : : {
1223 : 0 : PERR ("Failed to commit: %d", errcode);
1224 : : // gnc_engine_signal_commit_error( errcode );
1225 : 0 : }
1226 : :
1227 : : #define GNC_FEATURES "features"
1228 : : static void
1229 : 0 : add_feature_to_hash (const gchar *key, KvpValue *value, GHashTable * user_data)
1230 : : {
1231 : 0 : gchar *descr = g_strdup(value->get<const char*>());
1232 : 0 : g_hash_table_insert (user_data, (gchar*)key, descr);
1233 : 0 : }
1234 : :
1235 : : GHashTable *
1236 : 0 : qof_book_get_features (QofBook *book)
1237 : : {
1238 : 0 : KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1239 : 0 : GHashTable *features = g_hash_table_new_full (g_str_hash, g_str_equal,
1240 : 0 : nullptr, g_free);
1241 : :
1242 : 0 : PWARN ("qof_book_get_features is now deprecated.");
1243 : :
1244 : 0 : auto slot = frame->get_slot({GNC_FEATURES});
1245 : 0 : if (slot != nullptr)
1246 : : {
1247 : 0 : frame = slot->get<KvpFrame*>();
1248 : 0 : frame->for_each_slot_temp(&add_feature_to_hash, features);
1249 : : }
1250 : 0 : return features;
1251 : : }
1252 : :
1253 : : void
1254 : 40 : qof_book_set_feature (QofBook *book, const gchar *key, const gchar *descr)
1255 : : {
1256 : 40 : KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1257 : 40 : KvpValue* feature = nullptr;
1258 : 80 : auto feature_slot = frame->get_slot({GNC_FEATURES});
1259 : 40 : if (feature_slot)
1260 : : {
1261 : 26 : auto feature_frame = feature_slot->get<KvpFrame*>();
1262 : 78 : feature = feature_frame->get_slot({key});
1263 : : }
1264 : 40 : if (feature == nullptr || g_strcmp0 (feature->get<const char*>(), descr))
1265 : : {
1266 : 17 : qof_book_begin_edit (book);
1267 : 102 : delete frame->set_path({GNC_FEATURES, key}, new KvpValue(g_strdup (descr)));
1268 : 17 : qof_instance_set_dirty (QOF_INSTANCE (book));
1269 : 17 : qof_book_commit_edit (book);
1270 : : }
1271 : 169 : }
1272 : :
1273 : : FeatureSet
1274 : 6 : qof_book_get_unknown_features (QofBook *book, const FeaturesTable& features)
1275 : : {
1276 : 6 : FeatureSet rv;
1277 : 3 : auto test_feature = [&](const KvpFrameImpl::map_type::value_type& feature)
1278 : : {
1279 : 3 : if (features.find (feature.first) == features.end ())
1280 : 2 : rv.emplace_back (feature.first, feature.second->get<const char*>());
1281 : 3 : };
1282 : 6 : auto frame = qof_instance_get_slots (QOF_INSTANCE (book));
1283 : 12 : auto slot = frame->get_slot({GNC_FEATURES});
1284 : 6 : if (slot != nullptr)
1285 : : {
1286 : 5 : frame = slot->get<KvpFrame*>();
1287 : 5 : std::for_each (frame->begin (), frame->end (), test_feature);
1288 : : }
1289 : 12 : return rv;
1290 : 0 : }
1291 : :
1292 : : bool
1293 : 26 : qof_book_test_feature (QofBook *book, const char *feature)
1294 : : {
1295 : 26 : auto frame = qof_instance_get_slots (QOF_INSTANCE (book));
1296 : 130 : return (frame->get_slot({GNC_FEATURES, feature}) != nullptr);
1297 : 78 : }
1298 : :
1299 : : void
1300 : 3 : qof_book_unset_feature (QofBook *book, const gchar *key)
1301 : : {
1302 : 3 : KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1303 : 12 : auto feature_slot = frame->get_slot({GNC_FEATURES, key});
1304 : 3 : if (!feature_slot)
1305 : : {
1306 : 0 : PWARN ("no feature %s. bail out.", key);
1307 : 0 : return;
1308 : : }
1309 : 3 : qof_book_begin_edit (book);
1310 : 12 : delete frame->set_path({GNC_FEATURES, key}, nullptr);
1311 : 3 : qof_instance_set_dirty (QOF_INSTANCE (book));
1312 : 3 : qof_book_commit_edit (book);
1313 : 18 : }
1314 : :
1315 : : void
1316 : 0 : qof_book_load_options (QofBook *book, GncOptionLoad load_cb, GncOptionDB *odb)
1317 : : {
1318 : 0 : load_cb (odb, book);
1319 : 0 : }
1320 : :
1321 : : void
1322 : 0 : qof_book_save_options (QofBook *book, GncOptionSave save_cb,
1323 : : GncOptionDB* odb, gboolean clear)
1324 : : {
1325 : : /* Wrap this in begin/commit so that it commits only once instead of doing
1326 : : * so for every option. Qof_book_set_option will take care of dirtying the
1327 : : * book.
1328 : : */
1329 : 0 : qof_book_begin_edit (book);
1330 : 0 : save_cb (odb, book, clear);
1331 : 0 : qof_book_commit_edit (book);
1332 : 0 : }
1333 : :
1334 : 49 : static void noop (QofInstance *inst) {}
1335 : :
1336 : : void
1337 : 50 : qof_book_commit_edit(QofBook *book)
1338 : : {
1339 : 50 : if (!qof_commit_edit (QOF_INSTANCE(book))) return;
1340 : 49 : qof_commit_edit_part2 (&book->inst, commit_err, noop, noop/*lot_free*/);
1341 : : }
1342 : :
1343 : : /* Deal with the fact that some options are not in the "options" tree but rather
1344 : : * in the "counters" or "counter_formats" tree */
1345 : 513 : static Path gslist_to_option_path (GSList *gspath)
1346 : : {
1347 : 513 : Path tmp_path;
1348 : 513 : if (!gspath) return tmp_path;
1349 : :
1350 : 1539 : Path path_v {str_KVP_OPTION_PATH};
1351 : 1618 : for (auto item = gspath; item != nullptr; item = g_slist_next(item))
1352 : 2210 : tmp_path.push_back(static_cast<const char*>(item->data));
1353 : 513 : if ((tmp_path.front() == "counters") || (tmp_path.front() == "counter_formats"))
1354 : 0 : return tmp_path;
1355 : :
1356 : 513 : path_v.insert(path_v.end(), tmp_path.begin(), tmp_path.end());
1357 : 513 : return path_v;
1358 : 1026 : }
1359 : :
1360 : : void
1361 : 5 : qof_book_set_option (QofBook *book, KvpValue *value, GSList *path)
1362 : : {
1363 : 5 : KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE (book));
1364 : 5 : qof_book_begin_edit (book);
1365 : 5 : delete root->set_path(gslist_to_option_path(path), value);
1366 : 5 : qof_instance_set_dirty (QOF_INSTANCE (book));
1367 : 5 : qof_book_commit_edit (book);
1368 : :
1369 : : // Also, mark any cached value as invalid
1370 : 5 : book->cached_num_field_source_isvalid = FALSE;
1371 : 5 : }
1372 : :
1373 : : KvpValue*
1374 : 508 : qof_book_get_option (QofBook *book, GSList *path)
1375 : : {
1376 : 508 : KvpFrame *root = qof_instance_get_slots(QOF_INSTANCE (book));
1377 : 508 : return root->get_slot(gslist_to_option_path(path));
1378 : : }
1379 : :
1380 : : void
1381 : 1 : qof_book_options_delete (QofBook *book, GSList *path)
1382 : : {
1383 : 1 : KvpFrame *root = qof_instance_get_slots(QOF_INSTANCE (book));
1384 : 1 : if (path != nullptr)
1385 : : {
1386 : 0 : Path path_v {str_KVP_OPTION_PATH};
1387 : 0 : Path tmp_path;
1388 : 0 : for (auto item = path; item != nullptr; item = g_slist_next(item))
1389 : 0 : tmp_path.push_back(static_cast<const char*>(item->data));
1390 : 0 : delete root->set_path(gslist_to_option_path(path), nullptr);
1391 : 0 : }
1392 : : else
1393 : 3 : delete root->set_path({str_KVP_OPTION_PATH}, nullptr);
1394 : 2 : }
1395 : :
1396 : : /* QofObject function implementation and registration */
1397 : 118 : gboolean qof_book_register (void)
1398 : : {
1399 : : static QofParam params[] =
1400 : : {
1401 : : { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_entity_get_guid, nullptr },
1402 : : { QOF_PARAM_KVP, QOF_TYPE_KVP, (QofAccessFunc)qof_instance_get_slots, nullptr },
1403 : : { nullptr },
1404 : : };
1405 : :
1406 : 118 : qof_class_register (QOF_ID_BOOK, nullptr, params);
1407 : :
1408 : 118 : return TRUE;
1409 : : }
1410 : :
1411 : : /* ========================== END OF FILE =============================== */
|