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