GnuCash fec5643+
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 /* No match. PRICE_SOURCE_INVALID is only a loop sentinel, never a settable
541 source, so its string form "invalid" reaches here as well -- and like any
542 other unrecognized string it leaves the source unchanged. Name the price
543 (commodity -> currency and date) so a user who spots this in the trace log
544 can find it in the price editor to correct or remove it. */
545 gnc_commodity *commodity = gnc_price_get_commodity(p);
546 gnc_commodity *currency = gnc_price_get_currency(p);
547 char datebuff[MAX_DATE_LENGTH + 1];
548 memset(datebuff, 0, sizeof(datebuff));
549 qof_print_date_buff(datebuff, sizeof(datebuff), gnc_price_get_time64(p));
550 PWARN("unknown price source string \"%s\" for %s:%s -> %s on %s;"
551 " leaving the source unchanged", str,
552 commodity ? gnc_commodity_get_namespace(commodity) : "?",
553 commodity ? gnc_commodity_get_mnemonic(commodity) : "?",
554 currency ? gnc_commodity_get_mnemonic(currency) : "?",
555 datebuff);
556}
557void
558gnc_price_set_typestr(GNCPrice *p, const char* type)
559{
560 if (!p) return;
561 if (g_strcmp0(p->type, type) != 0)
562 {
563 gnc_price_begin_edit (p);
564 CACHE_REPLACE(p->type, type);
565 gnc_price_set_dirty(p);
566 gnc_price_commit_edit (p);
567 }
568}
569
570void
571gnc_price_set_value(GNCPrice *p, gnc_numeric value)
572{
573 if (!p) return;
574 if (!gnc_numeric_eq(p->value, value))
575 {
576 gnc_price_begin_edit (p);
577 p->value = value;
578 gnc_price_set_dirty(p);
579 gnc_price_commit_edit (p);
580 }
581}
582
583/* ==================================================================== */
584/* getters */
585
586GNCPrice *
587gnc_price_lookup (const GncGUID *guid, QofBook *book)
588{
589 QofCollection *col;
590
591 if (!guid || !book) return nullptr;
592 col = qof_book_get_collection (book, GNC_ID_PRICE);
593 return (GNCPrice *) qof_collection_lookup_entity (col, guid);
594}
595
596gnc_commodity *
597gnc_price_get_commodity(const GNCPrice *p)
598{
599 if (!p) return nullptr;
600 return p->commodity;
601}
602
603time64
604gnc_price_get_time64(const GNCPrice *p)
605{
606 return p ? p->tmspec : 0;
607}
608
610gnc_price_get_source(const GNCPrice *p)
611{
612 if (!p) return PRICE_SOURCE_INVALID;
613 return p->source;
614}
615
616const char*
617gnc_price_get_source_string(const GNCPrice *p)
618{
619 if (!p) return nullptr;
620 return source_names[p->source];
621}
622
623const char *
624gnc_price_get_typestr(const GNCPrice *p)
625{
626 if (!p) return nullptr;
627 return p->type;
628}
629
630gnc_numeric
631gnc_price_get_value(const GNCPrice *p)
632{
633 if (!p)
634 {
635 PERR("price nullptr.\n");
636 return gnc_numeric_zero();
637 }
638 return p->value;
639}
640
641gnc_commodity *
642gnc_price_get_currency(const GNCPrice *p)
643{
644 if (!p) return nullptr;
645 return p->currency;
646}
647
648gboolean
649gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
650{
651 time64 time1, time2;
652
653 if (p1 == p2) return TRUE;
654 if (!p1 || !p2) return FALSE;
655
656 if (!gnc_commodity_equiv (gnc_price_get_commodity (p1),
657 gnc_price_get_commodity (p2)))
658 return FALSE;
659
660 if (!gnc_commodity_equiv (gnc_price_get_currency (p1),
661 gnc_price_get_currency (p2)))
662 return FALSE;
663
664 time1 = gnc_price_get_time64 (p1);
665 time2 = gnc_price_get_time64 (p2);
666
667 if (time1 != time2)
668 return FALSE;
669
670 if (gnc_price_get_source (p1) != gnc_price_get_source (p2))
671 return FALSE;
672
673 if (g_strcmp0 (gnc_price_get_typestr (p1),
674 gnc_price_get_typestr (p2)) != 0)
675 return FALSE;
676
677 if (!gnc_numeric_eq (gnc_price_get_value (p1),
678 gnc_price_get_value (p2)))
679 return FALSE;
680
681 return TRUE;
682}
683
684/* ==================================================================== */
685/* price list manipulation functions */
686
687static gint
688compare_prices_by_date(gconstpointer a, gconstpointer b)
689{
690 time64 time_a, time_b;
691 gint result;
692
693 if (!a && !b) return 0;
694 /* nothing is always less than something */
695 if (!a) return -1;
696
697 time_a = gnc_price_get_time64((GNCPrice *) a);
698 time_b = gnc_price_get_time64((GNCPrice *) b);
699
700 /* Note we return -1 if time_b is before time_a. */
701 result = time64_cmp(time_b, time_a);
702 if (result) return result;
703
704 /* For a stable sort */
705 return guid_compare (gnc_price_get_guid((GNCPrice *) a),
706 gnc_price_get_guid((GNCPrice *) b));
707}
708
709static int
710price_is_duplicate (const GNCPrice *p_price, const GNCPrice *c_price)
711{
712 /* If the date, currency, commodity and price match, it's a duplicate */
713 return time64CanonicalDayTime (gnc_price_get_time64 (p_price)) != time64CanonicalDayTime (gnc_price_get_time64 (c_price)) ||
714 gnc_numeric_compare (gnc_price_get_value (p_price), gnc_price_get_value (c_price)) ||
715 gnc_commodity_compare (gnc_price_get_commodity (p_price), gnc_price_get_commodity (c_price)) ||
716 gnc_commodity_compare (gnc_price_get_currency (p_price), gnc_price_get_currency (c_price));
717}
718
719gboolean
720gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
721{
722 if (!prices || !p) return FALSE;
723 gnc_price_ref(p);
724
725 if (check_dupl && g_list_find_custom (*prices, p, (GCompareFunc)price_is_duplicate))
726 return true;
727
728 auto result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date);
729 if (!result_list)
730 return false;
731
732 *prices = result_list;
733 return true;
734}
735
736gboolean
737gnc_price_list_remove(PriceList **prices, GNCPrice *p)
738{
739 GList *result_list;
740 GList *found_element;
741
742 if (!prices || !p) return FALSE;
743
744 found_element = g_list_find(*prices, p);
745 if (!found_element) return TRUE;
746
747 result_list = g_list_remove_link(*prices, found_element);
748 gnc_price_unref((GNCPrice *) found_element->data);
749 g_list_free(found_element);
750
751 *prices = result_list;
752 return TRUE;
753}
754
755void
756gnc_price_list_destroy(PriceList *prices)
757{
758 g_list_free_full (prices, (GDestroyNotify)gnc_price_unref);
759}
760
761gboolean
762gnc_price_list_equal(PriceList *prices1, PriceList *prices2)
763{
764 if (prices1 == prices2) return TRUE;
765
766 for (auto n1 = prices1, n2 = prices2; n1 || n2; n1 = g_list_next (n1), n2 = g_list_next (n2))
767 {
768 if (!n1)
769 {
770 PINFO ("prices2 has extra prices");
771 return FALSE;
772 }
773 if (!n2)
774 {
775 PINFO ("prices1 has extra prices");
776 return FALSE;
777 }
778 if (!gnc_price_equal (static_cast<GNCPrice*>(n1->data), static_cast<GNCPrice*>(n2->data)))
779 return FALSE;
780 };
781
782 return TRUE;
783}
784
785/* ==================================================================== */
786/* GNCPriceDB functions
787
788 Structurally a GNCPriceDB contains a hash mapping price commodities
789 (of type gnc_commodity*) to hashes mapping price currencies (of
790 type gnc_commodity*) to GNCPrice lists (see gnc-pricedb.h for a
791 description of GNCPrice lists). The top-level key is the commodity
792 you want the prices for, and the second level key is the commodity
793 that the value is expressed in terms of.
794 */
795
796/* GObject Initialization */
797QOF_GOBJECT_IMPL(gnc_pricedb, GNCPriceDB, QOF_TYPE_INSTANCE)
798
799static void
800gnc_pricedb_init(GNCPriceDB* pdb)
801{
802 pdb->reset_nth_price_cache = FALSE;
803}
804
805static void
806gnc_pricedb_dispose_real (GObject *pdbp)
807{
808}
809
810static void
811gnc_pricedb_finalize_real(GObject* pdbp)
812{
813}
814
815static GNCPriceDB *
816gnc_pricedb_create(QofBook * book)
817{
818 GNCPriceDB * result;
819 QofCollection *col;
820
821 g_return_val_if_fail (book, nullptr);
822
823 /* There can only be one pricedb per book. So if one exits already,
824 * then use that. Warn user, they shouldn't be creating two ...
825 */
826 col = qof_book_get_collection (book, GNC_ID_PRICEDB);
827 result = static_cast<GNCPriceDB*>(qof_collection_get_data (col));
828 if (result)
829 {
830 PWARN ("A price database already exists for this book!");
831 return result;
832 }
833
834 result = static_cast<GNCPriceDB*>(g_object_new(GNC_TYPE_PRICEDB, nullptr));
835 qof_instance_init_data (&result->inst, GNC_ID_PRICEDB, book);
837
841 qof_collection_set_data (col, result);
842
843 result->commodity_hash = g_hash_table_new(nullptr, nullptr);
844 g_return_val_if_fail (result->commodity_hash, nullptr);
845 return result;
846}
847
848static void
849destroy_pricedb_currency_hash_data(gpointer key,
850 gpointer data,
851 gpointer user_data)
852{
853 GList *price_list = (GList *) data;
854 GList *node;
855 GNCPrice *p;
856
857 for (node = price_list; node; node = node->next)
858 {
859 p = static_cast<GNCPrice*>(node->data);
860
861 p->db = nullptr;
862 }
863
864 gnc_price_list_destroy(price_list);
865}
866
867static void
868destroy_pricedb_commodity_hash_data(gpointer key,
869 gpointer data,
870 gpointer user_data)
871{
872 GHashTable *currency_hash = (GHashTable *) data;
873 if (!currency_hash) return;
874 g_hash_table_foreach (currency_hash,
875 destroy_pricedb_currency_hash_data,
876 nullptr);
877 g_hash_table_destroy(currency_hash);
878}
879
880void
881gnc_pricedb_destroy(GNCPriceDB *db)
882{
883 if (!db) return;
884 if (db->commodity_hash)
885 {
886 g_hash_table_foreach (db->commodity_hash,
887 destroy_pricedb_commodity_hash_data,
888 nullptr);
889 }
890 g_hash_table_destroy (db->commodity_hash);
891 db->commodity_hash = nullptr;
892 /* qof_instance_release (&db->inst); */
893 g_object_unref(db);
894}
895
896void
897gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
898{
899 db->bulk_update = bulk_update;
900}
901
902/* ==================================================================== */
903/* This is kind of weird, the way its done. Each collection of prices
904 * for a given commodity should get its own guid, be its own entity, etc.
905 * We really shouldn't be using the collection data. But, hey I guess its OK,
906 * yeah? Umm, possibly not. (NW). See TODO below.
907*/
917GNCPriceDB *
918gnc_collection_get_pricedb(QofCollection *col)
919{
920 if (!col) return nullptr;
921 return static_cast<GNCPriceDB*>(qof_collection_get_data (col));
922}
923
924GNCPriceDB *
926{
927 QofCollection *col;
928
929 if (!book) return nullptr;
930 col = qof_book_get_collection (book, GNC_ID_PRICEDB);
931 return gnc_collection_get_pricedb (col);
932}
933
934/* ==================================================================== */
935
936static gboolean
937num_prices_helper (GNCPrice *p, gpointer user_data)
938{
939 auto count = static_cast<guint*>(user_data);
940
941 *count += 1;
942
943 return TRUE;
944}
945
946guint
948{
949 guint count;
950
951 if (!db) return 0;
952
953 count = 0;
954
955 gnc_pricedb_foreach_price(db, num_prices_helper, &count, FALSE);
956
957 return count;
958}
959
960/* ==================================================================== */
961
962typedef struct
963{
964 gboolean equal;
965 GNCPriceDB *db2;
966 gnc_commodity *commodity;
968
969static void
970pricedb_equal_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
971{
972 auto equal_data = static_cast<GNCPriceDBEqualData*>(user_data);
973 auto currency = static_cast<gnc_commodity*>(key);
974 auto price_list1 = static_cast<GList*>(val);
975 auto price_list2 = gnc_pricedb_get_prices (equal_data->db2,
976 equal_data->commodity,
977 currency);
978
979 if (!gnc_price_list_equal (price_list1, price_list2))
980 equal_data->equal = FALSE;
981
982 gnc_price_list_destroy (price_list2);
983}
984
985static void
986pricedb_equal_foreach_currencies_hash (gpointer key, gpointer val,
987 gpointer user_data)
988{
989 auto currencies_hash = static_cast<GHashTable*>(val);
990 auto equal_data = static_cast<GNCPriceDBEqualData *>(user_data);
991
992 equal_data->commodity = static_cast<gnc_commodity*>(key);
993
994 g_hash_table_foreach (currencies_hash,
995 pricedb_equal_foreach_pricelist,
996 equal_data);
997}
998
999gboolean
1000gnc_pricedb_equal (GNCPriceDB *db1, GNCPriceDB *db2)
1001{
1002 GNCPriceDBEqualData equal_data;
1003
1004 if (db1 == db2) return TRUE;
1005
1006 if (!db1 || !db2)
1007 {
1008 PWARN ("one is nullptr");
1009 return FALSE;
1010 }
1011
1012 equal_data.equal = TRUE;
1013 equal_data.db2 = db2;
1014
1015 g_hash_table_foreach (db1->commodity_hash,
1016 pricedb_equal_foreach_currencies_hash,
1017 &equal_data);
1018
1019 return equal_data.equal;
1020}
1021
1022/* ==================================================================== */
1023/* The add_price() function is a utility that only manages the
1024 * dual hash table insertion */
1025
1026static gboolean
1027add_price(GNCPriceDB *db, GNCPrice *p)
1028{
1029 /* This function will use p, adding a ref, so treat p as read-only
1030 if this function succeeds. */
1031 GList *price_list;
1032 gnc_commodity *commodity;
1033 gnc_commodity *currency;
1034 GHashTable *currency_hash;
1035
1036 if (!db || !p) return FALSE;
1037 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1040
1041 if (!qof_instance_books_equal(db, p))
1042 {
1043 PERR ("attempted to mix up prices across different books");
1044 LEAVE (" ");
1045 return FALSE;
1046 }
1047
1048 commodity = gnc_price_get_commodity(p);
1049 if (!commodity)
1050 {
1051 PWARN("no commodity");
1052 LEAVE (" ");
1053 return FALSE;
1054 }
1055 currency = gnc_price_get_currency(p);
1056 if (!currency)
1057 {
1058 PWARN("no currency");
1059 LEAVE (" ");
1060 return FALSE;
1061 }
1062 if (!db->commodity_hash)
1063 {
1064 LEAVE ("no commodity hash found ");
1065 return FALSE;
1066 }
1067/* Check for an existing price on the same day. If there is no existing price,
1068 * add this one. If this price is of equal or better precedence than the old
1069 * one, copy this one over the old one.
1070 */
1071 if (!db->bulk_update)
1072 {
1073 GNCPrice *old_price = gnc_pricedb_lookup_day_t64(db, p->commodity,
1074 p->currency, p->tmspec);
1075 if (old_price != nullptr)
1076 {
1077 if (p->source > old_price->source)
1078 {
1079 gnc_price_unref(p);
1080 LEAVE ("Better price already in DB.");
1081 return FALSE;
1082 }
1083 gnc_pricedb_remove_price(db, old_price);
1084 }
1085 }
1086
1087 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1088 if (!currency_hash)
1089 {
1090 currency_hash = g_hash_table_new(nullptr, nullptr);
1091 g_hash_table_insert(db->commodity_hash, commodity, currency_hash);
1092 }
1093
1094 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
1095 if (!gnc_price_list_insert(&price_list, p, !db->bulk_update))
1096 {
1097 LEAVE ("gnc_price_list_insert failed");
1098 return FALSE;
1099 }
1100
1101 if (!price_list)
1102 {
1103 LEAVE (" no price list");
1104 return FALSE;
1105 }
1106
1107 g_hash_table_insert(currency_hash, currency, price_list);
1108 p->db = db;
1109
1110 qof_event_gen (&p->inst, QOF_EVENT_ADD, nullptr);
1111
1112 LEAVE ("db=%p, pr=%p dirty=%d dextroying=%d commodity=%s/%s currency_hash=%p",
1115 gnc_commodity_get_namespace(p->commodity),
1116 gnc_commodity_get_mnemonic(p->commodity),
1117 currency_hash);
1118 return TRUE;
1119}
1120
1121/* If gnc_pricedb_add_price() succeeds, it takes ownership of the
1122 passed-in GNCPrice and inserts it into the pricedb. Writing to this
1123 pointer afterwards will have interesting results, so don't.
1124 */
1125gboolean
1126gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
1127{
1128 if (!db || !p) return FALSE;
1129
1130 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1133
1134 if (FALSE == add_price(db, p))
1135 {
1136 LEAVE (" failed to add price");
1137 return FALSE;
1138 }
1139
1141 qof_instance_set_dirty(&db->inst);
1143
1144 LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
1147
1148 return TRUE;
1149}
1150
1151/* remove_price() is a utility; its only function is to remove the price
1152 * from the double-hash tables.
1153 */
1154
1155static gboolean
1156remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup)
1157{
1158 GList *price_list;
1159 gnc_commodity *commodity;
1160 gnc_commodity *currency;
1161 GHashTable *currency_hash;
1162
1163 if (!db || !p) return FALSE;
1164 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1167
1168 commodity = gnc_price_get_commodity(p);
1169 if (!commodity)
1170 {
1171 LEAVE (" no commodity");
1172 return FALSE;
1173 }
1174 currency = gnc_price_get_currency(p);
1175 if (!currency)
1176 {
1177 LEAVE (" no currency");
1178 return FALSE;
1179 }
1180 if (!db->commodity_hash)
1181 {
1182 LEAVE (" no commodity hash");
1183 return FALSE;
1184 }
1185
1186 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1187 if (!currency_hash)
1188 {
1189 LEAVE (" no currency hash");
1190 return FALSE;
1191 }
1192
1193 qof_event_gen (&p->inst, QOF_EVENT_REMOVE, nullptr);
1194 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
1195 gnc_price_ref(p);
1196 if (!gnc_price_list_remove(&price_list, p))
1197 {
1198 gnc_price_unref(p);
1199 LEAVE (" cannot remove price list");
1200 return FALSE;
1201 }
1202
1203 /* if the price list is empty, then remove this currency from the
1204 commodity hash */
1205 if (price_list)
1206 {
1207 g_hash_table_insert(currency_hash, currency, price_list);
1208 }
1209 else
1210 {
1211 g_hash_table_remove(currency_hash, currency);
1212
1213 if (cleanup)
1214 {
1215 /* chances are good that this commodity had only one currency.
1216 * If there are no currencies, we may as well destroy the
1217 * commodity too. */
1218 guint num_currencies = g_hash_table_size (currency_hash);
1219 if (0 == num_currencies)
1220 {
1221 g_hash_table_remove (db->commodity_hash, commodity);
1222 g_hash_table_destroy (currency_hash);
1223 }
1224 }
1225 }
1226
1227 gnc_price_unref(p);
1228 LEAVE ("db=%p, pr=%p", db, p);
1229 return TRUE;
1230}
1231
1232gboolean
1233gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
1234{
1235 gboolean rc;
1236 char datebuff[MAX_DATE_LENGTH + 1];
1237 memset(datebuff, 0, sizeof(datebuff));
1238 if (!db || !p) return FALSE;
1239 ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1242
1243 gnc_price_ref(p);
1244 qof_print_date_buff(datebuff, sizeof(datebuff), gnc_price_get_time64 (p));
1245 DEBUG("Remove Date is %s, Commodity is %s, Source is %s", datebuff,
1246 gnc_commodity_get_fullname (gnc_price_get_commodity (p)),
1247 gnc_price_get_source_string (p));
1248
1249 rc = remove_price (db, p, TRUE);
1251 qof_instance_set_dirty(&db->inst);
1253
1254 /* invoke the backend to delete this price */
1255 gnc_price_begin_edit (p);
1256 qof_instance_set_destroying(p, TRUE);
1257 gnc_price_commit_edit (p);
1258 p->db = nullptr;
1259 gnc_price_unref(p);
1260 LEAVE ("db=%p, pr=%p", db, p);
1261 return rc;
1262}
1263
1264typedef struct
1265{
1266 GNCPriceDB *db;
1267 time64 cutoff;
1268 gboolean delete_fq;
1269 gboolean delete_user;
1270 gboolean delete_app;
1271 GSList *list;
1272} remove_info;
1273
1274static gboolean
1275check_one_price_date (GNCPrice *price, gpointer user_data)
1276{
1277 auto data = static_cast<remove_info*>(user_data);
1278 PriceSource source;
1279 time64 time;
1280
1281 ENTER("price %p (%s), data %p", price,
1282 gnc_commodity_get_mnemonic(gnc_price_get_commodity(price)),
1283 user_data);
1284
1285 source = gnc_price_get_source (price);
1286
1287 if ((source == PRICE_SOURCE_FQ) && data->delete_fq)
1288 PINFO ("Delete Quote Source");
1289 else if ((source == PRICE_SOURCE_USER_PRICE) && data->delete_user)
1290 PINFO ("Delete User Source");
1291 else if ((source != PRICE_SOURCE_FQ) && (source != PRICE_SOURCE_USER_PRICE) && data->delete_app)
1292 PINFO ("Delete App Source");
1293 else
1294 {
1295 LEAVE("Not a matching source");
1296 return TRUE;
1297 }
1298
1299 time = gnc_price_get_time64 (price);
1300 {
1301 gchar buf[40];
1302 gnc_time64_to_iso8601_buff(time, buf);
1303 DEBUG("checking date %s", buf);
1304 }
1305 if (time < data->cutoff)
1306 {
1307 data->list = g_slist_prepend(data->list, price);
1308 DEBUG("will delete");
1309 }
1310 LEAVE(" ");
1311 return TRUE;
1312}
1313
1314static void
1315pricedb_remove_foreach_pricelist (gpointer key,
1316 gpointer val,
1317 gpointer user_data)
1318{
1319 GList *price_list = (GList *) val;
1320 GList *node = price_list;
1321 remove_info *data = (remove_info *) user_data;
1322
1323 ENTER("key %p, value %p, data %p", key, val, user_data);
1324
1325 /* now check each item in the list */
1326 g_list_foreach(node, (GFunc)check_one_price_date, data);
1327
1328 LEAVE(" ");
1329}
1330
1331static gint
1332compare_prices_by_commodity_date (gconstpointer a, gconstpointer b)
1333{
1334 time64 time_a, time_b;
1335 gnc_commodity *comma;
1336 gnc_commodity *commb;
1337 gnc_commodity *curra;
1338 gnc_commodity *currb;
1339 gint result;
1340
1341 if (!a && !b) return 0;
1342 /* nothing is always less than something */
1343 if (!a) return -1;
1344 if (!b) return 1;
1345
1346 comma = gnc_price_get_commodity ((GNCPrice *) a);
1347 commb = gnc_price_get_commodity ((GNCPrice *) b);
1348
1349 if (!gnc_commodity_equal(comma, commb))
1350 return gnc_commodity_compare(comma, commb);
1351
1352 curra = gnc_price_get_currency ((GNCPrice *) a);
1353 currb = gnc_price_get_currency ((GNCPrice *) b);
1354
1355 if (!gnc_commodity_equal(curra, currb))
1356 return gnc_commodity_compare(curra, currb);
1357
1358 time_a = gnc_price_get_time64((GNCPrice *) a);
1359 time_b = gnc_price_get_time64((GNCPrice *) b);
1360
1361 /* Note we return -1 if time_b is before time_a. */
1362 result = time64_cmp(time_b, time_a);
1363 if (result) return result;
1364
1365 /* For a stable sort */
1366 return guid_compare (gnc_price_get_guid((GNCPrice *) a),
1367 gnc_price_get_guid((GNCPrice *) b));
1368}
1369
1370static gboolean
1371price_commodity_and_currency_equal (GNCPrice *a, GNCPrice *b)
1372{
1373 gboolean ret_comm = FALSE;
1374 gboolean ret_curr = FALSE;
1375
1376 if (gnc_commodity_equal (gnc_price_get_commodity(a), gnc_price_get_commodity (b)))
1377 ret_comm = TRUE;
1378
1379 if (gnc_commodity_equal (gnc_price_get_currency(a), gnc_price_get_currency (b)))
1380 ret_curr = TRUE;
1381
1382 return (ret_comm && ret_curr);
1383}
1384
1385static void
1386gnc_pricedb_remove_old_prices_pinfo (GNCPrice *price, gboolean keep_message)
1387{
1388 GDate price_date = time64_to_gdate (gnc_price_get_time64 (price));
1389 char date_buf[MAX_DATE_LENGTH+1];
1390
1391 if (g_date_valid (&price_date))
1392 {
1393 qof_print_gdate (date_buf, MAX_DATE_LENGTH, &price_date);
1394
1395 if (keep_message)
1396 {
1397 PINFO("#### Keep price with date %s, commodity is %s, currency is %s", date_buf,
1398 gnc_commodity_get_printname(gnc_price_get_commodity(price)),
1399 gnc_commodity_get_printname(gnc_price_get_currency(price)));
1400 }
1401 else
1402 PINFO("## Remove price with date %s", date_buf);
1403 }
1404 else
1405 PINFO("Keep price date is invalid");
1406}
1407
1408static void
1409clone_price (GNCPrice **price, GNCPrice *source_price)
1410{
1411 QofBook *book;
1412
1413 if (!source_price) return;
1414 if (price == nullptr) return;
1415
1416 book = qof_instance_get_book (QOF_INSTANCE(source_price));
1417
1418 if (*price)
1419 gnc_price_unref (*price);
1420
1421 *price = gnc_price_clone (source_price, book);
1422
1423 gnc_pricedb_remove_old_prices_pinfo (source_price, TRUE);
1424}
1425
1426static gint
1427roundUp (gint numToRound, gint multiple)
1428{
1429 gint remainder;
1430
1431 if (multiple == 0)
1432 return numToRound;
1433
1434 remainder = numToRound % multiple;
1435 if (remainder == 0)
1436 return numToRound;
1437
1438 return numToRound + multiple - remainder;
1439}
1440
1441static gint
1442get_fiscal_quarter (GDate *date, GDateMonth fiscal_start)
1443{
1444 GDateMonth month = g_date_get_month (date);
1445
1446 gint q = ((roundUp (22 - fiscal_start + month, 3)/3) % 4) + 1;
1447
1448 PINFO("Return fiscal quarter is %d", q);
1449 return q;
1450}
1451
1452static void
1453gnc_pricedb_process_removal_list (GNCPriceDB *db, GDate *fiscal_end_date,
1454 remove_info data, PriceRemoveKeepOptions keep)
1455{
1456 GSList *item;
1457 gboolean save_first_price = FALSE;
1458 gint saved_test_value = 0, next_test_value = 0;
1459 GNCPrice *cloned_price = nullptr;
1460 GDateMonth fiscal_month_start;
1461 GDate *tmp_date = g_date_new_dmy (g_date_get_day (fiscal_end_date),
1462 g_date_get_month (fiscal_end_date),
1463 g_date_get_year (fiscal_end_date));
1464
1465 // get the fiscal start month
1466 g_date_subtract_months (tmp_date, 12);
1467 fiscal_month_start = static_cast<GDateMonth>(g_date_get_month (tmp_date) + 1);
1468 g_date_free (tmp_date);
1469
1470 // sort the list by commodity / currency / date
1471 data.list = g_slist_sort (data.list, compare_prices_by_commodity_date);
1472
1473 /* Now run this external list deleting prices */
1474 for (item = data.list; item; item = g_slist_next(item))
1475 {
1476 GDate saved_price_date;
1477 GDate next_price_date;
1478 auto price = static_cast<GNCPrice*>(item->data);
1479
1480 // Keep None
1481 if (keep == PRICE_REMOVE_KEEP_NONE)
1482 {
1483 gnc_pricedb_remove_old_prices_pinfo (price, FALSE);
1484 gnc_pricedb_remove_price (db, price);
1485 continue;
1486 }
1487
1488 save_first_price = !price_commodity_and_currency_equal (price, cloned_price); // Not Equal
1489 if (save_first_price == TRUE)
1490 {
1491 clone_price (&cloned_price, price);
1492 continue;
1493 }
1494
1495 // get the price dates
1496 saved_price_date = time64_to_gdate (gnc_price_get_time64 (cloned_price));
1497 next_price_date = time64_to_gdate (gnc_price_get_time64 (price));
1498
1499 // Keep last price in fiscal year
1500 if (keep == PRICE_REMOVE_KEEP_LAST_PERIOD && save_first_price == FALSE)
1501 {
1502 GDate *saved_fiscal_end = g_date_new_dmy (g_date_get_day (&saved_price_date),
1503 g_date_get_month (&saved_price_date),
1504 g_date_get_year (&saved_price_date));
1505
1506 GDate *next_fiscal_end = g_date_new_dmy (g_date_get_day (&next_price_date),
1507 g_date_get_month (&next_price_date),
1508 g_date_get_year (&next_price_date));
1509
1510 gnc_gdate_set_fiscal_year_end (saved_fiscal_end, fiscal_end_date);
1511 gnc_gdate_set_fiscal_year_end (next_fiscal_end, fiscal_end_date);
1512
1513 saved_test_value = g_date_get_year (saved_fiscal_end);
1514 next_test_value = g_date_get_year (next_fiscal_end);
1515
1516 PINFO("Keep last price in fiscal year");
1517
1518 g_date_free (saved_fiscal_end);
1519 g_date_free (next_fiscal_end);
1520 }
1521
1522 // Keep last price in fiscal quarter
1523 if (keep == PRICE_REMOVE_KEEP_LAST_QUARTERLY && save_first_price == FALSE)
1524 {
1525 saved_test_value = get_fiscal_quarter (&saved_price_date, fiscal_month_start);
1526 next_test_value = get_fiscal_quarter (&next_price_date, fiscal_month_start);
1527
1528 PINFO("Keep last price in fiscal quarter");
1529 }
1530
1531 // Keep last price of every month
1532 if (keep == PRICE_REMOVE_KEEP_LAST_MONTHLY && save_first_price == FALSE)
1533 {
1534 saved_test_value = g_date_get_month (&saved_price_date);
1535 next_test_value = g_date_get_month (&next_price_date);
1536
1537 PINFO("Keep last price of every month");
1538 }
1539
1540 // Keep last price of every week
1541 if (keep == PRICE_REMOVE_KEEP_LAST_WEEKLY && save_first_price == FALSE)
1542 {
1543 saved_test_value = g_date_get_iso8601_week_of_year (&saved_price_date);
1544 next_test_value = g_date_get_iso8601_week_of_year (&next_price_date);
1545
1546 PINFO("Keep last price of every week");
1547 }
1548
1549 // Now compare the values
1550 if (saved_test_value == next_test_value)
1551 {
1552 gnc_pricedb_remove_old_prices_pinfo (price, FALSE);
1553 gnc_pricedb_remove_price (db, price);
1554 }
1555 else
1556 clone_price (&cloned_price, price);
1557 }
1558 if (cloned_price)
1559 gnc_price_unref (cloned_price);
1560}
1561
1562gboolean
1563gnc_pricedb_remove_old_prices (GNCPriceDB *db, GList *comm_list,
1564 GDate *fiscal_end_date, time64 cutoff,
1565 PriceRemoveSourceFlags source,
1566 PriceRemoveKeepOptions keep)
1567{
1568 remove_info data;
1569 GList *node;
1570 char datebuff[MAX_DATE_LENGTH + 1];
1571 memset (datebuff, 0, sizeof(datebuff));
1572
1573 data.db = db;
1574 data.cutoff = cutoff;
1575 data.list = nullptr;
1576 data.delete_fq = FALSE;
1577 data.delete_user = FALSE;
1578 data.delete_app = FALSE;
1579
1580 ENTER("Remove Prices for Source %d, keeping %d", source, keep);
1581
1582 // setup the source options
1583 if (source & PRICE_REMOVE_SOURCE_APP)
1584 data.delete_app = TRUE;
1585
1586 if (source & PRICE_REMOVE_SOURCE_FQ)
1587 data.delete_fq = TRUE;
1588
1589 if (source & PRICE_REMOVE_SOURCE_USER)
1590 data.delete_user = TRUE;
1591
1592 // Walk the list of commodities
1593 for (node = g_list_first (comm_list); node; node = g_list_next (node))
1594 {
1595 auto currencies_hash = static_cast<GHashTable*>(g_hash_table_lookup (db->commodity_hash, node->data));
1596 g_hash_table_foreach (currencies_hash, pricedb_remove_foreach_pricelist, &data);
1597 }
1598
1599 if (data.list == nullptr)
1600 {
1601 LEAVE("Empty price list");
1602 return FALSE;
1603 }
1604 qof_print_date_buff (datebuff, sizeof(datebuff), cutoff);
1605 DEBUG("Number of Prices in list is %d, Cutoff date is %s",
1606 g_slist_length (data.list), datebuff);
1607
1608 // Check for a valid fiscal end of year date
1609 if (fiscal_end_date == nullptr || !g_date_valid (fiscal_end_date))
1610 {
1611 auto ymd = GncDate().year_month_day();
1612 GDate end_this_year;
1613 g_date_set_dmy (&end_this_year, 31, GDateMonth(12), ymd.year);
1614 gnc_pricedb_process_removal_list (db, &end_this_year, data, keep);
1615 }
1616 else
1617 gnc_pricedb_process_removal_list (db, fiscal_end_date, data, keep);
1618
1619 g_slist_free (data.list);
1620 LEAVE(" ");
1621 return TRUE;
1622}
1623
1624/* ==================================================================== */
1625/* lookup/query functions */
1626
1627static PriceList *pricedb_price_list_merge (PriceList *a, PriceList *b);
1628
1629static void
1630hash_values_helper(gpointer key, gpointer value, gpointer data)
1631{
1632 auto l = static_cast<GList**>(data);
1633 if (*l)
1634 {
1635 GList *new_l;
1636 new_l = pricedb_price_list_merge(*l, static_cast<PriceList*>(value));
1637 g_list_free (*l);
1638 *l = new_l;
1639 }
1640 else
1641 *l = g_list_copy (static_cast<GList*>(value));
1642}
1643
1644static PriceList *
1645price_list_from_hashtable (GHashTable *hash, const gnc_commodity *currency)
1646{
1647 GList *price_list = nullptr, *result = nullptr ;
1648 if (currency)
1649 {
1650 price_list = static_cast<GList*>(g_hash_table_lookup(hash, currency));
1651 if (!price_list)
1652 {
1653 LEAVE (" no price list");
1654 return nullptr;
1655 }
1656 result = g_list_copy (price_list);
1657 }
1658 else
1659 {
1660 g_hash_table_foreach(hash, hash_values_helper, (gpointer)&result);
1661 }
1662 return result;
1663}
1664
1665static PriceList *
1666pricedb_price_list_merge (PriceList *a, PriceList *b)
1667{
1668 PriceList *merged_list = nullptr;
1669 GList *next_a = a;
1670 GList *next_b = b;
1671
1672 while (next_a || next_b)
1673 {
1674 if (next_a == nullptr)
1675 {
1676 merged_list = g_list_prepend (merged_list, next_b->data);
1677 next_b = next_b->next;
1678 }
1679 else if (next_b == nullptr)
1680 {
1681 merged_list = g_list_prepend (merged_list, next_a->data);
1682 next_a = next_a->next;
1683 }
1684 /* We're building the list in reverse order so reverse the comparison. */
1685 else if (compare_prices_by_date (next_a->data, next_b->data) < 0)
1686 {
1687 merged_list = g_list_prepend (merged_list, next_a->data);
1688 next_a = next_a->next;
1689 }
1690 else
1691 {
1692 merged_list = g_list_prepend (merged_list, next_b->data);
1693 next_b = next_b->next;
1694 }
1695 }
1696 return g_list_reverse (merged_list);
1697}
1698
1699static PriceList*
1700pricedb_get_prices_internal(GNCPriceDB *db, const gnc_commodity *commodity,
1701 const gnc_commodity *currency, gboolean bidi)
1702{
1703 GHashTable *forward_hash = nullptr, *reverse_hash = nullptr;
1704 PriceList *forward_list = nullptr, *reverse_list = nullptr;
1705 g_return_val_if_fail (db != nullptr, nullptr);
1706 g_return_val_if_fail (commodity != nullptr, nullptr);
1707 forward_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
1708 if (currency && bidi)
1709 reverse_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, currency));
1710 if (!forward_hash && !reverse_hash)
1711 {
1712 LEAVE (" no currency hash");
1713 return nullptr;
1714 }
1715 if (forward_hash)
1716 forward_list = price_list_from_hashtable (forward_hash, currency);
1717 if (currency && reverse_hash)
1718 {
1719 reverse_list = price_list_from_hashtable (reverse_hash, commodity);
1720 if (reverse_list)
1721 {
1722 if (forward_list)
1723 {
1724 /* Since we have a currency both lists are a direct copy of a price
1725 list in the price DB. This means the lists are already sorted
1726 from newest to oldest and we can just merge them together. This
1727 is substantially faster than concatenating them and sorting the
1728 resulting list. */
1729 PriceList *merged_list;
1730 merged_list = pricedb_price_list_merge (forward_list, reverse_list);
1731 g_list_free (forward_list);
1732 g_list_free (reverse_list);
1733 forward_list = merged_list;
1734 }
1735 else
1736 {
1737 forward_list = reverse_list;
1738 }
1739 }
1740 }
1741
1742 return forward_list;
1743}
1744
1745GNCPrice *gnc_pricedb_lookup_latest(GNCPriceDB *db,
1746 const gnc_commodity *commodity,
1747 const gnc_commodity *currency)
1748{
1749 GList *price_list;
1750 GNCPrice *result;
1751
1752 if (!db || !commodity || !currency) return nullptr;
1753 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
1754
1755 price_list = pricedb_get_prices_internal(db, commodity, currency, TRUE);
1756 if (!price_list) return nullptr;
1757 /* This works magically because prices are inserted in date-sorted
1758 * order, and the latest date always comes first. So return the
1759 * first in the list. */
1760 result = static_cast<GNCPrice*>(price_list->data);
1761 gnc_price_ref(result);
1762 g_list_free (price_list);
1763 LEAVE("price is %p", result);
1764 return result;
1765}
1766
1767typedef struct
1768{
1769 GList **list;
1770 const gnc_commodity *com;
1771 time64 t;
1773
1774/* price_list_scan_any_currency is the helper function used with
1775 * pricedb_pricelist_traversal by the "any_currency" price lookup functions. It
1776 * builds a list of prices that are either to or from the commodity "com".
1777 * The resulting list will include the last price newer than "t" and the first
1778 * price older than "t". All other prices will be ignored. Since in the most
1779 * common cases we will be looking for recent prices which are at the front of
1780 * the various price lists, this is considerably faster than concatenating all
1781 * the relevant price lists and sorting the result.
1782*/
1783
1784static gboolean
1785price_list_scan_any_currency(GList *price_list, gpointer data)
1786{
1787 UsesCommodity *helper = (UsesCommodity*)data;
1788 gnc_commodity *com;
1789 gnc_commodity *cur;
1790
1791 if (!price_list)
1792 return TRUE;
1793
1794 auto price = static_cast<GNCPrice*>(price_list->data);
1795 com = gnc_price_get_commodity(price);
1796 cur = gnc_price_get_currency(price);
1797
1798 /* if this price list isn't for the commodity we are interested in,
1799 ignore it. */
1800 if (com != helper->com && cur != helper->com)
1801 return TRUE;
1802
1803 /* The price list is sorted in decreasing order of time. Find the first
1804 price on it that is older than the requested time and add it and the
1805 previous price to the result list. */
1806 for (auto node = price_list; node; node = g_list_next (node))
1807 {
1808 price = static_cast<GNCPrice*>(node->data);
1809 time64 price_t = gnc_price_get_time64(price);
1810 if (price_t < helper->t)
1811 {
1812 /* If there is a previous price add it to the results. */
1813 if (node->prev)
1814 {
1815 auto prev_price = static_cast<GNCPrice*>(node->prev->data);
1816 gnc_price_ref(prev_price);
1817 *helper->list = g_list_prepend(*helper->list, prev_price);
1818 }
1819 /* Add the first price before the desired time */
1820 gnc_price_ref(price);
1821 *helper->list = g_list_prepend(*helper->list, price);
1822 /* No point in looking further, they will all be older */
1823 break;
1824 }
1825 else if (node->next == nullptr)
1826 {
1827 /* The last price is later than given time, add it */
1828 gnc_price_ref(price);
1829 *helper->list = g_list_prepend(*helper->list, price);
1830 }
1831 }
1832
1833 return TRUE;
1834}
1835
1836/* This operates on the principal that the prices are sorted by date and that we
1837 * want only the first one before the specified time containing both the target
1838 * and some other commodity. */
1839static PriceList*
1840latest_before (PriceList *prices, const gnc_commodity* target, time64 t)
1841{
1842 GList *node, *found_coms = nullptr, *retval = nullptr;
1843 for (node = prices; node != nullptr; node = g_list_next(node))
1844 {
1845 GNCPrice *price = (GNCPrice*)node->data;
1846 gnc_commodity *com = gnc_price_get_commodity(price);
1847 gnc_commodity *cur = gnc_price_get_currency(price);
1848 time64 price_t = gnc_price_get_time64(price);
1849
1850 if (t < price_t ||
1851 (com == target && g_list_find (found_coms, cur)) ||
1852 (cur == target && g_list_find (found_coms, com)))
1853 continue;
1854
1855 gnc_price_ref (price);
1856 retval = g_list_prepend (retval, price);
1857 found_coms = g_list_prepend (found_coms, com == target ? cur : com);
1858 }
1859 g_list_free (found_coms);
1860 return g_list_reverse(retval);
1861}
1862
1863static GNCPrice**
1864find_comtime(GPtrArray* array, gnc_commodity *com)
1865{
1866 unsigned int index = 0;
1867 GNCPrice** retval = nullptr;
1868 for (index = 0; index < array->len; ++index)
1869 {
1870 auto price_p = static_cast<GNCPrice**>(g_ptr_array_index(array, index));
1871 if (gnc_price_get_commodity(*price_p) == com ||
1872 gnc_price_get_currency(*price_p) == com)
1873 retval = price_p;
1874 }
1875 return retval;
1876}
1877
1878static GList*
1879add_nearest_price(GList *target_list, GPtrArray *price_array, GNCPrice *price,
1880 const gnc_commodity *target, time64 t)
1881{
1882 gnc_commodity *com = gnc_price_get_commodity(price);
1883 gnc_commodity *cur = gnc_price_get_currency(price);
1884 time64 price_t = gnc_price_get_time64(price);
1885 gnc_commodity *other = com == target ? cur : com;
1886 GNCPrice **com_price = find_comtime(price_array, other);
1887 time64 com_t;
1888 if (com_price == nullptr)
1889 {
1890 com_price = (GNCPrice**)g_slice_new(gpointer);
1891 *com_price = price;
1892 g_ptr_array_add(price_array, com_price);
1893 /* If the first price we see for this commodity is not newer than
1894 the target date add it to the return list. */
1895 if (price_t <= t)
1896 {
1897 gnc_price_ref(price);
1898 target_list = g_list_prepend(target_list, price);
1899 }
1900 return target_list;
1901 }
1902 com_t = gnc_price_get_time64(*com_price);
1903 if (com_t <= t)
1904 /* No point in checking any more prices, they'll all be further from
1905 * t. */
1906 return target_list;
1907 if (price_t > t)
1908 /* The price list is sorted newest->oldest, so as long as this price
1909 * is newer than t then it should replace the saved one. */
1910 {
1911 *com_price = price;
1912 }
1913 else
1914 {
1915 time64 com_diff = com_t - t;
1916 time64 price_diff = t - price_t;
1917 if (com_diff < price_diff)
1918 {
1919 gnc_price_ref(*com_price);
1920 target_list = g_list_prepend(target_list, *com_price);
1921 }
1922 else
1923 {
1924 gnc_price_ref(price);
1925 target_list = g_list_prepend(target_list, price);
1926 }
1927 *com_price = price;
1928 }
1929 return target_list;
1930}
1931
1932static PriceList *
1933nearest_to (PriceList *prices, const gnc_commodity* target, time64 t)
1934{
1935 GList *node, *retval = nullptr;
1936 const guint prealloc_size = 5; /*More than 5 "other" is unlikely as long as
1937 * target isn't the book's default
1938 * currency. */
1939
1940
1941 GPtrArray *price_array = g_ptr_array_sized_new(prealloc_size);
1942 guint index;
1943 for (node = prices; node != nullptr; node = g_list_next(node))
1944 {
1945 GNCPrice *price = (GNCPrice*)node->data;
1946 retval = add_nearest_price(retval, price_array, price, target, t);
1947 }
1948 /* There might be some prices in price_array that are newer than t. Those
1949 * will be cases where there wasn't a price older than t to push one or the
1950 * other into the retval, so we need to get them now.
1951 */
1952 for (index = 0; index < price_array->len; ++index)
1953 {
1954 auto com_price = static_cast<GNCPrice**>(g_ptr_array_index(price_array, index));
1955 time64 price_t = gnc_price_get_time64(*com_price);
1956 if (price_t >= t)
1957 {
1958 gnc_price_ref(*com_price);
1959 retval = g_list_prepend(retval, *com_price);
1960 }
1961 }
1962 g_ptr_array_free(price_array, TRUE);
1963 return g_list_sort(retval, compare_prices_by_date);
1964}
1965
1966
1967
1968PriceList *
1970 const gnc_commodity *commodity)
1971{
1973 gnc_time(nullptr));
1974}
1975
1976PriceList *
1978 const gnc_commodity *commodity,
1979 time64 t)
1980{
1981 GList *prices = nullptr, *result;
1982 UsesCommodity helper = {&prices, commodity, t};
1983 result = nullptr;
1984
1985 if (!db || !commodity) return nullptr;
1986 ENTER ("db=%p commodity=%p", db, commodity);
1987
1988 pricedb_pricelist_traversal(db, price_list_scan_any_currency, &helper);
1989 prices = g_list_sort(prices, compare_prices_by_date);
1990 result = nearest_to(prices, commodity, t);
1991 gnc_price_list_destroy(prices);
1992 LEAVE(" ");
1993 return result;
1994}
1995
1996PriceList *
1998 const gnc_commodity *commodity,
1999 time64 t)
2000{
2001 GList *prices = nullptr, *result;
2002 UsesCommodity helper = {&prices, commodity, t};
2003 result = nullptr;
2004
2005 if (!db || !commodity) return nullptr;
2006 ENTER ("db=%p commodity=%p", db, commodity);
2007
2008 pricedb_pricelist_traversal(db, price_list_scan_any_currency,
2009 &helper);
2010 prices = g_list_sort(prices, compare_prices_by_date);
2011 result = latest_before(prices, commodity, t);
2012 gnc_price_list_destroy(prices);
2013 LEAVE(" ");
2014 return result;
2015}
2016
2017/* gnc_pricedb_has_prices is used explicitly for filtering cases where the
2018 * commodity is the left side of commodity->currency price, so it checks only in
2019 * that direction.
2020 */
2021gboolean
2023 const gnc_commodity *commodity,
2024 const gnc_commodity *currency)
2025{
2026 GList *price_list;
2027 GHashTable *currency_hash;
2028 gint size;
2029
2030 if (!db || !commodity) return FALSE;
2031 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
2032 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, commodity));
2033 if (!currency_hash)
2034 {
2035 LEAVE("no, no currency_hash table");
2036 return FALSE;
2037 }
2038
2039 if (currency)
2040 {
2041 price_list = static_cast<GList*>(g_hash_table_lookup(currency_hash, currency));
2042 if (price_list)
2043 {
2044 LEAVE("yes");
2045 return TRUE;
2046 }
2047 LEAVE("no, no price list");
2048 return FALSE;
2049 }
2050
2051 size = g_hash_table_size (currency_hash);
2052 LEAVE("%s", size > 0 ? "yes" : "no");
2053 return size > 0;
2054}
2055
2056
2057/* gnc_pricedb_get_prices is used to construct the tree in the Price Editor and
2058 * so needs to be single-direction.
2059 */
2060PriceList *
2062 const gnc_commodity *commodity,
2063 const gnc_commodity *currency)
2064{
2065 if (!db || !commodity) return nullptr;
2066 ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
2067 auto result = pricedb_get_prices_internal (db, commodity, currency, FALSE);
2068 if (!result) return nullptr;
2069 g_list_foreach (result, (GFunc)gnc_price_ref, nullptr);
2070 LEAVE (" ");
2071 return result;
2072}
2073
2074/* Return the number of prices in the data base for the given commodity
2075 */
2076static void
2077price_count_helper(gpointer key, gpointer value, gpointer data)
2078{
2079 auto result = static_cast<int*>(data);
2080 auto price_list = static_cast<GList*>(value);
2081
2082 *result += g_list_length(price_list);
2083}
2084
2085int
2087 const gnc_commodity *c)
2088{
2089 int result = 0;
2090 GHashTable *currency_hash;
2091
2092 if (!db || !c) return 0;
2093 ENTER ("db=%p commodity=%p", db, c);
2094
2095 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup(db->commodity_hash, c));
2096 if (currency_hash)
2097 {
2098 g_hash_table_foreach(currency_hash, price_count_helper, (gpointer)&result);
2099 }
2100
2101 LEAVE ("count=%d", result);
2102 return result;
2103}
2104
2105/* Helper function for combining the price lists in gnc_pricedb_nth_price. */
2106static void
2107list_combine (gpointer element, gpointer data)
2108{
2109 GList *list = *(GList**)data;
2110 auto lst = static_cast<GList*>(element);
2111 if (list == nullptr)
2112 *(GList**)data = g_list_copy (lst);
2113 else
2114 {
2115 GList *new_list = g_list_concat (list, g_list_copy (lst));
2116 *(GList**)data = new_list;
2117 }
2118}
2119
2120/* This function is used by gnc-tree-model-price.c for iterating through the
2121 * prices when building or filtering the pricedb dialog's
2122 * GtkTreeView. gtk-tree-view-price.c sorts the results after it has obtained
2123 * the values so there's nothing gained by sorting. However, for very large
2124 * collections of prices in multiple currencies (here commodity is the one being
2125 * priced and currency the one in which the price is denominated; note that they
2126 * may both be currencies or not) just concatenating the price lists together
2127 * can be expensive because the receiving list must be traversed to obtain its
2128 * end. To avoid that cost n times we cache the commodity and merged price list.
2129 * Since this is a GUI-driven function there is no concern about concurrency.
2130 */
2131
2132GNCPrice *
2134 const gnc_commodity *c,
2135 const int n)
2136{
2137 static const gnc_commodity *last_c = nullptr;
2138 static GList *prices = nullptr;
2139
2140 GNCPrice *result = nullptr;
2141 GHashTable *currency_hash;
2142 g_return_val_if_fail (GNC_IS_COMMODITY (c), nullptr);
2143
2144 if (!db || !c || n < 0) return nullptr;
2145 ENTER ("db=%p commodity=%s index=%d", db, gnc_commodity_get_mnemonic(c), n);
2146
2147 if (last_c && prices && last_c == c && db->reset_nth_price_cache == FALSE)
2148 {
2149 result = static_cast<GNCPrice*>(g_list_nth_data (prices, n));
2150 LEAVE ("price=%p", result);
2151 return result;
2152 }
2153
2154 last_c = c;
2155
2156 if (prices)
2157 {
2158 g_list_free (prices);
2159 prices = nullptr;
2160 }
2161
2162 db->reset_nth_price_cache = FALSE;
2163
2164 currency_hash = static_cast<GHashTable*>(g_hash_table_lookup (db->commodity_hash, c));
2165 if (currency_hash)
2166 {
2167 GList *currencies = g_hash_table_get_values (currency_hash);
2168 g_list_foreach (currencies, list_combine, &prices);
2169 result = static_cast<GNCPrice*>(g_list_nth_data (prices, n));
2170 g_list_free (currencies);
2171 }
2172
2173 LEAVE ("price=%p", result);
2174 return result;
2175}
2176
2177void
2178gnc_pricedb_nth_price_reset_cache (GNCPriceDB *db)
2179{
2180 if (db)
2181 db->reset_nth_price_cache = TRUE;
2182}
2183
2184GNCPrice *
2186 const gnc_commodity *c,
2187 const gnc_commodity *currency,
2188 time64 t)
2189{
2190 return lookup_nearest_in_time(db, c, currency, t, TRUE);
2191}
2192
2193static GNCPrice *
2194lookup_nearest_in_time(GNCPriceDB *db,
2195 const gnc_commodity *c,
2196 const gnc_commodity *currency,
2197 time64 t,
2198 gboolean sameday)
2199{
2200 GList *price_list;
2201 GNCPrice *current_price = nullptr;
2202 GNCPrice *next_price = nullptr;
2203 GNCPrice *result = nullptr;
2204
2205 if (!db || !c || !currency) return nullptr;
2206 if (t == INT64_MAX) return nullptr;
2207 ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2208 price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2209 if (!price_list) return nullptr;
2210
2211 /* default answer */
2212 current_price = static_cast<GNCPrice*>(price_list->data);
2213
2214 /* find the first candidate past the one we want. Remember that
2215 prices are in most-recent-first order. */
2216 for (auto item = price_list; item; item = g_list_next (item))
2217 {
2218 auto p = static_cast<GNCPrice*>(item->data);
2219 time64 price_time = gnc_price_get_time64(p);
2220 if (price_time <= t)
2221 {
2222 next_price = static_cast<GNCPrice*>(item->data);
2223 break;
2224 }
2225 current_price = static_cast<GNCPrice*>(item->data);
2226 }
2227
2228 if (current_price) /* How can this be null??? */
2229 {
2230 if (!next_price)
2231 {
2232 /* It's earlier than the last price on the list */
2233 result = current_price;
2234 if (sameday)
2235 {
2236 /* Must be on the same day. */
2237 time64 price_day;
2238 time64 t_day;
2239 price_day = time64CanonicalDayTime(gnc_price_get_time64(current_price));
2240 t_day = time64CanonicalDayTime(t);
2241 if (price_day != t_day)
2242 result = nullptr;
2243 }
2244 }
2245 else
2246 {
2247 /* If the requested time is not earlier than the first price on the
2248 list, then current_price and next_price will be the same. */
2249 time64 current_t = gnc_price_get_time64(current_price);
2250 time64 next_t = gnc_price_get_time64(next_price);
2251 time64 diff_current = current_t - t;
2252 time64 diff_next = next_t - t;
2253 time64 abs_current = llabs(diff_current);
2254 time64 abs_next = llabs(diff_next);
2255
2256 if (sameday)
2257 {
2258 /* Result must be on same day, see if either of the two isn't */
2259 time64 t_day = time64CanonicalDayTime(t);
2260 time64 current_day = time64CanonicalDayTime(current_t);
2261 time64 next_day = time64CanonicalDayTime(next_t);
2262 if (current_day == t_day)
2263 {
2264 if (next_day == t_day)
2265 {
2266 /* Both on same day, return nearest */
2267 if (abs_current < abs_next)
2268 result = current_price;
2269 else
2270 result = next_price;
2271 }
2272 else
2273 /* current_price on same day, next_price not */
2274 result = current_price;
2275 }
2276 else if (next_day == t_day)
2277 /* next_price on same day, current_price not */
2278 result = next_price;
2279 }
2280 else
2281 {
2282 /* Choose the price that is closest to the given time. In case of
2283 * a tie, prefer the older price since it actually existed at the
2284 * time. (This also fixes bug #541970.) */
2285 if (abs_current < abs_next)
2286 {
2287 result = current_price;
2288 }
2289 else
2290 {
2291 result = next_price;
2292 }
2293 }
2294 }
2295 }
2296
2297 gnc_price_ref(result);
2298 g_list_free (price_list);
2299 LEAVE (" ");
2300 return result;
2301}
2302
2303GNCPrice *
2305 const gnc_commodity *c,
2306 const gnc_commodity *currency,
2307 time64 t)
2308{
2309 return lookup_nearest_in_time(db, c, currency, t, FALSE);
2310}
2311
2312// return 0 if price's time is less or equal to time
2313static int price_time64_less_or_equal (GNCPrice *p, time64 *time)
2314{
2315 return !(gnc_price_get_time64 (p) <= *time);
2316}
2317
2318GNCPrice *
2320 const gnc_commodity *c,
2321 const gnc_commodity *currency,
2322 time64 t)
2323{
2324 GNCPrice *current_price = nullptr;
2325 if (!db || !c || !currency) return nullptr;
2326 ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2327 auto price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2328 if (!price_list) return nullptr;
2329 auto p = g_list_find_custom (price_list, &t, (GCompareFunc)price_time64_less_or_equal);
2330 if (p)
2331 {
2332 current_price = GNC_PRICE (p->data);
2333 gnc_price_ref (current_price);
2334 }
2335 g_list_free (price_list);
2336 LEAVE (" ");
2337 return current_price;
2338}
2339
2340
2341typedef struct
2342{
2343 GNCPrice *from;
2344 GNCPrice *to;
2345} PriceTuple;
2346
2347static PriceTuple
2348extract_common_prices (PriceList *from_prices, PriceList *to_prices,
2349 const gnc_commodity *from, const gnc_commodity *to)
2350{
2351 PriceTuple retval = {nullptr, nullptr};
2352 GList *from_node = nullptr, *to_node = nullptr;
2353 GNCPrice *from_price = nullptr, *to_price = nullptr;
2354
2355 for (from_node = from_prices; from_node != nullptr;
2356 from_node = g_list_next(from_node))
2357 {
2358 for (to_node = to_prices; to_node != nullptr;
2359 to_node = g_list_next(to_node))
2360 {
2361 gnc_commodity *to_com, *to_cur;
2362 gnc_commodity *from_com, *from_cur;
2363 to_price = GNC_PRICE(to_node->data);
2364 from_price = GNC_PRICE(from_node->data);
2365 to_com = gnc_price_get_commodity (to_price);
2366 to_cur = gnc_price_get_currency (to_price);
2367 from_com = gnc_price_get_commodity (from_price);
2368 from_cur = gnc_price_get_currency (from_price);
2369 if (((to_com == from_com || to_com == from_cur) &&
2370 (to_com != from && to_com != to)) ||
2371 ((to_cur == from_com || to_cur == from_cur) &&
2372 (to_cur != from && to_cur != to)))
2373 break;
2374 to_price = nullptr;
2375 from_price = nullptr;
2376 }
2377 if (to_price != nullptr && from_price != nullptr)
2378 break;
2379 }
2380 if (from_price == nullptr || to_price == nullptr)
2381 return retval;
2382 gnc_price_ref(from_price);
2383 gnc_price_ref(to_price);
2384 retval.from = from_price;
2385 retval.to = to_price;
2386 return retval;
2387}
2388
2389static gnc_numeric
2390convert_price (const gnc_commodity *from, const gnc_commodity *to, PriceTuple tuple)
2391{
2392 gnc_commodity *p1_com = gnc_price_get_commodity (tuple.from);
2393 gnc_commodity *p1_cur = gnc_price_get_currency (tuple.from);
2394 gnc_commodity *p2_com = gnc_price_get_commodity (tuple.to);
2395 gnc_commodity *p2_cur = gnc_price_get_currency (tuple.to);
2396 gnc_numeric p1_val = gnc_price_get_value (tuple.from);
2397 gnc_numeric p2_val = gnc_price_get_value (tuple.to);
2398
2399 gnc_price_unref (tuple.from);
2400 gnc_price_unref (tuple.to);
2401
2402 if ((p1_com == from && p2_com == to) ||
2403 (p1_cur == from && p2_cur == to))
2404 {
2405 auto price = gnc_numeric_div (p2_val, p1_val, GNC_DENOM_AUTO,
2406 GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
2407
2408 if (p1_cur == from)
2409 return price;
2410
2411 return gnc_numeric_invert (price);
2412 }
2413
2414 auto price = gnc_numeric_mul (p1_val, p2_val, GNC_DENOM_AUTO,
2415 GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
2416 if (p1_cur == from)
2417 return gnc_numeric_invert (price);
2418
2419 return price;
2420}
2421
2422static gnc_numeric
2423indirect_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2424 const gnc_commodity *to, time64 t, gboolean before_date)
2425{
2426 GList *from_prices = nullptr, *to_prices = nullptr;
2427 PriceTuple tuple;
2428 gnc_numeric zero = gnc_numeric_zero();
2429 if (!from || !to)
2430 return zero;
2431 if (t == INT64_MAX)
2432 {
2433 from_prices = gnc_pricedb_lookup_latest_any_currency(db, from);
2434 /* "to" is often the book currency which may have lots of prices,
2435 so avoid getting them if they aren't needed. */
2436 if (from_prices)
2437 to_prices = gnc_pricedb_lookup_latest_any_currency(db, to);
2438 }
2439 else if (before_date)
2440 {
2441 from_prices = gnc_pricedb_lookup_nearest_before_any_currency_t64 (db, from, t);
2442 if (from_prices)
2444 }
2445 else
2446 {
2447 from_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, from, t);
2448 if (from_prices)
2450 }
2451 if (!from_prices || !to_prices)
2452 {
2453 gnc_price_list_destroy (from_prices);
2454 gnc_price_list_destroy (to_prices);
2455 return zero;
2456 }
2457 tuple = extract_common_prices (from_prices, to_prices, from, to);
2458 gnc_price_list_destroy (from_prices);
2459 gnc_price_list_destroy (to_prices);
2460 if (tuple.from)
2461 return convert_price (from, to, tuple);
2462 return zero;
2463}
2464
2465
2466static gnc_numeric
2467direct_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2468 const gnc_commodity *to, time64 t, gboolean before_date)
2469{
2470 GNCPrice *price;
2471 gnc_numeric retval = gnc_numeric_zero();
2472
2473 if (!from || !to) return retval;
2474
2475 if (t == INT64_MAX)
2476 price = gnc_pricedb_lookup_latest(db, from, to);
2477 else if (before_date)
2478 price = gnc_pricedb_lookup_nearest_before_t64(db, from, to, t);
2479 else
2480 price = gnc_pricedb_lookup_nearest_in_time64(db, from, to, t);
2481
2482 if (!price) return retval;
2483
2484 retval = gnc_price_get_value (price);
2485
2486 if (gnc_price_get_commodity (price) != from)
2487 retval = gnc_numeric_invert (retval);
2488
2489 gnc_price_unref (price);
2490 return retval;
2491}
2492
2493static gnc_numeric
2494get_nearest_price (GNCPriceDB *pdb,
2495 const gnc_commodity *orig_curr,
2496 const gnc_commodity *new_curr,
2497 const time64 t,
2498 gboolean before)
2499{
2500 gnc_numeric price;
2501
2502 if (gnc_commodity_equiv (orig_curr, new_curr))
2503 return gnc_numeric_create (1, 1);
2504
2505 /* Look for a direct price. */
2506 price = direct_price_conversion (pdb, orig_curr, new_curr, t, before);
2507
2508 /*
2509 * no direct price found, try find a price in another currency
2510 */
2511 if (gnc_numeric_zero_p (price))
2512 price = indirect_price_conversion (pdb, orig_curr, new_curr, t, before);
2513
2514 return gnc_numeric_reduce (price);
2515}
2516
2517gnc_numeric
2519 const gnc_commodity *orig_currency,
2520 const gnc_commodity *new_currency,
2521 const time64 t)
2522{
2523 return get_nearest_price (pdb, orig_currency, new_currency, t, TRUE);
2524}
2525
2526gnc_numeric
2528 const gnc_commodity *orig_currency,
2529 const gnc_commodity *new_currency,
2530 const time64 t)
2531{
2532 return get_nearest_price (pdb, orig_currency, new_currency, t, FALSE);
2533}
2534
2535gnc_numeric
2537 const gnc_commodity *orig_currency,
2538 const gnc_commodity *new_currency)
2539{
2540 return get_nearest_price (pdb, orig_currency, new_currency, INT64_MAX, FALSE);
2541}
2542
2543static gnc_numeric
2544convert_amount_at_date (GNCPriceDB *pdb,
2545 gnc_numeric amount,
2546 const gnc_commodity *orig_currency,
2547 const gnc_commodity *new_currency,
2548 const time64 t,
2549 gboolean before_date)
2550{
2551 gnc_numeric price;
2552
2553 if (gnc_numeric_zero_p (amount))
2554 return amount;
2555
2556 price = get_nearest_price (pdb, orig_currency, new_currency, t, before_date);
2557
2558 /* the price retrieved may be invalid. return zero. see 798015 */
2559 if (gnc_numeric_check (price))
2560 return gnc_numeric_zero ();
2561
2562 return gnc_numeric_mul
2563 (amount, price, gnc_commodity_get_fraction (new_currency),
2564 GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND);
2565}
2566
2567/*
2568 * Convert a balance from one currency to another.
2569 */
2570gnc_numeric
2572 gnc_numeric balance,
2573 const gnc_commodity *balance_currency,
2574 const gnc_commodity *new_currency)
2575{
2576 return convert_amount_at_date
2577 (pdb, balance, balance_currency, new_currency, INT64_MAX, FALSE);
2578}
2579
2580gnc_numeric
2582 gnc_numeric balance,
2583 const gnc_commodity *balance_currency,
2584 const gnc_commodity *new_currency,
2585 time64 t)
2586{
2587 return convert_amount_at_date
2588 (pdb, balance, balance_currency, new_currency, t, FALSE);
2589}
2590
2591gnc_numeric
2593 gnc_numeric balance,
2594 const gnc_commodity *balance_currency,
2595 const gnc_commodity *new_currency,
2596 time64 t)
2597{
2598 return convert_amount_at_date
2599 (pdb, balance, balance_currency, new_currency, t, TRUE);
2600}
2601
2602/* ==================================================================== */
2603/* gnc_pricedb_foreach_price infrastructure
2604 */
2605
2606typedef struct
2607{
2608 gboolean ok;
2609 gboolean (*func)(GNCPrice *p, gpointer user_data);
2610 gpointer user_data;
2612
2613static void
2614pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2615{
2616 GList *price_list = (GList *) val;
2617 GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
2618
2619 /* stop traversal when func returns FALSE */
2620 foreach_data->ok = g_list_find_custom (price_list, foreach_data->user_data, (GCompareFunc)foreach_data->func)
2621 != nullptr;
2622}
2623
2624static void
2625pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2626{
2627 GHashTable *currencies_hash = (GHashTable *) val;
2628 g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
2629}
2630
2631static gboolean
2632unstable_price_traversal(GNCPriceDB *db,
2633 gboolean (*f)(GNCPrice *p, gpointer user_data),
2634 gpointer user_data)
2635{
2636 GNCPriceDBForeachData foreach_data;
2637
2638 if (!db || !f) return FALSE;
2639 foreach_data.ok = TRUE;
2640 foreach_data.func = f;
2641 foreach_data.user_data = user_data;
2642 if (db->commodity_hash == nullptr)
2643 {
2644 return FALSE;
2645 }
2646 g_hash_table_foreach(db->commodity_hash,
2647 pricedb_foreach_currencies_hash,
2648 &foreach_data);
2649
2650 return foreach_data.ok;
2651}
2652
2653/* foreach_pricelist */
2654typedef struct
2655{
2656 gboolean ok;
2657 gboolean (*func)(GList *p, gpointer user_data);
2658 gpointer user_data;
2660
2661static void
2662pricedb_pricelist_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2663{
2664 GList *price_list = (GList *) val;
2665 GNCPriceListForeachData *foreach_data = (GNCPriceListForeachData *) user_data;
2666 if (foreach_data->ok)
2667 {
2668 foreach_data->ok = foreach_data->func(price_list, foreach_data->user_data);
2669 }
2670}
2671
2672static void
2673pricedb_pricelist_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2674{
2675 GHashTable *currencies_hash = (GHashTable *) val;
2676 g_hash_table_foreach(currencies_hash, pricedb_pricelist_foreach_pricelist, user_data);
2677}
2678
2679static gboolean
2680pricedb_pricelist_traversal(GNCPriceDB *db,
2681 gboolean (*f)(GList *p, gpointer user_data),
2682 gpointer user_data)
2683{
2684 GNCPriceListForeachData foreach_data;
2685
2686 if (!db || !f) return FALSE;
2687 foreach_data.ok = TRUE;
2688 foreach_data.func = f;
2689 foreach_data.user_data = user_data;
2690 if (db->commodity_hash == nullptr)
2691 {
2692 return FALSE;
2693 }
2694 g_hash_table_foreach(db->commodity_hash,
2695 pricedb_pricelist_foreach_currencies_hash,
2696 &foreach_data);
2697
2698 return foreach_data.ok;
2699}
2700
2701static bool
2702compare_hash_entries_by_commodity_key (const CommodityPtrPair& he_a, const CommodityPtrPair& he_b)
2703{
2704 auto ca = he_a.first;
2705 auto cb = he_b.first;
2706
2707 if (ca == cb || !cb)
2708 return false;
2709
2710 if (!ca)
2711 return true;
2712
2713 auto cmp_result = g_strcmp0 (gnc_commodity_get_namespace (ca), gnc_commodity_get_namespace (cb));
2714
2715 if (cmp_result)
2716 return (cmp_result < 0);
2717
2718 return g_strcmp0(gnc_commodity_get_mnemonic (ca), gnc_commodity_get_mnemonic (cb)) < 0;
2719}
2720
2721static bool
2722stable_price_traversal(GNCPriceDB *db,
2723 gboolean (*f)(GNCPrice *p, gpointer user_data),
2724 gpointer user_data)
2725{
2726 g_return_val_if_fail (db && f, false);
2727
2728 auto currency_hashes = hash_table_to_vector (db->commodity_hash);
2729 std::sort (currency_hashes.begin(), currency_hashes.end(), compare_hash_entries_by_commodity_key);
2730
2731 for (const auto& entry : currency_hashes)
2732 {
2733 auto price_lists = hash_table_to_vector (static_cast<GHashTable*>(entry.second));
2734 std::sort (price_lists.begin(), price_lists.end(), compare_hash_entries_by_commodity_key);
2735
2736 for (const auto& pricelist_entry : price_lists)
2737 if (g_list_find_custom (static_cast<GList*>(pricelist_entry.second), user_data, (GCompareFunc)f))
2738 return false;
2739 }
2740
2741 return true;
2742}
2743
2744gboolean
2746 GncPriceForeachFunc f,
2747 gpointer user_data,
2748 gboolean stable_order)
2749{
2750 ENTER ("db=%p f=%p", db, f);
2751 if (stable_order)
2752 {
2753 LEAVE (" stable order found");
2754 return stable_price_traversal(db, f, user_data);
2755 }
2756 LEAVE (" use unstable order");
2757 return unstable_price_traversal(db, f, user_data);
2758}
2759
2760/***************************************************************************/
2761
2762/* Semi-lame debugging code */
2763
2764void
2765gnc_price_print(GNCPrice *p, FILE *f, int indent)
2766{
2767 gnc_commodity *commodity;
2768 gnc_commodity *currency;
2769 gchar *istr = nullptr; /* indent string */
2770 const char *str;
2771
2772 if (!p) return;
2773 if (!f) return;
2774
2775 commodity = gnc_price_get_commodity(p);
2776 currency = gnc_price_get_currency(p);
2777
2778 if (!commodity) return;
2779 if (!currency) return;
2780
2781 istr = g_strnfill(indent, ' ');
2782
2783 fprintf(f, "%s<pdb:price>\n", istr);
2784 fprintf(f, "%s <pdb:commodity pointer=%p>\n", istr, commodity);
2785 str = gnc_commodity_get_namespace(commodity);
2786 str = str ? str : "(null)";
2787 fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2788 str = gnc_commodity_get_mnemonic(commodity);
2789 str = str ? str : "(null)";
2790 fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2791 fprintf(f, "%s </pdb:commodity>\n", istr);
2792 fprintf(f, "%s <pdb:currency pointer=%p>\n", istr, currency);
2793 str = gnc_commodity_get_namespace(currency);
2794 str = str ? str : "(null)";
2795 fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2796 str = gnc_commodity_get_mnemonic(currency);
2797 str = str ? str : "(null)";
2798 fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2799 fprintf(f, "%s </pdb:currency>\n", istr);
2800 str = source_names[gnc_price_get_source(p)];
2801 str = str ? str : "invalid";
2802 fprintf(f, "%s %s\n", istr, str);
2803 str = gnc_price_get_typestr(p);
2804 str = str ? str : "(null)";
2805 fprintf(f, "%s %s\n", istr, str);
2806 fprintf(f, "%s %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
2807 fprintf(f, "%s</pdb:price>\n", istr);
2808
2809 g_free(istr);
2810}
2811
2812static gboolean
2813print_pricedb_adapter(GNCPrice *p, gpointer user_data)
2814{
2815 FILE *f = (FILE *) user_data;
2816 gnc_price_print(p, f, 1);
2817 return TRUE;
2818}
2819
2820void
2821gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
2822{
2823 if (!db)
2824 {
2825 PERR("nullptr PriceDB\n");
2826 return;
2827 }
2828 if (!f)
2829 {
2830 PERR("nullptr FILE*\n");
2831 return;
2832 }
2833
2834 fprintf(f, "<gnc:pricedb>\n");
2835 gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
2836 fprintf(f, "</gnc:pricedb>\n");
2837}
2838
2839/* ==================================================================== */
2840/* gncObject function implementation and registration */
2841
2842static void
2843pricedb_book_begin (QofBook *book)
2844{
2845 gnc_pricedb_create(book);
2846}
2847
2848static void
2849pricedb_book_end (QofBook *book)
2850{
2851 QofCollection *col;
2852
2853 if (!book)
2854 return;
2855 col = qof_book_get_collection(book, GNC_ID_PRICEDB);
2856 auto db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2857 qof_collection_set_data(col, nullptr);
2859}
2860
2861static gpointer
2862price_create (QofBook *book)
2863{
2864 return gnc_price_create(book);
2865}
2866
2867/* ==================================================================== */
2868/* a non-boolean foreach. Ugh */
2869
2870typedef struct
2871{
2872 void (*func)(GNCPrice *p, gpointer user_data);
2873 gpointer user_data;
2874}
2876
2877static void
2878void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2879{
2880 GList *price_list = (GList *) val;
2881 VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
2882
2883 g_list_foreach (price_list, (GFunc)foreach_data->func, foreach_data->user_data);
2884}
2885
2886static void
2887void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2888{
2889 GHashTable *currencies_hash = (GHashTable *) val;
2890 g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
2891}
2892
2893static void
2894void_unstable_price_traversal(GNCPriceDB *db,
2895 void (*f)(GNCPrice *p, gpointer user_data),
2896 gpointer user_data)
2897{
2898 VoidGNCPriceDBForeachData foreach_data;
2899
2900 if (!db || !f) return;
2901 foreach_data.func = f;
2902 foreach_data.user_data = user_data;
2903
2904 g_hash_table_foreach(db->commodity_hash,
2905 void_pricedb_foreach_currencies_hash,
2906 &foreach_data);
2907}
2908
2909static void
2910price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
2911{
2912 GNCPriceDB *db;
2913
2914 db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2915 void_unstable_price_traversal(db,
2916 (void (*)(GNCPrice *, gpointer)) cb,
2917 data);
2918}
2919
2920/* ==================================================================== */
2921
2922#ifdef DUMP_FUNCTIONS
2923/* For debugging only, don't delete this */
2924static void price_list_dump(GList *price_list, const char *tag);
2925#endif
2926
2927static const char *
2928price_printable(gpointer obj)
2929{
2930 auto pr = static_cast<GNCPrice*>(obj);
2931 gnc_commodity *commodity;
2932 gnc_commodity *currency;
2933 static char buff[2048]; /* nasty static OK for printing */
2934 char *val, *da;
2935
2936 if (!pr) return "";
2937
2938#ifdef DUMP_FUNCTIONS
2939 /* Reference it so the compiler doesn't optimize it out. bit
2940 don't actually call it. */
2941 if (obj == buff)
2942 price_list_dump(nullptr, "");
2943#endif
2944
2945 val = gnc_numeric_to_string (pr->value);
2946 da = qof_print_date (pr->tmspec);
2947
2948 commodity = gnc_price_get_commodity(pr);
2949 currency = gnc_price_get_currency(pr);
2950
2951 g_snprintf (buff, 2048, "%s %s / %s on %s", val,
2954 da);
2955 g_free (val);
2956 g_free (da);
2957 return buff;
2958}
2959
2960#ifdef DUMP_FUNCTIONS
2961/* For debugging only, don't delete this */
2962static void
2963price_list_dump(GList *price_list, const char *tag)
2964{
2965 GNCPrice *price;
2966 GList *node;
2967 printf("Price list %s\n", tag);
2968 for (node = price_list; node != nullptr; node = node->next)
2969 {
2970 printf("%s\n", price_printable(node->data));
2971 }
2972}
2973#endif
2974
2975#ifdef _MSC_VER
2976/* MSVC compiler doesn't have C99 "designated initializers"
2977 * so we wrap them in a macro that is empty on MSVC. */
2978# define DI(x) /* */
2979#else
2980# define DI(x) x
2981#endif
2982static QofObject price_object_def =
2983{
2984 DI(.interface_version = ) QOF_OBJECT_VERSION,
2985 DI(.e_type = ) GNC_ID_PRICE,
2986 DI(.type_label = ) "Price",
2987 DI(.create = ) price_create,
2988 DI(.book_begin = ) nullptr,
2989 DI(.book_end = ) nullptr,
2990 DI(.is_dirty = ) qof_collection_is_dirty,
2991 DI(.mark_clean = ) qof_collection_mark_clean,
2992 DI(.foreach = ) price_foreach,
2993 DI(.printable = ) price_printable,
2994 DI(.version_cmp = ) nullptr,
2995};
2996
2997static QofObject pricedb_object_def =
2998{
2999 DI(.interface_version = ) QOF_OBJECT_VERSION,
3000 DI(.e_type = ) GNC_ID_PRICEDB,
3001 DI(.type_label = ) "PriceDB",
3002 DI(.create = ) nullptr,
3003 DI(.book_begin = ) pricedb_book_begin,
3004 DI(.book_end = ) pricedb_book_end,
3005 DI(.is_dirty = ) qof_collection_is_dirty,
3006 DI(.mark_clean = ) qof_collection_mark_clean,
3007 DI(.foreach = ) nullptr,
3008 DI(.printable = ) nullptr,
3009 DI(.version_cmp = ) nullptr,
3010};
3011
3012gboolean
3013gnc_pricedb_register (void)
3014{
3015 static QofParam params[] =
3016 {
3017 { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
3018 { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
3019 { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time64, (QofSetterFunc)gnc_price_set_time64 },
3020 { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
3021 { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
3022 { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
3023 { nullptr },
3024 };
3025
3026 qof_class_register (GNC_ID_PRICE, nullptr, params);
3027
3028 if (!qof_object_register (&price_object_def))
3029 return FALSE;
3030 return qof_object_register (&pricedb_object_def);
3031}
3032
3033/* ========================= 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:124
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition qofclass.h:131
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