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