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