GnuCash  4.11-517-g41de4cefce
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  if (from_cur == from && to_cur == to)
2497  return price;
2498 
2499  if (from_com == from && to_com == to)
2500  return gnc_numeric_invert (price);
2501 
2502  price = gnc_numeric_mul (from_val, to_val, GNC_DENOM_AUTO, no_round);
2503 
2504  if (from_cur == from)
2505  return gnc_numeric_invert (price);
2506 
2507  return price;
2508 }
2509 
2510 static gnc_numeric
2511 indirect_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2512  const gnc_commodity *to, time64 t, gboolean before_date)
2513 {
2514  GList *from_prices = NULL, *to_prices = NULL;
2515  PriceTuple tuple;
2516  gnc_numeric zero = gnc_numeric_zero();
2517  if (!from || !to)
2518  return zero;
2519  if (t == INT64_MAX)
2520  {
2521  from_prices = gnc_pricedb_lookup_latest_any_currency(db, from);
2522  /* "to" is often the book currency which may have lots of prices,
2523  so avoid getting them if they aren't needed. */
2524  if (from_prices)
2525  to_prices = gnc_pricedb_lookup_latest_any_currency(db, to);
2526  }
2527  else if (before_date)
2528  {
2529  from_prices = gnc_pricedb_lookup_nearest_before_any_currency_t64 (db, from, t);
2530  if (from_prices)
2532  }
2533  else
2534  {
2535  from_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, from, t);
2536  if (from_prices)
2538  }
2539  if (!from_prices || !to_prices)
2540  {
2541  gnc_price_list_destroy (from_prices);
2542  gnc_price_list_destroy (to_prices);
2543  return zero;
2544  }
2545  tuple = extract_common_prices (from_prices, to_prices, from, to);
2546  gnc_price_list_destroy (from_prices);
2547  gnc_price_list_destroy (to_prices);
2548  if (tuple.from)
2549  return convert_price (from, to, tuple);
2550  return zero;
2551 }
2552 
2553 
2554 static gnc_numeric
2555 direct_price_conversion (GNCPriceDB *db, const gnc_commodity *from,
2556  const gnc_commodity *to, time64 t, gboolean before_date)
2557 {
2558  GNCPrice *price;
2559  gnc_numeric retval = gnc_numeric_zero();
2560 
2561  if (!from || !to) return retval;
2562 
2563  if (t == INT64_MAX)
2564  price = gnc_pricedb_lookup_latest(db, from, to);
2565  else if (before_date)
2566  price = gnc_pricedb_lookup_nearest_before_t64(db, from, to, t);
2567  else
2568  price = gnc_pricedb_lookup_nearest_in_time64(db, from, to, t);
2569 
2570  if (!price) return retval;
2571 
2572  retval = gnc_price_get_value (price);
2573 
2574  if (gnc_price_get_commodity (price) != from)
2575  retval = gnc_numeric_invert (retval);
2576 
2577  gnc_price_unref (price);
2578  return retval;
2579 }
2580 
2581 static gnc_numeric
2582 get_nearest_price (GNCPriceDB *pdb,
2583  const gnc_commodity *orig_curr,
2584  const gnc_commodity *new_curr,
2585  const time64 t,
2586  gboolean before)
2587 {
2588  gnc_numeric price;
2589 
2590  if (gnc_commodity_equiv (orig_curr, new_curr))
2591  return gnc_numeric_create (1, 1);
2592 
2593  /* Look for a direct price. */
2594  price = direct_price_conversion (pdb, orig_curr, new_curr, t, before);
2595 
2596  /*
2597  * no direct price found, try find a price in another currency
2598  */
2599  if (gnc_numeric_zero_p (price))
2600  price = indirect_price_conversion (pdb, orig_curr, new_curr, t, before);
2601 
2602  return gnc_numeric_reduce (price);
2603 }
2604 
2605 gnc_numeric
2607  const gnc_commodity *orig_currency,
2608  const gnc_commodity *new_currency,
2609  const time64 t)
2610 {
2611  return get_nearest_price (pdb, orig_currency, new_currency, t, TRUE);
2612 }
2613 
2614 gnc_numeric
2616  const gnc_commodity *orig_currency,
2617  const gnc_commodity *new_currency,
2618  const time64 t)
2619 {
2620  return get_nearest_price (pdb, orig_currency, new_currency, t, FALSE);
2621 }
2622 
2623 gnc_numeric
2625  const gnc_commodity *orig_currency,
2626  const gnc_commodity *new_currency)
2627 {
2628  return get_nearest_price (pdb, orig_currency, new_currency, INT64_MAX, FALSE);
2629 }
2630 
2631 static gnc_numeric
2632 convert_amount_at_date (GNCPriceDB *pdb,
2633  gnc_numeric amount,
2634  const gnc_commodity *orig_currency,
2635  const gnc_commodity *new_currency,
2636  const time64 t,
2637  gboolean before_date)
2638 {
2639  gnc_numeric price;
2640 
2641  if (gnc_numeric_zero_p (amount))
2642  return amount;
2643 
2644  price = get_nearest_price (pdb, orig_currency, new_currency, t, before_date);
2645 
2646  /* the price retrieved may be invalid. return zero. see 798015 */
2647  if (gnc_numeric_check (price))
2648  return gnc_numeric_zero ();
2649 
2650  return gnc_numeric_mul
2651  (amount, price, gnc_commodity_get_fraction (new_currency),
2653 }
2654 
2655 /*
2656  * Convert a balance from one currency to another.
2657  */
2658 gnc_numeric
2660  gnc_numeric balance,
2661  const gnc_commodity *balance_currency,
2662  const gnc_commodity *new_currency)
2663 {
2664  return convert_amount_at_date
2665  (pdb, balance, balance_currency, new_currency, INT64_MAX, FALSE);
2666 }
2667 
2668 gnc_numeric
2670  gnc_numeric balance,
2671  const gnc_commodity *balance_currency,
2672  const gnc_commodity *new_currency,
2673  time64 t)
2674 {
2675  return convert_amount_at_date
2676  (pdb, balance, balance_currency, new_currency, t, FALSE);
2677 }
2678 
2679 gnc_numeric
2681  gnc_numeric balance,
2682  const gnc_commodity *balance_currency,
2683  const gnc_commodity *new_currency,
2684  time64 t)
2685 {
2686  return convert_amount_at_date
2687  (pdb, balance, balance_currency, new_currency, t, TRUE);
2688 }
2689 
2690 /* ==================================================================== */
2691 /* gnc_pricedb_foreach_price infrastructure
2692  */
2693 
2694 typedef struct
2695 {
2696  gboolean ok;
2697  gboolean (*func)(GNCPrice *p, gpointer user_data);
2698  gpointer user_data;
2700 
2701 static void
2702 pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2703 {
2704  GList *price_list = (GList *) val;
2705  GList *node = price_list;
2706  GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
2707 
2708  /* stop traversal when func returns FALSE */
2709  while (foreach_data->ok && node)
2710  {
2711  GNCPrice *p = (GNCPrice *) node->data;
2712  foreach_data->ok = foreach_data->func(p, foreach_data->user_data);
2713  node = node->next;
2714  }
2715 }
2716 
2717 static void
2718 pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2719 {
2720  GHashTable *currencies_hash = (GHashTable *) val;
2721  g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
2722 }
2723 
2724 static gboolean
2725 unstable_price_traversal(GNCPriceDB *db,
2726  gboolean (*f)(GNCPrice *p, gpointer user_data),
2727  gpointer user_data)
2728 {
2729  GNCPriceDBForeachData foreach_data;
2730 
2731  if (!db || !f) return FALSE;
2732  foreach_data.ok = TRUE;
2733  foreach_data.func = f;
2734  foreach_data.user_data = user_data;
2735  if (db->commodity_hash == NULL)
2736  {
2737  return FALSE;
2738  }
2739  g_hash_table_foreach(db->commodity_hash,
2740  pricedb_foreach_currencies_hash,
2741  &foreach_data);
2742 
2743  return foreach_data.ok;
2744 }
2745 
2746 /* foreach_pricelist */
2747 typedef struct
2748 {
2749  gboolean ok;
2750  gboolean (*func)(GList *p, gpointer user_data);
2751  gpointer user_data;
2753 
2754 static void
2755 pricedb_pricelist_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2756 {
2757  GList *price_list = (GList *) val;
2758  GNCPriceListForeachData *foreach_data = (GNCPriceListForeachData *) user_data;
2759  if (foreach_data->ok)
2760  {
2761  foreach_data->ok = foreach_data->func(price_list, foreach_data->user_data);
2762  }
2763 }
2764 
2765 static void
2766 pricedb_pricelist_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2767 {
2768  GHashTable *currencies_hash = (GHashTable *) val;
2769  g_hash_table_foreach(currencies_hash, pricedb_pricelist_foreach_pricelist, user_data);
2770 }
2771 
2772 static gboolean
2773 pricedb_pricelist_traversal(GNCPriceDB *db,
2774  gboolean (*f)(GList *p, gpointer user_data),
2775  gpointer user_data)
2776 {
2777  GNCPriceListForeachData foreach_data;
2778 
2779  if (!db || !f) return FALSE;
2780  foreach_data.ok = TRUE;
2781  foreach_data.func = f;
2782  foreach_data.user_data = user_data;
2783  if (db->commodity_hash == NULL)
2784  {
2785  return FALSE;
2786  }
2787  g_hash_table_foreach(db->commodity_hash,
2788  pricedb_pricelist_foreach_currencies_hash,
2789  &foreach_data);
2790 
2791  return foreach_data.ok;
2792 }
2793 
2794 static gint
2795 compare_hash_entries_by_commodity_key(gconstpointer a, gconstpointer b)
2796 {
2797  HashEntry *he_a = (HashEntry *) a;
2798  HashEntry *he_b = (HashEntry *) b;
2799  gnc_commodity *ca;
2800  gnc_commodity *cb;
2801  int cmp_result;
2802 
2803  if (a == b) return 0;
2804  if (!a && !b) return 0;
2805  if (!a) return -1;
2806  if (!b) return 1;
2807 
2808  ca = (gnc_commodity *) he_a->key;
2809  cb = (gnc_commodity *) he_b->key;
2810 
2811  cmp_result = g_strcmp0(gnc_commodity_get_namespace(ca),
2813 
2814  if (cmp_result != 0) return cmp_result;
2815 
2816  return g_strcmp0(gnc_commodity_get_mnemonic(ca),
2818 }
2819 
2820 static gboolean
2821 stable_price_traversal(GNCPriceDB *db,
2822  gboolean (*f)(GNCPrice *p, gpointer user_data),
2823  gpointer user_data)
2824 {
2825  GSList *currency_hashes = NULL;
2826  gboolean ok = TRUE;
2827  GSList *i = NULL;
2828 
2829  if (!db || !f) return FALSE;
2830 
2831  currency_hashes = hash_table_to_list(db->commodity_hash);
2832  currency_hashes = g_slist_sort(currency_hashes,
2833  compare_hash_entries_by_commodity_key);
2834 
2835  for (i = currency_hashes; i; i = i->next)
2836  {
2837  HashEntry *entry = (HashEntry *) i->data;
2838  GHashTable *currency_hash = (GHashTable *) entry->value;
2839  GSList *price_lists = hash_table_to_list(currency_hash);
2840  GSList *j;
2841 
2842  price_lists = g_slist_sort(price_lists, compare_hash_entries_by_commodity_key);
2843  for (j = price_lists; j; j = j->next)
2844  {
2845  HashEntry *pricelist_entry = (HashEntry *) j->data;
2846  GList *price_list = (GList *) pricelist_entry->value;
2847  GList *node;
2848 
2849  for (node = (GList *) price_list; node; node = node->next)
2850  {
2851  GNCPrice *price = (GNCPrice *) node->data;
2852 
2853  /* stop traversal when f returns FALSE */
2854  if (FALSE == ok) break;
2855  if (!f(price, user_data)) ok = FALSE;
2856  }
2857  }
2858  if (price_lists)
2859  {
2860  g_slist_foreach(price_lists, hash_entry_free_gfunc, NULL);
2861  g_slist_free(price_lists);
2862  price_lists = NULL;
2863  }
2864  }
2865 
2866  if (currency_hashes)
2867  {
2868  g_slist_foreach(currency_hashes, hash_entry_free_gfunc, NULL);
2869  g_slist_free(currency_hashes);
2870  }
2871  return ok;
2872 }
2873 
2874 gboolean
2876  GncPriceForeachFunc f,
2877  gpointer user_data,
2878  gboolean stable_order)
2879 {
2880  ENTER ("db=%p f=%p", db, f);
2881  if (stable_order)
2882  {
2883  LEAVE (" stable order found");
2884  return stable_price_traversal(db, f, user_data);
2885  }
2886  LEAVE (" use unstable order");
2887  return unstable_price_traversal(db, f, user_data);
2888 }
2889 
2890 /* ==================================================================== */
2891 /* commodity substitution */
2892 
2893 typedef struct
2894 {
2895  gnc_commodity *old_c;
2896  gnc_commodity *new_c;
2898 
2899 static gboolean
2900 add_price_to_list (GNCPrice *p, gpointer data)
2901 {
2902  GList **list = data;
2903 
2904  *list = g_list_prepend (*list, p);
2905 
2906  return TRUE;
2907 }
2908 
2909 static void
2910 gnc_price_fixup_legacy_commods(gpointer data, gpointer user_data)
2911 {
2912  GNCPrice *p = data;
2913  GNCPriceFixupData *fixup_data = user_data;
2914  gnc_commodity *price_c;
2915 
2916  if (!p) return;
2917 
2918  price_c = gnc_price_get_commodity(p);
2919  if (gnc_commodity_equiv(price_c, fixup_data->old_c))
2920  {
2921  gnc_price_set_commodity (p, fixup_data->new_c);
2922  }
2923  price_c = gnc_price_get_currency(p);
2924  if (gnc_commodity_equiv(price_c, fixup_data->old_c))
2925  {
2926  gnc_price_set_currency (p, fixup_data->new_c);
2927  }
2928 }
2929 
2930 void
2931 gnc_pricedb_substitute_commodity(GNCPriceDB *db,
2932  gnc_commodity *old_c,
2933  gnc_commodity *new_c)
2934 {
2935  GNCPriceFixupData data;
2936  GList *prices = NULL;
2937 
2938  if (!db || !old_c || !new_c) return;
2939 
2940  data.old_c = old_c;
2941  data.new_c = new_c;
2942 
2943  gnc_pricedb_foreach_price (db, add_price_to_list, &prices, FALSE);
2944 
2945  g_list_foreach (prices, gnc_price_fixup_legacy_commods, &data);
2946 
2947  g_list_free (prices);
2948 }
2949 
2950 /***************************************************************************/
2951 
2952 /* Semi-lame debugging code */
2953 
2954 void
2955 gnc_price_print(GNCPrice *p, FILE *f, int indent)
2956 {
2957  gnc_commodity *commodity;
2958  gnc_commodity *currency;
2959  gchar *istr = NULL; /* indent string */
2960  const char *str;
2961 
2962  if (!p) return;
2963  if (!f) return;
2964 
2965  commodity = gnc_price_get_commodity(p);
2966  currency = gnc_price_get_currency(p);
2967 
2968  if (!commodity) return;
2969  if (!currency) return;
2970 
2971  istr = g_strnfill(indent, ' ');
2972 
2973  fprintf(f, "%s<pdb:price>\n", istr);
2974  fprintf(f, "%s <pdb:commodity pointer=%p>\n", istr, commodity);
2975  str = gnc_commodity_get_namespace(commodity);
2976  str = str ? str : "(null)";
2977  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2978  str = gnc_commodity_get_mnemonic(commodity);
2979  str = str ? str : "(null)";
2980  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2981  fprintf(f, "%s </pdb:commodity>\n", istr);
2982  fprintf(f, "%s <pdb:currency pointer=%p>\n", istr, currency);
2983  str = gnc_commodity_get_namespace(currency);
2984  str = str ? str : "(null)";
2985  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2986  str = gnc_commodity_get_mnemonic(currency);
2987  str = str ? str : "(null)";
2988  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2989  fprintf(f, "%s </pdb:currency>\n", istr);
2990  str = source_names[gnc_price_get_source(p)];
2991  str = str ? str : "invalid";
2992  fprintf(f, "%s %s\n", istr, str);
2993  str = gnc_price_get_typestr(p);
2994  str = str ? str : "(null)";
2995  fprintf(f, "%s %s\n", istr, str);
2996  fprintf(f, "%s %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
2997  fprintf(f, "%s</pdb:price>\n", istr);
2998 
2999  g_free(istr);
3000 }
3001 
3002 static gboolean
3003 print_pricedb_adapter(GNCPrice *p, gpointer user_data)
3004 {
3005  FILE *f = (FILE *) user_data;
3006  gnc_price_print(p, f, 1);
3007  return TRUE;
3008 }
3009 
3010 void
3011 gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
3012 {
3013  if (!db)
3014  {
3015  PERR("NULL PriceDB\n");
3016  return;
3017  }
3018  if (!f)
3019  {
3020  PERR("NULL FILE*\n");
3021  return;
3022  }
3023 
3024  fprintf(f, "<gnc:pricedb>\n");
3025  gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
3026  fprintf(f, "</gnc:pricedb>\n");
3027 }
3028 
3029 /* ==================================================================== */
3030 /* gncObject function implementation and registration */
3031 
3032 static void
3033 pricedb_book_begin (QofBook *book)
3034 {
3035  gnc_pricedb_create(book);
3036 }
3037 
3038 static void
3039 pricedb_book_end (QofBook *book)
3040 {
3041  GNCPriceDB *db;
3042  QofCollection *col;
3043 
3044  if (!book)
3045  return;
3046  col = qof_book_get_collection(book, GNC_ID_PRICEDB);
3047  db = qof_collection_get_data(col);
3048  qof_collection_set_data(col, NULL);
3049  gnc_pricedb_destroy(db);
3050 }
3051 
3052 static gpointer
3053 price_create (QofBook *book)
3054 {
3055  return gnc_price_create(book);
3056 }
3057 
3058 /* ==================================================================== */
3059 /* a non-boolean foreach. Ugh */
3060 
3061 typedef struct
3062 {
3063  void (*func)(GNCPrice *p, gpointer user_data);
3064  gpointer user_data;
3065 }
3067 
3068 static void
3069 void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
3070 {
3071  GList *price_list = (GList *) val;
3072  GList *node = price_list;
3073  VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
3074 
3075  while (node)
3076  {
3077  GNCPrice *p = (GNCPrice *) node->data;
3078  foreach_data->func(p, foreach_data->user_data);
3079  node = node->next;
3080  }
3081 }
3082 
3083 static void
3084 void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
3085 {
3086  GHashTable *currencies_hash = (GHashTable *) val;
3087  g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
3088 }
3089 
3090 static void
3091 void_unstable_price_traversal(GNCPriceDB *db,
3092  void (*f)(GNCPrice *p, gpointer user_data),
3093  gpointer user_data)
3094 {
3095  VoidGNCPriceDBForeachData foreach_data;
3096 
3097  if (!db || !f) return;
3098  foreach_data.func = f;
3099  foreach_data.user_data = user_data;
3100 
3101  g_hash_table_foreach(db->commodity_hash,
3102  void_pricedb_foreach_currencies_hash,
3103  &foreach_data);
3104 }
3105 
3106 static void
3107 price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
3108 {
3109  GNCPriceDB *db;
3110 
3111  db = qof_collection_get_data(col);
3112  void_unstable_price_traversal(db,
3113  (void (*)(GNCPrice *, gpointer)) cb,
3114  data);
3115 }
3116 
3117 /* ==================================================================== */
3118 
3119 #ifdef DUMP_FUNCTIONS
3120 /* For debugging only, don't delete this */
3121 static void price_list_dump(GList *price_list, const char *tag);
3122 #endif
3123 
3124 static const char *
3125 price_printable(gpointer obj)
3126 {
3127  GNCPrice *pr = obj;
3128  gnc_commodity *commodity;
3129  gnc_commodity *currency;
3130  static char buff[2048]; /* nasty static OK for printing */
3131  char *val, *da;
3132 
3133  if (!pr) return "";
3134 
3135 #ifdef DUMP_FUNCTIONS
3136  /* Reference it so the compiler doesn't optimize it out. bit
3137  don't actually call it. */
3138  if (obj == buff)
3139  price_list_dump(NULL, "");
3140 #endif
3141 
3142  val = gnc_numeric_to_string (pr->value);
3143  da = qof_print_date (pr->tmspec);
3144 
3145  commodity = gnc_price_get_commodity(pr);
3146  currency = gnc_price_get_currency(pr);
3147 
3148  g_snprintf (buff, 2048, "%s %s / %s on %s", val,
3149  gnc_commodity_get_unique_name(commodity),
3151  da);
3152  g_free (val);
3153  g_free (da);
3154  return buff;
3155 }
3156 
3157 #ifdef DUMP_FUNCTIONS
3158 /* For debugging only, don't delete this */
3159 static void
3160 price_list_dump(GList *price_list, const char *tag)
3161 {
3162  GNCPrice *price;
3163  GList *node;
3164  printf("Price list %s\n", tag);
3165  for (node = price_list; node != NULL; node = node->next)
3166  {
3167  printf("%s\n", price_printable(node->data));
3168  }
3169 }
3170 #endif
3171 
3172 #ifdef _MSC_VER
3173 /* MSVC compiler doesn't have C99 "designated initializers"
3174  * so we wrap them in a macro that is empty on MSVC. */
3175 # define DI(x) /* */
3176 #else
3177 # define DI(x) x
3178 #endif
3179 static QofObject price_object_def =
3180 {
3181  DI(.interface_version = ) QOF_OBJECT_VERSION,
3182  DI(.e_type = ) GNC_ID_PRICE,
3183  DI(.type_label = ) "Price",
3184  DI(.create = ) price_create,
3185  DI(.book_begin = ) NULL,
3186  DI(.book_end = ) NULL,
3187  DI(.is_dirty = ) qof_collection_is_dirty,
3188  DI(.mark_clean = ) qof_collection_mark_clean,
3189  DI(.foreach = ) price_foreach,
3190  DI(.printable = ) price_printable,
3191  DI(.version_cmp = ) NULL,
3192 };
3193 
3194 static QofObject pricedb_object_def =
3195 {
3196  DI(.interface_version = ) QOF_OBJECT_VERSION,
3197  DI(.e_type = ) GNC_ID_PRICEDB,
3198  DI(.type_label = ) "PriceDB",
3199  DI(.create = ) NULL,
3200  DI(.book_begin = ) pricedb_book_begin,
3201  DI(.book_end = ) pricedb_book_end,
3202  DI(.is_dirty = ) qof_collection_is_dirty,
3203  DI(.mark_clean = ) qof_collection_mark_clean,
3204  DI(.foreach = ) NULL,
3205  DI(.printable = ) NULL,
3206  DI(.version_cmp = ) NULL,
3207 };
3208 
3209 gboolean
3210 gnc_pricedb_register (void)
3211 {
3212  static QofParam params[] =
3213  {
3214  { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
3215  { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
3216  { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time64, (QofSetterFunc)gnc_price_set_time64 },
3217  { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
3218  { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
3219  { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
3220  { NULL },
3221  };
3222 
3223  qof_class_register (GNC_ID_PRICE, NULL, params);
3224 
3225  if (!qof_object_register (&price_object_def))
3226  return FALSE;
3227  return qof_object_register (&pricedb_object_def);
3228 }
3229 
3230 /* ========================= 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:1615
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:2615
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:2624
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:2680
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:2659
#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:2955
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:2606
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:2669
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:530
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:2875
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:3011
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