GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-pricedb.cpp
1/********************************************************************
2 * gnc-pricedb.c -- a simple price database for gnucash. *
3 * Copyright (C) 2001 Rob Browning *
4 * Copyright (C) 2001,2003 Linas Vepstas <linas@linas.org> *
5 * *
6 * This program is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU General Public License as *
8 * published by the Free Software Foundation; either version 2 of *
9 * the License, or (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License*
17 * along with this program; if not, contact: *
18 * *
19 * Free Software Foundation Voice: +1-617-542-5942 *
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 * Boston, MA 02110-1301, USA gnu@gnu.org *
22 * *
23 *******************************************************************/
24
25#include <config.h>
26
27#include <glib.h>
28#include <string.h>
29#include <stdint.h>
30#include <stdlib.h>
31#include "gnc-date.h"
32#include "gnc-datetime.hpp"
33#include "gnc-numeric.h"
34#include "gnc-pricedb-p.h"
35#include <qofinstance-p.h>
36
37/* This static indicates the debugging module that this .o belongs to. */
38static QofLogModule log_module = GNC_MOD_PRICE;
39
40static gboolean add_price(GNCPriceDB *db, GNCPrice *p);
41static gboolean remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup);
42static GNCPrice *lookup_nearest_in_time(GNCPriceDB *db, const gnc_commodity *c,
43 const gnc_commodity *currency,
44 time64 t, gboolean sameday);
45static gboolean
46pricedb_pricelist_traversal(GNCPriceDB *db,
47 gboolean (*f)(GList *p, gpointer user_data),
48 gpointer user_data);
49
50enum
51{
52 PROP_0,
53 PROP_COMMODITY, /* Table */
54 PROP_CURRENCY, /* Table */
55 PROP_DATE, /* Table */
56 PROP_SOURCE, /* Table */
57 PROP_TYPE, /* Table */
58 PROP_VALUE, /* Table, 2 fields (numeric) */
59};
60
61/* Like strcmp, returns -1 if a < b, +1 if a > b, and 0 if they're equal. */
62static inline int
63time64_cmp (time64 a, time64 b)
64{
65 return a < b ? -1 : a > b ? 1 : 0;
66}
67
68using CommodityPtrPair = std::pair<const gnc_commodity*, gpointer>;
69using CommodityPtrPairVec = std::vector<CommodityPtrPair>;
70
71static void
72hash_entry_insert(const gnc_commodity* key, const gpointer val, CommodityPtrPairVec *result)
73{
74 result->emplace_back (key, val);
75}
76
77static CommodityPtrPairVec
78hash_table_to_vector (GHashTable *table)
79{
80 CommodityPtrPairVec result_vec;
81 result_vec.reserve (g_hash_table_size (table));
82 g_hash_table_foreach(table, (GHFunc)hash_entry_insert, &result_vec);
83 return result_vec;
84}
85
86/* GObject Initialization */
87G_DEFINE_TYPE(GNCPrice, gnc_price, QOF_TYPE_INSTANCE)
88
89static void
90gnc_price_init(GNCPrice* price)
91{
92 price->refcount = 1;
93 price->value = gnc_numeric_zero();
94 price->type = nullptr;
95 price->source = PRICE_SOURCE_INVALID;
96}
97
98/* Array of char constants for converting price-source enums. Be sure to keep in
99 * sync with the enum values in gnc-pricedb.h The string user:price-editor is
100 * explicitly used by price_to_gui() in dialog-price-editor.c. Beware
101 * that the strings are used to store the enum values in the backends so any
102 * changes will affect backward data compatibility.
103 * The last two values, temporary and invalid, are *not* used.
104 */
105static const char* source_names[(size_t)PRICE_SOURCE_INVALID + 1] =
106{
107 /* sync with price_to_gui in dialog-price-editor.c */
108 "user:price-editor",
109 "Finance::Quote",
110 "user:price",
111 /* String retained for backwards compatibility. */
112 "user:xfer-dialog",
113 "user:split-register",
114 "user:split-import",
115 "user:stock-split",
116 "user:stock-transaction",
117 "user:invoice-post", /* Retained for backwards compatibility */
118 "temporary",
119 "invalid"
120};
121
122static void
123gnc_price_dispose(GObject *pricep)
124{
125 G_OBJECT_CLASS(gnc_price_parent_class)->dispose(pricep);
126}
127
128static void
129gnc_price_finalize(GObject* pricep)
130{
131 G_OBJECT_CLASS(gnc_price_parent_class)->finalize(pricep);
132}
133
134/* Note that g_value_set_object() refs the object, as does
135 * g_object_get(). But g_object_get() only unrefs once when it disgorges
136 * the object, leaving an unbalanced ref, which leaks. So instead of
137 * using g_value_set_object(), use g_value_take_object() which doesn't
138 * ref the object when used in get_property().
139 */
140static void
141gnc_price_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
142{
143 GNCPrice* price;
144
145 g_return_if_fail(GNC_IS_PRICE(object));
146
147 price = GNC_PRICE(object);
148 switch (prop_id)
149 {
150 case PROP_SOURCE:
151 g_value_set_string(value, gnc_price_get_source_string(price));
152 break;
153 case PROP_TYPE:
154 g_value_set_string(value, price->type);
155 break;
156 case PROP_VALUE:
157 g_value_set_boxed(value, &price->value);
158 break;
159 case PROP_COMMODITY:
160 g_value_take_object(value, price->commodity);
161 break;
162 case PROP_CURRENCY:
163 g_value_take_object(value, price->currency);
164 break;
165 case PROP_DATE:
166 g_value_set_boxed(value, &price->tmspec);
167 break;
168 default:
169 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
170 break;
171 }
172}
173
174static void
175gnc_price_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
176{
177 GNCPrice* price;
178 gnc_numeric* number;
179 Time64* time;
180
181 g_return_if_fail(GNC_IS_PRICE(object));
182
183 price = GNC_PRICE(object);
184 g_assert (qof_instance_get_editlevel(price));
185
186 switch (prop_id)
187 {
188 case PROP_SOURCE:
189 gnc_price_set_source_string(price, g_value_get_string(value));
190 break;
191 case PROP_TYPE:
192 gnc_price_set_typestr(price, g_value_get_string(value));
193 break;
194 case PROP_VALUE:
195 number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
196 gnc_price_set_value(price, *number);
197 break;
198 case PROP_COMMODITY:
199 gnc_price_set_commodity(price, static_cast<gnc_commodity*>(g_value_get_object(value)));
200 break;
201 case PROP_CURRENCY:
202 gnc_price_set_currency(price, static_cast<gnc_commodity*>(g_value_get_object(value)));
203 break;
204 case PROP_DATE:
205 time = static_cast<Time64*>(g_value_get_boxed(value));
206 gnc_price_set_time64(price, time->t);
207 break;
208 default:
209 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
210 break;
211 }
212}
213
214static void
215gnc_price_class_init(GNCPriceClass *klass)
216{
217 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
218
219 gobject_class->dispose = gnc_price_dispose;
220 gobject_class->finalize = gnc_price_finalize;
221 gobject_class->set_property = gnc_price_set_property;
222 gobject_class->get_property = gnc_price_get_property;
223
224 g_object_class_install_property
225 (gobject_class,
226 PROP_COMMODITY,
227 g_param_spec_object ("commodity",
228 "Commodity",
229 "The commodity field denotes the base kind of "
230 "'stuff' for the units of this quote, whether "
231 "it is USD, gold, stock, etc.",
232 GNC_TYPE_COMMODITY,
233 G_PARAM_READWRITE));
234
235 g_object_class_install_property
236 (gobject_class,
237 PROP_CURRENCY,
238 g_param_spec_object ("currency",
239 "Currency",
240 "The currency field denotes the external kind "
241 "'stuff' for the units of this quote, whether "
242 "it is USD, gold, stock, etc.",
243 GNC_TYPE_COMMODITY,
244 G_PARAM_READWRITE));
245
246 g_object_class_install_property
247 (gobject_class,
248 PROP_SOURCE,
249 g_param_spec_string ("source",
250 "Price source",
251 "The price source is PriceSource enum describing how"
252 " the price was created. This property works on the"
253 " string values in source_names for SQL database"
254 " compatibility.",
255 nullptr,
256 G_PARAM_READWRITE));
257
258 g_object_class_install_property
259 (gobject_class,
260 PROP_TYPE,
261 g_param_spec_string ("type",
262 "Quote type",
263 "The quote type is a string describing the "
264 "type of a price quote. Types possible now "
265 "are 'bid', 'ask', 'last', 'nav', 'transaction', "
266 "and 'unknown'.",
267 nullptr,
268 G_PARAM_READWRITE));
269
270 g_object_class_install_property
271 (gobject_class,
272 PROP_DATE,
273 g_param_spec_boxed("date",
274 "Date",
275 "The date of the price quote.",
276 GNC_TYPE_NUMERIC,
277 G_PARAM_READWRITE));
278
279 g_object_class_install_property
280 (gobject_class,
281 PROP_VALUE,
282 g_param_spec_boxed("value",
283 "Value",
284 "The value of the price quote.",
285 GNC_TYPE_NUMERIC,
286 G_PARAM_READWRITE));
287}
288
289/* ==================================================================== */
290/* GNCPrice functions
291 */
292
293/* allocation */
294GNCPrice *
296{
297 GNCPrice *p;
298
299 g_return_val_if_fail (book, nullptr);
300
301 ENTER(" ");
302 p = static_cast<GNCPrice*>(g_object_new(GNC_TYPE_PRICE, nullptr));
303
304 qof_instance_init_data (&p->inst, GNC_ID_PRICE, book);
305 qof_event_gen (&p->inst, QOF_EVENT_CREATE, nullptr);
306 LEAVE ("price created %p", p);
307 return p;
308}
309
310static void
311gnc_price_destroy (GNCPrice *p)
312{
313 ENTER("destroy price %p", p);
314 qof_event_gen (&p->inst, QOF_EVENT_DESTROY, nullptr);
315
316 if (p->type) CACHE_REMOVE(p->type);
317
318 /* qof_instance_release (&p->inst); */
319 g_object_unref(p);
320 LEAVE (" ");
321}
322
323void
324gnc_price_ref(GNCPrice *p)
325{
326 if (!p) return;
327 p->refcount++;
328}
329
330void
331gnc_price_unref(GNCPrice *p)
332{
333 if (!p) return;
334 if (p->refcount == 0)
335 {
336 return;
337 }
338
339 p->refcount--;
340
341 if (p->refcount <= 0)
342 {
343 if (nullptr != p->db)
344 {
345 PERR("last unref while price in database");
346 }
347 gnc_price_destroy (p);
348 }
349}
350
351/* ==================================================================== */
352
353GNCPrice *
354gnc_price_clone (GNCPrice* p, QofBook *book)
355{
356 /* the clone doesn't belong to a PriceDB */
357 GNCPrice *new_p;
358
359 g_return_val_if_fail (book, nullptr);
360
361 ENTER ("pr=%p", p);
362
363 if (!p)
364 {
365 LEAVE ("return nullptr");
366 return nullptr;
367 }
368
369 new_p = gnc_price_create(book);
370 if (!new_p)
371 {
372 LEAVE ("return nullptr");
373 return nullptr;
374 }
375
376 qof_instance_copy_version(new_p, p);
377
378 gnc_price_begin_edit(new_p);
379 /* never ever clone guid's */
380 gnc_price_set_commodity(new_p, gnc_price_get_commodity(p));
381 gnc_price_set_time64(new_p, gnc_price_get_time64(p));
382 gnc_price_set_source(new_p, gnc_price_get_source(p));
383 gnc_price_set_typestr(new_p, gnc_price_get_typestr(p));
384 gnc_price_set_value(new_p, gnc_price_get_value(p));
385 gnc_price_set_currency(new_p, gnc_price_get_currency(p));
386 gnc_price_commit_edit(new_p);
387 LEAVE ("return cloned price %p", new_p);
388 return(new_p);
389}
390
391GNCPrice *
392gnc_price_invert (GNCPrice *p)
393{
394 QofBook *book = qof_instance_get_book (QOF_INSTANCE(p));
395 GNCPrice *new_p = gnc_price_create (book);
396 qof_instance_copy_version(new_p, p);
397 gnc_price_begin_edit(new_p);
398 gnc_price_set_time64(new_p, gnc_price_get_time64(p));
399 gnc_price_set_source(new_p, PRICE_SOURCE_TEMP);
400 gnc_price_set_typestr(new_p, gnc_price_get_typestr(p));
401 gnc_price_set_commodity(new_p, gnc_price_get_currency(p));
402 gnc_price_set_currency(new_p, gnc_price_get_commodity(p));
403 gnc_price_set_value(new_p, gnc_numeric_invert(gnc_price_get_value(p)));
404 gnc_price_commit_edit(new_p);
405 return new_p;
406}
407
408/* ==================================================================== */
409
410void
411gnc_price_begin_edit (GNCPrice *p)
412{
413 qof_begin_edit(&p->inst);
414}
415
416static void commit_err (QofInstance *inst, QofBackendError errcode)
417{
418 PERR ("Failed to commit: %d", errcode);
419 gnc_engine_signal_commit_error( errcode );
420}
421
422static void noop (QofInstance *inst) {}
423
424void
425gnc_price_commit_edit (GNCPrice *p)
426{
427 if (!qof_commit_edit (QOF_INSTANCE(p))) return;
428 qof_commit_edit_part2 (&p->inst, commit_err, noop, noop);
429}
430
431/* ==================================================================== */
432
433void
434gnc_pricedb_begin_edit (GNCPriceDB *pdb)
435{
436 qof_begin_edit(&pdb->inst);
437}
438
439void
440gnc_pricedb_commit_edit (GNCPriceDB *pdb)
441{
442 if (!qof_commit_edit (QOF_INSTANCE(pdb))) return;
443 qof_commit_edit_part2 (&pdb->inst, commit_err, noop, noop);
444}
445
446/* ==================================================================== */
447/* setters */
448
449static void
450gnc_price_set_dirty (GNCPrice *p)
451{
452 qof_instance_set_dirty(&p->inst);
453 qof_event_gen(&p->inst, QOF_EVENT_MODIFY, nullptr);
454}
455
456void
457gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c)
458{
459 if (!p) return;
460
461 if (!gnc_commodity_equiv(p->commodity, c))
462 {
463 /* Changing the commodity requires the hash table
464 * position to be modified. The easiest way of doing
465 * this is to remove and reinsert. */
466 gnc_price_ref (p);
467 remove_price (p->db, p, TRUE);
468 gnc_price_begin_edit (p);
469 p->commodity = c;
470 gnc_price_set_dirty(p);
471 gnc_price_commit_edit (p);
472 add_price (p->db, p);
473 gnc_price_unref (p);
474 }
475}
476
477
478void
479gnc_price_set_currency(GNCPrice *p, gnc_commodity *c)
480{
481 if (!p) return;
482
483 if (!gnc_commodity_equiv(p->currency, c))
484 {
485 /* Changing the currency requires the hash table
486 * position to be modified. The easiest way of doing
487 * this is to remove and reinsert. */
488 gnc_price_ref (p);
489 remove_price (p->db, p, TRUE);
490 gnc_price_begin_edit (p);
491 p->currency = c;
492 gnc_price_set_dirty(p);
493 gnc_price_commit_edit (p);
494 add_price (p->db, p);
495 gnc_price_unref (p);
496 }
497}
498
499void
500gnc_price_set_time64(GNCPrice *p, time64 t)
501{
502 if (!p) return;
503 if (p->tmspec != t)
504 {
505 /* Changing the datestamp requires the hash table
506 * position to be modified. The easiest way of doing
507 * this is to remove and reinsert. */
508 gnc_price_ref (p);
509 remove_price (p->db, p, FALSE);
510 gnc_price_begin_edit (p);
511 p->tmspec = t;
512 gnc_price_set_dirty(p);
513 gnc_price_commit_edit (p);
514 add_price (p->db, p);
515 gnc_price_unref (p);
516 }
517}
518
519void
520gnc_price_set_source(GNCPrice *p, PriceSource s)
521{
522 if (!p) return;
523 gnc_price_begin_edit (p);
524 p->source = s;
525 gnc_price_set_dirty(p);
526 gnc_price_commit_edit(p);
527}
528
529void
530gnc_price_set_source_string(GNCPrice *p, const char* str)
531{
532 if (!p) return;
533 for (PriceSource s = PRICE_SOURCE_EDIT_DLG;
534 s < PRICE_SOURCE_INVALID; s = PriceSource(s + 1))
535 if (strcmp(source_names[s], str) == 0)
536 {
537 gnc_price_set_source(p, s);
538 return;
539 }
540
541
542}
543void
544gnc_price_set_typestr(GNCPrice *p, const char* type)
545{
546 if (!p) return;
547 if (g_strcmp0(p->type, type) != 0)
548 {
549 gnc_price_begin_edit (p);
550 CACHE_REPLACE(p->type, type);
551 gnc_price_set_dirty(p);
552 gnc_price_commit_edit (p);
553 }
554}
555
556void
557gnc_price_set_value(GNCPrice *p, gnc_numeric value)
558{
559 if (!p) return;
560 if (!gnc_numeric_eq(p->value, value))
561 {
562 gnc_price_begin_edit (p);
563 p->value = value;
564 gnc_price_set_dirty(p);
565 gnc_price_commit_edit (p);
566 }
567}
568
569/* ==================================================================== */
570/* getters */
571
572GNCPrice *
573gnc_price_lookup (const GncGUID *guid, QofBook *book)
574{
575 QofCollection *col;
576
577 if (!guid || !book) return nullptr;
578 col = qof_book_get_collection (book, GNC_ID_PRICE);
579 return (GNCPrice *) qof_collection_lookup_entity (col, guid);
580}
581
582gnc_commodity *
583gnc_price_get_commodity(const GNCPrice *p)
584{
585 if (!p) return nullptr;
586 return p->commodity;
587}
588
589time64
590gnc_price_get_time64(const GNCPrice *p)
591{
592 return p ? p->tmspec : 0;
593}
594
596gnc_price_get_source(const GNCPrice *p)
597{
598 if (!p) return PRICE_SOURCE_INVALID;
599 return p->source;
600}
601
602const char*
603gnc_price_get_source_string(const GNCPrice *p)
604{
605 if (!p) return nullptr;
606 return source_names[p->source];
607}
608
609const char *
610gnc_price_get_typestr(const GNCPrice *p)
611{
612 if (!p) return nullptr;
613 return p->type;
614}
615
616gnc_numeric
617gnc_price_get_value(const GNCPrice *p)
618{
619 if (!p)
620 {
621 PERR("price nullptr.\n");
622 return gnc_numeric_zero();
623 }
624 return p->value;
625}
626
627gnc_commodity *
628gnc_price_get_currency(const GNCPrice *p)
629{
630 if (!p) return nullptr;
631 return p->currency;
632}
633
634gboolean
635gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
636{
637 time64 time1, time2;
638
639 if (p1 == p2) return TRUE;
640 if (!p1 || !p2) return FALSE;
641
642 if (!gnc_commodity_equiv (gnc_price_get_commodity (p1),
643 gnc_price_get_commodity (p2)))
644 return FALSE;
645
646 if (!gnc_commodity_equiv (gnc_price_get_currency (p1),
647 gnc_price_get_currency (p2)))
648 return FALSE;
649
650 time1 = gnc_price_get_time64 (p1);
651 time2 = gnc_price_get_time64 (p2);
652
653 if (time1 != time2)
654 return FALSE;
655
656 if (gnc_price_get_source (p1) != gnc_price_get_source (p2))
657 return FALSE;
658
659 if (g_strcmp0 (gnc_price_get_typestr (p1),
660 gnc_price_get_typestr (p2)) != 0)
661 return FALSE;
662
663 if (!gnc_numeric_eq (gnc_price_get_value (p1),
664 gnc_price_get_value (p2)))
665 return FALSE;
666
667 return TRUE;
668}
669
670/* ==================================================================== */
671/* price list manipulation functions */
672
673static gint
674compare_prices_by_date(gconstpointer a, gconstpointer b)
675{
676 time64 time_a, time_b;
677 gint result;
678
679 if (!a && !b) return 0;
680 /* nothing is always less than something */
681 if (!a) return -1;
682
683 time_a = gnc_price_get_time64((GNCPrice *) a);
684 time_b = gnc_price_get_time64((GNCPrice *) b);
685
686 /* Note we return -1 if time_b is before time_a. */
687 result = time64_cmp(time_b, time_a);
688 if (result) return result;
689
690 /* For a stable sort */
691 return guid_compare (gnc_price_get_guid((GNCPrice *) a),
692 gnc_price_get_guid((GNCPrice *) b));
693}
694
695static int
696price_is_duplicate (const GNCPrice *p_price, const GNCPrice *c_price)
697{
698 /* If the date, currency, commodity and price match, it's a duplicate */
699 return time64CanonicalDayTime (gnc_price_get_time64 (p_price)) != time64CanonicalDayTime (gnc_price_get_time64 (c_price)) ||
700 gnc_numeric_compare (gnc_price_get_value (p_price), gnc_price_get_value (c_price)) ||
701 gnc_commodity_compare (gnc_price_get_commodity (p_price), gnc_price_get_commodity (c_price)) ||
702 gnc_commodity_compare (gnc_price_get_currency (p_price), gnc_price_get_currency (c_price));
703}
704
705gboolean
706gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
707{
708 if (!prices || !p) return FALSE;
709 gnc_price_ref(p);
710
711 if (check_dupl && g_list_find_custom (*prices, p, (GCompareFunc)price_is_duplicate))
712 return true;
713
714 auto result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date);
715 if (!result_list)
716 return false;
717
718 *prices = result_list;
719 return true;
720}
721
722gboolean
723gnc_price_list_remove(PriceList **prices, GNCPrice *p)
724{
725 GList *result_list;
726 GList *found_element;
727
728 if (!prices || !p) return FALSE;
729
730 found_element = g_list_find(*prices, p);
731 if (!found_element) return TRUE;
732
733 result_list = g_list_remove_link(*prices, found_element);
734 gnc_price_unref((GNCPrice *) found_element->data);
735 g_list_free(found_element);
736
737 *prices = result_list;
738 return TRUE;
739}
740
741void
742gnc_price_list_destroy(PriceList *prices)
743{
744 g_list_free_full (prices, (GDestroyNotify)gnc_price_unref);
745}
746
747gboolean
748gnc_price_list_equal(PriceList *prices1, PriceList *prices2)
749{
750 if (prices1 == prices2) return TRUE;
751
752 for (auto n1 = prices1, n2 = prices2; n1 || n2; n1 = g_list_next (n1), n2 = g_list_next (n2))
753 {
754 if (!n1)
755 {
756 PINFO ("prices2 has extra prices");
757 return FALSE;
758 }
759 if (!n2)
760 {
761 PINFO ("prices1 has extra prices");
762 return FALSE;
763 }
764 if (!gnc_price_equal (static_cast<GNCPrice*>(n1->data), static_cast<GNCPrice*>(n2->data)))
765 return FALSE;
766 };
767
768 return TRUE;
769}
770
771/* ==================================================================== */
772/* GNCPriceDB functions
773
774 Structurally a GNCPriceDB contains a hash mapping price commodities
775 (of type gnc_commodity*) to hashes mapping price currencies (of
776 type gnc_commodity*) to GNCPrice lists (see gnc-pricedb.h for a
777 description of GNCPrice lists). The top-level key is the commodity
778 you want the prices for, and the second level key is the commodity
779 that the value is expressed in terms of.
780 */
781
782/* GObject Initialization */
783QOF_GOBJECT_IMPL(gnc_pricedb, GNCPriceDB, QOF_TYPE_INSTANCE)
784
785static void
786gnc_pricedb_init(GNCPriceDB* pdb)
787{
788 pdb->reset_nth_price_cache = FALSE;
789}
790
791static void
792gnc_pricedb_dispose_real (GObject *pdbp)
793{
794}
795
796static void
797gnc_pricedb_finalize_real(GObject* pdbp)
798{
799}
800
801static GNCPriceDB *
802gnc_pricedb_create(QofBook * book)
803{
804 GNCPriceDB * result;
805 QofCollection *col;
806
807 g_return_val_if_fail (book, nullptr);
808
809 /* There can only be one pricedb per book. So if one exits already,
810 * then use that. Warn user, they shouldn't be creating two ...
811 */
812 col = qof_book_get_collection (book, GNC_ID_PRICEDB);
813 result = static_cast<GNCPriceDB*>(qof_collection_get_data (col));
814 if (result)
815 {
816 PWARN ("A price database already exists for this book!");
817 return result;
818 }
819
820 result = static_cast<GNCPriceDB*>(g_object_new(GNC_TYPE_PRICEDB, nullptr));
821 qof_instance_init_data (&result->inst, GNC_ID_PRICEDB, book);
823
827 qof_collection_set_data (col, result);
828
829 result->commodity_hash = g_hash_table_new(nullptr, nullptr);
830 g_return_val_if_fail (result->commodity_hash, nullptr);
831 return result;
832}
833
834static void
835destroy_pricedb_currency_hash_data(gpointer key,
836 gpointer data,
837 gpointer user_data)
838{
839 GList *price_list = (GList *) data;
840 GList *node;
841 GNCPrice *p;
842
843 for (node = price_list; node; node = node->next)
844 {
845 p = static_cast<GNCPrice*>(node->data);
846
847 p->db = nullptr;
848 }
849
850 gnc_price_list_destroy(price_list);
851}
852
853static void
854destroy_pricedb_commodity_hash_data(gpointer key,
855 gpointer data,
856 gpointer user_data)
857{
858 GHashTable *currency_hash = (GHashTable *) data;
859 if (!currency_hash) return;
860 g_hash_table_foreach (currency_hash,
861 destroy_pricedb_currency_hash_data,
862 nullptr);
863 g_hash_table_destroy(currency_hash);
864}
865
866void
867gnc_pricedb_destroy(GNCPriceDB *db)
868{
869 if (!db) return;
870 if (db->commodity_hash)
871 {
872 g_hash_table_foreach (db->commodity_hash,
873 destroy_pricedb_commodity_hash_data,
874 nullptr);
875 }
876 g_hash_table_destroy (db->commodity_hash);
877 db->commodity_hash = nullptr;
878 /* qof_instance_release (&db->inst); */
879 g_object_unref(db);
880}
881
882void
883gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
884{
885 db->bulk_update = bulk_update;
886}
887
888/* ==================================================================== */
889/* This is kind of weird, the way its done. Each collection of prices
890 * for a given commodity should get its own guid, be its own entity, etc.
891 * We really shouldn't be using the collection data. But, hey I guess its OK,
892 * yeah? Umm, possibly not. (NW). See TODO below.
893*/
903GNCPriceDB *
904gnc_collection_get_pricedb(QofCollection *col)
905{
906 if (!col) return nullptr;
907 return static_cast<GNCPriceDB*>(qof_collection_get_data (col));
908}
909
910GNCPriceDB *
912{
913 QofCollection *col;
914
915 if (!book) return nullptr;
916 col = qof_book_get_collection (book, GNC_ID_PRICEDB);
917 return gnc_collection_get_pricedb (col);
918}
919
920/* ==================================================================== */
921
922static gboolean
923num_prices_helper (GNCPrice *p, gpointer user_data)
924{
925 auto count = static_cast<guint*>(user_data);
926
927 *count += 1;
928
929 return TRUE;
930}
931
932guint
934{
935 guint count;
936
937 if (!db) return 0;
938
939 count = 0;
940
941 gnc_pricedb_foreach_price(db, num_prices_helper, &count, FALSE);
942
943 return count;
944}
945
946/* ==================================================================== */
947
948typedef struct
949{
950 gboolean equal;
951 GNCPriceDB *db2;
952 gnc_commodity *commodity;
954
955static void
956pricedb_equal_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
957{
958 auto equal_data = static_cast<GNCPriceDBEqualData*>(user_data);
959 auto currency = static_cast<gnc_commodity*>(key);
960 auto price_list1 = static_cast<GList*>(val);
961 auto price_list2 = gnc_pricedb_get_prices (equal_data->db2,
962 equal_data->commodity,
963 currency);
964
965 if (!gnc_price_list_equal (price_list1, price_list2))
966 equal_data->equal = FALSE;
967
968 gnc_price_list_destroy (price_list2);
969}
970
971static void
972pricedb_equal_foreach_currencies_hash (gpointer key, gpointer val,
973 gpointer user_data)
974{
975 auto currencies_hash = static_cast<GHashTable*>(val);
976 auto equal_data = static_cast<GNCPriceDBEqualData *>(user_data);
977
978 equal_data->commodity = static_cast<gnc_commodity*>(key);
979
980 g_hash_table_foreach (currencies_hash,
981 pricedb_equal_foreach_pricelist,
982 equal_data);
983}
984
985gboolean
986gnc_pricedb_equal (GNCPriceDB *db1, GNCPriceDB *db2)
987{
988 GNCPriceDBEqualData equal_data;
989
990 if (db1 == db2) return TRUE;
991
992 if (!db1 || !db2)
993 {
994 PWARN ("one is nullptr");
995 return FALSE;
996 }
997
998 equal_data.equal = TRUE;
999 equal_data.db2 = db2;
1000
1001 g_hash_table_foreach (db1->commodity_hash,
1002 pricedb_equal_foreach_currencies_hash,
1003 &equal_data);
1004
1005 return equal_data.equal;
1006}
1007
1008/* ==================================================================== */
1009/* The add_price() function is a utility that only manages the
1010 * dual hash table insertion */
1011
1012static gboolean
1013add_price(GNCPriceDB *db, GNCPrice *p)
1014{
1015 /* This function will use p, adding a ref, so treat p as read-only
1016 if this function succeeds. */
1017 GList *price_list;
1018 gnc_commodity *commodity;
1019 gnc_commodity *currency;
1020 GHashTable *currency_hash;
1021
1022 if (!db || !p) return FALSE;
1023 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1026
1027 if (!qof_instance_books_equal(db, p))
1028 {
1029 PERR ("attempted to mix up prices across different books");
1030 LEAVE (" ");
1031 return FALSE;
1032 }
1033
1034 commodity = gnc_price_get_commodity(p);
1035 if (!commodity)
1036 {
1037 PWARN("no commodity");
1038 LEAVE (" ");
1039 return FALSE;
1040 }
1041 currency = gnc_price_get_currency(p);
1042 if (!currency)
1043 {
1044 PWARN("no currency");
1045 LEAVE (" ");
1046 return FALSE;
1047 }
1048 if (!db->commodity_hash)
1049 {
1050 LEAVE ("no commodity hash found ");
1051 return FALSE;
1052 }
1053/* Check for an existing price on the same day. If there is no existing price,
1054 * add this one. If this price is of equal or better precedence than the old
1055 * one, copy this one over the old one.
1056 */
1057 if (!db->bulk_update)
1058 {
1059 GNCPrice *old_price = gnc_pricedb_lookup_day_t64(db, p->commodity,
1060 p->currency, p->tmspec);
1061 if (old_price != nullptr)
1062 {
1063 if (p->source > old_price->source)
1064 {
1065 gnc_price_unref(p);
1066 LEAVE ("Better price already in DB.");
1067 return FALSE;
1068 }
1069 gnc_pricedb_remove_price(db, old_price);
1070 }
1071 }
1072
1073 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1074 if (!currency_hash)
1075 {
1076 currency_hash = g_hash_table_new(nullptr, nullptr);
1077 g_hash_table_insert(db->commodity_hash, commodity, currency_hash);
1078 }
1079
1080 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
1081 if (!gnc_price_list_insert(&price_list, p, !db->bulk_update))
1082 {
1083 LEAVE ("gnc_price_list_insert failed");
1084 return FALSE;
1085 }
1086
1087 if (!price_list)
1088 {
1089 LEAVE (" no price list");
1090 return FALSE;
1091 }
1092
1093 g_hash_table_insert(currency_hash, currency, price_list);
1094 p->db = db;
1095
1096 qof_event_gen (&p->inst, QOF_EVENT_ADD, nullptr);
1097
1098 LEAVE ("db=%p, pr=%p dirty=%d dextroying=%d commodity=%s/%s currency_hash=%p",
1101 gnc_commodity_get_namespace(p->commodity),
1102 gnc_commodity_get_mnemonic(p->commodity),
1103 currency_hash);
1104 return TRUE;
1105}
1106
1107/* If gnc_pricedb_add_price() succeeds, it takes ownership of the
1108 passed-in GNCPrice and inserts it into the pricedb. Writing to this
1109 pointer afterwards will have interesting results, so don't.
1110 */
1111gboolean
1112gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
1113{
1114 if (!db || !p) return FALSE;
1115
1116 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1119
1120 if (FALSE == add_price(db, p))
1121 {
1122 LEAVE (" failed to add price");
1123 return FALSE;
1124 }
1125
1127 qof_instance_set_dirty(&db->inst);
1129
1130 LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
1133
1134 return TRUE;
1135}
1136
1137/* remove_price() is a utility; its only function is to remove the price
1138 * from the double-hash tables.
1139 */
1140
1141static gboolean
1142remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup)
1143{
1144 GList *price_list;
1145 gnc_commodity *commodity;
1146 gnc_commodity *currency;
1147 GHashTable *currency_hash;
1148
1149 if (!db || !p) return FALSE;
1150 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1153
1154 commodity = gnc_price_get_commodity(p);
1155 if (!commodity)
1156 {
1157 LEAVE (" no commodity");
1158 return FALSE;
1159 }
1160 currency = gnc_price_get_currency(p);
1161 if (!currency)
1162 {
1163 LEAVE (" no currency");
1164 return FALSE;
1165 }
1166 if (!db->commodity_hash)
1167 {
1168 LEAVE (" no commodity hash");
1169 return FALSE;
1170 }
1171
1172 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1173 if (!currency_hash)
1174 {
1175 LEAVE (" no currency hash");
1176 return FALSE;
1177 }
1178
1179 qof_event_gen (&p->inst, QOF_EVENT_REMOVE, nullptr);
1180 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
1181 gnc_price_ref(p);
1182 if (!gnc_price_list_remove(&price_list, p))
1183 {
1184 gnc_price_unref(p);
1185 LEAVE (" cannot remove price list");
1186 return FALSE;
1187 }
1188
1189 /* if the price list is empty, then remove this currency from the
1190 commodity hash */
1191 if (price_list)
1192 {
1193 g_hash_table_insert(currency_hash, currency, price_list);
1194 }
1195 else
1196 {
1197 g_hash_table_remove(currency_hash, currency);
1198
1199 if (cleanup)
1200 {
1201 /* chances are good that this commodity had only one currency.
1202 * If there are no currencies, we may as well destroy the
1203 * commodity too. */
1204 guint num_currencies = g_hash_table_size (currency_hash);
1205 if (0 == num_currencies)
1206 {
1207 g_hash_table_remove (db->commodity_hash, commodity);
1208 g_hash_table_destroy (currency_hash);
1209 }
1210 }
1211 }
1212
1213 gnc_price_unref(p);
1214 LEAVE ("db=%p, pr=%p", db, p);
1215 return TRUE;
1216}
1217
1218gboolean
1219gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
1220{
1221 gboolean rc;
1222 char datebuff[MAX_DATE_LENGTH + 1];
1223 memset(datebuff, 0, sizeof(datebuff));
1224 if (!db || !p) return FALSE;
1225 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1228
1229 gnc_price_ref(p);
1230 qof_print_date_buff(datebuff, sizeof(datebuff), gnc_price_get_time64 (p));
1231 DEBUG("Remove Date is %s, Commodity is %s, Source is %s", datebuff,
1232 gnc_commodity_get_fullname (gnc_price_get_commodity (p)),
1233 gnc_price_get_source_string (p));
1234
1235 rc = remove_price (db, p, TRUE);
1237 qof_instance_set_dirty(&db->inst);
1239
1240 /* invoke the backend to delete this price */
1241 gnc_price_begin_edit (p);
1242 qof_instance_set_destroying(p, TRUE);
1243 gnc_price_commit_edit (p);
1244 p->db = nullptr;
1245 gnc_price_unref(p);
1246 LEAVE ("db=%p, pr=%p", db, p);
1247 return rc;
1248}
1249
1250typedef struct
1251{
1252 GNCPriceDB *db;
1253 time64 cutoff;
1254 gboolean delete_fq;
1255 gboolean delete_user;
1256 gboolean delete_app;
1257 GSList *list;
1258} remove_info;
1259
1260static gboolean
1261check_one_price_date (GNCPrice *price, gpointer user_data)
1262{
1263 auto data = static_cast<remove_info*>(user_data);
1264 PriceSource source;
1265 time64 time;
1266
1267 ENTER("price %p (%s), data %p", price,
1268 gnc_commodity_get_mnemonic(gnc_price_get_commodity(price)),
1269 user_data);
1270
1271 source = gnc_price_get_source (price);
1272
1273 if ((source == PRICE_SOURCE_FQ) && data->delete_fq)
1274 PINFO ("Delete Quote Source");
1275 else if ((source == PRICE_SOURCE_USER_PRICE) && data->delete_user)
1276 PINFO ("Delete User Source");
1277 else if ((source != PRICE_SOURCE_FQ) && (source != PRICE_SOURCE_USER_PRICE) && data->delete_app)
1278 PINFO ("Delete App Source");
1279 else
1280 {
1281 LEAVE("Not a matching source");
1282 return TRUE;
1283 }
1284
1285 time = gnc_price_get_time64 (price);
1286 {
1287 gchar buf[40];
1288 gnc_time64_to_iso8601_buff(time, buf);
1289 DEBUG("checking date %s", buf);
1290 }
1291 if (time < data->cutoff)
1292 {
1293 data->list = g_slist_prepend(data->list, price);
1294 DEBUG("will delete");
1295 }
1296 LEAVE(" ");
1297 return TRUE;
1298}
1299
1300static void
1301pricedb_remove_foreach_pricelist (gpointer key,
1302 gpointer val,
1303 gpointer user_data)
1304{
1305 GList *price_list = (GList *) val;
1306 GList *node = price_list;
1307 remove_info *data = (remove_info *) user_data;
1308
1309 ENTER("key %p, value %p, data %p", key, val, user_data);
1310
1311 /* now check each item in the list */
1312 g_list_foreach(node, (GFunc)check_one_price_date, data);
1313
1314 LEAVE(" ");
1315}
1316
1317static gint
1318compare_prices_by_commodity_date (gconstpointer a, gconstpointer b)
1319{
1320 time64 time_a, time_b;
1321 gnc_commodity *comma;
1322 gnc_commodity *commb;
1323 gnc_commodity *curra;
1324 gnc_commodity *currb;
1325 gint result;
1326
1327 if (!a && !b) return 0;
1328 /* nothing is always less than something */
1329 if (!a) return -1;
1330 if (!b) return 1;
1331
1332 comma = gnc_price_get_commodity ((GNCPrice *) a);
1333 commb = gnc_price_get_commodity ((GNCPrice *) b);
1334
1335 if (!gnc_commodity_equal(comma, commb))
1336 return gnc_commodity_compare(comma, commb);
1337
1338 curra = gnc_price_get_currency ((GNCPrice *) a);
1339 currb = gnc_price_get_currency ((GNCPrice *) b);
1340
1341 if (!gnc_commodity_equal(curra, currb))
1342 return gnc_commodity_compare(curra, currb);
1343
1344 time_a = gnc_price_get_time64((GNCPrice *) a);
1345 time_b = gnc_price_get_time64((GNCPrice *) b);
1346
1347 /* Note we return -1 if time_b is before time_a. */
1348 result = time64_cmp(time_b, time_a);
1349 if (result) return result;
1350
1351 /* For a stable sort */
1352 return guid_compare (gnc_price_get_guid((GNCPrice *) a),
1353 gnc_price_get_guid((GNCPrice *) b));
1354}
1355
1356static gboolean
1357price_commodity_and_currency_equal (GNCPrice *a, GNCPrice *b)
1358{
1359 gboolean ret_comm = FALSE;
1360 gboolean ret_curr = FALSE;
1361
1362 if (gnc_commodity_equal (gnc_price_get_commodity(a), gnc_price_get_commodity (b)))
1363 ret_comm = TRUE;
1364
1365 if (gnc_commodity_equal (gnc_price_get_currency(a), gnc_price_get_currency (b)))
1366 ret_curr = TRUE;
1367
1368 return (ret_comm && ret_curr);
1369}
1370
1371static void
1372gnc_pricedb_remove_old_prices_pinfo (GNCPrice *price, gboolean keep_message)
1373{
1374 GDate price_date = time64_to_gdate (gnc_price_get_time64 (price));
1375 char date_buf[MAX_DATE_LENGTH+1];
1376
1377 if (g_date_valid (&price_date))
1378 {
1379 qof_print_gdate (date_buf, MAX_DATE_LENGTH, &price_date);
1380
1381 if (keep_message)
1382 {
1383 PINFO("#### Keep price with date %s, commodity is %s, currency is %s", date_buf,
1384 gnc_commodity_get_printname(gnc_price_get_commodity(price)),
1385 gnc_commodity_get_printname(gnc_price_get_currency(price)));
1386 }
1387 else
1388 PINFO("## Remove price with date %s", date_buf);
1389 }
1390 else
1391 PINFO("Keep price date is invalid");
1392}
1393
1394static void
1395clone_price (GNCPrice **price, GNCPrice *source_price)
1396{
1397 QofBook *book;
1398
1399 if (!source_price) return;
1400 if (price == nullptr) return;
1401
1402 book = qof_instance_get_book (QOF_INSTANCE(source_price));
1403
1404 if (*price)
1405 gnc_price_unref (*price);
1406
1407 *price = gnc_price_clone (source_price, book);
1408
1409 gnc_pricedb_remove_old_prices_pinfo (source_price, TRUE);
1410}
1411
1412static gint
1413roundUp (gint numToRound, gint multiple)
1414{
1415 gint remainder;
1416
1417 if (multiple == 0)
1418 return numToRound;
1419
1420 remainder = numToRound % multiple;
1421 if (remainder == 0)
1422 return numToRound;
1423
1424 return numToRound + multiple - remainder;
1425}
1426
1427static gint
1428get_fiscal_quarter (GDate *date, GDateMonth fiscal_start)
1429{
1430 GDateMonth month = g_date_get_month (date);
1431
1432 gint q = ((roundUp (22 - fiscal_start + month, 3)/3) % 4) + 1;
1433
1434 PINFO("Return fiscal quarter is %d", q);
1435 return q;
1436}
1437
1438static void
1439gnc_pricedb_process_removal_list (GNCPriceDB *db, GDate *fiscal_end_date,
1440 remove_info data, PriceRemoveKeepOptions keep)
1441{
1442 GSList *item;
1443 gboolean save_first_price = FALSE;
1444 gint saved_test_value = 0, next_test_value = 0;
1445 GNCPrice *cloned_price = nullptr;
1446 GDateMonth fiscal_month_start;
1447 GDate *tmp_date = g_date_new_dmy (g_date_get_day (fiscal_end_date),
1448 g_date_get_month (fiscal_end_date),
1449 g_date_get_year (fiscal_end_date));
1450
1451 // get the fiscal start month
1452 g_date_subtract_months (tmp_date, 12);
1453 fiscal_month_start = static_cast<GDateMonth>(g_date_get_month (tmp_date) + 1);
1454 g_date_free (tmp_date);
1455
1456 // sort the list by commodity / currency / date
1457 data.list = g_slist_sort (data.list, compare_prices_by_commodity_date);
1458
1459 /* Now run this external list deleting prices */
1460 for (item = data.list; item; item = g_slist_next(item))
1461 {
1462 GDate saved_price_date;
1463 GDate next_price_date;
1464 auto price = static_cast<GNCPrice*>(item->data);
1465
1466 // Keep None
1467 if (keep == PRICE_REMOVE_KEEP_NONE)
1468 {
1469 gnc_pricedb_remove_old_prices_pinfo (price, FALSE);
1470 gnc_pricedb_remove_price (db, price);
1471 continue;
1472 }
1473
1474 save_first_price = !price_commodity_and_currency_equal (price, cloned_price); // Not Equal
1475 if (save_first_price == TRUE)
1476 {
1477 clone_price (&cloned_price, price);
1478 continue;
1479 }
1480
1481 // get the price dates
1482 saved_price_date = time64_to_gdate (gnc_price_get_time64 (cloned_price));
1483 next_price_date = time64_to_gdate (gnc_price_get_time64 (price));
1484
1485 // Keep last price in fiscal year
1486 if (keep == PRICE_REMOVE_KEEP_LAST_PERIOD && save_first_price == FALSE)
1487 {
1488 GDate *saved_fiscal_end = g_date_new_dmy (g_date_get_day (&saved_price_date),
1489 g_date_get_month (&saved_price_date),
1490 g_date_get_year (&saved_price_date));
1491
1492 GDate *next_fiscal_end = g_date_new_dmy (g_date_get_day (&next_price_date),
1493 g_date_get_month (&next_price_date),
1494 g_date_get_year (&next_price_date));
1495
1496 gnc_gdate_set_fiscal_year_end (saved_fiscal_end, fiscal_end_date);
1497 gnc_gdate_set_fiscal_year_end (next_fiscal_end, fiscal_end_date);
1498
1499 saved_test_value = g_date_get_year (saved_fiscal_end);
1500 next_test_value = g_date_get_year (next_fiscal_end);
1501
1502 PINFO("Keep last price in fiscal year");
1503
1504 g_date_free (saved_fiscal_end);
1505 g_date_free (next_fiscal_end);
1506 }
1507
1508 // Keep last price in fiscal quarter
1509 if (keep == PRICE_REMOVE_KEEP_LAST_QUARTERLY && save_first_price == FALSE)
1510 {
1511 saved_test_value = get_fiscal_quarter (&saved_price_date, fiscal_month_start);
1512 next_test_value = get_fiscal_quarter (&next_price_date, fiscal_month_start);
1513
1514 PINFO("Keep last price in fiscal quarter");
1515 }
1516
1517 // Keep last price of every month
1518 if (keep == PRICE_REMOVE_KEEP_LAST_MONTHLY && save_first_price == FALSE)
1519 {
1520 saved_test_value = g_date_get_month (&saved_price_date);
1521 next_test_value = g_date_get_month (&next_price_date);
1522
1523 PINFO("Keep last price of every month");
1524 }
1525
1526 // Keep last price of every week
1527 if (keep == PRICE_REMOVE_KEEP_LAST_WEEKLY && save_first_price == FALSE)
1528 {
1529 saved_test_value = g_date_get_iso8601_week_of_year (&saved_price_date);
1530 next_test_value = g_date_get_iso8601_week_of_year (&next_price_date);
1531
1532 PINFO("Keep last price of every week");
1533 }
1534
1535 // Now compare the values
1536 if (saved_test_value == next_test_value)
1537 {
1538 gnc_pricedb_remove_old_prices_pinfo (price, FALSE);
1539 gnc_pricedb_remove_price (db, price);
1540 }
1541 else
1542 clone_price (&cloned_price, price);
1543 }
1544 if (cloned_price)
1545 gnc_price_unref (cloned_price);
1546}
1547
1548gboolean
1549gnc_pricedb_remove_old_prices (GNCPriceDB *db, GList *comm_list,
1550 GDate *fiscal_end_date, time64 cutoff,
1551 PriceRemoveSourceFlags source,
1552 PriceRemoveKeepOptions keep)
1553{
1554 remove_info data;
1555 GList *node;
1556 char datebuff[MAX_DATE_LENGTH + 1];
1557 memset (datebuff, 0, sizeof(datebuff));
1558
1559 data.db = db;
1560 data.cutoff = cutoff;
1561 data.list = nullptr;
1562 data.delete_fq = FALSE;
1563 data.delete_user = FALSE;
1564 data.delete_app = FALSE;
1565
1566 ENTER("Remove Prices for Source %d, keeping %d", source, keep);
1567
1568 // setup the source options
1569 if (source & PRICE_REMOVE_SOURCE_APP)
1570 data.delete_app = TRUE;
1571
1572 if (source & PRICE_REMOVE_SOURCE_FQ)
1573 data.delete_fq = TRUE;
1574
1575 if (source & PRICE_REMOVE_SOURCE_USER)
1576 data.delete_user = TRUE;
1577
1578 // Walk the list of commodities
1579 for (node = g_list_first (comm_list); node; node = g_list_next (node))
1580 {
1581 auto currencies_hash = static_cast<GHashTable*>(g_hash_table_lookup (db->commodity_hash, node->data));
1582 g_hash_table_foreach (currencies_hash, pricedb_remove_foreach_pricelist, &data);
1583 }
1584
1585 if (data.list == nullptr)
1586 {
1587 LEAVE("Empty price list");
1588 return FALSE;
1589 }
1590 qof_print_date_buff (datebuff, sizeof(datebuff), cutoff);
1591 DEBUG("Number of Prices in list is %d, Cutoff date is %s",
1592 g_slist_length (data.list), datebuff);
1593
1594 // Check for a valid fiscal end of year date
1595 if (fiscal_end_date == nullptr || !g_date_valid (fiscal_end_date))
1596 {
1597 auto ymd = GncDate().year_month_day();
1598 GDate end_this_year;
1599 g_date_set_dmy (&end_this_year, 31, GDateMonth(12), ymd.year);
1600 gnc_pricedb_process_removal_list (db, &end_this_year, data, keep);
1601 }
1602 else
1603 gnc_pricedb_process_removal_list (db, fiscal_end_date, data, keep);
1604
1605 g_slist_free (data.list);
1606 LEAVE(" ");
1607 return TRUE;
1608}
1609
1610/* ==================================================================== */
1611/* lookup/query functions */
1612
1613static PriceList *pricedb_price_list_merge (PriceList *a, PriceList *b);
1614
1615static void
1616hash_values_helper(gpointer key, gpointer value, gpointer data)
1617{
1618 auto l = static_cast<GList**>(data);
1619 if (*l)
1620 {
1621 GList *new_l;
1622 new_l = pricedb_price_list_merge(*l, static_cast<PriceList*>(value));
1623 g_list_free (*l);
1624 *l = new_l;
1625 }
1626 else
1627 *l = g_list_copy (static_cast<GList*>(value));
1628}
1629
1630static PriceList *
1631price_list_from_hashtable (GHashTable *hash, const gnc_commodity *currency)
1632{
1633 GList *price_list = nullptr, *result = nullptr ;
1634 if (currency)
1635 {
1636 price_list = static_cast<GList*>(g_hash_table_lookup(hash, currency));
1637 if (!price_list)
1638 {
1639 LEAVE (" no price list");
1640 return nullptr;
1641 }
1642 result = g_list_copy (price_list);
1643 }
1644 else
1645 {
1646 g_hash_table_foreach(hash, hash_values_helper, (gpointer)&result);
1647 }
1648 return result;
1649}
1650
1651static PriceList *
1652pricedb_price_list_merge (PriceList *a, PriceList *b)
1653{
1654 PriceList *merged_list = nullptr;
1655 GList *next_a = a;
1656 GList *next_b = b;
1657
1658 while (next_a || next_b)
1659 {
1660 if (next_a == nullptr)
1661 {
1662 merged_list = g_list_prepend (merged_list, next_b->data);
1663 next_b = next_b->next;
1664 }
1665 else if (next_b == nullptr)
1666 {
1667 merged_list = g_list_prepend (merged_list, next_a->data);
1668 next_a = next_a->next;
1669 }
1670 /* We're building the list in reverse order so reverse the comparison. */
1671 else if (compare_prices_by_date (next_a->data, next_b->data) < 0)
1672 {
1673 merged_list = g_list_prepend (merged_list, next_a->data);
1674 next_a = next_a->next;
1675 }
1676 else
1677 {
1678 merged_list = g_list_prepend (merged_list, next_b->data);
1679 next_b = next_b->next;
1680 }
1681 }
1682 return g_list_reverse (merged_list);
1683}
1684
1685static PriceList*
1686pricedb_get_prices_internal(GNCPriceDB *db, const gnc_commodity *commodity,
1687 const gnc_commodity *currency, gboolean bidi)
1688{
1689 GHashTable *forward_hash = nullptr, *reverse_hash = nullptr;
1690 PriceList *forward_list = nullptr, *reverse_list = nullptr;
1691 g_return_val_if_fail (db != nullptr, nullptr);
1692 g_return_val_if_fail (commodity != nullptr, nullptr);
1693 forward_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1694 if (currency && bidi)
1695 reverse_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, currency));
1696 if (!forward_hash && !reverse_hash)
1697 {
1698 LEAVE (" no currency hash");
1699 return nullptr;
1700 }
1701 if (forward_hash)
1702 forward_list = price_list_from_hashtable (forward_hash, currency);
1703 if (currency && reverse_hash)
1704 {
1705 reverse_list = price_list_from_hashtable (reverse_hash, commodity);
1706 if (reverse_list)
1707 {
1708 if (forward_list)
1709 {
1710 /* Since we have a currency both lists are a direct copy of a price
1711 list in the price DB. This means the lists are already sorted
1712 from newest to oldest and we can just merge them together. This
1713 is substantially faster than concatenating them and sorting the
1714 resulting list. */
1715 PriceList *merged_list;
1716 merged_list = pricedb_price_list_merge (forward_list, reverse_list);
1717 g_list_free (forward_list);
1718 g_list_free (reverse_list);
1719 forward_list = merged_list;
1720 }
1721 else
1722 {
1723 forward_list = reverse_list;
1724 }
1725 }
1726 }
1727
1728 return forward_list;
1729}
1730
1731GNCPrice *gnc_pricedb_lookup_latest(GNCPriceDB *db,
1732 const gnc_commodity *commodity,
1733 const gnc_commodity *currency)
1734{
1735 GList *price_list;
1736 GNCPrice *result;
1737
1738 if (!db || !commodity || !currency) return nullptr;
1739 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
1740
1741 price_list = pricedb_get_prices_internal(db, commodity, currency, TRUE);
1742 if (!price_list) return nullptr;
1743 /* This works magically because prices are inserted in date-sorted
1744 * order, and the latest date always comes first. So return the
1745 * first in the list. */
1746 result = static_cast<GNCPrice*>(price_list->data);
1747 gnc_price_ref(result);
1748 g_list_free (price_list);
1749 LEAVE("price is %p", result);
1750 return result;
1751}
1752
1753typedef struct
1754{
1755 GList **list;
1756 const gnc_commodity *com;
1757 time64 t;
1759
1760/* price_list_scan_any_currency is the helper function used with
1761 * pricedb_pricelist_traversal by the "any_currency" price lookup functions. It
1762 * builds a list of prices that are either to or from the commodity "com".
1763 * The resulting list will include the last price newer than "t" and the first
1764 * price older than "t". All other prices will be ignored. Since in the most
1765 * common cases we will be looking for recent prices which are at the front of
1766 * the various price lists, this is considerably faster than concatenating all
1767 * the relevant price lists and sorting the result.
1768*/
1769
1770static gboolean
1771price_list_scan_any_currency(GList *price_list, gpointer data)
1772{
1773 UsesCommodity *helper = (UsesCommodity*)data;
1774 gnc_commodity *com;
1775 gnc_commodity *cur;
1776
1777 if (!price_list)
1778 return TRUE;
1779
1780 auto price = static_cast<GNCPrice*>(price_list->data);
1781 com = gnc_price_get_commodity(price);
1782 cur = gnc_price_get_currency(price);
1783
1784 /* if this price list isn't for the commodity we are interested in,
1785 ignore it. */
1786 if (com != helper->com && cur != helper->com)
1787 return TRUE;
1788
1789 /* The price list is sorted in decreasing order of time. Find the first
1790 price on it that is older than the requested time and add it and the
1791 previous price to the result list. */
1792 for (auto node = price_list; node; node = g_list_next (node))
1793 {
1794 price = static_cast<GNCPrice*>(node->data);
1795 time64 price_t = gnc_price_get_time64(price);
1796 if (price_t < helper->t)
1797 {
1798 /* If there is a previous price add it to the results. */
1799 if (node->prev)
1800 {
1801 auto prev_price = static_cast<GNCPrice*>(node->prev->data);
1802 gnc_price_ref(prev_price);
1803 *helper->list = g_list_prepend(*helper->list, prev_price);
1804 }
1805 /* Add the first price before the desired time */
1806 gnc_price_ref(price);
1807 *helper->list = g_list_prepend(*helper->list, price);
1808 /* No point in looking further, they will all be older */
1809 break;
1810 }
1811 else if (node->next == nullptr)
1812 {
1813 /* The last price is later than given time, add it */
1814 gnc_price_ref(price);
1815 *helper->list = g_list_prepend(*helper->list, price);
1816 }
1817 }
1818
1819 return TRUE;
1820}
1821
1822/* This operates on the principal that the prices are sorted by date and that we
1823 * want only the first one before the specified time containing both the target
1824 * and some other commodity. */
1825static PriceList*
1826latest_before (PriceList *prices, const gnc_commodity* target, time64 t)
1827{
1828 GList *node, *found_coms = nullptr, *retval = nullptr;
1829 for (node = prices; node != nullptr; node = g_list_next(node))
1830 {
1831 GNCPrice *price = (GNCPrice*)node->data;
1832 gnc_commodity *com = gnc_price_get_commodity(price);
1833 gnc_commodity *cur = gnc_price_get_currency(price);
1834 time64 price_t = gnc_price_get_time64(price);
1835
1836 if (t < price_t ||
1837 (com == target && g_list_find (found_coms, cur)) ||
1838 (cur == target && g_list_find (found_coms, com)))
1839 continue;
1840
1841 gnc_price_ref (price);
1842 retval = g_list_prepend (retval, price);
1843 found_coms = g_list_prepend (found_coms, com == target ? cur : com);
1844 }
1845 g_list_free (found_coms);
1846 return g_list_reverse(retval);
1847}
1848
1849static GNCPrice**
1850find_comtime(GPtrArray* array, gnc_commodity *com)
1851{
1852 unsigned int index = 0;
1853 GNCPrice** retval = nullptr;
1854 for (index = 0; index < array->len; ++index)
1855 {
1856 auto price_p = static_cast<GNCPrice**>(g_ptr_array_index(array, index));
1857 if (gnc_price_get_commodity(*price_p) == com ||
1858 gnc_price_get_currency(*price_p) == com)
1859 retval = price_p;
1860 }
1861 return retval;
1862}
1863
1864static GList*
1865add_nearest_price(GList *target_list, GPtrArray *price_array, GNCPrice *price,
1866 const gnc_commodity *target, time64 t)
1867{
1868 gnc_commodity *com = gnc_price_get_commodity(price);
1869 gnc_commodity *cur = gnc_price_get_currency(price);
1870 time64 price_t = gnc_price_get_time64(price);
1871 gnc_commodity *other = com == target ? cur : com;
1872 GNCPrice **com_price = find_comtime(price_array, other);
1873 time64 com_t;
1874 if (com_price == nullptr)
1875 {
1876 com_price = (GNCPrice**)g_slice_new(gpointer);
1877 *com_price = price;
1878 g_ptr_array_add(price_array, com_price);
1879 /* If the first price we see for this commodity is not newer than
1880 the target date add it to the return list. */
1881 if (price_t <= t)
1882 {
1883 gnc_price_ref(price);
1884 target_list = g_list_prepend(target_list, price);
1885 }
1886 return target_list;
1887 }
1888 com_t = gnc_price_get_time64(*com_price);
1889 if (com_t <= t)
1890 /* No point in checking any more prices, they'll all be further from
1891 * t. */
1892 return target_list;
1893 if (price_t > t)
1894 /* The price list is sorted newest->oldest, so as long as this price
1895 * is newer than t then it should replace the saved one. */
1896 {
1897 *com_price = price;
1898 }
1899 else
1900 {
1901 time64 com_diff = com_t - t;
1902 time64 price_diff = t - price_t;
1903 if (com_diff < price_diff)
1904 {
1905 gnc_price_ref(*com_price);
1906 target_list = g_list_prepend(target_list, *com_price);
1907 }
1908 else
1909 {
1910 gnc_price_ref(price);
1911 target_list = g_list_prepend(target_list, price);
1912 }
1913 *com_price = price;
1914 }
1915 return target_list;
1916}
1917
1918static PriceList *
1919nearest_to (PriceList *prices, const gnc_commodity* target, time64 t)
1920{
1921 GList *node, *retval = nullptr;
1922 const guint prealloc_size = 5; /*More than 5 "other" is unlikely as long as
1923 * target isn't the book's default
1924 * currency. */
1925
1926
1927 GPtrArray *price_array = g_ptr_array_sized_new(prealloc_size);
1928 guint index;
1929 for (node = prices; node != nullptr; node = g_list_next(node))
1930 {
1931 GNCPrice *price = (GNCPrice*)node->data;
1932 retval = add_nearest_price(retval, price_array, price, target, t);
1933 }
1934 /* There might be some prices in price_array that are newer than t. Those
1935 * will be cases where there wasn't a price older than t to push one or the
1936 * other into the retval, so we need to get them now.
1937 */
1938 for (index = 0; index < price_array->len; ++index)
1939 {
1940 auto com_price = static_cast<GNCPrice**>(g_ptr_array_index(price_array, index));
1941 time64 price_t = gnc_price_get_time64(*com_price);
1942 if (price_t >= t)
1943 {
1944 gnc_price_ref(*com_price);
1945 retval = g_list_prepend(retval, *com_price);
1946 }
1947 }
1948 g_ptr_array_free(price_array, TRUE);
1949 return g_list_sort(retval, compare_prices_by_date);
1950}
1951
1952
1953
1954PriceList *
1956 const gnc_commodity *commodity)
1957{
1959 gnc_time(nullptr));
1960}
1961
1962PriceList *
1964 const gnc_commodity *commodity,
1965 time64 t)
1966{
1967 GList *prices = nullptr, *result;
1968 UsesCommodity helper = {&prices, commodity, t};
1969 result = nullptr;
1970
1971 if (!db || !commodity) return nullptr;
1972 ENTER ("db=%p commodity=%p", db, commodity);
1973
1974 pricedb_pricelist_traversal(db, price_list_scan_any_currency, &helper);
1975 prices = g_list_sort(prices, compare_prices_by_date);
1976 result = nearest_to(prices, commodity, t);
1977 gnc_price_list_destroy(prices);
1978 LEAVE(" ");
1979 return result;
1980}
1981
1982PriceList *
1984 const gnc_commodity *commodity,
1985 time64 t)
1986{
1987 GList *prices = nullptr, *result;
1988 UsesCommodity helper = {&prices, commodity, t};
1989 result = nullptr;
1990
1991 if (!db || !commodity) return nullptr;
1992 ENTER ("db=%p commodity=%p", db, commodity);
1993
1994 pricedb_pricelist_traversal(db, price_list_scan_any_currency,
1995 &helper);
1996 prices = g_list_sort(prices, compare_prices_by_date);
1997 result = latest_before(prices, commodity, t);
1998 gnc_price_list_destroy(prices);
1999 LEAVE(" ");
2000 return result;
2001}
2002
2003/* gnc_pricedb_has_prices is used explicitly for filtering cases where the
2004 * commodity is the left side of commodity->currency price, so it checks only in
2005 * that direction.
2006 */
2007gboolean
2009 const gnc_commodity *commodity,
2010 const gnc_commodity *currency)
2011{
2012 GList *price_list;
2013 GHashTable *currency_hash;
2014 gint size;
2015
2016 if (!db || !commodity) return FALSE;
2017 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
2018 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
2019 if (!currency_hash)
2020 {
2021 LEAVE("no, no currency_hash table");
2022 return FALSE;
2023 }
2024
2025 if (currency)
2026 {
2027 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
2028 if (price_list)
2029 {
2030 LEAVE("yes");
2031 return TRUE;
2032 }
2033 LEAVE("no, no price list");
2034 return FALSE;
2035 }
2036
2037 size = g_hash_table_size (currency_hash);
2038 LEAVE("%s", size > 0 ? "yes" : "no");
2039 return size > 0;
2040}
2041
2042
2043/* gnc_pricedb_get_prices is used to construct the tree in the Price Editor and
2044 * so needs to be single-direction.
2045 */
2046PriceList *
2048 const gnc_commodity *commodity,
2049 const gnc_commodity *currency)
2050{
2051 if (!db || !commodity) return nullptr;
2052 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
2053 auto result = pricedb_get_prices_internal (db, commodity, currency, FALSE);
2054 if (!result) return nullptr;
2055 g_list_foreach (result, (GFunc)gnc_price_ref, nullptr);
2056 LEAVE (" ");
2057 return result;
2058}
2059
2060/* Return the number of prices in the data base for the given commodity
2061 */
2062static void
2063price_count_helper(gpointer key, gpointer value, gpointer data)
2064{
2065 auto result = static_cast<int*>(data);
2066 auto price_list = static_cast<GList*>(value);
2067
2068 *result += g_list_length(price_list);
2069}
2070
2071int
2073 const gnc_commodity *c)
2074{
2075 int result = 0;
2076 GHashTable *currency_hash;
2077
2078 if (!db || !c) return 0;
2079 ENTER ("db=%p commodity=%p", db, c);
2080
2081 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, c));
2082 if (currency_hash)
2083 {
2084 g_hash_table_foreach(currency_hash, price_count_helper, (gpointer)&result);
2085 }
2086
2087 LEAVE ("count=%d", result);
2088 return result;
2089}
2090
2091/* Helper function for combining the price lists in gnc_pricedb_nth_price. */
2092static void
2093list_combine (gpointer element, gpointer data)
2094{
2095 GList *list = *(GList**)data;
2096 auto lst = static_cast<GList*>(element);
2097 if (list == nullptr)
2098 *(GList**)data = g_list_copy (lst);
2099 else
2100 {
2101 GList *new_list = g_list_concat (list, g_list_copy (lst));
2102 *(GList**)data = new_list;
2103 }
2104}
2105
2106/* This function is used by gnc-tree-model-price.c for iterating through the
2107 * prices when building or filtering the pricedb dialog's
2108 * GtkTreeView. gtk-tree-view-price.c sorts the results after it has obtained
2109 * the values so there's nothing gained by sorting. However, for very large
2110 * collections of prices in multiple currencies (here commodity is the one being
2111 * priced and currency the one in which the price is denominated; note that they
2112 * may both be currencies or not) just concatenating the price lists together
2113 * can be expensive because the receiving list must be traversed to obtain its
2114 * end. To avoid that cost n times we cache the commodity and merged price list.
2115 * Since this is a GUI-driven function there is no concern about concurrency.
2116 */
2117
2118GNCPrice *
2120 const gnc_commodity *c,
2121 const int n)
2122{
2123 static const gnc_commodity *last_c = nullptr;
2124 static GList *prices = nullptr;
2125
2126 GNCPrice *result = nullptr;
2127 GHashTable *currency_hash;
2128 g_return_val_if_fail (GNC_IS_COMMODITY (c), nullptr);
2129
2130 if (!db || !c || n < 0) return nullptr;
2131 ENTER ("db=%p commodity=%s index=%d", db, gnc_commodity_get_mnemonic(c), n);
2132
2133 if (last_c && prices && last_c == c && db->reset_nth_price_cache == FALSE)
2134 {
2135 result = static_cast<GNCPrice*>(g_list_nth_data (prices, n));
2136 LEAVE ("price=%p", result);
2137 return result;
2138 }
2139
2140 last_c = c;
2141
2142 if (prices)
2143 {
2144 g_list_free (prices);
2145 prices = nullptr;
2146 }
2147
2148 db->reset_nth_price_cache = FALSE;
2149
2150 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup (db->commodity_hash, c));
2151 if (currency_hash)
2152 {
2153 GList *currencies = g_hash_table_get_values (currency_hash);
2154 g_list_foreach (currencies, list_combine, &prices);
2155 result = static_cast<GNCPrice*>(g_list_nth_data (prices, n));
2156 g_list_free (currencies);
2157 }
2158
2159 LEAVE ("price=%p", result);
2160 return result;
2161}
2162
2163void
2164gnc_pricedb_nth_price_reset_cache (GNCPriceDB *db)
2165{
2166 if (db)
2167 db->reset_nth_price_cache = TRUE;
2168}
2169
2170GNCPrice *
2172 const gnc_commodity *c,
2173 const gnc_commodity *currency,
2174 time64 t)
2175{
2176 return lookup_nearest_in_time(db, c, currency, t, TRUE);
2177}
2178
2179static GNCPrice *
2180lookup_nearest_in_time(GNCPriceDB *db,
2181 const gnc_commodity *c,
2182 const gnc_commodity *currency,
2183 time64 t,
2184 gboolean sameday)
2185{
2186 GList *price_list;
2187 GNCPrice *current_price = nullptr;
2188 GNCPrice *next_price = nullptr;
2189 GNCPrice *result = nullptr;
2190
2191 if (!db || !c || !currency) return nullptr;
2192 if (t == INT64_MAX) return nullptr;
2193 ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2194 price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2195 if (!price_list) return nullptr;
2196
2197 /* default answer */
2198 current_price = static_cast<GNCPrice*>(price_list->data);
2199
2200 /* find the first candidate past the one we want. Remember that
2201 prices are in most-recent-first order. */
2202 for (auto item = price_list; item; item = g_list_next (item))
2203 {
2204 auto p = static_cast<GNCPrice*>(item->data);
2205 time64 price_time = gnc_price_get_time64(p);
2206 if (price_time <= t)
2207 {
2208 next_price = static_cast<GNCPrice*>(item->data);
2209 break;
2210 }
2211 current_price = static_cast<GNCPrice*>(item->data);
2212 }
2213
2214 if (current_price) /* How can this be null??? */
2215 {
2216 if (!next_price)
2217 {
2218 /* It's earlier than the last price on the list */
2219 result = current_price;
2220 if (sameday)
2221 {
2222 /* Must be on the same day. */
2223 time64 price_day;
2224 time64 t_day;
2225 price_day = time64CanonicalDayTime(gnc_price_get_time64(current_price));
2226 t_day = time64CanonicalDayTime(t);
2227 if (price_day != t_day)
2228 result = nullptr;
2229 }
2230 }
2231 else
2232 {
2233 /* If the requested time is not earlier than the first price on the
2234 list, then current_price and next_price will be the same. */
2235 time64 current_t = gnc_price_get_time64(current_price);
2236 time64 next_t = gnc_price_get_time64(next_price);
2237 time64 diff_current = current_t - t;
2238 time64 diff_next = next_t - t;
2239 time64 abs_current = llabs(diff_current);
2240 time64 abs_next = llabs(diff_next);
2241
2242 if (sameday)
2243 {
2244 /* Result must be on same day, see if either of the two isn't */
2245 time64 t_day = time64CanonicalDayTime(t);
2246 time64 current_day = time64CanonicalDayTime(current_t);
2247 time64 next_day = time64CanonicalDayTime(next_t);
2248 if (current_day == t_day)
2249 {
2250 if (next_day == t_day)
2251 {
2252 /* Both on same day, return nearest */
2253 if (abs_current < abs_next)
2254 result = current_price;
2255 else
2256 result = next_price;
2257 }
2258 else
2259 /* current_price on same day, next_price not */
2260 result = current_price;
2261 }
2262 else if (next_day == t_day)
2263 /* next_price on same day, current_price not */
2264 result = next_price;
2265 }
2266 else
2267 {
2268 /* Choose the price that is closest to the given time. In case of
2269 * a tie, prefer the older price since it actually existed at the
2270 * time. (This also fixes bug #541970.) */
2271 if (abs_current < abs_next)
2272 {
2273 result = current_price;
2274 }
2275 else
2276 {
2277 result = next_price;
2278 }
2279 }
2280 }
2281 }
2282
2283 gnc_price_ref(result);
2284 g_list_free (price_list);
2285 LEAVE (" ");
2286 return result;
2287}
2288
2289GNCPrice *
2291 const gnc_commodity *c,
2292 const gnc_commodity *currency,
2293 time64 t)
2294{
2295 return lookup_nearest_in_time(db, c, currency, t, FALSE);
2296}
2297
2298// return 0 if price's time is less or equal to time
2299static int price_time64_less_or_equal (GNCPrice *p, time64 *time)
2300{
2301 return !(gnc_price_get_time64 (p) <= *time);
2302}
2303
2304GNCPrice *
2306 const gnc_commodity *c,
2307 const gnc_commodity *currency,
2308 time64 t)
2309{
2310 GNCPrice *current_price = nullptr;
2311 if (!db || !c || !currency) return nullptr;
2312 ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2313 auto price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2314 if (!price_list) return nullptr;
2315 auto p = g_list_find_custom (price_list, &t, (GCompareFunc)price_time64_less_or_equal);
2316 if (p)
2317 {
2318 current_price = GNC_PRICE (p->data);
2319 gnc_price_ref (current_price);
2320 }
2321 g_list_free (price_list);
2322 LEAVE (" ");
2323 return current_price;
2324}
2325
2326
2327typedef struct
2328{
2329 GNCPrice *from;
2330 GNCPrice *to;
2331} PriceTuple;
2332
2333static PriceTuple
2334extract_common_prices (PriceList *from_prices, PriceList *to_prices,
2335 const gnc_commodity *from, const gnc_commodity *to)
2336{
2337 PriceTuple retval = {nullptr, nullptr};
2338 GList *from_node = nullptr, *to_node = nullptr;
2339 GNCPrice *from_price = nullptr, *to_price = nullptr;
2340
2341 for (from_node = from_prices; from_node != nullptr;
2342 from_node = g_list_next(from_node))
2343 {
2344 for (to_node = to_prices; to_node != nullptr;
2345 to_node = g_list_next(to_node))
2346 {
2347 gnc_commodity *to_com, *to_cur;
2348 gnc_commodity *from_com, *from_cur;
2349 to_price = GNC_PRICE(to_node->data);
2350 from_price = GNC_PRICE(from_node->data);
2351 to_com = gnc_price_get_commodity (to_price);
2352 to_cur = gnc_price_get_currency (to_price);
2353 from_com = gnc_price_get_commodity (from_price);
2354 from_cur = gnc_price_get_currency (from_price);
2355 if (((to_com == from_com || to_com == from_cur) &&
2356 (to_com != from && to_com != to)) ||
2357 ((to_cur == from_com || to_cur == from_cur) &&
2358 (to_cur != from && to_cur != to)))
2359 break;
2360 to_price = nullptr;
2361 from_price = nullptr;
2362 }
2363 if (to_price != nullptr && from_price != nullptr)
2364 break;
2365 }
2366 if (from_price == nullptr || to_price == nullptr)
2367 return retval;
2368 gnc_price_ref(from_price);
2369 gnc_price_ref(to_price);
2370 retval.from = from_price;
2371 retval.to = to_price;
2372 return retval;
2373}
2374
2375static gnc_numeric
2376convert_price (const gnc_commodity *from, const gnc_commodity *to, PriceTuple tuple)
2377{
2378 gnc_commodity *p1_com = gnc_price_get_commodity (tuple.from);
2379 gnc_commodity *p1_cur = gnc_price_get_currency (tuple.from);
2380 gnc_commodity *p2_com = gnc_price_get_commodity (tuple.to);
2381 gnc_commodity *p2_cur = gnc_price_get_currency (tuple.to);
2382 gnc_numeric p1_val = gnc_price_get_value (tuple.from);
2383 gnc_numeric p2_val = gnc_price_get_value (tuple.to);
2384
2385 gnc_price_unref (tuple.from);
2386 gnc_price_unref (tuple.to);
2387
2388 if ((p1_com == from && p2_com == to) ||
2389 (p1_cur == from && p2_cur == to))
2390 {
2391 auto price = gnc_numeric_div (p2_val, p1_val, GNC_DENOM_AUTO,
2392 GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
2393
2394 if (p1_cur == from)
2395 return price;
2396
2397 return gnc_numeric_invert (price);
2398 }
2399
2400 auto price = gnc_numeric_mul (p1_val, p2_val, GNC_DENOM_AUTO,
2401 GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
2402 if (p1_cur == from)
2403 return gnc_numeric_invert (price);
2404
2405 return price;
2406}
2407
2408static gnc_numeric
2409indirect_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2410 const gnc_commodity *to, time64 t, gboolean before_date)
2411{
2412 GList *from_prices = nullptr, *to_prices = nullptr;
2413 PriceTuple tuple;
2414 gnc_numeric zero = gnc_numeric_zero();
2415 if (!from || !to)
2416 return zero;
2417 if (t == INT64_MAX)
2418 {
2419 from_prices = gnc_pricedb_lookup_latest_any_currency(db, from);
2420 /* "to" is often the book currency which may have lots of prices,
2421 so avoid getting them if they aren't needed. */
2422 if (from_prices)
2423 to_prices = gnc_pricedb_lookup_latest_any_currency(db, to);
2424 }
2425 else if (before_date)
2426 {
2427 from_prices = gnc_pricedb_lookup_nearest_before_any_currency_t64 (db, from, t);
2428 if (from_prices)
2430 }
2431 else
2432 {
2433 from_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, from, t);
2434 if (from_prices)
2436 }
2437 if (!from_prices || !to_prices)
2438 {
2439 gnc_price_list_destroy (from_prices);
2440 gnc_price_list_destroy (to_prices);
2441 return zero;
2442 }
2443 tuple = extract_common_prices (from_prices, to_prices, from, to);
2444 gnc_price_list_destroy (from_prices);
2445 gnc_price_list_destroy (to_prices);
2446 if (tuple.from)
2447 return convert_price (from, to, tuple);
2448 return zero;
2449}
2450
2451
2452static gnc_numeric
2453direct_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2454 const gnc_commodity *to, time64 t, gboolean before_date)
2455{
2456 GNCPrice *price;
2457 gnc_numeric retval = gnc_numeric_zero();
2458
2459 if (!from || !to) return retval;
2460
2461 if (t == INT64_MAX)
2462 price = gnc_pricedb_lookup_latest(db, from, to);
2463 else if (before_date)
2464 price = gnc_pricedb_lookup_nearest_before_t64(db, from, to, t);
2465 else
2466 price = gnc_pricedb_lookup_nearest_in_time64(db, from, to, t);
2467
2468 if (!price) return retval;
2469
2470 retval = gnc_price_get_value (price);
2471
2472 if (gnc_price_get_commodity (price) != from)
2473 retval = gnc_numeric_invert (retval);
2474
2475 gnc_price_unref (price);
2476 return retval;
2477}
2478
2479static gnc_numeric
2480get_nearest_price (GNCPriceDB *pdb,
2481 const gnc_commodity *orig_curr,
2482 const gnc_commodity *new_curr,
2483 const time64 t,
2484 gboolean before)
2485{
2486 gnc_numeric price;
2487
2488 if (gnc_commodity_equiv (orig_curr, new_curr))
2489 return gnc_numeric_create (1, 1);
2490
2491 /* Look for a direct price. */
2492 price = direct_price_conversion (pdb, orig_curr, new_curr, t, before);
2493
2494 /*
2495 * no direct price found, try find a price in another currency
2496 */
2497 if (gnc_numeric_zero_p (price))
2498 price = indirect_price_conversion (pdb, orig_curr, new_curr, t, before);
2499
2500 return gnc_numeric_reduce (price);
2501}
2502
2503gnc_numeric
2505 const gnc_commodity *orig_currency,
2506 const gnc_commodity *new_currency,
2507 const time64 t)
2508{
2509 return get_nearest_price (pdb, orig_currency, new_currency, t, TRUE);
2510}
2511
2512gnc_numeric
2514 const gnc_commodity *orig_currency,
2515 const gnc_commodity *new_currency,
2516 const time64 t)
2517{
2518 return get_nearest_price (pdb, orig_currency, new_currency, t, FALSE);
2519}
2520
2521gnc_numeric
2523 const gnc_commodity *orig_currency,
2524 const gnc_commodity *new_currency)
2525{
2526 return get_nearest_price (pdb, orig_currency, new_currency, INT64_MAX, FALSE);
2527}
2528
2529static gnc_numeric
2530convert_amount_at_date (GNCPriceDB *pdb,
2531 gnc_numeric amount,
2532 const gnc_commodity *orig_currency,
2533 const gnc_commodity *new_currency,
2534 const time64 t,
2535 gboolean before_date)
2536{
2537 gnc_numeric price;
2538
2539 if (gnc_numeric_zero_p (amount))
2540 return amount;
2541
2542 price = get_nearest_price (pdb, orig_currency, new_currency, t, before_date);
2543
2544 /* the price retrieved may be invalid. return zero. see 798015 */
2545 if (gnc_numeric_check (price))
2546 return gnc_numeric_zero ();
2547
2548 return gnc_numeric_mul
2549 (amount, price, gnc_commodity_get_fraction (new_currency),
2550 GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND);
2551}
2552
2553/*
2554 * Convert a balance from one currency to another.
2555 */
2556gnc_numeric
2558 gnc_numeric balance,
2559 const gnc_commodity *balance_currency,
2560 const gnc_commodity *new_currency)
2561{
2562 return convert_amount_at_date
2563 (pdb, balance, balance_currency, new_currency, INT64_MAX, FALSE);
2564}
2565
2566gnc_numeric
2568 gnc_numeric balance,
2569 const gnc_commodity *balance_currency,
2570 const gnc_commodity *new_currency,
2571 time64 t)
2572{
2573 return convert_amount_at_date
2574 (pdb, balance, balance_currency, new_currency, t, FALSE);
2575}
2576
2577gnc_numeric
2579 gnc_numeric balance,
2580 const gnc_commodity *balance_currency,
2581 const gnc_commodity *new_currency,
2582 time64 t)
2583{
2584 return convert_amount_at_date
2585 (pdb, balance, balance_currency, new_currency, t, TRUE);
2586}
2587
2588/* ==================================================================== */
2589/* gnc_pricedb_foreach_price infrastructure
2590 */
2591
2592typedef struct
2593{
2594 gboolean ok;
2595 gboolean (*func)(GNCPrice *p, gpointer user_data);
2596 gpointer user_data;
2598
2599static void
2600pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2601{
2602 GList *price_list = (GList *) val;
2603 GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
2604
2605 /* stop traversal when func returns FALSE */
2606 foreach_data->ok = g_list_find_custom (price_list, foreach_data->user_data, (GCompareFunc)foreach_data->func)
2607 != nullptr;
2608}
2609
2610static void
2611pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2612{
2613 GHashTable *currencies_hash = (GHashTable *) val;
2614 g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
2615}
2616
2617static gboolean
2618unstable_price_traversal(GNCPriceDB *db,
2619 gboolean (*f)(GNCPrice *p, gpointer user_data),
2620 gpointer user_data)
2621{
2622 GNCPriceDBForeachData foreach_data;
2623
2624 if (!db || !f) return FALSE;
2625 foreach_data.ok = TRUE;
2626 foreach_data.func = f;
2627 foreach_data.user_data = user_data;
2628 if (db->commodity_hash == nullptr)
2629 {
2630 return FALSE;
2631 }
2632 g_hash_table_foreach(db->commodity_hash,
2633 pricedb_foreach_currencies_hash,
2634 &foreach_data);
2635
2636 return foreach_data.ok;
2637}
2638
2639/* foreach_pricelist */
2640typedef struct
2641{
2642 gboolean ok;
2643 gboolean (*func)(GList *p, gpointer user_data);
2644 gpointer user_data;
2646
2647static void
2648pricedb_pricelist_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2649{
2650 GList *price_list = (GList *) val;
2651 GNCPriceListForeachData *foreach_data = (GNCPriceListForeachData *) user_data;
2652 if (foreach_data->ok)
2653 {
2654 foreach_data->ok = foreach_data->func(price_list, foreach_data->user_data);
2655 }
2656}
2657
2658static void
2659pricedb_pricelist_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2660{
2661 GHashTable *currencies_hash = (GHashTable *) val;
2662 g_hash_table_foreach(currencies_hash, pricedb_pricelist_foreach_pricelist, user_data);
2663}
2664
2665static gboolean
2666pricedb_pricelist_traversal(GNCPriceDB *db,
2667 gboolean (*f)(GList *p, gpointer user_data),
2668 gpointer user_data)
2669{
2670 GNCPriceListForeachData foreach_data;
2671
2672 if (!db || !f) return FALSE;
2673 foreach_data.ok = TRUE;
2674 foreach_data.func = f;
2675 foreach_data.user_data = user_data;
2676 if (db->commodity_hash == nullptr)
2677 {
2678 return FALSE;
2679 }
2680 g_hash_table_foreach(db->commodity_hash,
2681 pricedb_pricelist_foreach_currencies_hash,
2682 &foreach_data);
2683
2684 return foreach_data.ok;
2685}
2686
2687static bool
2688compare_hash_entries_by_commodity_key (const CommodityPtrPair& he_a, const CommodityPtrPair& he_b)
2689{
2690 auto ca = he_a.first;
2691 auto cb = he_b.first;
2692
2693 if (ca == cb || !cb)
2694 return false;
2695
2696 if (!ca)
2697 return true;
2698
2699 auto cmp_result = g_strcmp0 (gnc_commodity_get_namespace (ca), gnc_commodity_get_namespace (cb));
2700
2701 if (cmp_result)
2702 return (cmp_result < 0);
2703
2704 return g_strcmp0(gnc_commodity_get_mnemonic (ca), gnc_commodity_get_mnemonic (cb)) < 0;
2705}
2706
2707static bool
2708stable_price_traversal(GNCPriceDB *db,
2709 gboolean (*f)(GNCPrice *p, gpointer user_data),
2710 gpointer user_data)
2711{
2712 g_return_val_if_fail (db && f, false);
2713
2714 auto currency_hashes = hash_table_to_vector (db->commodity_hash);
2715 std::sort (currency_hashes.begin(), currency_hashes.end(), compare_hash_entries_by_commodity_key);
2716
2717 for (const auto& entry : currency_hashes)
2718 {
2719 auto price_lists = hash_table_to_vector (static_cast<GHashTable*>(entry.second));
2720 std::sort (price_lists.begin(), price_lists.end(), compare_hash_entries_by_commodity_key);
2721
2722 for (const auto& pricelist_entry : price_lists)
2723 if (g_list_find_custom (static_cast<GList*>(pricelist_entry.second), user_data, (GCompareFunc)f))
2724 return false;
2725 }
2726
2727 return true;
2728}
2729
2730gboolean
2732 GncPriceForeachFunc f,
2733 gpointer user_data,
2734 gboolean stable_order)
2735{
2736 ENTER ("db=%p f=%p", db, f);
2737 if (stable_order)
2738 {
2739 LEAVE (" stable order found");
2740 return stable_price_traversal(db, f, user_data);
2741 }
2742 LEAVE (" use unstable order");
2743 return unstable_price_traversal(db, f, user_data);
2744}
2745
2746/***************************************************************************/
2747
2748/* Semi-lame debugging code */
2749
2750void
2751gnc_price_print(GNCPrice *p, FILE *f, int indent)
2752{
2753 gnc_commodity *commodity;
2754 gnc_commodity *currency;
2755 gchar *istr = nullptr; /* indent string */
2756 const char *str;
2757
2758 if (!p) return;
2759 if (!f) return;
2760
2761 commodity = gnc_price_get_commodity(p);
2762 currency = gnc_price_get_currency(p);
2763
2764 if (!commodity) return;
2765 if (!currency) return;
2766
2767 istr = g_strnfill(indent, ' ');
2768
2769 fprintf(f, "%s<pdb:price>\n", istr);
2770 fprintf(f, "%s <pdb:commodity pointer=%p>\n", istr, commodity);
2771 str = gnc_commodity_get_namespace(commodity);
2772 str = str ? str : "(null)";
2773 fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2774 str = gnc_commodity_get_mnemonic(commodity);
2775 str = str ? str : "(null)";
2776 fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2777 fprintf(f, "%s </pdb:commodity>\n", istr);
2778 fprintf(f, "%s <pdb:currency pointer=%p>\n", istr, currency);
2779 str = gnc_commodity_get_namespace(currency);
2780 str = str ? str : "(null)";
2781 fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2782 str = gnc_commodity_get_mnemonic(currency);
2783 str = str ? str : "(null)";
2784 fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2785 fprintf(f, "%s </pdb:currency>\n", istr);
2786 str = source_names[gnc_price_get_source(p)];
2787 str = str ? str : "invalid";
2788 fprintf(f, "%s %s\n", istr, str);
2789 str = gnc_price_get_typestr(p);
2790 str = str ? str : "(null)";
2791 fprintf(f, "%s %s\n", istr, str);
2792 fprintf(f, "%s %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
2793 fprintf(f, "%s</pdb:price>\n", istr);
2794
2795 g_free(istr);
2796}
2797
2798static gboolean
2799print_pricedb_adapter(GNCPrice *p, gpointer user_data)
2800{
2801 FILE *f = (FILE *) user_data;
2802 gnc_price_print(p, f, 1);
2803 return TRUE;
2804}
2805
2806void
2807gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
2808{
2809 if (!db)
2810 {
2811 PERR("nullptr PriceDB\n");
2812 return;
2813 }
2814 if (!f)
2815 {
2816 PERR("nullptr FILE*\n");
2817 return;
2818 }
2819
2820 fprintf(f, "<gnc:pricedb>\n");
2821 gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
2822 fprintf(f, "</gnc:pricedb>\n");
2823}
2824
2825/* ==================================================================== */
2826/* gncObject function implementation and registration */
2827
2828static void
2829pricedb_book_begin (QofBook *book)
2830{
2831 gnc_pricedb_create(book);
2832}
2833
2834static void
2835pricedb_book_end (QofBook *book)
2836{
2837 QofCollection *col;
2838
2839 if (!book)
2840 return;
2841 col = qof_book_get_collection(book, GNC_ID_PRICEDB);
2842 auto db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2843 qof_collection_set_data(col, nullptr);
2845}
2846
2847static gpointer
2848price_create (QofBook *book)
2849{
2850 return gnc_price_create(book);
2851}
2852
2853/* ==================================================================== */
2854/* a non-boolean foreach. Ugh */
2855
2856typedef struct
2857{
2858 void (*func)(GNCPrice *p, gpointer user_data);
2859 gpointer user_data;
2860}
2862
2863static void
2864void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2865{
2866 GList *price_list = (GList *) val;
2867 VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
2868
2869 g_list_foreach (price_list, (GFunc)foreach_data->func, foreach_data->user_data);
2870}
2871
2872static void
2873void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2874{
2875 GHashTable *currencies_hash = (GHashTable *) val;
2876 g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
2877}
2878
2879static void
2880void_unstable_price_traversal(GNCPriceDB *db,
2881 void (*f)(GNCPrice *p, gpointer user_data),
2882 gpointer user_data)
2883{
2884 VoidGNCPriceDBForeachData foreach_data;
2885
2886 if (!db || !f) return;
2887 foreach_data.func = f;
2888 foreach_data.user_data = user_data;
2889
2890 g_hash_table_foreach(db->commodity_hash,
2891 void_pricedb_foreach_currencies_hash,
2892 &foreach_data);
2893}
2894
2895static void
2896price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
2897{
2898 GNCPriceDB *db;
2899
2900 db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2901 void_unstable_price_traversal(db,
2902 (void (*)(GNCPrice *, gpointer)) cb,
2903 data);
2904}
2905
2906/* ==================================================================== */
2907
2908#ifdef DUMP_FUNCTIONS
2909/* For debugging only, don't delete this */
2910static void price_list_dump(GList *price_list, const char *tag);
2911#endif
2912
2913static const char *
2914price_printable(gpointer obj)
2915{
2916 auto pr = static_cast<GNCPrice*>(obj);
2917 gnc_commodity *commodity;
2918 gnc_commodity *currency;
2919 static char buff[2048]; /* nasty static OK for printing */
2920 char *val, *da;
2921
2922 if (!pr) return "";
2923
2924#ifdef DUMP_FUNCTIONS
2925 /* Reference it so the compiler doesn't optimize it out. bit
2926 don't actually call it. */
2927 if (obj == buff)
2928 price_list_dump(nullptr, "");
2929#endif
2930
2931 val = gnc_numeric_to_string (pr->value);
2932 da = qof_print_date (pr->tmspec);
2933
2934 commodity = gnc_price_get_commodity(pr);
2935 currency = gnc_price_get_currency(pr);
2936
2937 g_snprintf (buff, 2048, "%s %s / %s on %s", val,
2940 da);
2941 g_free (val);
2942 g_free (da);
2943 return buff;
2944}
2945
2946#ifdef DUMP_FUNCTIONS
2947/* For debugging only, don't delete this */
2948static void
2949price_list_dump(GList *price_list, const char *tag)
2950{
2951 GNCPrice *price;
2952 GList *node;
2953 printf("Price list %s\n", tag);
2954 for (node = price_list; node != nullptr; node = node->next)
2955 {
2956 printf("%s\n", price_printable(node->data));
2957 }
2958}
2959#endif
2960
2961#ifdef _MSC_VER
2962/* MSVC compiler doesn't have C99 "designated initializers"
2963 * so we wrap them in a macro that is empty on MSVC. */
2964# define DI(x) /* */
2965#else
2966# define DI(x) x
2967#endif
2968static QofObject price_object_def =
2969{
2970 DI(.interface_version = ) QOF_OBJECT_VERSION,
2971 DI(.e_type = ) GNC_ID_PRICE,
2972 DI(.type_label = ) "Price",
2973 DI(.create = ) price_create,
2974 DI(.book_begin = ) nullptr,
2975 DI(.book_end = ) nullptr,
2976 DI(.is_dirty = ) qof_collection_is_dirty,
2977 DI(.mark_clean = ) qof_collection_mark_clean,
2978 DI(.foreach = ) price_foreach,
2979 DI(.printable = ) price_printable,
2980 DI(.version_cmp = ) nullptr,
2981};
2982
2983static QofObject pricedb_object_def =
2984{
2985 DI(.interface_version = ) QOF_OBJECT_VERSION,
2986 DI(.e_type = ) GNC_ID_PRICEDB,
2987 DI(.type_label = ) "PriceDB",
2988 DI(.create = ) nullptr,
2989 DI(.book_begin = ) pricedb_book_begin,
2990 DI(.book_end = ) pricedb_book_end,
2991 DI(.is_dirty = ) qof_collection_is_dirty,
2992 DI(.mark_clean = ) qof_collection_mark_clean,
2993 DI(.foreach = ) nullptr,
2994 DI(.printable = ) nullptr,
2995 DI(.version_cmp = ) nullptr,
2996};
2997
2998gboolean
2999gnc_pricedb_register (void)
3000{
3001 static QofParam params[] =
3002 {
3003 { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
3004 { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
3005 { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time64, (QofSetterFunc)gnc_price_set_time64 },
3006 { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
3007 { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
3008 { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
3009 { nullptr },
3010 };
3011
3012 qof_class_register (GNC_ID_PRICE, nullptr, params);
3013
3014 if (!qof_object_register (&price_object_def))
3015 return FALSE;
3016 return qof_object_register (&pricedb_object_def);
3017}
3018
3019/* ========================= END OF FILE ============================== */
GnuCash Date class.
gnc_ymd year_month_day() const
Get the year, month, and day from the date as a gnc_ymd.
Date and Time handling routines.
An exact-rational-number library for gnucash.
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition qofbackend.h:58
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition qofbook.cpp:521
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
Definition qofclass.h:123
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition qofclass.h:130
void qof_class_register(QofIdTypeConst obj_name, QofSortFunc default_sort_function, const QofParam *params)
This function registers a new object class with the Qof subsystem.
Definition qofclass.cpp:86
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent.
gboolean gnc_commodity_equal(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equal.
int gnc_commodity_compare(const gnc_commodity *a, const gnc_commodity *b)
This routine returns 0 if the two commodities are equal, 1 otherwise.
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
const char * gnc_commodity_get_namespace(const gnc_commodity *cm)
Retrieve the namespace for the specified commodity.
int gnc_commodity_get_fraction(const gnc_commodity *cm)
Retrieve the fraction for the specified commodity.
const char * gnc_commodity_get_printname(const gnc_commodity *cm)
Retrieve the 'print' name for the specified commodity.
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
const char * gnc_commodity_get_unique_name(const gnc_commodity *cm)
Retrieve the 'unique' name for the specified commodity.
char * gnc_time64_to_iso8601_buff(time64 time, char *buff)
The gnc_time64_to_iso8601_buff() routine takes the input UTC time64 value and prints it as an ISO-860...
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition gnc-date.h:108
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
size_t qof_print_date_buff(char *buff, const size_t len, time64 t)
Convenience: calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:574
char * qof_print_date(time64 t)
Convenience; calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:610
GDate time64_to_gdate(time64 t)
Returns the GDate in which the time64 occurs.
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
size_t qof_print_gdate(char *buf, size_t len, const GDate *gd)
Convenience; calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:598
time64 time64CanonicalDayTime(time64 t)
convert a time64 on a certain day (localtime) to the time64 representing midday on that day.
Definition gnc-date.cpp:404
void gnc_gdate_set_fiscal_year_end(GDate *date, const GDate *fy_end)
This function modifies a GDate to set it to the last day of the fiscal year in which it falls.
QofInstance * qof_collection_lookup_entity(const QofCollection *col, const GncGUID *guid)
Find the entity going only from its guid.
Definition qofid.cpp:209
void(* QofInstanceForeachCB)(QofInstance *, gpointer user_data)
Callback type for qof_collection_foreach.
Definition qofid.h:146
gboolean qof_collection_is_dirty(const QofCollection *col)
Return value of 'dirty' flag on collection.
Definition qofid.cpp:232
gpointer qof_collection_get_data(const QofCollection *col)
Store and retrieve arbitrary object-defined data.
Definition qofid.cpp:266
void qof_event_gen(QofInstance *entity, QofEventId event_id, gpointer event_data)
Invoke all registered event handlers using the given arguments.
Definition qofevent.cpp:231
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
void qof_instance_init_data(QofInstance *inst, QofIdType type, QofBook *book)
Initialise the settings associated with an instance.
gboolean qof_instance_get_dirty_flag(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object has been modified.
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
gboolean qof_instance_books_equal(gconstpointer ptr1, gconstpointer ptr2)
See if two QofInstances share the same book.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
gboolean gnc_numeric_eq(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b are exactly the same (have the same numerator and ...
gchar * gnc_numeric_to_string(gnc_numeric n)
Convert to string.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
gnc_numeric gnc_numeric_div(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Division.
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Multiply a times b, returning the product.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
int gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
Returns 1 if a>b, -1 if b>a, 0 if a == b
gnc_numeric gnc_numeric_invert(gnc_numeric num)
Invert a gnc_numeric.
gnc_numeric gnc_numeric_reduce(gnc_numeric in)
Return input after reducing it by Greater Common Factor (GCF) elimination.
void qof_collection_mark_clean(QofCollection *)
reset value of dirty flag
Definition qofid.cpp:238
#define QOF_OBJECT_VERSION
Defines the version of the core object object registration interface.
Definition qofobject.h:63
gboolean qof_object_register(const QofObject *object)
Register new types of object objects.
GNCPrice * gnc_pricedb_lookup_nearest_before_t64(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, time64 t)
Return the nearest price between the given commodities before the given time.
void gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
Set flag to indicate whether duplication checks should be performed.
PriceList * gnc_pricedb_lookup_latest_any_currency(GNCPriceDB *db, const gnc_commodity *commodity)
Find the most recent price between a commodity and all other commodities.
gboolean gnc_pricedb_has_prices(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Report whether the pricedb contains prices for one commodity in another.
gnc_numeric gnc_pricedb_convert_balance_nearest_before_price_t64(GNCPriceDB *pdb, gnc_numeric balance, const gnc_commodity *balance_currency, const gnc_commodity *new_currency, time64 t)
Convert a balance from one currency to another using the price nearest to before the given time.
GNCPrice * gnc_pricedb_nth_price(GNCPriceDB *db, const gnc_commodity *c, const int n)
Get the nth price for the given commodity in reverse date order.
gboolean gnc_pricedb_foreach_price(GNCPriceDB *db, GncPriceForeachFunc f, gpointer user_data, gboolean stable_order)
Call a GncPriceForeachFunction once for each price in db, until the function returns FALSE.
gnc_numeric gnc_pricedb_get_nearest_before_price(GNCPriceDB *pdb, const gnc_commodity *orig_currency, const gnc_commodity *new_currency, const time64 t)
Retrieve the price one currency to another using the price nearest to before the given time.
void gnc_pricedb_commit_edit(GNCPriceDB *pdb)
Commit an edit.
gboolean gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
Remove a price from the pricedb and unref the price.
gnc_numeric gnc_pricedb_get_nearest_price(GNCPriceDB *pdb, const gnc_commodity *orig_currency, const gnc_commodity *new_currency, const time64 t)
Retrieve the price one currency to another using the price nearest to the given time.
gnc_numeric gnc_pricedb_convert_balance_latest_price(GNCPriceDB *pdb, gnc_numeric balance, const gnc_commodity *balance_currency, const gnc_commodity *new_currency)
Convert a balance from one currency to another using the most recent price between the two.
PriceList * gnc_pricedb_lookup_nearest_before_any_currency_t64(GNCPriceDB *db, const gnc_commodity *commodity, time64 t)
Return the nearest price between the given commodity and any other before the given time.
gboolean gnc_pricedb_remove_old_prices(GNCPriceDB *db, GList *comm_list, GDate *fiscal_end_date, time64 cutoff, PriceRemoveSourceFlags source, PriceRemoveKeepOptions keep)
Remove and unref prices older than a certain time.
GNCPriceDB * gnc_collection_get_pricedb(QofCollection *col)
Return the pricedb via the Book's collection.
gnc_numeric gnc_pricedb_convert_balance_nearest_price_t64(GNCPriceDB *pdb, gnc_numeric balance, const gnc_commodity *balance_currency, const gnc_commodity *new_currency, time64 t)
Convert a balance from one currency to another using the price nearest to the given time.
guint gnc_pricedb_get_num_prices(GNCPriceDB *db)
Return the number of prices in the database.
PriceList * gnc_pricedb_lookup_nearest_in_time_any_currency_t64(GNCPriceDB *db, const gnc_commodity *commodity, time64 t)
Return the price nearest in time to that given between the given commodity and every other.
GNCPrice * gnc_pricedb_lookup_nearest_in_time64(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, time64 t)
Return the price between the two commoditiesz nearest to the given time.
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
void gnc_pricedb_begin_edit(GNCPriceDB *pdb)
Begin an edit.
int gnc_pricedb_num_prices(GNCPriceDB *db, const gnc_commodity *c)
Get the number of prices, in any currency, for a given commodity.
void gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
This simple function can be useful for debugging the pricedb code.
PriceList * gnc_pricedb_get_prices(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Return all the prices for a given commodity in another.
GNCPrice * gnc_pricedb_lookup_latest(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Find the most recent price between the two commodities.
void gnc_pricedb_destroy(GNCPriceDB *db)
Destroy the given pricedb and unref all of the prices it contains.
gboolean gnc_pricedb_equal(GNCPriceDB *db1, GNCPriceDB *db2)
Test equality of two pricedbs.
GNCPrice * gnc_pricedb_lookup_day_t64(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, time64 t)
Return the price between the two commodities on the indicated day.
gnc_numeric gnc_pricedb_get_latest_price(GNCPriceDB *pdb, const gnc_commodity *orig_currency, const gnc_commodity *new_currency)
Retrieve the price one currency to another using the latest price.
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
Add a price to the pricedb.
void gnc_price_print(GNCPrice *p, FILE *f, int indent)
This simple function can be useful for debugging the price code.
void gnc_price_list_destroy(PriceList *prices)
gnc_price_list_destroy - destroy the given price list, calling gnc_price_unref on all the prices incl...
void gnc_price_ref(GNCPrice *p)
gnc_price_ref - indicate your need for a given price to stick around (i.e.
GNCPrice * gnc_price_invert(GNCPrice *p)
Return a newly-allocated price that's the inverse of the given price, p.
gboolean gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
gnc_price_list_insert - insert a price into the given list, calling gnc_price_ref on it during the pr...
GNCPrice * gnc_price_clone(GNCPrice *p, QofBook *book)
gnc_price_clone - returns a newly allocated price that's a content-wise duplicate of the given price,...
PriceSource
Price source enum.
gboolean gnc_price_list_remove(PriceList **prices, GNCPrice *p)
gnc_price_list_remove - remove the price, p, from the given list, calling gnc_price_unref on it durin...
GNCPrice * gnc_price_create(QofBook *book)
gnc_price_create - returns a newly allocated and initialized price with a reference count of 1.
void gnc_price_unref(GNCPrice *p)
gnc_price_unref - indicate you're finished with a price (i.e.
gboolean qof_commit_edit_part2(QofInstance *inst, void(*on_error)(QofInstance *, QofBackendError), void(*on_done)(QofInstance *), void(*on_free)(QofInstance *))
part2 – deal with the backend
gboolean qof_commit_edit(QofInstance *inst)
commit_edit helpers
gboolean qof_begin_edit(QofInstance *inst)
begin_edit
The type used to store guids in C.
Definition guid.h:75
QofBook reference.
Definition qofbook-p.hpp:47