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