LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-pricedb.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 75.2 % 1297 975
Test Date: 2025-12-10 02:32:13 Functions: 85.7 % 140 120
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

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

Generated by: LCOV version 2.0-1