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