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

Generated by: LCOV version 2.0-1