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