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