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 static GNCPrice *
2182 lookup_nearest_in_time(GNCPriceDB *db,
2183  const gnc_commodity *c,
2184  const gnc_commodity *currency,
2185  time64 t,
2186  gboolean sameday)
2187 {
2188  GList *price_list;
2189  GNCPrice *current_price = nullptr;
2190  GNCPrice *next_price = nullptr;
2191  GNCPrice *result = nullptr;
2192 
2193  if (!db || !c || !currency) return nullptr;
2194  if (t == INT64_MAX) return nullptr;
2195  ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2196  price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2197  if (!price_list) return nullptr;
2198 
2199  /* default answer */
2200  current_price = static_cast<GNCPrice*>(price_list->data);
2201 
2202  /* find the first candidate past the one we want. Remember that
2203  prices are in most-recent-first order. */
2204  for (auto item = price_list; item; item = g_list_next (item))
2205  {
2206  auto p = static_cast<GNCPrice*>(item->data);
2207  time64 price_time = gnc_price_get_time64(p);
2208  if (price_time <= t)
2209  {
2210  next_price = static_cast<GNCPrice*>(item->data);
2211  break;
2212  }
2213  current_price = static_cast<GNCPrice*>(item->data);
2214  }
2215 
2216  if (current_price) /* How can this be null??? */
2217  {
2218  if (!next_price)
2219  {
2220  /* It's earlier than the last price on the list */
2221  result = current_price;
2222  if (sameday)
2223  {
2224  /* Must be on the same day. */
2225  time64 price_day;
2226  time64 t_day;
2227  price_day = time64CanonicalDayTime(gnc_price_get_time64(current_price));
2228  t_day = time64CanonicalDayTime(t);
2229  if (price_day != t_day)
2230  result = nullptr;
2231  }
2232  }
2233  else
2234  {
2235  /* If the requested time is not earlier than the first price on the
2236  list, then current_price and next_price will be the same. */
2237  time64 current_t = gnc_price_get_time64(current_price);
2238  time64 next_t = gnc_price_get_time64(next_price);
2239  time64 diff_current = current_t - t;
2240  time64 diff_next = next_t - t;
2241  time64 abs_current = llabs(diff_current);
2242  time64 abs_next = llabs(diff_next);
2243 
2244  if (sameday)
2245  {
2246  /* Result must be on same day, see if either of the two isn't */
2247  time64 t_day = time64CanonicalDayTime(t);
2248  time64 current_day = time64CanonicalDayTime(current_t);
2249  time64 next_day = time64CanonicalDayTime(next_t);
2250  if (current_day == t_day)
2251  {
2252  if (next_day == t_day)
2253  {
2254  /* Both on same day, return nearest */
2255  if (abs_current < abs_next)
2256  result = current_price;
2257  else
2258  result = next_price;
2259  }
2260  else
2261  /* current_price on same day, next_price not */
2262  result = current_price;
2263  }
2264  else if (next_day == t_day)
2265  /* next_price on same day, current_price not */
2266  result = next_price;
2267  }
2268  else
2269  {
2270  /* Choose the price that is closest to the given time. In case of
2271  * a tie, prefer the older price since it actually existed at the
2272  * time. (This also fixes bug #541970.) */
2273  if (abs_current < abs_next)
2274  {
2275  result = current_price;
2276  }
2277  else
2278  {
2279  result = next_price;
2280  }
2281  }
2282  }
2283  }
2284 
2285  gnc_price_ref(result);
2286  g_list_free (price_list);
2287  LEAVE (" ");
2288  return result;
2289 }
2290 
2291 GNCPrice *
2293  const gnc_commodity *c,
2294  const gnc_commodity *currency,
2295  time64 t)
2296 {
2297  return lookup_nearest_in_time(db, c, currency, t, FALSE);
2298 }
2299 
2300 // return 0 if price's time is less or equal to time
2301 static int price_time64_less_or_equal (GNCPrice *p, time64 *time)
2302 {
2303  return !(gnc_price_get_time64 (p) <= *time);
2304 }
2305 
2306 GNCPrice *
2308  const gnc_commodity *c,
2309  const gnc_commodity *currency,
2310  time64 t)
2311 {
2312  GNCPrice *current_price = nullptr;
2313  if (!db || !c || !currency) return nullptr;
2314  ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
2315  auto price_list = pricedb_get_prices_internal (db, c, currency, TRUE);
2316  if (!price_list) return nullptr;
2317  auto p = g_list_find_custom (price_list, &t, (GCompareFunc)price_time64_less_or_equal);
2318  if (p)
2319  {
2320  current_price = GNC_PRICE (p->data);
2321  gnc_price_ref (current_price);
2322  }
2323  g_list_free (price_list);
2324  LEAVE (" ");
2325  return current_price;
2326 }
2327 
2328 
2329 typedef struct
2330 {
2331  GNCPrice *from;
2332  GNCPrice *to;
2333 } PriceTuple;
2334 
2335 static PriceTuple
2336 extract_common_prices (PriceList *from_prices, PriceList *to_prices,
2337  const gnc_commodity *from, const gnc_commodity *to)
2338 {
2339  PriceTuple retval = {nullptr, nullptr};
2340  GList *from_node = nullptr, *to_node = nullptr;
2341  GNCPrice *from_price = nullptr, *to_price = nullptr;
2342 
2343  for (from_node = from_prices; from_node != nullptr;
2344  from_node = g_list_next(from_node))
2345  {
2346  for (to_node = to_prices; to_node != nullptr;
2347  to_node = g_list_next(to_node))
2348  {
2349  gnc_commodity *to_com, *to_cur;
2350  gnc_commodity *from_com, *from_cur;
2351  to_price = GNC_PRICE(to_node->data);
2352  from_price = GNC_PRICE(from_node->data);
2353  to_com = gnc_price_get_commodity (to_price);
2354  to_cur = gnc_price_get_currency (to_price);
2355  from_com = gnc_price_get_commodity (from_price);
2356  from_cur = gnc_price_get_currency (from_price);
2357  if (((to_com == from_com || to_com == from_cur) &&
2358  (to_com != from && to_com != to)) ||
2359  ((to_cur == from_com || to_cur == from_cur) &&
2360  (to_cur != from && to_cur != to)))
2361  break;
2362  to_price = nullptr;
2363  from_price = nullptr;
2364  }
2365  if (to_price != nullptr && from_price != nullptr)
2366  break;
2367  }
2368  if (from_price == nullptr || to_price == nullptr)
2369  return retval;
2370  gnc_price_ref(from_price);
2371  gnc_price_ref(to_price);
2372  retval.from = from_price;
2373  retval.to = to_price;
2374  return retval;
2375 }
2376 
2377 
2378 static gnc_numeric
2379 convert_price (const gnc_commodity *from, const gnc_commodity *to, PriceTuple tuple)
2380 {
2381  gnc_commodity *from_com = gnc_price_get_commodity (tuple.from);
2382  gnc_commodity *from_cur = gnc_price_get_currency (tuple.from);
2383  gnc_commodity *to_com = gnc_price_get_commodity (tuple.to);
2384  gnc_commodity *to_cur = gnc_price_get_currency (tuple.to);
2385  gnc_numeric from_val = gnc_price_get_value (tuple.from);
2386  gnc_numeric to_val = gnc_price_get_value (tuple.to);
2387  gnc_numeric price;
2388  int no_round = GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER;
2389 
2390  price = gnc_numeric_div (to_val, from_val, GNC_DENOM_AUTO, no_round);
2391 
2392  gnc_price_unref (tuple.from);
2393  gnc_price_unref (tuple.to);
2394 
2395  if (from_cur == from && to_cur == to)
2396  return price;
2397 
2398  if (from_com == from && to_com == to)
2399  return gnc_numeric_invert (price);
2400 
2401  price = gnc_numeric_mul (from_val, to_val, GNC_DENOM_AUTO, no_round);
2402 
2403  if (from_cur == from)
2404  return gnc_numeric_invert (price);
2405 
2406  return price;
2407 }
2408 
2409 static gnc_numeric
2410 indirect_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2411  const gnc_commodity *to, time64 t, gboolean before_date)
2412 {
2413  GList *from_prices = nullptr, *to_prices = nullptr;
2414  PriceTuple tuple;
2415  gnc_numeric zero = gnc_numeric_zero();
2416  if (!from || !to)
2417  return zero;
2418  if (t == INT64_MAX)
2419  {
2420  from_prices = gnc_pricedb_lookup_latest_any_currency(db, from);
2421  /* "to" is often the book currency which may have lots of prices,
2422  so avoid getting them if they aren't needed. */
2423  if (from_prices)
2424  to_prices = gnc_pricedb_lookup_latest_any_currency(db, to);
2425  }
2426  else if (before_date)
2427  {
2428  from_prices = gnc_pricedb_lookup_nearest_before_any_currency_t64 (db, from, t);
2429  if (from_prices)
2431  }
2432  else
2433  {
2434  from_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, from, t);
2435  if (from_prices)
2437  }
2438  if (!from_prices || !to_prices)
2439  {
2440  gnc_price_list_destroy (from_prices);
2441  gnc_price_list_destroy (to_prices);
2442  return zero;
2443  }
2444  tuple = extract_common_prices (from_prices, to_prices, from, to);
2445  gnc_price_list_destroy (from_prices);
2446  gnc_price_list_destroy (to_prices);
2447  if (tuple.from)
2448  return convert_price (from, to, tuple);
2449  return zero;
2450 }
2451 
2452 
2453 static gnc_numeric
2454 direct_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2455  const gnc_commodity *to, time64 t, gboolean before_date)
2456 {
2457  GNCPrice *price;
2458  gnc_numeric retval = gnc_numeric_zero();
2459 
2460  if (!from || !to) return retval;
2461 
2462  if (t == INT64_MAX)
2463  price = gnc_pricedb_lookup_latest(db, from, to);
2464  else if (before_date)
2465  price = gnc_pricedb_lookup_nearest_before_t64(db, from, to, t);
2466  else
2467  price = gnc_pricedb_lookup_nearest_in_time64(db, from, to, t);
2468 
2469  if (!price) return retval;
2470 
2471  retval = gnc_price_get_value (price);
2472 
2473  if (gnc_price_get_commodity (price) != from)
2474  retval = gnc_numeric_invert (retval);
2475 
2476  gnc_price_unref (price);
2477  return retval;
2478 }
2479 
2480 static gnc_numeric
2481 get_nearest_price (GNCPriceDB *pdb,
2482  const gnc_commodity *orig_curr,
2483  const gnc_commodity *new_curr,
2484  const time64 t,
2485  gboolean before)
2486 {
2487  gnc_numeric price;
2488 
2489  if (gnc_commodity_equiv (orig_curr, new_curr))
2490  return gnc_numeric_create (1, 1);
2491 
2492  /* Look for a direct price. */
2493  price = direct_price_conversion (pdb, orig_curr, new_curr, t, before);
2494 
2495  /*
2496  * no direct price found, try find a price in another currency
2497  */
2498  if (gnc_numeric_zero_p (price))
2499  price = indirect_price_conversion (pdb, orig_curr, new_curr, t, before);
2500 
2501  return gnc_numeric_reduce (price);
2502 }
2503 
2504 gnc_numeric
2506  const gnc_commodity *orig_currency,
2507  const gnc_commodity *new_currency,
2508  const time64 t)
2509 {
2510  return get_nearest_price (pdb, orig_currency, new_currency, t, TRUE);
2511 }
2512 
2513 gnc_numeric
2515  const gnc_commodity *orig_currency,
2516  const gnc_commodity *new_currency,
2517  const time64 t)
2518 {
2519  return get_nearest_price (pdb, orig_currency, new_currency, t, FALSE);
2520 }
2521 
2522 gnc_numeric
2524  const gnc_commodity *orig_currency,
2525  const gnc_commodity *new_currency)
2526 {
2527  return get_nearest_price (pdb, orig_currency, new_currency, INT64_MAX, FALSE);
2528 }
2529 
2530 static gnc_numeric
2531 convert_amount_at_date (GNCPriceDB *pdb,
2532  gnc_numeric amount,
2533  const gnc_commodity *orig_currency,
2534  const gnc_commodity *new_currency,
2535  const time64 t,
2536  gboolean before_date)
2537 {
2538  gnc_numeric price;
2539 
2540  if (gnc_numeric_zero_p (amount))
2541  return amount;
2542 
2543  price = get_nearest_price (pdb, orig_currency, new_currency, t, before_date);
2544 
2545  /* the price retrieved may be invalid. return zero. see 798015 */
2546  if (gnc_numeric_check (price))
2547  return gnc_numeric_zero ();
2548 
2549  return gnc_numeric_mul
2550  (amount, price, gnc_commodity_get_fraction (new_currency),
2552 }
2553 
2554 /*
2555  * Convert a balance from one currency to another.
2556  */
2557 gnc_numeric
2559  gnc_numeric balance,
2560  const gnc_commodity *balance_currency,
2561  const gnc_commodity *new_currency)
2562 {
2563  return convert_amount_at_date
2564  (pdb, balance, balance_currency, new_currency, INT64_MAX, FALSE);
2565 }
2566 
2567 gnc_numeric
2569  gnc_numeric balance,
2570  const gnc_commodity *balance_currency,
2571  const gnc_commodity *new_currency,
2572  time64 t)
2573 {
2574  return convert_amount_at_date
2575  (pdb, balance, balance_currency, new_currency, t, FALSE);
2576 }
2577 
2578 gnc_numeric
2580  gnc_numeric balance,
2581  const gnc_commodity *balance_currency,
2582  const gnc_commodity *new_currency,
2583  time64 t)
2584 {
2585  return convert_amount_at_date
2586  (pdb, balance, balance_currency, new_currency, t, TRUE);
2587 }
2588 
2589 /* ==================================================================== */
2590 /* gnc_pricedb_foreach_price infrastructure
2591  */
2592 
2593 typedef struct
2594 {
2595  gboolean ok;
2596  gboolean (*func)(GNCPrice *p, gpointer user_data);
2597  gpointer user_data;
2599 
2600 static void
2601 pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2602 {
2603  GList *price_list = (GList *) val;
2604  GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
2605 
2606  /* stop traversal when func returns FALSE */
2607  foreach_data->ok = g_list_find_custom (price_list, foreach_data->user_data, (GCompareFunc)foreach_data->func)
2608  != nullptr;
2609 }
2610 
2611 static void
2612 pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2613 {
2614  GHashTable *currencies_hash = (GHashTable *) val;
2615  g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
2616 }
2617 
2618 static gboolean
2619 unstable_price_traversal(GNCPriceDB *db,
2620  gboolean (*f)(GNCPrice *p, gpointer user_data),
2621  gpointer user_data)
2622 {
2623  GNCPriceDBForeachData foreach_data;
2624 
2625  if (!db || !f) return FALSE;
2626  foreach_data.ok = TRUE;
2627  foreach_data.func = f;
2628  foreach_data.user_data = user_data;
2629  if (db->commodity_hash == nullptr)
2630  {
2631  return FALSE;
2632  }
2633  g_hash_table_foreach(db->commodity_hash,
2634  pricedb_foreach_currencies_hash,
2635  &foreach_data);
2636 
2637  return foreach_data.ok;
2638 }
2639 
2640 /* foreach_pricelist */
2641 typedef struct
2642 {
2643  gboolean ok;
2644  gboolean (*func)(GList *p, gpointer user_data);
2645  gpointer user_data;
2647 
2648 static void
2649 pricedb_pricelist_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2650 {
2651  GList *price_list = (GList *) val;
2652  GNCPriceListForeachData *foreach_data = (GNCPriceListForeachData *) user_data;
2653  if (foreach_data->ok)
2654  {
2655  foreach_data->ok = foreach_data->func(price_list, foreach_data->user_data);
2656  }
2657 }
2658 
2659 static void
2660 pricedb_pricelist_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2661 {
2662  GHashTable *currencies_hash = (GHashTable *) val;
2663  g_hash_table_foreach(currencies_hash, pricedb_pricelist_foreach_pricelist, user_data);
2664 }
2665 
2666 static gboolean
2667 pricedb_pricelist_traversal(GNCPriceDB *db,
2668  gboolean (*f)(GList *p, gpointer user_data),
2669  gpointer user_data)
2670 {
2671  GNCPriceListForeachData foreach_data;
2672 
2673  if (!db || !f) return FALSE;
2674  foreach_data.ok = TRUE;
2675  foreach_data.func = f;
2676  foreach_data.user_data = user_data;
2677  if (db->commodity_hash == nullptr)
2678  {
2679  return FALSE;
2680  }
2681  g_hash_table_foreach(db->commodity_hash,
2682  pricedb_pricelist_foreach_currencies_hash,
2683  &foreach_data);
2684 
2685  return foreach_data.ok;
2686 }
2687 
2688 static bool
2689 compare_hash_entries_by_commodity_key (const CommodityPtrPair& he_a, const CommodityPtrPair& he_b)
2690 {
2691  auto ca = he_a.first;
2692  auto cb = he_b.first;
2693 
2694  if (ca == cb || !cb)
2695  return false;
2696 
2697  if (!ca)
2698  return true;
2699 
2700  auto cmp_result = g_strcmp0 (gnc_commodity_get_namespace (ca), gnc_commodity_get_namespace (cb));
2701 
2702  if (cmp_result)
2703  return (cmp_result < 0);
2704 
2705  return g_strcmp0(gnc_commodity_get_mnemonic (ca), gnc_commodity_get_mnemonic (cb)) < 0;
2706 }
2707 
2708 static bool
2709 stable_price_traversal(GNCPriceDB *db,
2710  gboolean (*f)(GNCPrice *p, gpointer user_data),
2711  gpointer user_data)
2712 {
2713  g_return_val_if_fail (db && f, false);
2714 
2715  auto currency_hashes = hash_table_to_vector (db->commodity_hash);
2716  std::sort (currency_hashes.begin(), currency_hashes.end(), compare_hash_entries_by_commodity_key);
2717 
2718  for (const auto& entry : currency_hashes)
2719  {
2720  auto price_lists = hash_table_to_vector (static_cast<GHashTable*>(entry.second));
2721  std::sort (price_lists.begin(), price_lists.end(), compare_hash_entries_by_commodity_key);
2722 
2723  for (const auto& pricelist_entry : price_lists)
2724  if (g_list_find_custom (static_cast<GList*>(pricelist_entry.second), user_data, (GCompareFunc)f))
2725  return false;
2726  }
2727 
2728  return true;
2729 }
2730 
2731 gboolean
2733  GncPriceForeachFunc f,
2734  gpointer user_data,
2735  gboolean stable_order)
2736 {
2737  ENTER ("db=%p f=%p", db, f);
2738  if (stable_order)
2739  {
2740  LEAVE (" stable order found");
2741  return stable_price_traversal(db, f, user_data);
2742  }
2743  LEAVE (" use unstable order");
2744  return unstable_price_traversal(db, f, user_data);
2745 }
2746 
2747 /***************************************************************************/
2748 
2749 /* Semi-lame debugging code */
2750 
2751 void
2752 gnc_price_print(GNCPrice *p, FILE *f, int indent)
2753 {
2754  gnc_commodity *commodity;
2755  gnc_commodity *currency;
2756  gchar *istr = nullptr; /* indent string */
2757  const char *str;
2758 
2759  if (!p) return;
2760  if (!f) return;
2761 
2762  commodity = gnc_price_get_commodity(p);
2763  currency = gnc_price_get_currency(p);
2764 
2765  if (!commodity) return;
2766  if (!currency) return;
2767 
2768  istr = g_strnfill(indent, ' ');
2769 
2770  fprintf(f, "%s<pdb:price>\n", istr);
2771  fprintf(f, "%s <pdb:commodity pointer=%p>\n", istr, commodity);
2772  str = gnc_commodity_get_namespace(commodity);
2773  str = str ? str : "(null)";
2774  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2775  str = gnc_commodity_get_mnemonic(commodity);
2776  str = str ? str : "(null)";
2777  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2778  fprintf(f, "%s </pdb:commodity>\n", istr);
2779  fprintf(f, "%s <pdb:currency pointer=%p>\n", istr, currency);
2780  str = gnc_commodity_get_namespace(currency);
2781  str = str ? str : "(null)";
2782  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2783  str = gnc_commodity_get_mnemonic(currency);
2784  str = str ? str : "(null)";
2785  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2786  fprintf(f, "%s </pdb:currency>\n", istr);
2787  str = source_names[gnc_price_get_source(p)];
2788  str = str ? str : "invalid";
2789  fprintf(f, "%s %s\n", istr, str);
2790  str = gnc_price_get_typestr(p);
2791  str = str ? str : "(null)";
2792  fprintf(f, "%s %s\n", istr, str);
2793  fprintf(f, "%s %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
2794  fprintf(f, "%s</pdb:price>\n", istr);
2795 
2796  g_free(istr);
2797 }
2798 
2799 static gboolean
2800 print_pricedb_adapter(GNCPrice *p, gpointer user_data)
2801 {
2802  FILE *f = (FILE *) user_data;
2803  gnc_price_print(p, f, 1);
2804  return TRUE;
2805 }
2806 
2807 void
2808 gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
2809 {
2810  if (!db)
2811  {
2812  PERR("nullptr PriceDB\n");
2813  return;
2814  }
2815  if (!f)
2816  {
2817  PERR("nullptr FILE*\n");
2818  return;
2819  }
2820 
2821  fprintf(f, "<gnc:pricedb>\n");
2822  gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
2823  fprintf(f, "</gnc:pricedb>\n");
2824 }
2825 
2826 /* ==================================================================== */
2827 /* gncObject function implementation and registration */
2828 
2829 static void
2830 pricedb_book_begin (QofBook *book)
2831 {
2832  gnc_pricedb_create(book);
2833 }
2834 
2835 static void
2836 pricedb_book_end (QofBook *book)
2837 {
2838  QofCollection *col;
2839 
2840  if (!book)
2841  return;
2842  col = qof_book_get_collection(book, GNC_ID_PRICEDB);
2843  auto db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2844  qof_collection_set_data(col, nullptr);
2845  gnc_pricedb_destroy(db);
2846 }
2847 
2848 static gpointer
2849 price_create (QofBook *book)
2850 {
2851  return gnc_price_create(book);
2852 }
2853 
2854 /* ==================================================================== */
2855 /* a non-boolean foreach. Ugh */
2856 
2857 typedef struct
2858 {
2859  void (*func)(GNCPrice *p, gpointer user_data);
2860  gpointer user_data;
2861 }
2863 
2864 static void
2865 void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2866 {
2867  GList *price_list = (GList *) val;
2868  VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
2869 
2870  g_list_foreach (price_list, (GFunc)foreach_data->func, foreach_data->user_data);
2871 }
2872 
2873 static void
2874 void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2875 {
2876  GHashTable *currencies_hash = (GHashTable *) val;
2877  g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
2878 }
2879 
2880 static void
2881 void_unstable_price_traversal(GNCPriceDB *db,
2882  void (*f)(GNCPrice *p, gpointer user_data),
2883  gpointer user_data)
2884 {
2885  VoidGNCPriceDBForeachData foreach_data;
2886 
2887  if (!db || !f) return;
2888  foreach_data.func = f;
2889  foreach_data.user_data = user_data;
2890 
2891  g_hash_table_foreach(db->commodity_hash,
2892  void_pricedb_foreach_currencies_hash,
2893  &foreach_data);
2894 }
2895 
2896 static void
2897 price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
2898 {
2899  GNCPriceDB *db;
2900 
2901  db = static_cast<GNCPriceDB*>(qof_collection_get_data(col));
2902  void_unstable_price_traversal(db,
2903  (void (*)(GNCPrice *, gpointer)) cb,
2904  data);
2905 }
2906 
2907 /* ==================================================================== */
2908 
2909 #ifdef DUMP_FUNCTIONS
2910 /* For debugging only, don't delete this */
2911 static void price_list_dump(GList *price_list, const char *tag);
2912 #endif
2913 
2914 static const char *
2915 price_printable(gpointer obj)
2916 {
2917  auto pr = static_cast<GNCPrice*>(obj);
2918  gnc_commodity *commodity;
2919  gnc_commodity *currency;
2920  static char buff[2048]; /* nasty static OK for printing */
2921  char *val, *da;
2922 
2923  if (!pr) return "";
2924 
2925 #ifdef DUMP_FUNCTIONS
2926  /* Reference it so the compiler doesn't optimize it out. bit
2927  don't actually call it. */
2928  if (obj == buff)
2929  price_list_dump(nullptr, "");
2930 #endif
2931 
2932  val = gnc_numeric_to_string (pr->value);
2933  da = qof_print_date (pr->tmspec);
2934 
2935  commodity = gnc_price_get_commodity(pr);
2936  currency = gnc_price_get_currency(pr);
2937 
2938  g_snprintf (buff, 2048, "%s %s / %s on %s", val,
2939  gnc_commodity_get_unique_name(commodity),
2941  da);
2942  g_free (val);
2943  g_free (da);
2944  return buff;
2945 }
2946 
2947 #ifdef DUMP_FUNCTIONS
2948 /* For debugging only, don't delete this */
2949 static void
2950 price_list_dump(GList *price_list, const char *tag)
2951 {
2952  GNCPrice *price;
2953  GList *node;
2954  printf("Price list %s\n", tag);
2955  for (node = price_list; node != nullptr; node = node->next)
2956  {
2957  printf("%s\n", price_printable(node->data));
2958  }
2959 }
2960 #endif
2961 
2962 #ifdef _MSC_VER
2963 /* MSVC compiler doesn't have C99 "designated initializers"
2964  * so we wrap them in a macro that is empty on MSVC. */
2965 # define DI(x) /* */
2966 #else
2967 # define DI(x) x
2968 #endif
2969 static QofObject price_object_def =
2970 {
2971  DI(.interface_version = ) QOF_OBJECT_VERSION,
2972  DI(.e_type = ) GNC_ID_PRICE,
2973  DI(.type_label = ) "Price",
2974  DI(.create = ) price_create,
2975  DI(.book_begin = ) nullptr,
2976  DI(.book_end = ) nullptr,
2977  DI(.is_dirty = ) qof_collection_is_dirty,
2978  DI(.mark_clean = ) qof_collection_mark_clean,
2979  DI(.foreach = ) price_foreach,
2980  DI(.printable = ) price_printable,
2981  DI(.version_cmp = ) nullptr,
2982 };
2983 
2984 static QofObject pricedb_object_def =
2985 {
2986  DI(.interface_version = ) QOF_OBJECT_VERSION,
2987  DI(.e_type = ) GNC_ID_PRICEDB,
2988  DI(.type_label = ) "PriceDB",
2989  DI(.create = ) nullptr,
2990  DI(.book_begin = ) pricedb_book_begin,
2991  DI(.book_end = ) pricedb_book_end,
2992  DI(.is_dirty = ) qof_collection_is_dirty,
2993  DI(.mark_clean = ) qof_collection_mark_clean,
2994  DI(.foreach = ) nullptr,
2995  DI(.printable = ) nullptr,
2996  DI(.version_cmp = ) nullptr,
2997 };
2998 
2999 gboolean
3000 gnc_pricedb_register (void)
3001 {
3002  static QofParam params[] =
3003  {
3004  { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
3005  { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
3006  { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time64, (QofSetterFunc)gnc_price_set_time64 },
3007  { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
3008  { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
3009  { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
3010  { nullptr },
3011  };
3012 
3013  qof_class_register (GNC_ID_PRICE, nullptr, params);
3014 
3015  if (!qof_object_register (&price_object_def))
3016  return FALSE;
3017  return qof_object_register (&pricedb_object_def);
3018 }
3019 
3020 /* ========================= 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.
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:1604
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:597
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:1211
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:609
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:403
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:1140
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:573
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:1225