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