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.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 : 14198 : G_DEFINE_TYPE(QofBook, qof_book, QOF_TYPE_INSTANCE)
91 : 459 : QOF_GOBJECT_DISPOSE(qof_book);
92 : 459 : 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 : 4935 : static void coll_destroy(gpointer col)
100 : : {
101 : 4935 : qof_collection_destroy((QofCollection *) col);
102 : 4935 : }
103 : :
104 : : static void
105 : 617 : qof_book_init (QofBook *book)
106 : : {
107 : 617 : if (!book) return;
108 : :
109 : 617 : 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 : 617 : qof_instance_init_data (&book->inst, QOF_ID_BOOK, book);
115 : :
116 : 617 : book->data_tables = g_hash_table_new_full (g_str_hash, g_str_equal,
117 : : (GDestroyNotify)qof_string_cache_remove, nullptr);
118 : 617 : book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
119 : :
120 : 617 : book->book_open = 'y';
121 : 617 : book->read_only = FALSE;
122 : 617 : book->session_dirty = FALSE;
123 : 617 : book->version = 0;
124 : 617 : book->cached_num_field_source_isvalid = FALSE;
125 : 617 : 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 : 617 : 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 : 617 : 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 : 6513 : qof_book_get_property (GObject* object,
154 : : guint prop_id,
155 : : GValue* value,
156 : : GParamSpec* pspec)
157 : : {
158 : : QofBook *book;
159 : :
160 : 6513 : g_return_if_fail (QOF_IS_BOOK (object));
161 : 6513 : book = QOF_BOOK (object);
162 : 6513 : switch (prop_id)
163 : : {
164 : 6085 : case PROP_OPT_TRADING_ACCOUNTS:
165 : 30425 : qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH,
166 : : str_OPTION_SECTION_ACCOUNTS, str_OPTION_NAME_TRADING_ACCOUNTS});
167 : 6085 : 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 : 168 : case PROP_OPT_NUM_FIELD_SOURCE:
173 : 840 : 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 : 168 : 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 : 251 : case PROP_OPT_FY_END:
181 : 502 : qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {"fy_end"});
182 : 251 : break;
183 : 0 : default:
184 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 : 0 : break;
186 : : }
187 : 6262 : }
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 : 74 : qof_book_class_init (QofBookClass *klass)
230 : : {
231 : 74 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
232 : 74 : gobject_class->dispose = qof_book_dispose;
233 : 74 : gobject_class->finalize = qof_book_finalize;
234 : 74 : gobject_class->get_property = qof_book_get_property;
235 : 74 : gobject_class->set_property = qof_book_set_property;
236 : :
237 : : g_object_class_install_property
238 : 74 : (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 : 74 : (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 : 74 : (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 : 74 : (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 : 74 : (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 : 74 : }
288 : :
289 : : QofBook *
290 : 615 : qof_book_new (void)
291 : : {
292 : : QofBook *book;
293 : :
294 : 615 : ENTER (" ");
295 : 615 : book = static_cast<QofBook*>(g_object_new(QOF_TYPE_BOOK, nullptr));
296 : 615 : qof_object_book_begin (book);
297 : :
298 : 615 : qof_event_gen (&book->inst, QOF_EVENT_CREATE, nullptr);
299 : 615 : LEAVE ("book=%p", book);
300 : 615 : 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 : 459 : qof_book_dispose_real (G_GNUC_UNUSED GObject *bookp)
315 : : {
316 : 459 : }
317 : :
318 : : static void
319 : 459 : qof_book_finalize_real (G_GNUC_UNUSED GObject *bookp)
320 : : {
321 : 459 : }
322 : :
323 : : static void
324 : 102 : destroy_lot(QofInstance *inst, [[maybe_unused]]void* data)
325 : : {
326 : 102 : auto lot{GNC_LOT(inst)};
327 : 102 : gnc_lot_destroy(lot);
328 : 102 : }
329 : :
330 : : void
331 : 485 : qof_book_destroy (QofBook *book)
332 : : {
333 : : GHashTable* cols;
334 : :
335 : 485 : if (!book || !book->hash_of_collections) return;
336 : 479 : ENTER ("book=%p", book);
337 : :
338 : 479 : book->shutting_down = TRUE;
339 : 479 : 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 : 479 : 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 : 479 : auto lots{qof_book_get_collection(book, GNC_ID_LOT)};
350 : 479 : qof_collection_foreach(lots, destroy_lot, nullptr);
351 : 479 : qof_object_book_end (book);
352 : :
353 : 479 : g_hash_table_destroy (book->data_table_finalizers);
354 : 479 : book->data_table_finalizers = nullptr;
355 : 479 : g_hash_table_destroy (book->data_tables);
356 : 479 : 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 : 479 : cols = book->hash_of_collections;
366 : 479 : g_object_unref (book);
367 : 479 : g_hash_table_destroy (cols);
368 : :
369 : 479 : 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 : 70 : qof_book_mark_session_saved (QofBook *book)
384 : : {
385 : 70 : if (!book) return;
386 : :
387 : 70 : book->dirty_time = 0;
388 : 70 : if (book->session_dirty)
389 : : {
390 : : /* Set the session clean upfront, because the callback will check. */
391 : 67 : book->session_dirty = FALSE;
392 : 67 : if (book->dirty_cb)
393 : 2 : book->dirty_cb(book, FALSE, book->dirty_data);
394 : : }
395 : : }
396 : :
397 : 112597 : void qof_book_mark_session_dirty (QofBook *book)
398 : : {
399 : 112597 : if (!book) return;
400 : 112594 : if (!book->session_dirty)
401 : : {
402 : : /* Set the session dirty upfront, because the callback will check. */
403 : 626 : book->session_dirty = TRUE;
404 : 626 : book->dirty_time = gnc_time (nullptr);
405 : 626 : 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 : 309998 : qof_book_get_backend (const QofBook *book)
441 : : {
442 : 309998 : if (!book) return nullptr;
443 : 309950 : return book->backend;
444 : : }
445 : :
446 : : gboolean
447 : 38292 : qof_book_shutting_down (const QofBook *book)
448 : : {
449 : 38292 : if (!book) return FALSE;
450 : 38291 : return book->shutting_down;
451 : : }
452 : :
453 : : /* ====================================================================== */
454 : : /* setters */
455 : :
456 : : void
457 : 301 : qof_book_set_backend (QofBook *book, QofBackend *be)
458 : : {
459 : 301 : if (!book) return;
460 : 286 : ENTER ("book=%p be=%p", book, be);
461 : 286 : book->backend = be;
462 : 286 : LEAVE (" ");
463 : : }
464 : :
465 : : /* ====================================================================== */
466 : : /* Store arbitrary pointers in the QofBook for data storage extensibility */
467 : : void
468 : 1193 : qof_book_set_data (QofBook *book, const char *key, gpointer data)
469 : : {
470 : 1193 : if (!book || !key) return;
471 : 1191 : if (data)
472 : 1002 : g_hash_table_insert (book->data_tables, (gpointer)CACHE_INSERT(key), data);
473 : : else
474 : 189 : 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 : 118828 : qof_book_get_data (const QofBook *book, const char *key)
490 : : {
491 : 118828 : if (!book || !key) return nullptr;
492 : 118826 : return g_hash_table_lookup (book->data_tables, (gpointer)key);
493 : : }
494 : :
495 : : /* ====================================================================== */
496 : : gboolean
497 : 9990 : qof_book_is_readonly(const QofBook *book)
498 : : {
499 : 9990 : g_return_val_if_fail( book != nullptr, TRUE );
500 : 9990 : 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 : 63 : qof_book_empty(const QofBook *book)
512 : : {
513 : 63 : if (!book) return TRUE;
514 : 63 : auto root_acct_col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
515 : 63 : return qof_collection_get_data(root_acct_col) == nullptr;
516 : : }
517 : :
518 : : /* ====================================================================== */
519 : :
520 : : QofCollection *
521 : 106441 : qof_book_get_collection (const QofBook *book, QofIdType entity_type)
522 : : {
523 : : QofCollection *col;
524 : :
525 : 106441 : if (!book || !entity_type) return nullptr;
526 : :
527 : 106439 : col = static_cast<QofCollection*>(g_hash_table_lookup (book->hash_of_collections, entity_type));
528 : 106439 : if (!col)
529 : : {
530 : 6276 : col = qof_collection_new (entity_type);
531 : 6276 : g_hash_table_insert(
532 : 6276 : book->hash_of_collections,
533 : 6276 : (gpointer)qof_string_cache_insert(entity_type), col);
534 : : }
535 : 106439 : 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 : 13694 : gboolean qof_book_is_open (const QofBook *book)
580 : : {
581 : 13694 : g_return_val_if_fail (book, FALSE);
582 : 13694 : return book->book_open == 'y';
583 : : }
584 : :
585 : 4 : void qof_book_swap_books_readonly (QofBook *book, QofBook *other)
586 : : {
587 : 4 : g_return_if_fail (book && other);
588 : 4 : 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 : 21 : }
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 : 9 : }
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 : 21 : }
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 : 6085 : qof_book_use_trading_accounts (const QofBook *book)
922 : : {
923 : 6085 : char *opt = nullptr;
924 : 6085 : qof_instance_get (QOF_INSTANCE (book), "trading-accts", &opt, nullptr);
925 : 6085 : auto retval = (opt && opt[0] == 't' && opt[1] == 0);
926 : 6085 : g_free (opt);
927 : 6085 : 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 : 677519 : qof_book_use_split_action_for_num_field (const QofBook *book)
934 : : {
935 : 677519 : g_return_val_if_fail (book, FALSE);
936 : 677519 : if (!book->cached_num_field_source_isvalid)
937 : : {
938 : : // No cached value? Then do the expensive KVP lookup
939 : : gboolean result;
940 : 168 : char *opt = nullptr;
941 : 168 : qof_instance_get (QOF_INSTANCE (book),
942 : : PARAM_NAME_NUM_FIELD_SOURCE, &opt,
943 : : nullptr);
944 : :
945 : 168 : if (opt && opt[0] == 't' && opt[1] == 0)
946 : 2 : result = TRUE;
947 : : else
948 : 166 : result = FALSE;
949 : 168 : 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 : 168 : const_cast<QofBook*>(book)->cached_num_field_source = result;
955 : 168 : const_cast<QofBook*>(book)->cached_num_field_source_isvalid = TRUE;
956 : : }
957 : : // Value is cached now. Use the cheap variable returning.
958 : 677519 : 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 : 51 : qof_book_begin_edit (QofBook *book)
1235 : : {
1236 : 51 : qof_begin_edit(&book->inst);
1237 : 51 : }
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 : 43 : qof_book_set_feature (QofBook *book, const gchar *key, const gchar *descr)
1273 : : {
1274 : 43 : KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
1275 : 43 : KvpValue* feature = nullptr;
1276 : 86 : auto feature_slot = frame->get_slot({GNC_FEATURES});
1277 : 43 : if (feature_slot)
1278 : : {
1279 : 28 : auto feature_frame = feature_slot->get<KvpFrame*>();
1280 : 84 : feature = feature_frame->get_slot({key});
1281 : : }
1282 : 43 : if (feature == nullptr || g_strcmp0 (feature->get<const char*>(), descr))
1283 : : {
1284 : 18 : qof_book_begin_edit (book);
1285 : 108 : delete frame->set_path({GNC_FEATURES, key}, new KvpValue(g_strdup (descr)));
1286 : 18 : qof_instance_set_dirty (QOF_INSTANCE (book));
1287 : 18 : qof_book_commit_edit (book);
1288 : : }
1289 : 181 : }
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 : 12 : 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 : 78 : }
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 : 18 : }
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 : 50 : static void noop (QofInstance *inst) {}
1353 : :
1354 : : void
1355 : 51 : qof_book_commit_edit(QofBook *book)
1356 : : {
1357 : 51 : if (!qof_commit_edit (QOF_INSTANCE(book))) return;
1358 : 50 : 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 : 1054 : 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 : 329 : 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 : 119 : 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 : 119 : qof_class_register (QOF_ID_BOOK, nullptr, params);
1425 : :
1426 : 119 : 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 =============================== */
|