LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-pricedb.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 77.1 % 1302 1004
Test Date: 2026-11-02 18:19:43 Functions: 87.1 % 140 122
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1