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
time64_cmp(time64 a,time64 b)67 time64_cmp (time64 a, time64 b)
68 {
69     return a < b ? -1 : a > b ? 1 : 0;
70 }
71 
72 static void
hash_entry_insert(gpointer key,gpointer val,gpointer user_data)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 *
hash_table_to_list(GHashTable * table)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
hash_entry_free_gfunc(gpointer data,G_GNUC_UNUSED gpointer user_data)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
gnc_price_init(GNCPrice * price)102 gnc_price_init(GNCPrice* price)
103 {
104     price->refcount = 1;
105     price->value = gnc_numeric_zero();
106     price->type = NULL;
107     price->source = PRICE_SOURCE_INVALID;
108 }
109 
110 /* Array of char constants for converting price-source enums. Be sure to keep in
111  * sync with the enum values in gnc-pricedb.h The string user:price-editor is
112  * explicitly used by price_to_gui() in dialog-price-editor.c. Beware
113  * that the strings are used to store the enum values in the backends so any
114  * changes will affect backward data compatibility.
115  * The last two values, temporary and invalid, are *not* used.
116  */
117 static const char* source_names[(size_t)PRICE_SOURCE_INVALID + 1] =
118 {
119     /* sync with price_to_gui in dialog-price-editor.c */
120     "user:price-editor",
121     /* sync with commidity-tz-quote->price in price-quotes.scm */
122     "Finance::Quote",
123     "user:price",
124     /* String retained for backwards compatibility. */
125     "user:xfer-dialog",
126     "user:split-register",
127     "user:split-import",
128     "user:stock-split",
129     "user:invoice-post", /* Retained for backwards compatibility */
130     "temporary",
131     "invalid"
132 };
133 
134 static void
gnc_price_dispose(GObject * pricep)135 gnc_price_dispose(GObject *pricep)
136 {
137     G_OBJECT_CLASS(gnc_price_parent_class)->dispose(pricep);
138 }
139 
140 static void
gnc_price_finalize(GObject * pricep)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
gnc_price_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)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
gnc_price_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)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
gnc_price_class_init(GNCPriceClass * klass)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 *
gnc_price_create(QofBook * book)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
gnc_price_destroy(GNCPrice * p)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
gnc_price_ref(GNCPrice * p)336 gnc_price_ref(GNCPrice *p)
337 {
338     if (!p) return;
339     p->refcount++;
340 }
341 
342 void
gnc_price_unref(GNCPrice * p)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 *
gnc_price_clone(GNCPrice * p,QofBook * book)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 *
gnc_price_invert(GNCPrice * p)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
gnc_price_begin_edit(GNCPrice * p)423 gnc_price_begin_edit (GNCPrice *p)
424 {
425     qof_begin_edit(&p->inst);
426 }
427 
commit_err(QofInstance * inst,QofBackendError errcode)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 
noop(QofInstance * inst)434 static void noop (QofInstance *inst) {}
435 
436 void
gnc_price_commit_edit(GNCPrice * p)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
gnc_pricedb_begin_edit(GNCPriceDB * pdb)446 gnc_pricedb_begin_edit (GNCPriceDB *pdb)
447 {
448     qof_begin_edit(&pdb->inst);
449 }
450 
451 void
gnc_pricedb_commit_edit(GNCPriceDB * pdb)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
gnc_price_set_dirty(GNCPrice * p)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
gnc_price_set_commodity(GNCPrice * p,gnc_commodity * c)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
gnc_price_set_currency(GNCPrice * p,gnc_commodity * c)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
gnc_price_set_time64(GNCPrice * p,time64 t)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
gnc_price_set_source(GNCPrice * p,PriceSource s)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
gnc_price_set_source_string(GNCPrice * p,const char * str)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
gnc_price_set_typestr(GNCPrice * p,const char * type)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
gnc_price_set_value(GNCPrice * p,gnc_numeric value)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 *
gnc_price_lookup(const GncGUID * guid,QofBook * book)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 *
gnc_price_get_commodity(const GNCPrice * p)595 gnc_price_get_commodity(const GNCPrice *p)
596 {
597     if (!p) return NULL;
598     return p->commodity;
599 }
600 
601 time64
gnc_price_get_time64(const GNCPrice * p)602 gnc_price_get_time64(const GNCPrice *p)
603 {
604     return p ? p->tmspec : 0;
605 }
606 
607 PriceSource
gnc_price_get_source(const GNCPrice * p)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*
gnc_price_get_source_string(const GNCPrice * p)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 *
gnc_price_get_typestr(const GNCPrice * p)622 gnc_price_get_typestr(const GNCPrice *p)
623 {
624     if (!p) return NULL;
625     return p->type;
626 }
627 
628 gnc_numeric
gnc_price_get_value(const GNCPrice * p)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 *
gnc_price_get_currency(const GNCPrice * p)640 gnc_price_get_currency(const GNCPrice *p)
641 {
642     if (!p) return NULL;
643     return p->currency;
644 }
645 
646 gboolean
gnc_price_equal(const GNCPrice * p1,const GNCPrice * p2)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
compare_prices_by_date(gconstpointer a,gconstpointer b)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;
711 } PriceListIsDuplStruct;
712 
713 static void
price_list_is_duplicate(gpointer data,gpointer user_data)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
gnc_price_list_insert(PriceList ** prices,GNCPrice * p,gboolean check_dupl)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
gnc_price_list_remove(PriceList ** prices,GNCPrice * p)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
price_list_destroy_helper(gpointer data,gpointer user_data)784 price_list_destroy_helper(gpointer data, gpointer user_data)
785 {
786     gnc_price_unref((GNCPrice *) data);
787 }
788 
789 void
gnc_price_list_destroy(PriceList * prices)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
gnc_price_list_equal(PriceList * prices1,PriceList * prices2)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
gnc_pricedb_init(GNCPriceDB * pdb)841 gnc_pricedb_init(GNCPriceDB* pdb)
842 {
843     pdb->reset_nth_price_cache = FALSE;
844 }
845 
846 static void
gnc_pricedb_dispose_real(GObject * pdbp)847 gnc_pricedb_dispose_real (GObject *pdbp)
848 {
849 }
850 
851 static void
gnc_pricedb_finalize_real(GObject * pdbp)852 gnc_pricedb_finalize_real(GObject* pdbp)
853 {
854 }
855 
856 static GNCPriceDB *
gnc_pricedb_create(QofBook * book)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);
877     qof_collection_mark_clean(col);
878 
879     /** \todo This leaks result when the collection is destroyed.  When
880        qofcollection is fixed to allow a destroy notifier, we'll need to
881        provide one here. */
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
destroy_pricedb_currency_hash_data(gpointer key,gpointer data,gpointer user_data)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
destroy_pricedb_commodity_hash_data(gpointer key,gpointer data,gpointer user_data)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
gnc_pricedb_destroy(GNCPriceDB * db)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
gnc_pricedb_set_bulk_update(GNCPriceDB * db,gboolean bulk_update)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 */
949 /** \todo Collections of prices are not destroyed fully.
950 
951     \par
952     gnc_pricedb_destroy does not clean up properly because
953     gnc_pricedb_create reports an existing PriceDB after
954     running gnc_pricedb_destroy. To change the pricedb, we need to
955     destroy and recreate the book. Yuk.
956  */
957 
958 GNCPriceDB *
gnc_collection_get_pricedb(QofCollection * col)959 gnc_collection_get_pricedb(QofCollection *col)
960 {
961     if (!col) return NULL;
962     return qof_collection_get_data (col);
963 }
964 
965 GNCPriceDB *
gnc_pricedb_get_db(QofBook * book)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
num_prices_helper(GNCPrice * p,gpointer user_data)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
gnc_pricedb_get_num_prices(GNCPriceDB * db)988 gnc_pricedb_get_num_prices(GNCPriceDB *db)
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;
1008 } GNCPriceDBEqualData;
1009 
1010 static void
pricedb_equal_foreach_pricelist(gpointer key,gpointer val,gpointer user_data)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
pricedb_equal_foreach_currencies_hash(gpointer key,gpointer val,gpointer user_data)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
gnc_pricedb_equal(GNCPriceDB * db1,GNCPriceDB * db2)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
add_price(GNCPriceDB * db,GNCPrice * p)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),
1082            qof_instance_get_destroying(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),
1157            qof_instance_get_destroying(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
gnc_pricedb_add_price(GNCPriceDB * db,GNCPrice * p)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),
1175            qof_instance_get_destroying(p));
1176 
1177     if (FALSE == add_price(db, p))
1178     {
1179         LEAVE (" failed to add price");
1180         return FALSE;
1181     }
1182 
1183     gnc_pricedb_begin_edit(db);
1184     qof_instance_set_dirty(&db->inst);
1185     gnc_pricedb_commit_edit(db);
1186 
1187     LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
1188            db, p, qof_instance_get_dirty_flag(p),
1189            qof_instance_get_destroying(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
remove_price(GNCPriceDB * db,GNCPrice * p,gboolean cleanup)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),
1209            qof_instance_get_destroying(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
gnc_pricedb_remove_price(GNCPriceDB * db,GNCPrice * p)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),
1284            qof_instance_get_destroying(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);
1293     gnc_pricedb_begin_edit(db);
1294     qof_instance_set_dirty(&db->inst);
1295     gnc_pricedb_commit_edit(db);
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
check_one_price_date(GNCPrice * price,gpointer user_data)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
pricedb_remove_foreach_pricelist(gpointer key,gpointer val,gpointer user_data)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
compare_prices_by_commodity_date(gconstpointer a,gconstpointer b)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
price_commodity_and_currency_equal(GNCPrice * a,GNCPrice * b)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
gnc_pricedb_remove_old_prices_pinfo(GNCPrice * price,gboolean keep_message)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
clone_price(GNCPrice ** price,GNCPrice * source_price)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
roundUp(gint numToRound,gint multiple)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
get_fiscal_quarter(GDate * date,GDateMonth fiscal_start)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
gnc_pricedb_process_removal_list(GNCPriceDB * db,GDate * fiscal_end_date,remove_info data,PriceRemoveKeepOptions keep)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
gnc_pricedb_remove_old_prices(GNCPriceDB * db,GList * comm_list,GDate * fiscal_end_date,time64 cutoff,PriceRemoveSourceFlags source,PriceRemoveKeepOptions keep)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
hash_values_helper(gpointer key,gpointer value,gpointer data)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 *
price_list_from_hashtable(GHashTable * hash,const gnc_commodity * currency)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 *
pricedb_price_list_merge(PriceList * a,PriceList * b)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*
pricedb_get_prices_internal(GNCPriceDB * db,const gnc_commodity * commodity,const gnc_commodity * currency,gboolean bidi)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 
gnc_pricedb_lookup_latest(GNCPriceDB * db,const gnc_commodity * commodity,const gnc_commodity * currency)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
price_list_scan_any_currency(GList * price_list,gpointer data)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*
latest_before(PriceList * prices,const gnc_commodity * target,time64 t)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**
find_comtime(GPtrArray * array,gnc_commodity * com)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*
add_nearest_price(GList * target_list,GPtrArray * price_array,GNCPrice * price,const gnc_commodity * target,time64 t)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 *
nearest_to(PriceList * prices,const gnc_commodity * target,time64 t)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 *
gnc_pricedb_lookup_latest_any_currency(GNCPriceDB * db,const gnc_commodity * commodity)2016 gnc_pricedb_lookup_latest_any_currency(GNCPriceDB *db,
2017                                        const gnc_commodity *commodity)
2018 {
2019     return gnc_pricedb_lookup_nearest_before_any_currency_t64(db, commodity,
2020                                                               gnc_time(NULL));
2021 }
2022 
2023 PriceList *
gnc_pricedb_lookup_nearest_in_time_any_currency_t64(GNCPriceDB * db,const gnc_commodity * commodity,time64 t)2024 gnc_pricedb_lookup_nearest_in_time_any_currency_t64(GNCPriceDB *db,
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 *
gnc_pricedb_lookup_nearest_before_any_currency_t64(GNCPriceDB * db,const gnc_commodity * commodity,time64 t)2044 gnc_pricedb_lookup_nearest_before_any_currency_t64(GNCPriceDB *db,
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
gnc_pricedb_has_prices(GNCPriceDB * db,const gnc_commodity * commodity,const gnc_commodity * currency)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 *
gnc_pricedb_get_prices(GNCPriceDB * db,const gnc_commodity * commodity,const gnc_commodity * currency)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
price_count_helper(gpointer key,gpointer value,gpointer data)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
gnc_pricedb_num_prices(GNCPriceDB * db,const gnc_commodity * c)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
list_combine(gpointer element,gpointer data)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 *
gnc_pricedb_nth_price(GNCPriceDB * db,const gnc_commodity * c,const int n)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
gnc_pricedb_nth_price_reset_cache(GNCPriceDB * db)2230 gnc_pricedb_nth_price_reset_cache (GNCPriceDB *db)
2231 {
2232     if (db)
2233         db->reset_nth_price_cache = TRUE;
2234 }
2235 
2236 GNCPrice *
gnc_pricedb_lookup_day_t64(GNCPriceDB * db,const gnc_commodity * c,const gnc_commodity * currency,time64 t)2237 gnc_pricedb_lookup_day_t64(GNCPriceDB *db,
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 *
gnc_pricedb_lookup_at_time64(GNCPriceDB * db,const gnc_commodity * c,const gnc_commodity * currency,time64 t)2246 gnc_pricedb_lookup_at_time64(GNCPriceDB *db,
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 *
lookup_nearest_in_time(GNCPriceDB * db,const gnc_commodity * c,const gnc_commodity * currency,time64 t,gboolean sameday)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 *
gnc_pricedb_lookup_nearest_in_time64(GNCPriceDB * db,const gnc_commodity * c,const gnc_commodity * currency,time64 t)2390 gnc_pricedb_lookup_nearest_in_time64(GNCPriceDB *db,
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 *
gnc_pricedb_lookup_nearest_before_t64(GNCPriceDB * db,const gnc_commodity * c,const gnc_commodity * currency,time64 t)2400 gnc_pricedb_lookup_nearest_before_t64 (GNCPriceDB *db,
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
extract_common_prices(PriceList * from_prices,PriceList * to_prices,const gnc_commodity * from,const gnc_commodity * to)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
convert_price(const gnc_commodity * from,const gnc_commodity * to,PriceTuple tuple)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
indirect_price_conversion(GNCPriceDB * db,const gnc_commodity * from,const gnc_commodity * to,time64 t,gboolean before_date)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)
2530             to_prices = gnc_pricedb_lookup_nearest_before_any_currency_t64 (db, to, t);
2531     }
2532     else
2533     {
2534         from_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, from, t);
2535         if (from_prices)
2536             to_prices = gnc_pricedb_lookup_nearest_in_time_any_currency_t64 (db, to, t);
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
direct_price_conversion(GNCPriceDB * db,const gnc_commodity * from,const gnc_commodity * to,time64 t,gboolean before_date)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
get_nearest_price(GNCPriceDB * pdb,const gnc_commodity * orig_curr,const gnc_commodity * new_curr,const time64 t,gboolean before)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
gnc_pricedb_get_nearest_before_price(GNCPriceDB * pdb,const gnc_commodity * orig_currency,const gnc_commodity * new_currency,const time64 t)2605 gnc_pricedb_get_nearest_before_price (GNCPriceDB *pdb,
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
gnc_pricedb_get_nearest_price(GNCPriceDB * pdb,const gnc_commodity * orig_currency,const gnc_commodity * new_currency,const time64 t)2614 gnc_pricedb_get_nearest_price (GNCPriceDB *pdb,
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
gnc_pricedb_get_latest_price(GNCPriceDB * pdb,const gnc_commodity * orig_currency,const gnc_commodity * new_currency)2623 gnc_pricedb_get_latest_price (GNCPriceDB *pdb,
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
convert_amount_at_date(GNCPriceDB * pdb,gnc_numeric amount,const gnc_commodity * orig_currency,const gnc_commodity * new_currency,const time64 t,gboolean before_date)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),
2651          GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND);
2652 }
2653 
2654 /*
2655  * Convert a balance from one currency to another.
2656  */
2657 gnc_numeric
gnc_pricedb_convert_balance_latest_price(GNCPriceDB * pdb,gnc_numeric balance,const gnc_commodity * balance_currency,const gnc_commodity * new_currency)2658 gnc_pricedb_convert_balance_latest_price (GNCPriceDB *pdb,
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
gnc_pricedb_convert_balance_nearest_price_t64(GNCPriceDB * pdb,gnc_numeric balance,const gnc_commodity * balance_currency,const gnc_commodity * new_currency,time64 t)2668 gnc_pricedb_convert_balance_nearest_price_t64(GNCPriceDB *pdb,
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
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)2679 gnc_pricedb_convert_balance_nearest_before_price_t64 (GNCPriceDB *pdb,
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;
2698 } GNCPriceDBForeachData;
2699 
2700 static void
pricedb_foreach_pricelist(gpointer key,gpointer val,gpointer user_data)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
pricedb_foreach_currencies_hash(gpointer key,gpointer val,gpointer user_data)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
unstable_price_traversal(GNCPriceDB * db,gboolean (* f)(GNCPrice * p,gpointer user_data),gpointer user_data)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;
2751 } GNCPriceListForeachData;
2752 
2753 static void
pricedb_pricelist_foreach_pricelist(gpointer key,gpointer val,gpointer user_data)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
pricedb_pricelist_foreach_currencies_hash(gpointer key,gpointer val,gpointer user_data)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
pricedb_pricelist_traversal(GNCPriceDB * db,gboolean (* f)(GList * p,gpointer user_data),gpointer user_data)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
compare_hash_entries_by_commodity_key(gconstpointer a,gconstpointer b)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),
2811                            gnc_commodity_get_namespace(cb));
2812 
2813     if (cmp_result != 0) return cmp_result;
2814 
2815     return g_strcmp0(gnc_commodity_get_mnemonic(ca),
2816                      gnc_commodity_get_mnemonic(cb));
2817 }
2818 
2819 static gboolean
stable_price_traversal(GNCPriceDB * db,gboolean (* f)(GNCPrice * p,gpointer user_data),gpointer user_data)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
gnc_pricedb_foreach_price(GNCPriceDB * db,GncPriceForeachFunc f,gpointer user_data,gboolean stable_order)2874 gnc_pricedb_foreach_price(GNCPriceDB *db,
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;
2896 } GNCPriceFixupData;
2897 
2898 static gboolean
add_price_to_list(GNCPrice * p,gpointer data)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
gnc_price_fixup_legacy_commods(gpointer data,gpointer user_data)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
gnc_pricedb_substitute_commodity(GNCPriceDB * db,gnc_commodity * old_c,gnc_commodity * new_c)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
gnc_price_print(GNCPrice * p,FILE * f,int indent)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
print_pricedb_adapter(GNCPrice * p,gpointer user_data)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
gnc_pricedb_print_contents(GNCPriceDB * db,FILE * f)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
pricedb_book_begin(QofBook * book)3032 pricedb_book_begin (QofBook *book)
3033 {
3034     gnc_pricedb_create(book);
3035 }
3036 
3037 static void
pricedb_book_end(QofBook * book)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
price_create(QofBook * book)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 }
3065 VoidGNCPriceDBForeachData;
3066 
3067 static void
void_pricedb_foreach_pricelist(gpointer key,gpointer val,gpointer user_data)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
void_pricedb_foreach_currencies_hash(gpointer key,gpointer val,gpointer user_data)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
void_unstable_price_traversal(GNCPriceDB * db,void (* f)(GNCPrice * p,gpointer user_data),gpointer user_data)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
price_foreach(const QofCollection * col,QofInstanceForeachCB cb,gpointer data)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 *
price_printable(gpointer obj)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),
3149                 gnc_commodity_get_unique_name(currency),
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
price_list_dump(GList * price_list,const char * tag)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
gnc_pricedb_register(void)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 ============================== */
3230