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