GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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 <[email protected]> *
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 [email protected] *
22  * *
23  *******************************************************************/
24 
25 #include "config.h"
26 
27 #include <glib.h>
28 #include <string.h>
29 #include "gnc-pricedb-p.h"
30 #include "qofbackend-p.h"
31 
32 /* This static indicates the debugging module that this .o belongs to. */
33 static QofLogModule log_module = GNC_MOD_PRICE;
34 
35 static gboolean add_price(GNCPriceDB *db, GNCPrice *p);
36 static gboolean remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup);
37 static GNCPrice *lookup_nearest_in_time(GNCPriceDB *db, const gnc_commodity *c,
38  const gnc_commodity *currency,
39  Timespec t, gboolean sameday);
40 
41 enum
42 {
43  PROP_0,
44  PROP_COMMODITY, /* Table */
45  PROP_CURRENCY, /* Table */
46  PROP_DATE, /* Table */
47  PROP_SOURCE, /* Table */
48  PROP_TYPE, /* Table */
49  PROP_VALUE, /* Table, 2 fields (numeric) */
50 };
51 
52 /* GObject Initialization */
53 G_DEFINE_TYPE(GNCPrice, gnc_price, QOF_TYPE_INSTANCE);
54 
55 static void
56 gnc_price_init(GNCPrice* price)
57 {
58  price->refcount = 1;
59  price->value = gnc_numeric_zero();
60  price->type = NULL;
61  price->source = NULL;
62 }
63 
64 static void
65 gnc_price_dispose(GObject *pricep)
66 {
67  G_OBJECT_CLASS(gnc_price_parent_class)->dispose(pricep);
68 }
69 
70 static void
71 gnc_price_finalize(GObject* pricep)
72 {
73  G_OBJECT_CLASS(gnc_price_parent_class)->finalize(pricep);
74 }
75 
76 /* Note that g_value_set_object() refs the object, as does
77  * g_object_get(). But g_object_get() only unrefs once when it disgorges
78  * the object, leaving an unbalanced ref, which leaks. So instead of
79  * using g_value_set_object(), use g_value_take_object() which doesn't
80  * ref the object when used in get_property().
81  */
82 static void
83 gnc_price_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
84 {
85  GNCPrice* price;
86 
87  g_return_if_fail(GNC_IS_PRICE(object));
88 
89  price = GNC_PRICE(object);
90  switch (prop_id)
91  {
92  case PROP_SOURCE:
93  g_value_set_string(value, price->source);
94  break;
95  case PROP_TYPE:
96  g_value_set_string(value, price->type);
97  break;
98  case PROP_VALUE:
99  g_value_set_boxed(value, &price->value);
100  break;
101  case PROP_COMMODITY:
102  g_value_take_object(value, price->commodity);
103  break;
104  case PROP_CURRENCY:
105  g_value_take_object(value, price->currency);
106  break;
107  case PROP_DATE:
108  g_value_set_boxed(value, &price->tmspec);
109  break;
110  default:
111  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
112  break;
113  }
114 }
115 
116 static void
117 gnc_price_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
118 {
119  GNCPrice* price;
120  gnc_numeric* number;
121  Timespec* ts;
122 
123  g_return_if_fail(GNC_IS_PRICE(object));
124 
125  price = GNC_PRICE(object);
126  g_assert (qof_instance_get_editlevel(price));
127 
128  switch (prop_id)
129  {
130  case PROP_SOURCE:
131  gnc_price_set_source(price, g_value_get_string(value));
132  break;
133  case PROP_TYPE:
134  gnc_price_set_typestr(price, g_value_get_string(value));
135  break;
136  case PROP_VALUE:
137  number = g_value_get_boxed(value);
138  gnc_price_set_value(price, *number);
139  break;
140  case PROP_COMMODITY:
141  gnc_price_set_commodity(price, g_value_get_object(value));
142  break;
143  case PROP_CURRENCY:
144  gnc_price_set_currency(price, g_value_get_object(value));
145  break;
146  case PROP_DATE:
147  ts = g_value_get_boxed(value);
148  gnc_price_set_time(price, *ts);
149  break;
150  default:
151  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
152  break;
153  }
154 }
155 
156 static void
157 gnc_price_class_init(GNCPriceClass *klass)
158 {
159  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
160 
161  gobject_class->dispose = gnc_price_dispose;
162  gobject_class->finalize = gnc_price_finalize;
163  gobject_class->set_property = gnc_price_set_property;
164  gobject_class->get_property = gnc_price_get_property;
165 
166  g_object_class_install_property
167  (gobject_class,
168  PROP_COMMODITY,
169  g_param_spec_object ("commodity",
170  "Commodity",
171  "The commodity field denotes the base kind of "
172  "'stuff' for the units of this quote, whether "
173  "it is USD, gold, stock, etc.",
174  GNC_TYPE_COMMODITY,
175  G_PARAM_READWRITE));
176 
177  g_object_class_install_property
178  (gobject_class,
179  PROP_CURRENCY,
180  g_param_spec_object ("currency",
181  "Currency",
182  "The currency field denotes the external kind "
183  "'stuff' for the units of this quote, whether "
184  "it is USD, gold, stock, etc.",
185  GNC_TYPE_COMMODITY,
186  G_PARAM_READWRITE));
187 
188  g_object_class_install_property
189  (gobject_class,
190  PROP_SOURCE,
191  g_param_spec_string ("source",
192  "Price source",
193  "The price source is a string describing the "
194  "source of a price quote. It will be something "
195  "like this: 'Finance::Quote', 'user:misc', "
196  "'user:foo', etc.",
197  NULL,
198  G_PARAM_READWRITE));
199 
200  g_object_class_install_property
201  (gobject_class,
202  PROP_TYPE,
203  g_param_spec_string ("type",
204  "Quote type",
205  "The quote type is a string describing the "
206  "type of a price quote. Types possible now "
207  "are 'bid', 'ask', 'last', 'nav' and 'unknown'.",
208  NULL,
209  G_PARAM_READWRITE));
210 
211  g_object_class_install_property
212  (gobject_class,
213  PROP_DATE,
214  g_param_spec_boxed("date",
215  "Date",
216  "The date of the price quote.",
217  GNC_TYPE_NUMERIC,
218  G_PARAM_READWRITE));
219 
220  g_object_class_install_property
221  (gobject_class,
222  PROP_VALUE,
223  g_param_spec_boxed("value",
224  "Value",
225  "The value of the price quote.",
226  GNC_TYPE_NUMERIC,
227  G_PARAM_READWRITE));
228 }
229 
230 /* ==================================================================== */
231 /* GNCPrice functions
232  */
233 
234 /* allocation */
235 GNCPrice *
237 {
238  GNCPrice *p;
239 
240  g_return_val_if_fail (book, NULL);
241 
242  p = g_object_new(GNC_TYPE_PRICE, NULL);
243 
244  qof_instance_init_data (&p->inst, GNC_ID_PRICE, book);
245  qof_event_gen (&p->inst, QOF_EVENT_CREATE, NULL);
246 
247  return p;
248 }
249 
250 static void
251 gnc_price_destroy (GNCPrice *p)
252 {
253  ENTER(" ");
254  qof_event_gen (&p->inst, QOF_EVENT_DESTROY, NULL);
255 
256  if (p->type) CACHE_REMOVE(p->type);
257  if (p->source) CACHE_REMOVE(p->source);
258 
259  /* qof_instance_release (&p->inst); */
260  g_object_unref(p);
261  LEAVE (" ");
262 }
263 
264 void
266 {
267  if (!p) return;
268  p->refcount++;
269 }
270 
271 void
273 {
274  if (!p) return;
275  if (p->refcount == 0)
276  {
277  return;
278  }
279 
280  p->refcount--;
281 
282  if (p->refcount <= 0)
283  {
284  if (NULL != p->db)
285  {
286  PERR("last unref while price in database");
287  }
288  gnc_price_destroy (p);
289  }
290 }
291 
292 /* ==================================================================== */
293 
294 GNCPrice *
296 {
297  /* the clone doesn't belong to a PriceDB */
298  GNCPrice *new_p;
299 
300  g_return_val_if_fail (book, NULL);
301 
302  ENTER ("pr=%p", p);
303 
304  if (!p)
305  {
306  LEAVE (" ");
307  return NULL;
308  }
309 
310  new_p = gnc_price_create(book);
311  if (!new_p)
312  {
313  LEAVE (" ");
314  return NULL;
315  }
316 
317  qof_instance_copy_version(new_p, p);
318 
319  gnc_price_begin_edit(new_p);
320  /* never ever clone guid's */
321  gnc_price_set_commodity(new_p, gnc_price_get_commodity(p));
322  gnc_price_set_time(new_p, gnc_price_get_time(p));
323  gnc_price_set_source(new_p, gnc_price_get_source(p));
324  gnc_price_set_typestr(new_p, gnc_price_get_typestr(p));
325  gnc_price_set_value(new_p, gnc_price_get_value(p));
326  gnc_price_set_currency(new_p, gnc_price_get_currency(p));
327  gnc_price_commit_edit(new_p);
328  LEAVE (" ");
329  return(new_p);
330 }
331 
332 /* ==================================================================== */
333 
334 void
335 gnc_price_begin_edit (GNCPrice *p)
336 {
337  qof_begin_edit(&p->inst);
338 }
339 
340 static void commit_err (QofInstance *inst, QofBackendError errcode)
341 {
342  PERR ("Failed to commit: %d", errcode);
343  gnc_engine_signal_commit_error( errcode );
344 }
345 
346 static void noop (QofInstance *inst) {}
347 
348 void
349 gnc_price_commit_edit (GNCPrice *p)
350 {
351  if (!qof_commit_edit (QOF_INSTANCE(p))) return;
352  qof_commit_edit_part2 (&p->inst, commit_err, noop, noop);
353 }
354 
355 /* ==================================================================== */
356 
357 void
359 {
360  qof_begin_edit(&pdb->inst);
361 }
362 
363 void
364 gnc_pricedb_commit_edit (GNCPriceDB *pdb)
365 {
366  if (!qof_commit_edit (QOF_INSTANCE(pdb))) return;
367  qof_commit_edit_part2 (&pdb->inst, commit_err, noop, noop);
368 }
369 
370 /* ==================================================================== */
371 /* setters */
372 
373 static void
374 gnc_price_set_dirty (GNCPrice *p)
375 {
376  qof_instance_set_dirty(&p->inst);
377  qof_event_gen(&p->inst, QOF_EVENT_MODIFY, NULL);
378 }
379 
380 void
381 gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c)
382 {
383  if (!p) return;
384 
385  if (!gnc_commodity_equiv(p->commodity, c))
386  {
387  /* Changing the commodity requires the hash table
388  * position to be modified. The easiest way of doing
389  * this is to remove and reinsert. */
390  gnc_price_ref (p);
391  remove_price (p->db, p, TRUE);
392  gnc_price_begin_edit (p);
393  p->commodity = c;
394  gnc_price_set_dirty(p);
395  gnc_price_commit_edit (p);
396  add_price (p->db, p);
397  gnc_price_unref (p);
398  }
399 }
400 
401 
402 void
403 gnc_price_set_currency(GNCPrice *p, gnc_commodity *c)
404 {
405  if (!p) return;
406 
407  if (!gnc_commodity_equiv(p->currency, c))
408  {
409  /* Changing the currency requires the hash table
410  * position to be modified. The easiest way of doing
411  * this is to remove and reinsert. */
412  gnc_price_ref (p);
413  remove_price (p->db, p, TRUE);
414  gnc_price_begin_edit (p);
415  p->currency = c;
416  gnc_price_set_dirty(p);
417  gnc_price_commit_edit (p);
418  add_price (p->db, p);
419  gnc_price_unref (p);
420  }
421 }
422 
423 void
424 gnc_price_set_time(GNCPrice *p, Timespec t)
425 {
426  if (!p) return;
427  if (!timespec_equal(&(p->tmspec), &t))
428  {
429  /* Changing the datestamp requires the hash table
430  * position to be modified. The easiest way of doing
431  * this is to remove and reinsert. */
432  gnc_price_ref (p);
433  remove_price (p->db, p, FALSE);
434  gnc_price_begin_edit (p);
435  p->tmspec = t;
436  gnc_price_set_dirty(p);
437  gnc_price_commit_edit (p);
438  add_price (p->db, p);
439  gnc_price_unref (p);
440  }
441 }
442 
443 void
444 gnc_price_set_source(GNCPrice *p, const char *s)
445 {
446  if (!p) return;
447  if (g_strcmp0(p->source, s) != 0)
448  {
449  char *tmp;
450 
451  gnc_price_begin_edit (p);
452  tmp = CACHE_INSERT((gpointer) s);
453  if (p->source) CACHE_REMOVE(p->source);
454  p->source = tmp;
455  gnc_price_set_dirty(p);
456  gnc_price_commit_edit (p);
457  }
458 }
459 
460 void
461 gnc_price_set_typestr(GNCPrice *p, const char* type)
462 {
463  if (!p) return;
464  if (g_strcmp0(p->type, type) != 0)
465  {
466  gchar *tmp;
467 
468  gnc_price_begin_edit (p);
469  tmp = CACHE_INSERT((gpointer) type);
470  if (p->type) CACHE_REMOVE(p->type);
471  p->type = tmp;
472  gnc_price_set_dirty(p);
473  gnc_price_commit_edit (p);
474  }
475 }
476 
477 void
478 gnc_price_set_value(GNCPrice *p, gnc_numeric value)
479 {
480  if (!p) return;
481  if (!gnc_numeric_eq(p->value, value))
482  {
483  gnc_price_begin_edit (p);
484  p->value = value;
485  gnc_price_set_dirty(p);
486  gnc_price_commit_edit (p);
487  }
488 }
489 
490 /* ==================================================================== */
491 /* getters */
492 
493 GNCPrice *
494 gnc_price_lookup (const GncGUID *guid, QofBook *book)
495 {
496  QofCollection *col;
497 
498  if (!guid || !book) return NULL;
499  col = qof_book_get_collection (book, GNC_ID_PRICE);
500  return (GNCPrice *) qof_collection_lookup_entity (col, guid);
501 }
502 
504 gnc_price_get_commodity(const GNCPrice *p)
505 {
506  if (!p) return NULL;
507  return p->commodity;
508 }
509 
510 Timespec
511 gnc_price_get_time(const GNCPrice *p)
512 {
513  if (!p)
514  {
515  Timespec result;
516  result.tv_sec = 0;
517  result.tv_nsec = 0;
518  return result;
519  }
520  return p->tmspec;
521 }
522 
523 const char *
524 gnc_price_get_source(const GNCPrice *p)
525 {
526  if (!p) return NULL;
527  return p->source;
528 }
529 
530 const char *
531 gnc_price_get_typestr(const GNCPrice *p)
532 {
533  if (!p) return NULL;
534  return p->type;
535 }
536 
538 gnc_price_get_value(const GNCPrice *p)
539 {
540  if (!p)
541  {
542  PERR("price NULL.\n");
543  return gnc_numeric_zero();
544  }
545  return p->value;
546 }
547 
549 gnc_price_get_currency(const GNCPrice *p)
550 {
551  if (!p) return NULL;
552  return p->currency;
553 }
554 
555 gboolean
556 gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
557 {
558  Timespec ts1;
559  Timespec ts2;
560 
561  if (p1 == p2) return TRUE;
562  if (!p1 || !p2) return FALSE;
563 
564  if (!gnc_commodity_equiv (gnc_price_get_commodity (p1),
565  gnc_price_get_commodity (p2)))
566  return FALSE;
567 
568  if (!gnc_commodity_equiv (gnc_price_get_currency (p1),
569  gnc_price_get_currency (p2)))
570  return FALSE;
571 
572  ts1 = gnc_price_get_time (p1);
573  ts2 = gnc_price_get_time (p2);
574 
575  if (!timespec_equal (&ts1, &ts2))
576  return FALSE;
577 
578  if (g_strcmp0 (gnc_price_get_source (p1),
579  gnc_price_get_source (p2)) != 0)
580  return FALSE;
581 
582  if (g_strcmp0 (gnc_price_get_typestr (p1),
583  gnc_price_get_typestr (p2)) != 0)
584  return FALSE;
585 
586  if (!gnc_numeric_eq (gnc_price_get_value (p1),
587  gnc_price_get_value (p2)))
588  return FALSE;
589 
590  return TRUE;
591 }
592 
593 /* ==================================================================== */
594 /* price list manipulation functions */
595 
596 static gint
597 compare_prices_by_date(gconstpointer a, gconstpointer b)
598 {
599  Timespec time_a;
600  Timespec time_b;
601  gint result;
602 
603  if (!a && !b) return 0;
604  /* nothing is always less than something */
605  if (!a) return -1;
606 
607  time_a = gnc_price_get_time((GNCPrice *) a);
608  time_b = gnc_price_get_time((GNCPrice *) b);
609 
610  result = -timespec_cmp(&time_a, &time_b);
611  if (result) return result;
612 
613  /* For a stable sort */
614  return guid_compare (gnc_price_get_guid((GNCPrice *) a),
615  gnc_price_get_guid((GNCPrice *) b));
616 }
617 
618 typedef struct
619 {
620  GNCPrice* pPrice;
621  gboolean isDupl;
623 
624 static void
625 price_list_is_duplicate( gpointer data, gpointer user_data )
626 {
627  GNCPrice* pPrice = (GNCPrice*)data;
628  PriceListIsDuplStruct* pStruct = (PriceListIsDuplStruct*)user_data;
629  Timespec time_a, time_b;
630 
631  time_a = timespecCanonicalDayTime( gnc_price_get_time( pPrice ) );
632  time_b = timespecCanonicalDayTime( gnc_price_get_time( pStruct->pPrice ) );
633 
634  /* If the date, currency, commodity and price match, it's a duplicate */
635  if ( !gnc_numeric_equal( gnc_price_get_value( pPrice ), gnc_price_get_value( pStruct->pPrice ) ) ) return;
636  if ( gnc_price_get_commodity( pPrice ) != gnc_price_get_commodity( pStruct->pPrice ) ) return;
637  if ( gnc_price_get_currency( pPrice ) != gnc_price_get_currency( pStruct->pPrice ) ) return;
638 
639  if ( timespec_cmp( &time_a, &time_b ) != 0 ) return;
640 
641  pStruct->isDupl = TRUE;
642 }
643 
644 gboolean
645 gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
646 {
647  GList *result_list;
648  PriceListIsDuplStruct* pStruct;
649  gboolean isDupl;
650 
651  if (!prices || !p) return FALSE;
652  gnc_price_ref(p);
653 
654  if (check_dupl)
655  {
656  pStruct = g_new0( PriceListIsDuplStruct, 1 );
657  pStruct->pPrice = p;
658  pStruct->isDupl = FALSE;
659  g_list_foreach( *prices, price_list_is_duplicate, pStruct );
660  isDupl = pStruct->isDupl;
661  g_free( pStruct );
662 
663  if ( isDupl )
664  {
665  return TRUE;
666  }
667  }
668 
669  result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date);
670  if (!result_list) return FALSE;
671  *prices = result_list;
672  return TRUE;
673 }
674 
675 gboolean
676 gnc_price_list_remove(PriceList **prices, GNCPrice *p)
677 {
678  GList *result_list;
679  GList *found_element;
680 
681  if (!prices || !p) return FALSE;
682 
683  found_element = g_list_find(*prices, p);
684  if (!found_element) return TRUE;
685 
686  result_list = g_list_remove_link(*prices, found_element);
687  gnc_price_unref((GNCPrice *) found_element->data);
688  g_list_free(found_element);
689 
690  *prices = result_list;
691  return TRUE;
692 }
693 
694 static void
695 price_list_destroy_helper(gpointer data, gpointer user_data)
696 {
697  gnc_price_unref((GNCPrice *) data);
698 }
699 
700 void
701 gnc_price_list_destroy(PriceList *prices)
702 {
703  g_list_foreach(prices, price_list_destroy_helper, NULL);
704  g_list_free(prices);
705 }
706 
707 gboolean
708 gnc_price_list_equal(PriceList *prices1, PriceList *prices2)
709 {
710  GList *n1, *n2;
711 
712  if (prices1 == prices2) return TRUE;
713 
714  if (g_list_length (prices1) < g_list_length (prices2))
715  {
716  PWARN ("prices2 has extra prices");
717  return FALSE;
718  }
719 
720  if (g_list_length (prices1) > g_list_length (prices2))
721  {
722  PWARN ("prices1 has extra prices");
723  return FALSE;
724  }
725 
726  for (n1 = prices1, n2 = prices2; n1 ; n1 = n1->next, n2 = n2->next)
727  if (!gnc_price_equal (n1->data, n2->data))
728  return FALSE;
729 
730  return TRUE;
731 }
732 
733 /* ==================================================================== */
734 /* GNCPriceDB functions
735 
736  Structurally a GNCPriceDB contains a hash mapping price commodities
737  (of type gnc_commodity*) to hashes mapping price currencies (of
738  type gnc_commodity*) to GNCPrice lists (see gnc-pricedb.h for a
739  description of GNCPrice lists). The top-level key is the commodity
740  you want the prices for, and the second level key is the commodity
741  that the value is expressed in terms of.
742  */
743 
744 /* GObject Initialization */
745 QOF_GOBJECT_IMPL(gnc_pricedb, GNCPriceDB, QOF_TYPE_INSTANCE);
746 
747 static void
748 gnc_pricedb_init(GNCPriceDB* pdb)
749 {
750 }
751 
752 static void
753 gnc_pricedb_dispose_real (GObject *pdbp)
754 {
755 }
756 
757 static void
758 gnc_pricedb_finalize_real(GObject* pdbp)
759 {
760 }
761 
762 static GNCPriceDB *
763 gnc_pricedb_create(QofBook * book)
764 {
765  GNCPriceDB * result;
766  QofCollection *col;
767 
768  g_return_val_if_fail (book, NULL);
769 
770  /* There can only be one pricedb per book. So if one exits already,
771  * then use that. Warn user, they shouldn't be creating two ...
772  */
773  col = qof_book_get_collection (book, GNC_ID_PRICEDB);
774  result = qof_collection_get_data (col);
775  if (result)
776  {
777  PWARN ("A price database already exists for this book!");
778  return result;
779  }
780 
781  result = g_object_new(GNC_TYPE_PRICEDB, NULL);
782  qof_instance_init_data (&result->inst, GNC_ID_PRICEDB, book);
784 
788  qof_collection_set_data (col, result);
789 
790  result->commodity_hash = g_hash_table_new(NULL, NULL);
791  g_return_val_if_fail (result->commodity_hash, NULL);
792  return result;
793 }
794 
795 static void
796 destroy_pricedb_currency_hash_data(gpointer key,
797  gpointer data,
798  gpointer user_data)
799 {
800  GList *price_list = (GList *) data;
801  GList *node;
802  GNCPrice *p;
803 
804  for (node = price_list; node; node = node->next)
805  {
806  p = node->data;
807 
808  p->db = NULL;
809  }
810 
811  gnc_price_list_destroy(price_list);
812 }
813 
814 static void
815 destroy_pricedb_commodity_hash_data(gpointer key,
816  gpointer data,
817  gpointer user_data)
818 {
819  GHashTable *currency_hash = (GHashTable *) data;
820  if (!currency_hash) return;
821  g_hash_table_foreach (currency_hash,
822  destroy_pricedb_currency_hash_data,
823  NULL);
824  g_hash_table_destroy(currency_hash);
825 }
826 
827 void
829 {
830  if (!db) return;
831  if (db->commodity_hash)
832  {
833  g_hash_table_foreach (db->commodity_hash,
834  destroy_pricedb_commodity_hash_data,
835  NULL);
836  }
837  g_hash_table_destroy (db->commodity_hash);
838  db->commodity_hash = NULL;
839  /* qof_instance_release (&db->inst); */
840  g_object_unref(db);
841 }
842 
843 void
844 gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
845 {
846  db->bulk_update = bulk_update;
847 }
848 
849 /* ==================================================================== */
850 /* This is kind of weird, the way its done. Each collection of prices
851  * for a given commodity should get its own guid, be its own entity, etc.
852  * We really shouldn't be using the collection data. But, hey I guess its OK,
853  * yeah? Umm, possibly not. (NW). See TODO below.
854 */
864 GNCPriceDB *
865 gnc_collection_get_pricedb(QofCollection *col)
866 {
867  if (!col) return NULL;
868  return qof_collection_get_data (col);
869 }
870 
871 GNCPriceDB *
873 {
874  QofCollection *col;
875 
876  if (!book) return NULL;
877  col = qof_book_get_collection (book, GNC_ID_PRICEDB);
878  return gnc_collection_get_pricedb (col);
879 }
880 
881 /* ==================================================================== */
882 
883 static gboolean
884 num_prices_helper (GNCPrice *p, gpointer user_data)
885 {
886  guint *count = user_data;
887 
888  *count += 1;
889 
890  return TRUE;
891 }
892 
893 guint
895 {
896  guint count;
897 
898  if (!db) return 0;
899 
900  count = 0;
901 
902  gnc_pricedb_foreach_price(db, num_prices_helper, &count, FALSE);
903 
904  return count;
905 }
906 
907 /* ==================================================================== */
908 
909 typedef struct
910 {
911  gboolean equal;
912  GNCPriceDB *db2;
913  gnc_commodity *commodity;
915 
916 static void
917 pricedb_equal_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
918 {
919  GNCPriceDBEqualData *equal_data = user_data;
920  gnc_commodity *currency = key;
921  GList *price_list1 = val;
922  GList *price_list2;
923 
924  price_list2 = gnc_pricedb_get_prices (equal_data->db2,
925  equal_data->commodity,
926  currency);
927 
928  if (!gnc_price_list_equal (price_list1, price_list2))
929  equal_data->equal = FALSE;
930 
931  gnc_price_list_destroy (price_list2);
932 }
933 
934 static void
935 pricedb_equal_foreach_currencies_hash (gpointer key, gpointer val,
936  gpointer user_data)
937 {
938  GHashTable *currencies_hash = val;
939  GNCPriceDBEqualData *equal_data = user_data;
940 
941  equal_data->commodity = key;
942 
943  g_hash_table_foreach (currencies_hash,
944  pricedb_equal_foreach_pricelist,
945  equal_data);
946 }
947 
948 gboolean
950 {
951  GNCPriceDBEqualData equal_data;
952 
953  if (db1 == db2) return TRUE;
954 
955  if (!db1 || !db2)
956  {
957  PWARN ("one is NULL");
958  return FALSE;
959  }
960 
961  equal_data.equal = TRUE;
962  equal_data.db2 = db2;
963 
964  g_hash_table_foreach (db1->commodity_hash,
965  pricedb_equal_foreach_currencies_hash,
966  &equal_data);
967 
968  return equal_data.equal;
969 }
970 
971 /* ==================================================================== */
972 /* The add_price() function is a utility that only manages the
973  * dual hash table instertion */
974 
975 static gboolean
976 add_price(GNCPriceDB *db, GNCPrice *p)
977 {
978  /* This function will use p, adding a ref, so treat p as read-only
979  if this function succeeds. */
980  GList *price_list;
981  gnc_commodity *commodity;
982  gnc_commodity *currency;
983  GHashTable *currency_hash;
984 
985  if (!db || !p) return FALSE;
986  ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
987  db, p, qof_instance_get_dirty_flag(p),
989 
990  if (!qof_instance_books_equal(db, p))
991  {
992  PERR ("attempted to mix up prices across different books");
993  LEAVE (" ");
994  return FALSE;
995  }
996 
997  commodity = gnc_price_get_commodity(p);
998  if (!commodity)
999  {
1000  PWARN("no commodity");
1001  LEAVE (" ");
1002  return FALSE;
1003  }
1004  currency = gnc_price_get_currency(p);
1005  if (!currency)
1006  {
1007  PWARN("no currency");
1008  LEAVE (" ");
1009  return FALSE;
1010  }
1011  if (!db->commodity_hash)
1012  {
1013  LEAVE ("no commodity hash found ");
1014  return FALSE;
1015  }
1016 
1017  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1018  if (!currency_hash)
1019  {
1020  currency_hash = g_hash_table_new(NULL, NULL);
1021  g_hash_table_insert(db->commodity_hash, commodity, currency_hash);
1022  }
1023 
1024  price_list = g_hash_table_lookup(currency_hash, currency);
1025  if (!gnc_price_list_insert(&price_list, p, !db->bulk_update))
1026  {
1027  LEAVE ("gnc_price_list_insert failed");
1028  return FALSE;
1029  }
1030  if (!price_list)
1031  {
1032  LEAVE (" no price list");
1033  return FALSE;
1034  }
1035  g_hash_table_insert(currency_hash, currency, price_list);
1036  p->db = db;
1037  qof_event_gen (&p->inst, QOF_EVENT_ADD, NULL);
1038 
1039  LEAVE ("db=%p, pr=%p dirty=%d dextroying=%d commodity=%s/%s currency_hash=%p",
1040  db, p, qof_instance_get_dirty_flag(p),
1042  gnc_commodity_get_namespace(p->commodity),
1043  gnc_commodity_get_mnemonic(p->commodity),
1044  currency_hash);
1045  return TRUE;
1046 }
1047 
1048 /* If gnc_pricedb_add_price() succeeds, it takes ownership of the
1049  passed-in GNCPrice and inserts it into the pricedb. Writing to this
1050  pointer afterwards will have interesting results, so don't.
1051  */
1052 gboolean
1054 {
1055  if (!db || !p) return FALSE;
1056 
1057  ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1058  db, p, qof_instance_get_dirty_flag(p),
1060 
1061  if (FALSE == add_price(db, p))
1062  {
1063  LEAVE (" failed to add price");
1064  return FALSE;
1065  }
1066 
1068  qof_instance_set_dirty(&db->inst);
1069  gnc_pricedb_commit_edit(db);
1070 
1071  LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
1072  db, p, qof_instance_get_dirty_flag(p),
1074 
1075  return TRUE;
1076 }
1077 
1078 /* remove_price() is a utility; its only function is to remove the price
1079  * from the double-hash tables.
1080  */
1081 
1082 static gboolean
1083 remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup)
1084 {
1085  GList *price_list;
1086  gnc_commodity *commodity;
1087  gnc_commodity *currency;
1088  GHashTable *currency_hash;
1089 
1090  if (!db || !p) return FALSE;
1091  ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1092  db, p, qof_instance_get_dirty_flag(p),
1094 
1095  commodity = gnc_price_get_commodity(p);
1096  if (!commodity)
1097  {
1098  LEAVE (" no commodity");
1099  return FALSE;
1100  }
1101  currency = gnc_price_get_currency(p);
1102  if (!currency)
1103  {
1104  LEAVE (" no currency");
1105  return FALSE;
1106  }
1107  if (!db->commodity_hash)
1108  {
1109  LEAVE (" no commodity hash");
1110  return FALSE;
1111  }
1112 
1113  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1114  if (!currency_hash)
1115  {
1116  LEAVE (" no currency hash");
1117  return FALSE;
1118  }
1119 
1120  qof_event_gen (&p->inst, QOF_EVENT_REMOVE, NULL);
1121  price_list = g_hash_table_lookup(currency_hash, currency);
1122  gnc_price_ref(p);
1123  if (!gnc_price_list_remove(&price_list, p))
1124  {
1125  gnc_price_unref(p);
1126  LEAVE (" cannot remove price list");
1127  return FALSE;
1128  }
1129 
1130  /* if the price list is empty, then remove this currency from the
1131  commodity hash */
1132  if (price_list)
1133  {
1134  g_hash_table_insert(currency_hash, currency, price_list);
1135  }
1136  else
1137  {
1138  g_hash_table_remove(currency_hash, currency);
1139 
1140  if (cleanup)
1141  {
1142  /* chances are good that this commodity had only one currency.
1143  * If there are no currencies, we may as well destroy the
1144  * commodity too. */
1145  guint num_currencies = g_hash_table_size (currency_hash);
1146  if (0 == num_currencies)
1147  {
1148  g_hash_table_remove (db->commodity_hash, commodity);
1149  g_hash_table_destroy (currency_hash);
1150  }
1151  }
1152  }
1153 
1154  gnc_price_unref(p);
1155  LEAVE ("db=%p, pr=%p", db, p);
1156  return TRUE;
1157 }
1158 
1159 gboolean
1161 {
1162  gboolean rc;
1163  if (!db || !p) return FALSE;
1164  ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
1165  db, p, qof_instance_get_dirty_flag(p),
1167 
1168  gnc_price_ref(p);
1169  rc = remove_price (db, p, TRUE);
1171  qof_instance_set_dirty(&db->inst);
1172  gnc_pricedb_commit_edit(db);
1173 
1174  /* invoke the backend to delete this price */
1175  gnc_price_begin_edit (p);
1176  qof_instance_set_destroying(p, TRUE);
1177  gnc_price_commit_edit (p);
1178  p->db = NULL;
1179  gnc_price_unref(p);
1180  LEAVE ("db=%p, pr=%p", db, p);
1181  return rc;
1182 }
1183 
1184 typedef struct
1185 {
1186  GNCPriceDB *db;
1187  Timespec cutoff;
1188  gboolean delete_user;
1189  gboolean delete_last;
1190  GSList *list;
1191 } remove_info;
1192 
1193 static gboolean
1194 check_one_price_date (GNCPrice *price, gpointer user_data)
1195 {
1196  remove_info *data = user_data;
1197  const gchar *source;
1198  Timespec pt;
1199 
1200  ENTER("price %p (%s), data %p", price,
1201  gnc_commodity_get_mnemonic(gnc_price_get_commodity(price)),
1202  user_data);
1203  if (!data->delete_user)
1204  {
1205  source = gnc_price_get_source (price);
1206  if (g_strcmp0(source, "Finance::Quote") != 0)
1207  {
1208  LEAVE("Not an automatic quote");
1209  return TRUE;
1210  }
1211  }
1212 
1213  pt = gnc_price_get_time (price);
1214  {
1215  gchar buf[40];
1216  gnc_timespec_to_iso8601_buff(pt , buf);
1217  DEBUG("checking date %s", buf);
1218  }
1219  if (timespec_cmp (&pt, &data->cutoff) < 0)
1220  {
1221  data->list = g_slist_prepend(data->list, price);
1222  DEBUG("will delete");
1223  }
1224  LEAVE(" ");
1225  return TRUE;
1226 }
1227 
1228 static void
1229 pricedb_remove_foreach_pricelist (gpointer key,
1230  gpointer val,
1231  gpointer user_data)
1232 {
1233  GList *price_list = (GList *) val;
1234  GList *node = price_list;
1235  remove_info *data = (remove_info *) user_data;
1236 
1237  ENTER("key %p, value %p, data %p", key, val, user_data);
1238 
1239  /* The most recent price is the first in the list */
1240  if (!data->delete_last)
1241  node = g_list_next(node);
1242 
1243  /* now check each item in the list */
1244  g_list_foreach(node, (GFunc)check_one_price_date, data);
1245 
1246  LEAVE(" ");
1247 }
1248 
1249 static void
1250 pricedb_remove_foreach_currencies_hash (gpointer key,
1251  gpointer val,
1252  gpointer user_data)
1253 {
1254  GHashTable *currencies_hash = (GHashTable *) val;
1255 
1256  ENTER("key %p, value %p, data %p", key, val, user_data);
1257  g_hash_table_foreach(currencies_hash,
1258  pricedb_remove_foreach_pricelist, user_data);
1259  LEAVE(" ");
1260 }
1261 
1262 
1263 gboolean
1264 gnc_pricedb_remove_old_prices(GNCPriceDB *db,
1265  Timespec cutoff,
1266  gboolean delete_user,
1267  gboolean delete_last)
1268 {
1269  remove_info data;
1270  GSList *item;
1271 
1272  data.db = db;
1273  data.cutoff = cutoff;
1274  data.delete_user = delete_user;
1275  data.delete_last = delete_last;
1276  data.list = NULL;
1277 
1278  ENTER("db %p, delet_user %d, delete_last %d", db, delete_user, delete_last);
1279  {
1280  gchar buf[40];
1281  gnc_timespec_to_iso8601_buff(cutoff, buf);
1282  DEBUG("checking date %s", buf);
1283  }
1284 
1285  /* Traverse the database once building up an external list of prices
1286  * to be deleted */
1287  g_hash_table_foreach(db->commodity_hash,
1288  pricedb_remove_foreach_currencies_hash,
1289  &data);
1290 
1291  if (data.list == NULL)
1292  return FALSE;
1293 
1294  /* Now run this external list deleting prices */
1295  for (item = data.list; item; item = g_slist_next(item))
1296  {
1297  gnc_pricedb_remove_price(db, item->data);
1298  }
1299 
1300  g_slist_free(data.list);
1301  LEAVE(" ");
1302  return TRUE;
1303 }
1304 
1305 /* ==================================================================== */
1306 /* lookup/query functions */
1307 
1308 GNCPrice *
1310  const gnc_commodity *commodity,
1311  const gnc_commodity *currency)
1312 {
1313  GList *price_list;
1314  GNCPrice *result;
1315  GHashTable *currency_hash;
1316  QofBook *book;
1317  QofBackend *be;
1318 
1319  if (!db || !commodity || !currency) return NULL;
1320  ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
1321  book = qof_instance_get_book(&db->inst);
1322  be = qof_book_get_backend(book);
1323 #ifdef GNUCASH_MAJOR_VERSION
1324  if (be && be->price_lookup)
1325  {
1326  GNCPriceLookup pl;
1327  pl.type = LOOKUP_LATEST;
1328  pl.prdb = db;
1329  pl.commodity = commodity;
1330  pl.currency = currency;
1331  (be->price_lookup) (be, &pl);
1332  }
1333 #endif
1334 
1335  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1336  if (!currency_hash)
1337  {
1338  LEAVE (" no currency hash");
1339  return NULL;
1340  }
1341 
1342  price_list = g_hash_table_lookup(currency_hash, currency);
1343  if (!price_list)
1344  {
1345  LEAVE (" no price list");
1346  return NULL;
1347  }
1348 
1349  /* This works magically because prices are inserted in date-sorted
1350  * order, and the latest date always comes first. So return the
1351  * first in the list. */
1352  result = price_list->data;
1353  gnc_price_ref(result);
1354  LEAVE(" ");
1355  return result;
1356 }
1357 
1358 
1359 static void
1360 lookup_latest(gpointer key, gpointer val, gpointer user_data)
1361 {
1362  //gnc_commodity *currency = (gnc_commodity *)key;
1363  GList *price_list = (GList *)val;
1364  GList **return_list = (GList **)user_data;
1365 
1366  if (!price_list) return;
1367 
1368  /* the latest price is the first in list */
1369  gnc_price_list_insert(return_list, price_list->data, FALSE);
1370 }
1371 
1372 PriceList *
1374  const gnc_commodity *commodity)
1375 {
1376  GList *result;
1377  GHashTable *currency_hash;
1378  QofBook *book;
1379  QofBackend *be;
1380 
1381  result = NULL;
1382 
1383  if (!db || !commodity) return NULL;
1384  ENTER ("db=%p commodity=%p", db, commodity);
1385  book = qof_instance_get_book(&db->inst);
1386  be = qof_book_get_backend(book);
1387 #ifdef GNUCASH_MAJOR_VERSION
1388  if (be && be->price_lookup)
1389  {
1390  GNCPriceLookup pl;
1391  pl.type = LOOKUP_LATEST;
1392  pl.prdb = db;
1393  pl.commodity = commodity;
1394  pl.currency = NULL; /* can the backend handle this??? */
1395  (be->price_lookup) (be, &pl);
1396  }
1397 #endif
1398  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1399  if (!currency_hash)
1400  {
1401  LEAVE (" no currency hash");
1402  return NULL;
1403  }
1404 
1405  g_hash_table_foreach(currency_hash, lookup_latest, &result);
1406 
1407  if (!result)
1408  {
1409  LEAVE (" ");
1410  return NULL;
1411  }
1412 
1413  result = g_list_sort(result, compare_prices_by_date);
1414 
1415  LEAVE(" ");
1416  return result;
1417 }
1418 
1419 
1420 static void
1421 hash_values_helper(gpointer key, gpointer value, gpointer data)
1422 {
1423  GList ** l = data;
1424  *l = g_list_concat(*l, g_list_copy (value));
1425 }
1426 
1427 gboolean
1429  const gnc_commodity *commodity,
1430  const gnc_commodity *currency)
1431 {
1432  GList *price_list;
1433  GHashTable *currency_hash;
1434  gint size;
1435  QofBook *book;
1436  QofBackend *be;
1437 
1438  if (!db || !commodity) return FALSE;
1439  ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
1440  book = qof_instance_get_book(&db->inst);
1441  be = qof_book_get_backend(book);
1442 #ifdef GNUCASH_MAJOR_VERSION
1443  if (book && be && be->price_lookup)
1444  {
1445  GNCPriceLookup pl;
1446  pl.type = LOOKUP_ALL;
1447  pl.prdb = db;
1448  pl.commodity = commodity;
1449  pl.currency = currency;
1450  (be->price_lookup) (be, &pl);
1451  }
1452 #endif
1453  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1454  if (!currency_hash)
1455  {
1456  LEAVE("no, no currency_hash table");
1457  return FALSE;
1458  }
1459 
1460  if (currency)
1461  {
1462  price_list = g_hash_table_lookup(currency_hash, currency);
1463  if (price_list)
1464  {
1465  LEAVE("yes");
1466  return TRUE;
1467  }
1468  LEAVE("no, no price list");
1469  return FALSE;
1470  }
1471 
1472  size = g_hash_table_size (currency_hash);
1473  LEAVE("%s", size > 0 ? "yes" : "no");
1474  return size > 0;
1475 }
1476 
1477 
1478 PriceList *
1480  const gnc_commodity *commodity,
1481  const gnc_commodity *currency)
1482 {
1483  GList *price_list;
1484  GList *result;
1485  GList *node;
1486  GHashTable *currency_hash;
1487  QofBook *book;
1488  QofBackend *be;
1489 
1490  if (!db || !commodity) return NULL;
1491  ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
1492  book = qof_instance_get_book(&db->inst);
1493  be = qof_book_get_backend(book);
1494 #ifdef GNUCASH_MAJOR_VERSION
1495  if (be && be->price_lookup)
1496  {
1497  GNCPriceLookup pl;
1498  pl.type = LOOKUP_ALL;
1499  pl.prdb = db;
1500  pl.commodity = commodity;
1501  pl.currency = currency;
1502  (be->price_lookup) (be, &pl);
1503  }
1504 #endif
1505  currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
1506  if (!currency_hash)
1507  {
1508  LEAVE (" no currency hash");
1509  return NULL;
1510  }
1511 
1512  if (currency)
1513  {
1514  price_list = g_hash_table_lookup(currency_hash, currency);
1515  if (!price_list)
1516  {
1517  LEAVE (" no price list");
1518  return NULL;
1519  }
1520  result = g_list_copy (price_list);
1521  }
1522  else
1523  {
1524  result = NULL;
1525  g_hash_table_foreach(currency_hash, hash_values_helper, (gpointer)&result);
1526  }
1527  for (node = result; node; node = node->next)
1528  gnc_price_ref (node->data);
1529 
1530  LEAVE (" ");
1531  return result;
1532 }
1533 
1534 
1535 GNCPrice *
1537  const gnc_commodity *c,
1538  const gnc_commodity *currency,
1539  Timespec t)
1540 {
1541  return lookup_nearest_in_time(db, c, currency, t, TRUE);
1542 }
1543 
1544 PriceList *
1546  const gnc_commodity *c,
1547  const gnc_commodity *currency,
1548  Timespec t)
1549 {
1550  GList *price_list;
1551  GList *result = NULL;
1552  GList *item = NULL;
1553  GHashTable *currency_hash;
1554  QofBook *book;
1555  QofBackend *be;
1556 
1557  if (!db || !c || !currency) return NULL;
1558  ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
1559  book = qof_instance_get_book(&db->inst);
1560  be = qof_book_get_backend(book);
1561 #ifdef GNUCASH_MAJOR_VERSION
1562  if (be && be->price_lookup)
1563  {
1564  GNCPriceLookup pl;
1565  pl.type = LOOKUP_AT_TIME;
1566  pl.prdb = db;
1567  pl.commodity = c;
1568  pl.currency = currency;
1569  pl.date = t;
1570  (be->price_lookup) (be, &pl);
1571  }
1572 #endif
1573  currency_hash = g_hash_table_lookup(db->commodity_hash, c);
1574  if (!currency_hash)
1575  {
1576  LEAVE (" no currency hash");
1577  return NULL;
1578  }
1579 
1580  price_list = g_hash_table_lookup(currency_hash, currency);
1581  if (!price_list)
1582  {
1583  LEAVE (" no price list");
1584  return NULL;
1585  }
1586 
1587  item = price_list;
1588  while (item)
1589  {
1590  GNCPrice *p = item->data;
1591  Timespec price_time = gnc_price_get_time(p);
1592  if (timespec_equal(&price_time, &t))
1593  {
1594  result = g_list_prepend(result, p);
1595  gnc_price_ref(p);
1596  }
1597  item = item->next;
1598  }
1599  LEAVE (" ");
1600  return result;
1601 }
1602 
1603 static GNCPrice *
1604 lookup_nearest_in_time(GNCPriceDB *db,
1605  const gnc_commodity *c,
1606  const gnc_commodity *currency,
1607  Timespec t,
1608  gboolean sameday)
1609 {
1610  GList *price_list;
1611  GNCPrice *current_price = NULL;
1612  GNCPrice *next_price = NULL;
1613  GNCPrice *result = NULL;
1614  GList *item = NULL;
1615  GHashTable *currency_hash;
1616  QofBook *book;
1617  QofBackend *be;
1618 
1619  if (!db || !c || !currency) return NULL;
1620  ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
1621  book = qof_instance_get_book(&db->inst);
1622  be = qof_book_get_backend(book);
1623 #ifdef GNUCASH_MAJOR_VERSION
1624  if (be && be->price_lookup)
1625  {
1626  GNCPriceLookup pl;
1627  pl.type = LOOKUP_NEAREST_IN_TIME;
1628  pl.prdb = db;
1629  pl.commodity = c;
1630  pl.currency = currency;
1631  pl.date = t;
1632  (be->price_lookup) (be, &pl);
1633  }
1634 #endif
1635  currency_hash = g_hash_table_lookup(db->commodity_hash, c);
1636  if (!currency_hash)
1637  {
1638  LEAVE ("no currency hash");
1639  return NULL;
1640  }
1641 
1642  price_list = g_hash_table_lookup(currency_hash, currency);
1643  if (!price_list)
1644  {
1645  LEAVE ("no price list");
1646  return NULL;
1647  }
1648 
1649  item = price_list;
1650 
1651  /* default answer */
1652  current_price = item->data;
1653 
1654  /* find the first candidate past the one we want. Remember that
1655  prices are in most-recent-first order. */
1656  while (!next_price && item)
1657  {
1658  GNCPrice *p = item->data;
1659  Timespec price_time = gnc_price_get_time(p);
1660  if (timespec_cmp(&price_time, &t) <= 0)
1661  {
1662  next_price = item->data;
1663  break;
1664  }
1665  current_price = item->data;
1666  item = item->next;
1667  }
1668 
1669  if (current_price) /* How can this be null??? */
1670  {
1671  if (!next_price)
1672  {
1673  /* It's earlier than the last price on the list */
1674  result = current_price;
1675  if (sameday)
1676  {
1677  /* Must be on the same day. */
1678  Timespec price_day;
1679  Timespec t_day;
1680  price_day = timespecCanonicalDayTime(gnc_price_get_time(current_price));
1681  t_day = timespecCanonicalDayTime(t);
1682  if (!timespec_equal(&price_day, &t_day))
1683  result = NULL;
1684  }
1685  }
1686  else
1687  {
1688  /* If the requested time is not earlier than the first price on the
1689  list, then current_price and next_price will be the same. */
1690  Timespec current_t = gnc_price_get_time(current_price);
1691  Timespec next_t = gnc_price_get_time(next_price);
1692  Timespec diff_current = timespec_diff(&current_t, &t);
1693  Timespec diff_next = timespec_diff(&next_t, &t);
1694  Timespec abs_current = timespec_abs(&diff_current);
1695  Timespec abs_next = timespec_abs(&diff_next);
1696 
1697  if (sameday)
1698  {
1699  /* Result must be on same day, see if either of the two isn't */
1701  Timespec current_day = timespecCanonicalDayTime(current_t);
1702  Timespec next_day = timespecCanonicalDayTime(next_t);
1703  if (timespec_equal(&current_day, &t_day))
1704  {
1705  if (timespec_equal(&next_day, &t_day))
1706  {
1707  /* Both on same day, return nearest */
1708  if (timespec_cmp(&abs_current, &abs_next) < 0)
1709  result = current_price;
1710  else
1711  result = next_price;
1712  }
1713  else
1714  /* current_price on same day, next_price not */
1715  result = current_price;
1716  }
1717  else if (timespec_equal(&next_day, &t_day))
1718  /* next_price on same day, current_price not */
1719  result = next_price;
1720  }
1721  else
1722  {
1723  /* Choose the price that is closest to the given time. In case of
1724  * a tie, prefer the older price since it actually existed at the
1725  * time. (This also fixes bug #541970.) */
1726  if (timespec_cmp(&abs_current, &abs_next) < 0)
1727  {
1728  result = current_price;
1729  }
1730  else
1731  {
1732  result = next_price;
1733  }
1734  }
1735  }
1736  }
1737 
1738  gnc_price_ref(result);
1739  LEAVE (" ");
1740  return result;
1741 }
1742 
1743 GNCPrice *
1745  const gnc_commodity *c,
1746  const gnc_commodity *currency,
1747  Timespec t)
1748 {
1749  return lookup_nearest_in_time(db, c, currency, t, FALSE);
1750 }
1751 
1752 GNCPrice *
1754  gnc_commodity *c,
1755  gnc_commodity *currency,
1756  Timespec t)
1757 {
1758  GList *price_list;
1759  GNCPrice *current_price = NULL;
1760  /* GNCPrice *next_price = NULL;
1761  GNCPrice *result = NULL;*/
1762  GList *item = NULL;
1763  GHashTable *currency_hash;
1764  QofBook *book;
1765  QofBackend *be;
1766  Timespec price_time;
1767 
1768  if (!db || !c || !currency) return NULL;
1769  ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
1770  book = qof_instance_get_book(&db->inst);
1771  be = qof_book_get_backend(book);
1772 #ifdef GNUCASH_MAJOR_VERSION
1773  if (be && be->price_lookup)
1774  {
1775  GNCPriceLookup pl;
1776  pl.type = LOOKUP_LATEST_BEFORE;
1777  pl.prdb = db;
1778  pl.commodity = c;
1779  pl.currency = currency;
1780  pl.date = t;
1781  (be->price_lookup) (be, &pl);
1782  }
1783 #endif
1784  currency_hash = g_hash_table_lookup(db->commodity_hash, c);
1785  if (!currency_hash)
1786  {
1787  LEAVE ("no currency hash");
1788  return NULL;
1789  }
1790 
1791  price_list = g_hash_table_lookup(currency_hash, currency);
1792  if (!price_list)
1793  {
1794  LEAVE ("no price list");
1795  return NULL;
1796  }
1797 
1798  item = price_list;
1799  do
1800  {
1801  price_time = gnc_price_get_time (item->data);
1802  if (timespec_cmp(&price_time, &t) <= 0)
1803  current_price = item->data;
1804  item = item->next;
1805  }
1806  while (timespec_cmp(&price_time, &t) > 0 && item);
1807  gnc_price_ref(current_price);
1808  LEAVE (" ");
1809  return current_price;
1810 }
1811 
1812 
1813 static void
1814 lookup_nearest(gpointer key, gpointer val, gpointer user_data)
1815 {
1816  //gnc_commodity *currency = (gnc_commodity *)key;
1817  GList *price_list = (GList *)val;
1818  GNCPrice *current_price = NULL;
1819  GNCPrice *next_price = NULL;
1820  GNCPrice *result = NULL;
1821  GList *item = NULL;
1822  GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
1823  GList **return_list = lookup_helper->return_list;
1824  Timespec t = lookup_helper->time;
1825 
1826  item = price_list;
1827 
1828  /* default answer */
1829  current_price = item->data;
1830 
1831  /* find the first candidate past the one we want. Remember that
1832  prices are in most-recent-first order. */
1833  while (!next_price && item)
1834  {
1835  GNCPrice *p = item->data;
1836  Timespec price_time = gnc_price_get_time(p);
1837  if (timespec_cmp(&price_time, &t) <= 0)
1838  {
1839  next_price = item->data;
1840  break;
1841  }
1842  current_price = item->data;
1843  item = item->next;
1844  }
1845 
1846  if (current_price)
1847  {
1848  if (!next_price)
1849  {
1850  result = current_price;
1851  }
1852  else
1853  {
1854  Timespec current_t = gnc_price_get_time(current_price);
1855  Timespec next_t = gnc_price_get_time(next_price);
1856  Timespec diff_current = timespec_diff(&current_t, &t);
1857  Timespec diff_next = timespec_diff(&next_t, &t);
1858  Timespec abs_current = timespec_abs(&diff_current);
1859  Timespec abs_next = timespec_abs(&diff_next);
1860 
1861  if (timespec_cmp(&abs_current, &abs_next) <= 0)
1862  {
1863  result = current_price;
1864  }
1865  else
1866  {
1867  result = next_price;
1868  }
1869  }
1870  }
1871 
1872  gnc_price_list_insert(return_list, result, FALSE);
1873 }
1874 
1875 
1876 static void
1877 lookup_latest_before(gpointer key, gpointer val, gpointer user_data)
1878 {
1879  //gnc_commodity *currency = (gnc_commodity *)key;
1880  GList *price_list = (GList *)val;
1881  GNCPrice *current_price = NULL;
1882  /* GNCPrice *next_price = NULL;
1883  GNCPrice *result = NULL;*/
1884  GList *item = NULL;
1885  GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
1886  GList **return_list = lookup_helper->return_list;
1887  Timespec t = lookup_helper->time;
1888  Timespec price_time;
1889 
1890  if (price_list)
1891  {
1892  item = price_list;
1893  do
1894  {
1895  price_time = gnc_price_get_time (item->data);
1896  if (timespec_cmp(&price_time, &t) <= 0)
1897  current_price = item->data;
1898  item = item->next;
1899  }
1900  while (timespec_cmp(&price_time, &t) > 0 && item);
1901  }
1902 
1903  gnc_price_list_insert(return_list, current_price, FALSE);
1904 }
1905 
1906 
1907 PriceList *
1909  const gnc_commodity *c,
1910  Timespec t)
1911 {
1912  GList *result = NULL;
1913  GHashTable *currency_hash;
1914  GNCPriceLookupHelper lookup_helper;
1915  QofBook *book;
1916  QofBackend *be;
1917 
1918  if (!db || !c) return NULL;
1919  ENTER ("db=%p commodity=%p", db, c);
1920  book = qof_instance_get_book(&db->inst);
1921  be = qof_book_get_backend(book);
1922 #ifdef GNUCASH_MAJOR_VERSION
1923  if (be && be->price_lookup)
1924  {
1925  GNCPriceLookup pl;
1926  pl.type = LOOKUP_NEAREST_IN_TIME;
1927  pl.prdb = db;
1928  pl.commodity = c;
1929  pl.currency = NULL; /* can the backend handle this??? */
1930  pl.date = t;
1931  (be->price_lookup) (be, &pl);
1932  }
1933 #endif
1934  currency_hash = g_hash_table_lookup(db->commodity_hash, c);
1935  if (!currency_hash)
1936  {
1937  LEAVE (" no currency hash");
1938  return NULL;
1939  }
1940 
1941  lookup_helper.return_list = &result;
1942  lookup_helper.time = t;
1943  g_hash_table_foreach(currency_hash, lookup_nearest, &lookup_helper);
1944 
1945  if (!result)
1946  {
1947  LEAVE (" ");
1948  return NULL;
1949  }
1950 
1951  result = g_list_sort(result, compare_prices_by_date);
1952 
1953  LEAVE (" ");
1954  return result;
1955 }
1956 
1957 
1958 PriceList *
1960  gnc_commodity *c,
1961  Timespec t)
1962 {
1963  GList *result = NULL;
1964  GHashTable *currency_hash;
1965  GNCPriceLookupHelper lookup_helper;
1966  QofBook *book;
1967  QofBackend *be;
1968 
1969  if (!db || !c) return NULL;
1970  ENTER ("db=%p commodity=%p", db, c);
1971  book = qof_instance_get_book(&db->inst);
1972  be = qof_book_get_backend(book);
1973 #ifdef GNUCASH_MAJOR_VERSION
1974  if (be && be->price_lookup)
1975  {
1976  GNCPriceLookup pl;
1977  pl.type = LOOKUP_LATEST_BEFORE;
1978  pl.prdb = db;
1979  pl.commodity = c;
1980  pl.currency = NULL; /* can the backend handle this??? */
1981  pl.date = t;
1982  (be->price_lookup) (be, &pl);
1983  }
1984 #endif
1985  currency_hash = g_hash_table_lookup(db->commodity_hash, c);
1986  if (!currency_hash)
1987  {
1988  LEAVE (" no currency hash");
1989  return NULL;
1990  }
1991 
1992  lookup_helper.return_list = &result;
1993  lookup_helper.time = t;
1994  g_hash_table_foreach(currency_hash, lookup_latest_before, &lookup_helper);
1995 
1996  if (!result)
1997  {
1998  LEAVE (" ");
1999  return NULL;
2000  }
2001 
2002  result = g_list_sort(result, compare_prices_by_date);
2003 
2004  LEAVE (" ");
2005  return result;
2006 }
2007 
2008 
2009 /*
2010  * Convert a balance from one currency to another.
2011  */
2014  gnc_numeric balance,
2015  const gnc_commodity *balance_currency,
2016  const gnc_commodity *new_currency)
2017 {
2018  GNCPrice *price, *currency_price;
2019  GList *price_list, *list_helper;
2020  gnc_numeric currency_price_value;
2021  gnc_commodity *intermediate_currency;
2022 
2023  if (gnc_numeric_zero_p (balance) ||
2024  gnc_commodity_equiv (balance_currency, new_currency))
2025  return balance;
2026 
2027  /* Look for a direct price. */
2028  price = gnc_pricedb_lookup_latest (pdb, balance_currency, new_currency);
2029  if (price)
2030  {
2031  balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
2032  gnc_commodity_get_fraction (new_currency),
2034  gnc_price_unref (price);
2035  return balance;
2036  }
2037 
2038  /* Look for a price of the new currency in the balance currency and use
2039  * the reciprocal if we find it
2040  */
2041  price = gnc_pricedb_lookup_latest (pdb, new_currency, balance_currency);
2042  if (price)
2043  {
2044  balance = gnc_numeric_div (balance, gnc_price_get_value (price),
2045  gnc_commodity_get_fraction (new_currency),
2047  gnc_price_unref (price);
2048  return balance;
2049  }
2050 
2051  /*
2052  * no direct price found, try if we find a price in another currency
2053  * and convert in two stages
2054  */
2055  price_list = gnc_pricedb_lookup_latest_any_currency(pdb, balance_currency);
2056  if (!price_list)
2057  {
2058  balance = gnc_numeric_zero ();
2059  return balance;
2060  }
2061 
2062  list_helper = price_list;
2063  currency_price_value = gnc_numeric_zero();
2064 
2065  do
2066  {
2067  price = (GNCPrice *)(list_helper->data);
2068 
2069  intermediate_currency = gnc_price_get_currency(price);
2070  currency_price = gnc_pricedb_lookup_latest(pdb, intermediate_currency,
2071  new_currency);
2072  if (currency_price)
2073  {
2074  currency_price_value = gnc_price_get_value(currency_price);
2075  gnc_price_unref(currency_price);
2076  }
2077  else
2078  {
2079  currency_price = gnc_pricedb_lookup_latest(pdb, new_currency,
2080  intermediate_currency);
2081  if (currency_price)
2082  {
2083  /* here we need the reciprocal */
2084  currency_price_value = gnc_numeric_div(gnc_numeric_create(1, 1),
2085  gnc_price_get_value(currency_price),
2088  gnc_price_unref(currency_price);
2089  }
2090  }
2091 
2092  list_helper = list_helper->next;
2093  }
2094  while ((list_helper != NULL) &&
2095  (gnc_numeric_zero_p(currency_price_value)));
2096 
2097  balance = gnc_numeric_mul (balance, currency_price_value,
2100  balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
2101  gnc_commodity_get_fraction (new_currency),
2103 
2104  gnc_price_list_destroy(price_list);
2105  return balance;
2106 }
2107 
2110  gnc_numeric balance,
2111  const gnc_commodity *balance_currency,
2112  const gnc_commodity *new_currency,
2113  Timespec t)
2114 {
2115  GNCPrice *price, *currency_price;
2116  GList *price_list, *list_helper;
2117  gnc_numeric currency_price_value;
2118  gnc_commodity *intermediate_currency;
2119 
2120  if (gnc_numeric_zero_p (balance) ||
2121  gnc_commodity_equiv (balance_currency, new_currency))
2122  return balance;
2123 
2124  /* Look for a direct price. */
2125  price = gnc_pricedb_lookup_nearest_in_time (pdb, balance_currency, new_currency, t);
2126  if (price)
2127  {
2128  balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
2129  gnc_commodity_get_fraction (new_currency),
2131  gnc_price_unref (price);
2132  return balance;
2133  }
2134 
2135  /* Look for a price of the new currency in the balance currency and use
2136  * the reciprocal if we find it
2137  */
2138  price = gnc_pricedb_lookup_nearest_in_time (pdb, new_currency, balance_currency, t);
2139  if (price)
2140  {
2141  balance = gnc_numeric_div (balance, gnc_price_get_value (price),
2142  gnc_commodity_get_fraction (new_currency),
2144  gnc_price_unref (price);
2145  return balance;
2146  }
2147 
2148  /*
2149  * no direct price found, try if we find a price in another currency
2150  * and convert in two stages
2151  */
2152  price_list = gnc_pricedb_lookup_nearest_in_time_any_currency(pdb, balance_currency, t);
2153  if (!price_list)
2154  {
2155  balance = gnc_numeric_zero ();
2156  return balance;
2157  }
2158 
2159  list_helper = price_list;
2160  currency_price_value = gnc_numeric_zero();
2161 
2162  do
2163  {
2164  price = (GNCPrice *)(list_helper->data);
2165 
2166  intermediate_currency = gnc_price_get_currency(price);
2167  currency_price = gnc_pricedb_lookup_nearest_in_time(pdb, intermediate_currency,
2168  new_currency, t);
2169  if (currency_price)
2170  {
2171  currency_price_value = gnc_price_get_value(currency_price);
2172  gnc_price_unref(currency_price);
2173  }
2174  else
2175  {
2176  currency_price = gnc_pricedb_lookup_nearest_in_time(pdb, new_currency,
2177  intermediate_currency, t);
2178  if (currency_price)
2179  {
2180  /* here we need the reciprocal */
2181  currency_price_value = gnc_numeric_div(gnc_numeric_create(1, 1),
2182  gnc_price_get_value(currency_price),
2185  gnc_price_unref(currency_price);
2186  }
2187  }
2188 
2189  list_helper = list_helper->next;
2190  }
2191  while ((list_helper != NULL) &&
2192  (gnc_numeric_zero_p(currency_price_value)));
2193 
2194  balance = gnc_numeric_mul (balance, currency_price_value,
2197 
2198  balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
2199  gnc_commodity_get_fraction (new_currency),
2201 
2202  gnc_price_list_destroy(price_list);
2203  return balance;
2204 }
2205 
2206 
2207 /* ==================================================================== */
2208 /* gnc_pricedb_foreach_price infrastructure
2209  */
2210 
2211 typedef struct
2212 {
2213  gboolean ok;
2214  gboolean (*func)(GNCPrice *p, gpointer user_data);
2215  gpointer user_data;
2217 
2218 static void
2219 pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2220 {
2221  GList *price_list = (GList *) val;
2222  GList *node = price_list;
2223  GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
2224 
2225  /* stop traversal when func returns FALSE */
2226  while (foreach_data->ok && node)
2227  {
2228  GNCPrice *p = (GNCPrice *) node->data;
2229  foreach_data->ok = foreach_data->func(p, foreach_data->user_data);
2230  node = node->next;
2231  }
2232 }
2233 
2234 static void
2235 pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2236 {
2237  GHashTable *currencies_hash = (GHashTable *) val;
2238  g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
2239 }
2240 
2241 static gboolean
2242 unstable_price_traversal(GNCPriceDB *db,
2243  gboolean (*f)(GNCPrice *p, gpointer user_data),
2244  gpointer user_data)
2245 {
2246  GNCPriceDBForeachData foreach_data;
2247 
2248  if (!db || !f) return FALSE;
2249  foreach_data.ok = TRUE;
2250  foreach_data.func = f;
2251  foreach_data.user_data = user_data;
2252  if (db->commodity_hash == NULL)
2253  {
2254  return FALSE;
2255  }
2256  g_hash_table_foreach(db->commodity_hash,
2257  pricedb_foreach_currencies_hash,
2258  &foreach_data);
2259 
2260  return foreach_data.ok;
2261 }
2262 
2263 static gint
2264 compare_kvpairs_by_commodity_key(gconstpointer a, gconstpointer b)
2265 {
2266  GHashTableKVPair *kvpa = (GHashTableKVPair *) a;
2267  GHashTableKVPair *kvpb = (GHashTableKVPair *) b;
2268  gnc_commodity *ca;
2269  gnc_commodity *cb;
2270  int cmp_result;
2271 
2272  if (a == b) return 0;
2273  if (!a && !b) return 0;
2274  if (!a) return -1;
2275  if (!b) return 1;
2276 
2277  ca = (gnc_commodity *) kvpa->key;
2278  cb = (gnc_commodity *) kvpb->key;
2279 
2280  cmp_result = g_strcmp0(gnc_commodity_get_namespace(ca),
2282 
2283  if (cmp_result != 0) return cmp_result;
2284 
2285  return g_strcmp0(gnc_commodity_get_mnemonic(ca),
2287 }
2288 
2289 static gboolean
2290 stable_price_traversal(GNCPriceDB *db,
2291  gboolean (*f)(GNCPrice *p, gpointer user_data),
2292  gpointer user_data)
2293 {
2294  GSList *currency_hashes = NULL;
2295  gboolean ok = TRUE;
2296  GSList *i = NULL;
2297 
2298  if (!db || !f) return FALSE;
2299 
2300  currency_hashes = g_hash_table_key_value_pairs(db->commodity_hash);
2301  currency_hashes = g_slist_sort(currency_hashes,
2302  compare_kvpairs_by_commodity_key);
2303 
2304  for (i = currency_hashes; i; i = i->next)
2305  {
2306  GHashTableKVPair *kv_pair = (GHashTableKVPair *) i->data;
2307  GHashTable *currency_hash = (GHashTable *) kv_pair->value;
2308  GSList *price_lists = g_hash_table_key_value_pairs(currency_hash);
2309  GSList *j;
2310 
2311  price_lists = g_slist_sort(price_lists, compare_kvpairs_by_commodity_key);
2312  for (j = price_lists; j; j = j->next)
2313  {
2314  GHashTableKVPair *pricelist_kvp = (GHashTableKVPair *) j->data;
2315  GList *price_list = (GList *) pricelist_kvp->value;
2316  GList *node;
2317 
2318  for (node = (GList *) price_list; node; node = node->next)
2319  {
2320  GNCPrice *price = (GNCPrice *) node->data;
2321 
2322  /* stop traversal when f returns FALSE */
2323  if (FALSE == ok) break;
2324  if (!f(price, user_data)) ok = FALSE;
2325  }
2326  }
2327  if (price_lists)
2328  {
2329  g_slist_foreach(price_lists, g_hash_table_kv_pair_free_gfunc, NULL);
2330  g_slist_free(price_lists);
2331  price_lists = NULL;
2332  }
2333  }
2334 
2335  if (currency_hashes)
2336  {
2337  g_slist_foreach(currency_hashes, g_hash_table_kv_pair_free_gfunc, NULL);
2338  g_slist_free(currency_hashes);
2339  }
2340  return ok;
2341 }
2342 
2343 gboolean
2345  gboolean (*f)(GNCPrice *p, gpointer user_data),
2346  gpointer user_data,
2347  gboolean stable_order)
2348 {
2349  ENTER ("db=%p f=%p", db, f);
2350  if (stable_order)
2351  {
2352  LEAVE (" stable order found");
2353  return stable_price_traversal(db, f, user_data);
2354  }
2355  LEAVE (" use unstable order");
2356  return unstable_price_traversal(db, f, user_data);
2357 }
2358 
2359 /* ==================================================================== */
2360 /* commodity substitution */
2361 
2362 typedef struct
2363 {
2364  gnc_commodity *old_c;
2365  gnc_commodity *new_c;
2367 
2368 static gboolean
2369 add_price_to_list (GNCPrice *p, gpointer data)
2370 {
2371  GList **list = data;
2372 
2373  *list = g_list_prepend (*list, p);
2374 
2375  return TRUE;
2376 }
2377 
2378 static void
2379 gnc_price_fixup_legacy_commods(gpointer data, gpointer user_data)
2380 {
2381  GNCPrice *p = data;
2382  GNCPriceFixupData *fixup_data = user_data;
2383  gnc_commodity *price_c;
2384 
2385  if (!p) return;
2386 
2387  price_c = gnc_price_get_commodity(p);
2388  if (gnc_commodity_equiv(price_c, fixup_data->old_c))
2389  {
2390  gnc_price_set_commodity (p, fixup_data->new_c);
2391  }
2392  price_c = gnc_price_get_currency(p);
2393  if (gnc_commodity_equiv(price_c, fixup_data->old_c))
2394  {
2395  gnc_price_set_currency (p, fixup_data->new_c);
2396  }
2397 }
2398 
2399 void
2400 gnc_pricedb_substitute_commodity(GNCPriceDB *db,
2401  gnc_commodity *old_c,
2402  gnc_commodity *new_c)
2403 {
2404  GNCPriceFixupData data;
2405  GList *prices = NULL;
2406 
2407  if (!db || !old_c || !new_c) return;
2408 
2409  data.old_c = old_c;
2410  data.new_c = new_c;
2411 
2412  gnc_pricedb_foreach_price (db, add_price_to_list, &prices, FALSE);
2413 
2414  g_list_foreach (prices, gnc_price_fixup_legacy_commods, &data);
2415 
2416  g_list_free (prices);
2417 }
2418 
2419 /***************************************************************************/
2420 
2421 /* Semi-lame debugging code */
2422 
2423 void
2424 gnc_price_print(GNCPrice *p, FILE *f, int indent)
2425 {
2426  gnc_commodity *commodity;
2427  gnc_commodity *currency;
2428  gchar *istr = NULL; /* indent string */
2429  const char *str;
2430 
2431  if (!p) return;
2432  if (!f) return;
2433 
2434  commodity = gnc_price_get_commodity(p);
2435  currency = gnc_price_get_currency(p);
2436 
2437  if (!commodity) return;
2438  if (!currency) return;
2439 
2440  istr = g_strnfill(indent, ' ');
2441 
2442  fprintf(f, "%s<pdb:price>\n", istr);
2443  fprintf(f, "%s <pdb:commodity pointer=%p>\n", istr, commodity);
2444  str = gnc_commodity_get_namespace(commodity);
2445  str = str ? str : "(null)";
2446  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2447  str = gnc_commodity_get_mnemonic(commodity);
2448  str = str ? str : "(null)";
2449  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2450  fprintf(f, "%s </pdb:commodity>\n", istr);
2451  fprintf(f, "%s <pdb:currency pointer=%p>\n", istr, currency);
2452  str = gnc_commodity_get_namespace(currency);
2453  str = str ? str : "(null)";
2454  fprintf(f, "%s <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
2455  str = gnc_commodity_get_mnemonic(currency);
2456  str = str ? str : "(null)";
2457  fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
2458  fprintf(f, "%s </pdb:currency>\n", istr);
2459  str = gnc_price_get_source(p);
2460  str = str ? str : "(null)";
2461  fprintf(f, "%s %s\n", istr, str);
2462  str = gnc_price_get_typestr(p);
2463  str = str ? str : "(null)";
2464  fprintf(f, "%s %s\n", istr, str);
2465  fprintf(f, "%s %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
2466  fprintf(f, "%s</pdb:price>\n", istr);
2467 
2468  g_free(istr);
2469 }
2470 
2471 static gboolean
2472 print_pricedb_adapter(GNCPrice *p, gpointer user_data)
2473 {
2474  FILE *f = (FILE *) user_data;
2475  gnc_price_print(p, f, 1);
2476  return TRUE;
2477 }
2478 
2479 void
2481 {
2482  if (!db)
2483  {
2484  PERR("NULL PriceDB\n");
2485  return;
2486  }
2487  if (!f)
2488  {
2489  PERR("NULL FILE*\n");
2490  return;
2491  }
2492 
2493  fprintf(f, "<gnc:pricedb>\n");
2494  gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
2495  fprintf(f, "</gnc:pricedb>\n");
2496 }
2497 
2498 /* ==================================================================== */
2499 /* gncObject function implementation and registration */
2500 
2501 static void
2502 pricedb_book_begin (QofBook *book)
2503 {
2504  gnc_pricedb_create(book);
2505 }
2506 
2507 static void
2508 pricedb_book_end (QofBook *book)
2509 {
2510  GNCPriceDB *db;
2511  QofCollection *col;
2512 
2513  if (!book)
2514  return;
2515  col = qof_book_get_collection(book, GNC_ID_PRICEDB);
2516  db = qof_collection_get_data(col);
2517  qof_collection_set_data(col, NULL);
2518  gnc_pricedb_destroy(db);
2519 }
2520 
2521 static gpointer
2522 price_create (QofBook *book)
2523 {
2524  return gnc_price_create(book);
2525 }
2526 
2527 /* ==================================================================== */
2528 /* a non-boolean foreach. Ugh */
2529 
2530 typedef struct
2531 {
2532  void (*func)(GNCPrice *p, gpointer user_data);
2533  gpointer user_data;
2534 }
2536 
2537 static void
2538 void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
2539 {
2540  GList *price_list = (GList *) val;
2541  GList *node = price_list;
2542  VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
2543 
2544  while (node)
2545  {
2546  GNCPrice *p = (GNCPrice *) node->data;
2547  foreach_data->func(p, foreach_data->user_data);
2548  node = node->next;
2549  }
2550 }
2551 
2552 static void
2553 void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
2554 {
2555  GHashTable *currencies_hash = (GHashTable *) val;
2556  g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
2557 }
2558 
2559 static void
2560 void_unstable_price_traversal(GNCPriceDB *db,
2561  void (*f)(GNCPrice *p, gpointer user_data),
2562  gpointer user_data)
2563 {
2564  VoidGNCPriceDBForeachData foreach_data;
2565 
2566  if (!db || !f) return;
2567  foreach_data.func = f;
2568  foreach_data.user_data = user_data;
2569 
2570  g_hash_table_foreach(db->commodity_hash,
2571  void_pricedb_foreach_currencies_hash,
2572  &foreach_data);
2573 }
2574 
2575 static void
2576 price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
2577 {
2578  GNCPriceDB *db;
2579 
2580  db = qof_collection_get_data(col);
2581  void_unstable_price_traversal(db,
2582  (void (*)(GNCPrice *, gpointer)) cb,
2583  data);
2584 }
2585 
2586 /* ==================================================================== */
2587 
2588 static const char *
2589 price_printable(gpointer obj)
2590 {
2591  GNCPrice *pr = obj;
2592  gnc_commodity *commodity;
2593  gnc_commodity *currency;
2594  static char buff[2048]; /* nasty static OK for printing */
2595  char *val, *da;
2596 
2597  if (!pr) return "";
2598 
2599  val = gnc_numeric_to_string (pr->value);
2600  da = qof_print_date (pr->tmspec.tv_sec);
2601 
2602  commodity = gnc_price_get_commodity(pr);
2603  currency = gnc_price_get_currency(pr);
2604 
2605  g_snprintf (buff, 2048, "%s %s / %s on %s", val,
2606  gnc_commodity_get_unique_name(commodity),
2608  da);
2609  g_free (val);
2610  g_free (da);
2611  return buff;
2612 }
2613 
2614 #ifdef _MSC_VER
2615 /* MSVC compiler doesn't have C99 "designated initializers"
2616  * so we wrap them in a macro that is empty on MSVC. */
2617 # define DI(x) /* */
2618 #else
2619 # define DI(x) x
2620 #endif
2621 static QofObject price_object_def =
2622 {
2623  DI(.interface_version = ) QOF_OBJECT_VERSION,
2624  DI(.e_type = ) GNC_ID_PRICE,
2625  DI(.type_label = ) "Price",
2626  DI(.create = ) price_create,
2627  DI(.book_begin = ) NULL,
2628  DI(.book_end = ) NULL,
2629  DI(.is_dirty = ) qof_collection_is_dirty,
2630  DI(.mark_clean = ) qof_collection_mark_clean,
2631  DI(.foreach = ) price_foreach,
2632  DI(.printable = ) price_printable,
2633  DI(.version_cmp = ) NULL,
2634 };
2635 
2636 static QofObject pricedb_object_def =
2637 {
2638  DI(.interface_version = ) QOF_OBJECT_VERSION,
2639  DI(.e_type = ) GNC_ID_PRICEDB,
2640  DI(.type_label = ) "PriceDB",
2641  DI(.create = ) NULL,
2642  DI(.book_begin = ) pricedb_book_begin,
2643  DI(.book_end = ) pricedb_book_end,
2644  DI(.is_dirty = ) qof_collection_is_dirty,
2645  DI(.mark_clean = ) qof_collection_mark_clean,
2646  DI(.foreach = ) NULL,
2647  DI(.printable = ) NULL,
2648  DI(.version_cmp = ) NULL,
2649 };
2650 
2651 gboolean
2652 gnc_pricedb_register (void)
2653 {
2654  static QofParam params[] =
2655  {
2656  { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
2657  { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
2658  { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time, (QofSetterFunc)gnc_price_set_time },
2659  { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
2660  { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
2661  { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
2662  { NULL },
2663  };
2664 
2665  qof_class_register (GNC_ID_PRICE, NULL, params);
2666 
2667  if (!qof_object_register (&price_object_def))
2668  return FALSE;
2669  return qof_object_register (&pricedb_object_def);
2670 }
2671 
2672 /* ========================= END OF FILE ============================== */
void gnc_price_list_destroy(PriceList *prices)
Definition: gnc-pricedb.c:701
GNCPrice * gnc_price_create(QofBook *book)
Definition: gnc-pricedb.c:236
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
gchar * gnc_timespec_to_iso8601_buff(Timespec ts, gchar *buff)
int gnc_commodity_get_fraction(const gnc_commodity *cm)
GNCPrice * gnc_pricedb_lookup_day(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, Timespec t)
Definition: gnc-pricedb.c:1536
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
PriceList * gnc_pricedb_lookup_nearest_in_time_any_currency(GNCPriceDB *db, const gnc_commodity *c, Timespec t)
Definition: gnc-pricedb.c:1908
QofBook * qof_instance_get_book(gconstpointer)
gboolean qof_collection_is_dirty(const QofCollection *col)
QofInstance * qof_collection_lookup_entity(const QofCollection *, const GncGUID *)
void(* price_lookup)(QofBackend *, gpointer)
Definition: qofbackend-p.h:336
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:59
Timespec timespecCanonicalDayTime(Timespec t)
void gnc_price_unref(GNCPrice *p)
Definition: gnc-pricedb.c:272
#define DEBUG(format, args...)
Definition: qoflog.h:255
gboolean qof_instance_get_destroying(gconstpointer ptr)
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
Definition: gnc-pricedb.c:1053
void qof_class_register(QofIdTypeConst obj_name, QofSortFunc default_sort_fcn, const QofParam *params)
gboolean timespec_equal(const Timespec *ta, const Timespec *tb)
GNCPriceDB * gnc_collection_get_pricedb(QofCollection *col)
Definition: gnc-pricedb.c:865
gboolean gnc_pricedb_equal(GNCPriceDB *db1, GNCPriceDB *db2)
Definition: gnc-pricedb.c:949
Use a 64-bit unsigned int timespec.
Definition: gnc-date.h:299
gboolean gnc_numeric_zero_p(gnc_numeric a)
const char * gnc_commodity_get_namespace(const gnc_commodity *cm)
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
Definition: qofclass.h:177
#define QOF_OBJECT_VERSION
Definition: qofobject.h:64
gchar * gnc_numeric_to_string(gnc_numeric n)
gboolean qof_commit_edit(QofInstance *inst)
#define PERR(format, args...)
Definition: qoflog.h:237
gboolean gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
Definition: gnc-pricedb.c:645
#define ENTER(format, args...)
Definition: qoflog.h:261
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Definition: gnc-pricedb.c:872
PriceList * gnc_pricedb_lookup_at_time(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, Timespec t)
Definition: gnc-pricedb.c:1545
Definition: guid.h:65
gnc_numeric gnc_pricedb_convert_balance_latest_price(GNCPriceDB *pdb, gnc_numeric balance, const gnc_commodity *balance_currency, const gnc_commodity *new_currency)
Definition: gnc-pricedb.c:2013
Timespec timespec_abs(const Timespec *t)
#define PWARN(format, args...)
Definition: qoflog.h:243
char * qof_print_date(time64 secs)
void qof_instance_init_data(QofInstance *, QofIdType, QofBook *)
gint timespec_cmp(const Timespec *ta, const Timespec *tb)
gboolean qof_begin_edit(QofInstance *inst)
gdouble gnc_numeric_to_double(gnc_numeric n)
GNCPrice * gnc_pricedb_lookup_latest_before(GNCPriceDB *db, gnc_commodity *c, gnc_commodity *currency, Timespec t)
Definition: gnc-pricedb.c:1753
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
gboolean qof_instance_get_dirty_flag(gconstpointer ptr)
void gnc_pricedb_destroy(GNCPriceDB *db)
Definition: gnc-pricedb.c:828
guint gnc_pricedb_get_num_prices(GNCPriceDB *db)
Definition: gnc-pricedb.c:894
GSList * g_hash_table_key_value_pairs(GHashTable *table)
PriceList * gnc_pricedb_lookup_latest_before_any_currency(GNCPriceDB *db, gnc_commodity *c, Timespec t)
Definition: gnc-pricedb.c:1959
gboolean gnc_pricedb_foreach_price(GNCPriceDB *db, gboolean(*f)(GNCPrice *p, gpointer user_data), gpointer user_data, gboolean stable_order)
Definition: gnc-pricedb.c:2344
void gnc_price_print(GNCPrice *p, FILE *f, int indent)
Definition: gnc-pricedb.c:2424
void gnc_pricedb_begin_edit(GNCPriceDB *pdb)
Definition: gnc-pricedb.c:358
PriceList * gnc_pricedb_lookup_latest_any_currency(GNCPriceDB *db, const gnc_commodity *commodity)
Definition: gnc-pricedb.c:1373
gboolean qof_commit_edit_part2(QofInstance *inst, void(*on_error)(QofInstance *, QofBackendError), void(*on_done)(QofInstance *), void(*on_free)(QofInstance *))
GNCPrice * gnc_pricedb_lookup_nearest_in_time(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, Timespec t)
Definition: gnc-pricedb.c:1744
void qof_collection_mark_clean(QofCollection *)
gnc_numeric gnc_numeric_div(gnc_numeric x, gnc_numeric y, gint64 denom, gint how)
gboolean gnc_numeric_eq(gnc_numeric a, gnc_numeric b)
gboolean qof_instance_books_equal(gconstpointer ptr1, gconstpointer ptr2)
void(* QofInstanceForeachCB)(QofInstance *, gpointer user_data)
Definition: qofid.h:186
gboolean gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
Definition: gnc-pricedb.c:1160
gboolean gnc_pricedb_has_prices(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Definition: gnc-pricedb.c:1428
private api for data storage backend
gboolean gnc_price_list_remove(PriceList **prices, GNCPrice *p)
Definition: gnc-pricedb.c:676
Timespec timespec_diff(const Timespec *ta, const Timespec *tb)
GNCPrice * gnc_price_clone(GNCPrice *p, QofBook *book)
Definition: gnc-pricedb.c:295
#define LEAVE(format, args...)
Definition: qoflog.h:271
void(* QofSetterFunc)(gpointer, gpointer)
Definition: qofclass.h:184
const char * gnc_commodity_get_unique_name(const gnc_commodity *cm)
void gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
Definition: gnc-pricedb.c:844
QofCollection * qof_book_get_collection(const QofBook *, QofIdType)
gnc_numeric gnc_pricedb_convert_balance_nearest_price(GNCPriceDB *pdb, gnc_numeric balance, const gnc_commodity *balance_currency, const gnc_commodity *new_currency, Timespec t)
Definition: gnc-pricedb.c:2109
gboolean qof_object_register(const QofObject *object)
GNCPrice * gnc_pricedb_lookup_latest(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Definition: gnc-pricedb.c:1309
gpointer qof_collection_get_data(const QofCollection *col)
QofBackend * qof_book_get_backend(const QofBook *book)
Retrieve the backend used by this book.
void qof_event_gen(QofInstance *entity, QofEventId event_type, gpointer event_data)
Invoke all registered event handlers using the given arguments.
#define GNC_DENOM_AUTO
Definition: gnc-numeric.h:246
void gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
Definition: gnc-pricedb.c:2480
void gnc_price_ref(GNCPrice *p)
Definition: gnc-pricedb.c:265
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
const gchar * QofLogModule
Definition: qofid.h:89
PriceList * gnc_pricedb_get_prices(GNCPriceDB *db, const gnc_commodity *commodity, const gnc_commodity *currency)
Definition: gnc-pricedb.c:1479