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