GnuCash 2.4.99
gnc-pricedb.c
00001 /********************************************************************
00002  * gnc-pricedb.c -- a simple price database for gnucash.            *
00003  * Copyright (C) 2001 Rob Browning                                  *
00004  * Copyright (C) 2001,2003 Linas Vepstas <linas@linas.org>          *
00005  *                                                                  *
00006  * This program is free software; you can redistribute it and/or    *
00007  * modify it under the terms of the GNU General Public License as   *
00008  * published by the Free Software Foundation; either version 2 of   *
00009  * the License, or (at your option) any later version.              *
00010  *                                                                  *
00011  * This program is distributed in the hope that it will be useful,  *
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00014  * GNU General Public License for more details.                     *
00015  *                                                                  *
00016  * You should have received a copy of the GNU General Public License*
00017  * along with this program; if not, contact:                        *
00018  *                                                                  *
00019  * Free Software Foundation           Voice:  +1-617-542-5942       *
00020  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00021  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00022  *                                                                  *
00023  *******************************************************************/
00024 
00025 #include "config.h"
00026 
00027 #include <glib.h>
00028 #include <string.h>
00029 #include "gnc-pricedb-p.h"
00030 #include "qofbackend-p.h"
00031 
00032 /* This static indicates the debugging module that this .o belongs to.  */
00033 static QofLogModule log_module = GNC_MOD_PRICE;
00034 
00035 static gboolean add_price(GNCPriceDB *db, GNCPrice *p);
00036 static gboolean remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup);
00037 
00038 enum
00039 {
00040     PROP_0,
00041     PROP_COMMODITY,
00042     PROP_CURRENCY,
00043     PROP_DATE,
00044     PROP_SOURCE,
00045     PROP_TYPE,
00046     PROP_VALUE
00047 };
00048 
00049 /* GObject Initialization */
00050 G_DEFINE_TYPE(GNCPrice, gnc_price, QOF_TYPE_INSTANCE);
00051 
00052 static void
00053 gnc_price_init(GNCPrice* price)
00054 {
00055     price->refcount = 1;
00056     price->value = gnc_numeric_zero();
00057     price->type = NULL;
00058     price->source = NULL;
00059 }
00060 
00061 static void
00062 gnc_price_dispose(GObject *pricep)
00063 {
00064     G_OBJECT_CLASS(gnc_price_parent_class)->dispose(pricep);
00065 }
00066 
00067 static void
00068 gnc_price_finalize(GObject* pricep)
00069 {
00070     G_OBJECT_CLASS(gnc_price_parent_class)->finalize(pricep);
00071 }
00072 
00073 static void
00074 gnc_price_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
00075 {
00076     GNCPrice* price;
00077 
00078     g_return_if_fail(GNC_IS_PRICE(object));
00079 
00080     price = GNC_PRICE(object);
00081     switch (prop_id)
00082     {
00083     case PROP_SOURCE:
00084         g_value_set_string(value, price->source);
00085         break;
00086     case PROP_TYPE:
00087         g_value_set_string(value, price->type);
00088         break;
00089     case PROP_VALUE:
00090         g_value_set_boxed(value, &price->value);
00091         break;
00092     case PROP_COMMODITY:
00093         g_value_set_object(value, price->commodity);
00094         break;
00095     case PROP_CURRENCY:
00096         g_value_set_object(value, price->currency);
00097         break;
00098     case PROP_DATE:
00099         g_value_set_boxed(value, &price->tmspec);
00100         break;
00101     default:
00102         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
00103         break;
00104     }
00105 }
00106 
00107 static void
00108 gnc_price_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
00109 {
00110     GNCPrice* price;
00111     gnc_numeric* number;
00112     Timespec* ts;
00113 
00114     g_return_if_fail(GNC_IS_PRICE(object));
00115 
00116     price = GNC_PRICE(object);
00117     switch (prop_id)
00118     {
00119     case PROP_SOURCE:
00120         gnc_price_set_source(price, g_value_get_string(value));
00121         break;
00122     case PROP_TYPE:
00123         gnc_price_set_typestr(price, g_value_get_string(value));
00124         break;
00125     case PROP_VALUE:
00126         number = g_value_get_boxed(value);
00127         gnc_price_set_value(price, *number);
00128         break;
00129     case PROP_COMMODITY:
00130         gnc_price_set_commodity(price, g_value_get_object(value));
00131         break;
00132     case PROP_CURRENCY:
00133         gnc_price_set_currency(price, g_value_get_object(value));
00134         break;
00135     case PROP_DATE:
00136         ts = g_value_get_boxed(value);
00137         gnc_price_set_time(price, *ts);
00138         break;
00139     default:
00140         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
00141         break;
00142     }
00143 }
00144 
00145 static void
00146 gnc_price_class_init(GNCPriceClass *klass)
00147 {
00148     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
00149 
00150     gobject_class->dispose = gnc_price_dispose;
00151     gobject_class->finalize = gnc_price_finalize;
00152     gobject_class->set_property = gnc_price_set_property;
00153     gobject_class->get_property = gnc_price_get_property;
00154 
00155     g_object_class_install_property
00156     (gobject_class,
00157      PROP_COMMODITY,
00158      g_param_spec_object ("commodity",
00159                           "Commodity",
00160                           "The commodity field denotes the base kind of "
00161                           "'stuff' for the units of this quote, whether "
00162                           "it is USD, gold, stock, etc.",
00163                           GNC_TYPE_COMMODITY,
00164                           G_PARAM_READWRITE));
00165 
00166     g_object_class_install_property
00167     (gobject_class,
00168      PROP_CURRENCY,
00169      g_param_spec_object ("currency",
00170                           "Currency",
00171                           "The currency field denotes the external kind "
00172                           "'stuff' for the units of this quote, whether "
00173                           "it is USD, gold, stock, etc.",
00174                           GNC_TYPE_COMMODITY,
00175                           G_PARAM_READWRITE));
00176 
00177     g_object_class_install_property
00178     (gobject_class,
00179      PROP_SOURCE,
00180      g_param_spec_string ("source",
00181                           "Price source",
00182                           "The price source is a string describing the "
00183                           "source of a price quote.  It will be something "
00184                           "like this: 'Finance::Quote', 'user:misc', "
00185                           "'user:foo', etc.",
00186                           NULL,
00187                           G_PARAM_READWRITE));
00188 
00189     g_object_class_install_property
00190     (gobject_class,
00191      PROP_TYPE,
00192      g_param_spec_string ("type",
00193                           "Quote type",
00194                           "The quote type is a string describing the "
00195                           "type of a price quote.  Types possible now "
00196                           "are 'bid', 'ask', 'last', 'nav' and 'unknown'.",
00197                           NULL,
00198                           G_PARAM_READWRITE));
00199 
00200     g_object_class_install_property
00201     (gobject_class,
00202      PROP_DATE,
00203      g_param_spec_boxed("date",
00204                         "Date",
00205                         "The date of the price quote.",
00206                         GNC_TYPE_NUMERIC,
00207                         G_PARAM_READWRITE));
00208 
00209     g_object_class_install_property
00210     (gobject_class,
00211      PROP_VALUE,
00212      g_param_spec_boxed("value",
00213                         "Value",
00214                         "The value of the price quote.",
00215                         GNC_TYPE_NUMERIC,
00216                         G_PARAM_READWRITE));
00217 }
00218 
00219 /* ==================================================================== */
00220 /* GNCPrice functions
00221  */
00222 
00223 /* allocation */
00224 GNCPrice *
00225 gnc_price_create (QofBook *book)
00226 {
00227     GNCPrice *p;
00228 
00229     g_return_val_if_fail (book, NULL);
00230 
00231     p = g_object_new(GNC_TYPE_PRICE, NULL);
00232 
00233     qof_instance_init_data (&p->inst, GNC_ID_PRICE, book);
00234     qof_event_gen (&p->inst, QOF_EVENT_CREATE, NULL);
00235 
00236     return p;
00237 }
00238 
00239 static void
00240 gnc_price_destroy (GNCPrice *p)
00241 {
00242     ENTER(" ");
00243     qof_event_gen (&p->inst, QOF_EVENT_DESTROY, NULL);
00244 
00245     if (p->type) CACHE_REMOVE(p->type);
00246     if (p->source) CACHE_REMOVE(p->source);
00247 
00248     /* qof_instance_release (&p->inst); */
00249     g_object_unref(p);
00250     LEAVE (" ");
00251 }
00252 
00253 void
00254 gnc_price_ref(GNCPrice *p)
00255 {
00256     if (!p) return;
00257     p->refcount++;
00258 }
00259 
00260 void
00261 gnc_price_unref(GNCPrice *p)
00262 {
00263     if (!p) return;
00264     if (p->refcount == 0)
00265     {
00266         return;
00267     }
00268 
00269     p->refcount--;
00270 
00271     if (p->refcount <= 0)
00272     {
00273         if (NULL != p->db)
00274         {
00275             PERR("last unref while price in database");
00276         }
00277         gnc_price_destroy (p);
00278     }
00279 }
00280 
00281 /* ==================================================================== */
00282 
00283 GNCPrice *
00284 gnc_price_clone (GNCPrice* p, QofBook *book)
00285 {
00286     /* the clone doesn't belong to a PriceDB */
00287     GNCPrice *new_p;
00288 
00289     g_return_val_if_fail (book, NULL);
00290 
00291     ENTER ("pr=%p", p);
00292 
00293     if (!p)
00294     {
00295         LEAVE (" ");
00296         return NULL;
00297     }
00298 
00299     new_p = gnc_price_create(book);
00300     if (!new_p)
00301     {
00302         LEAVE (" ");
00303         return NULL;
00304     }
00305 
00306     qof_instance_copy_version(new_p, p);
00307 
00308     gnc_price_begin_edit(new_p);
00309     /* never ever clone guid's */
00310     gnc_price_set_commodity(new_p, gnc_price_get_commodity(p));
00311     gnc_price_set_time(new_p, gnc_price_get_time(p));
00312     gnc_price_set_source(new_p, gnc_price_get_source(p));
00313     gnc_price_set_typestr(new_p, gnc_price_get_typestr(p));
00314     gnc_price_set_value(new_p, gnc_price_get_value(p));
00315     gnc_price_set_currency(new_p, gnc_price_get_currency(p));
00316     gnc_price_commit_edit(new_p);
00317     LEAVE (" ");
00318     return(new_p);
00319 }
00320 
00321 /* ==================================================================== */
00322 
00323 void
00324 gnc_price_begin_edit (GNCPrice *p)
00325 {
00326     qof_begin_edit(&p->inst);
00327 }
00328 
00329 static void commit_err (QofInstance *inst, QofBackendError errcode)
00330 {
00331     PERR ("Failed to commit: %d", errcode);
00332     gnc_engine_signal_commit_error( errcode );
00333 }
00334 
00335 static void noop (QofInstance *inst) {}
00336 
00337 void
00338 gnc_price_commit_edit (GNCPrice *p)
00339 {
00340     if (!qof_commit_edit (QOF_INSTANCE(p))) return;
00341     qof_commit_edit_part2 (&p->inst, commit_err, noop, noop);
00342 }
00343 
00344 /* ==================================================================== */
00345 
00346 void
00347 gnc_pricedb_begin_edit (GNCPriceDB *pdb)
00348 {
00349     qof_begin_edit(&pdb->inst);
00350 }
00351 
00352 void
00353 gnc_pricedb_commit_edit (GNCPriceDB *pdb)
00354 {
00355     if (!qof_commit_edit (QOF_INSTANCE(pdb))) return;
00356     qof_commit_edit_part2 (&pdb->inst, commit_err, noop, noop);
00357 }
00358 
00359 /* ==================================================================== */
00360 /* setters */
00361 
00362 static void
00363 gnc_price_set_dirty (GNCPrice *p)
00364 {
00365     qof_instance_set_dirty(&p->inst);
00366     qof_event_gen(&p->inst, QOF_EVENT_MODIFY, NULL);
00367 }
00368 
00369 void
00370 gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c)
00371 {
00372     if (!p) return;
00373 
00374     if (!gnc_commodity_equiv(p->commodity, c))
00375     {
00376         /* Changing the commodity requires the hash table
00377          * position to be modified. The easiest way of doing
00378          * this is to remove and reinsert. */
00379         gnc_price_ref (p);
00380         remove_price (p->db, p, TRUE);
00381         gnc_price_begin_edit (p);
00382         p->commodity = c;
00383         gnc_price_set_dirty(p);
00384         gnc_price_commit_edit (p);
00385         add_price (p->db, p);
00386         gnc_price_unref (p);
00387     }
00388 }
00389 
00390 
00391 void
00392 gnc_price_set_currency(GNCPrice *p, gnc_commodity *c)
00393 {
00394     if (!p) return;
00395 
00396     if (!gnc_commodity_equiv(p->currency, c))
00397     {
00398         /* Changing the currency requires the hash table
00399          * position to be modified. The easiest way of doing
00400          * this is to remove and reinsert. */
00401         gnc_price_ref (p);
00402         remove_price (p->db, p, TRUE);
00403         gnc_price_begin_edit (p);
00404         p->currency = c;
00405         gnc_price_set_dirty(p);
00406         gnc_price_commit_edit (p);
00407         add_price (p->db, p);
00408         gnc_price_unref (p);
00409     }
00410 }
00411 
00412 void
00413 gnc_price_set_time(GNCPrice *p, Timespec t)
00414 {
00415     if (!p) return;
00416     if (!timespec_equal(&(p->tmspec), &t))
00417     {
00418         /* Changing the datestamp requires the hash table
00419          * position to be modified. The easiest way of doing
00420          * this is to remove and reinsert. */
00421         gnc_price_ref (p);
00422         remove_price (p->db, p, FALSE);
00423         gnc_price_begin_edit (p);
00424         p->tmspec = t;
00425         gnc_price_set_dirty(p);
00426         gnc_price_commit_edit (p);
00427         add_price (p->db, p);
00428         gnc_price_unref (p);
00429     }
00430 }
00431 
00432 void
00433 gnc_price_set_source(GNCPrice *p, const char *s)
00434 {
00435     if (!p) return;
00436     if (safe_strcmp(p->source, s) != 0)
00437     {
00438         char *tmp;
00439 
00440         gnc_price_begin_edit (p);
00441         tmp = CACHE_INSERT((gpointer) s);
00442         if (p->source) CACHE_REMOVE(p->source);
00443         p->source = tmp;
00444         gnc_price_set_dirty(p);
00445         gnc_price_commit_edit (p);
00446     }
00447 }
00448 
00449 void
00450 gnc_price_set_typestr(GNCPrice *p, const char* type)
00451 {
00452     if (!p) return;
00453     if (safe_strcmp(p->type, type) != 0)
00454     {
00455         gchar *tmp;
00456 
00457         gnc_price_begin_edit (p);
00458         tmp = CACHE_INSERT((gpointer) type);
00459         if (p->type) CACHE_REMOVE(p->type);
00460         p->type = tmp;
00461         gnc_price_set_dirty(p);
00462         gnc_price_commit_edit (p);
00463     }
00464 }
00465 
00466 void
00467 gnc_price_set_value(GNCPrice *p, gnc_numeric value)
00468 {
00469     if (!p) return;
00470     if (!gnc_numeric_eq(p->value, value))
00471     {
00472         gnc_price_begin_edit (p);
00473         p->value = value;
00474         gnc_price_set_dirty(p);
00475         gnc_price_commit_edit (p);
00476     }
00477 }
00478 
00479 /* ==================================================================== */
00480 /* getters */
00481 
00482 GNCPrice *
00483 gnc_price_lookup (const GncGUID *guid, QofBook *book)
00484 {
00485     QofCollection *col;
00486 
00487     if (!guid || !book) return NULL;
00488     col = qof_book_get_collection (book, GNC_ID_PRICE);
00489     return (GNCPrice *) qof_collection_lookup_entity (col, guid);
00490 }
00491 
00492 gnc_commodity *
00493 gnc_price_get_commodity(const GNCPrice *p)
00494 {
00495     if (!p) return NULL;
00496     return p->commodity;
00497 }
00498 
00499 Timespec
00500 gnc_price_get_time(const GNCPrice *p)
00501 {
00502     if (!p)
00503     {
00504         Timespec result;
00505         result.tv_sec = 0;
00506         result.tv_nsec = 0;
00507         return result;
00508     }
00509     return p->tmspec;
00510 }
00511 
00512 const char *
00513 gnc_price_get_source(const GNCPrice *p)
00514 {
00515     if (!p) return NULL;
00516     return p->source;
00517 }
00518 
00519 const char *
00520 gnc_price_get_typestr(const GNCPrice *p)
00521 {
00522     if (!p) return NULL;
00523     return p->type;
00524 }
00525 
00526 gnc_numeric
00527 gnc_price_get_value(const GNCPrice *p)
00528 {
00529     if (!p)
00530     {
00531         PERR("price NULL.\n");
00532         return gnc_numeric_zero();
00533     }
00534     return p->value;
00535 }
00536 
00537 gnc_commodity *
00538 gnc_price_get_currency(const GNCPrice *p)
00539 {
00540     if (!p) return NULL;
00541     return p->currency;
00542 }
00543 
00544 gboolean
00545 gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
00546 {
00547     Timespec ts1;
00548     Timespec ts2;
00549 
00550     if (p1 == p2) return TRUE;
00551     if (!p1 || !p2) return FALSE;
00552 
00553     if (!gnc_commodity_equiv (gnc_price_get_commodity (p1),
00554                               gnc_price_get_commodity (p2)))
00555         return FALSE;
00556 
00557     if (!gnc_commodity_equiv (gnc_price_get_currency (p1),
00558                               gnc_price_get_currency (p2)))
00559         return FALSE;
00560 
00561     ts1 = gnc_price_get_time (p1);
00562     ts2 = gnc_price_get_time (p2);
00563 
00564     if (!timespec_equal (&ts1, &ts2))
00565         return FALSE;
00566 
00567     if (safe_strcmp (gnc_price_get_source (p1),
00568                      gnc_price_get_source (p2)) != 0)
00569         return FALSE;
00570 
00571     if (safe_strcmp (gnc_price_get_typestr (p1),
00572                      gnc_price_get_typestr (p2)) != 0)
00573         return FALSE;
00574 
00575     if (!gnc_numeric_eq (gnc_price_get_value (p1),
00576                          gnc_price_get_value (p2)))
00577         return FALSE;
00578 
00579     return TRUE;
00580 }
00581 
00582 /* ==================================================================== */
00583 /* price list manipulation functions */
00584 
00585 static gint
00586 compare_prices_by_date(gconstpointer a, gconstpointer b)
00587 {
00588     Timespec time_a;
00589     Timespec time_b;
00590     gint result;
00591 
00592     if (!a && !b) return 0;
00593     /* nothing is always less than something */
00594     if (!a) return -1;
00595 
00596     time_a = gnc_price_get_time((GNCPrice *) a);
00597     time_b = gnc_price_get_time((GNCPrice *) b);
00598 
00599     result = -timespec_cmp(&time_a, &time_b);
00600     if (result) return result;
00601 
00602     /* For a stable sort */
00603     return guid_compare (gnc_price_get_guid((GNCPrice *) a),
00604                          gnc_price_get_guid((GNCPrice *) b));
00605 }
00606 
00607 typedef struct
00608 {
00609     GNCPrice* pPrice;
00610     gboolean isDupl;
00611 } PriceListIsDuplStruct;
00612 
00613 static void
00614 price_list_is_duplicate( gpointer data, gpointer user_data )
00615 {
00616     GNCPrice* pPrice = (GNCPrice*)data;
00617     PriceListIsDuplStruct* pStruct = (PriceListIsDuplStruct*)user_data;
00618     Timespec time_a, time_b;
00619 
00620     time_a = timespecCanonicalDayTime( gnc_price_get_time( pPrice ) );
00621     time_b = timespecCanonicalDayTime( gnc_price_get_time( pStruct->pPrice ) );
00622 
00623     /* If the date, currency, commodity and price match, it's a duplicate */
00624     if ( !gnc_numeric_equal( gnc_price_get_value( pPrice ),  gnc_price_get_value( pStruct->pPrice ) ) ) return;
00625     if ( gnc_price_get_commodity( pPrice ) != gnc_price_get_commodity( pStruct->pPrice ) ) return;
00626     if ( gnc_price_get_currency( pPrice ) != gnc_price_get_currency( pStruct->pPrice ) ) return;
00627 
00628     if ( timespec_cmp( &time_a, &time_b ) != 0 ) return;
00629 
00630     pStruct->isDupl = TRUE;
00631 }
00632 
00633 gboolean
00634 gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
00635 {
00636     GList *result_list;
00637     PriceListIsDuplStruct* pStruct;
00638     gboolean isDupl;
00639 
00640     if (!prices || !p) return FALSE;
00641     gnc_price_ref(p);
00642 
00643     if (check_dupl)
00644     {
00645         pStruct = g_new0( PriceListIsDuplStruct, 1 );
00646         pStruct->pPrice = p;
00647         pStruct->isDupl = FALSE;
00648         g_list_foreach( *prices, price_list_is_duplicate, pStruct );
00649         isDupl = pStruct->isDupl;
00650         g_free( pStruct );
00651 
00652         if ( isDupl )
00653         {
00654             return TRUE;
00655         }
00656     }
00657 
00658     result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date);
00659     if (!result_list) return FALSE;
00660     *prices = result_list;
00661     return TRUE;
00662 }
00663 
00664 gboolean
00665 gnc_price_list_remove(PriceList **prices, GNCPrice *p)
00666 {
00667     GList *result_list;
00668     GList *found_element;
00669 
00670     if (!prices || !p) return FALSE;
00671 
00672     found_element = g_list_find(*prices, p);
00673     if (!found_element) return TRUE;
00674 
00675     result_list = g_list_remove_link(*prices, found_element);
00676     gnc_price_unref((GNCPrice *) found_element->data);
00677     g_list_free(found_element);
00678 
00679     *prices = result_list;
00680     return TRUE;
00681 }
00682 
00683 static void
00684 price_list_destroy_helper(gpointer data, gpointer user_data)
00685 {
00686     gnc_price_unref((GNCPrice *) data);
00687 }
00688 
00689 void
00690 gnc_price_list_destroy(PriceList *prices)
00691 {
00692     g_list_foreach(prices, price_list_destroy_helper, NULL);
00693     g_list_free(prices);
00694 }
00695 
00696 gboolean
00697 gnc_price_list_equal(PriceList *prices1, PriceList *prices2)
00698 {
00699     GList *n1, *n2;
00700 
00701     if (prices1 == prices2) return TRUE;
00702 
00703     if (g_list_length (prices1) < g_list_length (prices2))
00704     {
00705         PWARN ("prices2 has extra prices");
00706         return FALSE;
00707     }
00708 
00709     if (g_list_length (prices1) > g_list_length (prices2))
00710     {
00711         PWARN ("prices1 has extra prices");
00712         return FALSE;
00713     }
00714 
00715     for (n1 = prices1, n2 = prices2; n1 ; n1 = n1->next, n2 = n2->next)
00716         if (!gnc_price_equal (n1->data, n2->data))
00717             return FALSE;
00718 
00719     return TRUE;
00720 }
00721 
00722 /* ==================================================================== */
00723 /* GNCPriceDB functions
00724 
00725    Structurally a GNCPriceDB contains a hash mapping price commodities
00726    (of type gnc_commodity*) to hashes mapping price currencies (of
00727    type gnc_commodity*) to GNCPrice lists (see gnc-pricedb.h for a
00728    description of GNCPrice lists).  The top-level key is the commodity
00729    you want the prices for, and the second level key is the commodity
00730    that the value is expressed in terms of.
00731  */
00732 
00733 /* GObject Initialization */
00734 QOF_GOBJECT_IMPL(gnc_pricedb, GNCPriceDB, QOF_TYPE_INSTANCE);
00735 
00736 static void
00737 gnc_pricedb_init(GNCPriceDB* pdb)
00738 {
00739 }
00740 
00741 static void
00742 gnc_pricedb_dispose_real (GObject *pdbp)
00743 {
00744 }
00745 
00746 static void
00747 gnc_pricedb_finalize_real(GObject* pdbp)
00748 {
00749 }
00750 
00751 static GNCPriceDB *
00752 gnc_pricedb_create(QofBook * book)
00753 {
00754     GNCPriceDB * result;
00755     QofCollection *col;
00756 
00757     g_return_val_if_fail (book, NULL);
00758 
00759     /* There can only be one pricedb per book.  So if one exits already,
00760      * then use that.  Warn user, they shouldn't be creating two ...
00761      */
00762     col = qof_book_get_collection (book, GNC_ID_PRICEDB);
00763     result = qof_collection_get_data (col);
00764     if (result)
00765     {
00766         PWARN ("A price database already exists for this book!");
00767         return result;
00768     }
00769 
00770     result = g_object_new(GNC_TYPE_PRICEDB, NULL);
00771     qof_instance_init_data (&result->inst, GNC_ID_PRICEDB, book);
00772     qof_collection_mark_clean(col);
00773 
00777     qof_collection_set_data (col, result);
00778 
00779     result->commodity_hash = g_hash_table_new(NULL, NULL);
00780     g_return_val_if_fail (result->commodity_hash, NULL);
00781     return result;
00782 }
00783 
00784 static void
00785 destroy_pricedb_currency_hash_data(gpointer key,
00786                                    gpointer data,
00787                                    gpointer user_data)
00788 {
00789     GList *price_list = (GList *) data;
00790     GList *node;
00791     GNCPrice *p;
00792 
00793     for (node = price_list; node; node = node->next)
00794     {
00795         p = node->data;
00796 
00797         p->db = NULL;
00798     }
00799 
00800     gnc_price_list_destroy(price_list);
00801 }
00802 
00803 static void
00804 destroy_pricedb_commodity_hash_data(gpointer key,
00805                                     gpointer data,
00806                                     gpointer user_data)
00807 {
00808     GHashTable *currency_hash = (GHashTable *) data;
00809     if (!currency_hash) return;
00810     g_hash_table_foreach (currency_hash,
00811                           destroy_pricedb_currency_hash_data,
00812                           NULL);
00813     g_hash_table_destroy(currency_hash);
00814 }
00815 
00816 void
00817 gnc_pricedb_destroy(GNCPriceDB *db)
00818 {
00819     if (!db) return;
00820     if (db->commodity_hash)
00821     {
00822         g_hash_table_foreach (db->commodity_hash,
00823                               destroy_pricedb_commodity_hash_data,
00824                               NULL);
00825     }
00826     g_hash_table_destroy (db->commodity_hash);
00827     db->commodity_hash = NULL;
00828     /* qof_instance_release (&db->inst); */
00829     g_object_unref(db);
00830 }
00831 
00832 void
00833 gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
00834 {
00835     db->bulk_update = bulk_update;
00836 }
00837 
00838 /* ==================================================================== */
00839 /* This is kind of weird, the way its done.  Each collection of prices
00840  * for a given commodity should get its own guid, be its own entity, etc.
00841  * We really shouldn't be using the collection data.  But, hey I guess its OK,
00842  * yeah? Umm, possibly not. (NW). See TODO below.
00843 */
00853 GNCPriceDB *
00854 gnc_collection_get_pricedb(QofCollection *col)
00855 {
00856     if (!col) return NULL;
00857     return qof_collection_get_data (col);
00858 }
00859 
00860 GNCPriceDB *
00861 gnc_pricedb_get_db(QofBook *book)
00862 {
00863     QofCollection *col;
00864 
00865     if (!book) return NULL;
00866     col = qof_book_get_collection (book, GNC_ID_PRICEDB);
00867     return gnc_collection_get_pricedb (col);
00868 }
00869 
00870 /* ==================================================================== */
00871 
00872 static gboolean
00873 num_prices_helper (GNCPrice *p, gpointer user_data)
00874 {
00875     guint *count = user_data;
00876 
00877     *count += 1;
00878 
00879     return TRUE;
00880 }
00881 
00882 guint
00883 gnc_pricedb_get_num_prices(GNCPriceDB *db)
00884 {
00885     guint count;
00886 
00887     if (!db) return 0;
00888 
00889     count = 0;
00890 
00891     gnc_pricedb_foreach_price(db, num_prices_helper, &count, FALSE);
00892 
00893     return count;
00894 }
00895 
00896 /* ==================================================================== */
00897 
00898 typedef struct
00899 {
00900     gboolean equal;
00901     GNCPriceDB *db2;
00902     gnc_commodity *commodity;
00903 } GNCPriceDBEqualData;
00904 
00905 static void
00906 pricedb_equal_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
00907 {
00908     GNCPriceDBEqualData *equal_data = user_data;
00909     gnc_commodity *currency = key;
00910     GList *price_list1 = val;
00911     GList *price_list2;
00912 
00913     price_list2 = gnc_pricedb_get_prices (equal_data->db2,
00914                                           equal_data->commodity,
00915                                           currency);
00916 
00917     if (!gnc_price_list_equal (price_list1, price_list2))
00918         equal_data->equal = FALSE;
00919 
00920     gnc_price_list_destroy (price_list2);
00921 }
00922 
00923 static void
00924 pricedb_equal_foreach_currencies_hash (gpointer key, gpointer val,
00925                                        gpointer user_data)
00926 {
00927     GHashTable *currencies_hash = val;
00928     GNCPriceDBEqualData *equal_data = user_data;
00929 
00930     equal_data->commodity = key;
00931 
00932     g_hash_table_foreach (currencies_hash,
00933                           pricedb_equal_foreach_pricelist,
00934                           equal_data);
00935 }
00936 
00937 gboolean
00938 gnc_pricedb_equal (GNCPriceDB *db1, GNCPriceDB *db2)
00939 {
00940     GNCPriceDBEqualData equal_data;
00941 
00942     if (db1 == db2) return TRUE;
00943 
00944     if (!db1 || !db2)
00945     {
00946         PWARN ("one is NULL");
00947         return FALSE;
00948     }
00949 
00950     equal_data.equal = TRUE;
00951     equal_data.db2 = db2;
00952 
00953     g_hash_table_foreach (db1->commodity_hash,
00954                           pricedb_equal_foreach_currencies_hash,
00955                           &equal_data);
00956 
00957     return equal_data.equal;
00958 }
00959 
00960 /* ==================================================================== */
00961 /* The add_price() function is a utility that only manages the
00962  * dual hash table instertion */
00963 
00964 static gboolean
00965 add_price(GNCPriceDB *db, GNCPrice *p)
00966 {
00967     /* This function will use p, adding a ref, so treat p as read-only
00968        if this function succeeds. */
00969     GList *price_list;
00970     gnc_commodity *commodity;
00971     gnc_commodity *currency;
00972     GHashTable *currency_hash;
00973 
00974     if (!db || !p) return FALSE;
00975     ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
00976            db, p, qof_instance_get_dirty_flag(p),
00977            qof_instance_get_destroying(p));
00978 
00979     if (!qof_instance_books_equal(db, p))
00980     {
00981         PERR ("attempted to mix up prices across different books");
00982         LEAVE (" ");
00983         return FALSE;
00984     }
00985 
00986     commodity = gnc_price_get_commodity(p);
00987     if (!commodity)
00988     {
00989         PWARN("no commodity");
00990         LEAVE (" ");
00991         return FALSE;
00992     }
00993     currency = gnc_price_get_currency(p);
00994     if (!currency)
00995     {
00996         PWARN("no currency");
00997         LEAVE (" ");
00998         return FALSE;
00999     }
01000     if (!db->commodity_hash)
01001     {
01002         LEAVE ("no commodity hash found ");
01003         return FALSE;
01004     }
01005 
01006     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01007     if (!currency_hash)
01008     {
01009         currency_hash = g_hash_table_new(NULL, NULL);
01010         g_hash_table_insert(db->commodity_hash, commodity, currency_hash);
01011     }
01012 
01013     price_list = g_hash_table_lookup(currency_hash, currency);
01014     if (!gnc_price_list_insert(&price_list, p, !db->bulk_update))
01015     {
01016         LEAVE ("gnc_price_list_insert failed");
01017         return FALSE;
01018     }
01019     if (!price_list)
01020     {
01021         LEAVE (" no price list");
01022         return FALSE;
01023     }
01024     g_hash_table_insert(currency_hash, currency, price_list);
01025     p->db = db;
01026     qof_event_gen (&p->inst, QOF_EVENT_ADD, NULL);
01027 
01028     LEAVE ("db=%p, pr=%p dirty=%d dextroying=%d commodity=%s/%s currency_hash=%p",
01029            db, p, qof_instance_get_dirty_flag(p),
01030            qof_instance_get_destroying(p),
01031            gnc_commodity_get_namespace(p->commodity),
01032            gnc_commodity_get_mnemonic(p->commodity),
01033            currency_hash);
01034     return TRUE;
01035 }
01036 
01037 /* the gnc_pricedb_add_price() function will use p, adding a ref, so
01038    treat p as read-only if this function succeeds. (Huh ???) */
01039 gboolean
01040 gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
01041 {
01042     if (!db || !p) return FALSE;
01043 
01044     ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
01045            db, p, qof_instance_get_dirty_flag(p),
01046            qof_instance_get_destroying(p));
01047 
01048     if (FALSE == add_price(db, p))
01049     {
01050         LEAVE (" failed to add price");
01051         return FALSE;
01052     }
01053 
01054     gnc_pricedb_begin_edit(db);
01055     qof_instance_set_dirty(&db->inst);
01056     gnc_pricedb_commit_edit(db);
01057 
01058     LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
01059            db, p, qof_instance_get_dirty_flag(p),
01060            qof_instance_get_destroying(p));
01061 
01062     return TRUE;
01063 }
01064 
01065 /* remove_price() is a utility; its only function is to remove the price
01066  * from the double-hash tables.
01067  */
01068 
01069 static gboolean
01070 remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup)
01071 {
01072     GList *price_list;
01073     gnc_commodity *commodity;
01074     gnc_commodity *currency;
01075     GHashTable *currency_hash;
01076 
01077     if (!db || !p) return FALSE;
01078     ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
01079            db, p, qof_instance_get_dirty_flag(p),
01080            qof_instance_get_destroying(p));
01081 
01082     commodity = gnc_price_get_commodity(p);
01083     if (!commodity)
01084     {
01085         LEAVE (" no commodity");
01086         return FALSE;
01087     }
01088     currency = gnc_price_get_currency(p);
01089     if (!currency)
01090     {
01091         LEAVE (" no currency");
01092         return FALSE;
01093     }
01094     if (!db->commodity_hash)
01095     {
01096         LEAVE (" no commodity hash");
01097         return FALSE;
01098     }
01099 
01100     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01101     if (!currency_hash)
01102     {
01103         LEAVE (" no currency hash");
01104         return FALSE;
01105     }
01106 
01107     qof_event_gen (&p->inst, QOF_EVENT_REMOVE, NULL);
01108     price_list = g_hash_table_lookup(currency_hash, currency);
01109     gnc_price_ref(p);
01110     if (!gnc_price_list_remove(&price_list, p))
01111     {
01112         gnc_price_unref(p);
01113         LEAVE (" cannot remove price list");
01114         return FALSE;
01115     }
01116 
01117     /* if the price list is empty, then remove this currency from the
01118        commodity hash */
01119     if (price_list)
01120     {
01121         g_hash_table_insert(currency_hash, currency, price_list);
01122     }
01123     else
01124     {
01125         g_hash_table_remove(currency_hash, currency);
01126 
01127         if (cleanup)
01128         {
01129             /* chances are good that this commodity had only one currency.
01130              * If there are no currencies, we may as well destroy the
01131              * commodity too. */
01132             guint num_currencies = g_hash_table_size (currency_hash);
01133             if (0 == num_currencies)
01134             {
01135                 g_hash_table_remove (db->commodity_hash, commodity);
01136                 g_hash_table_destroy (currency_hash);
01137             }
01138         }
01139     }
01140 
01141     gnc_price_unref(p);
01142     LEAVE ("db=%p, pr=%p", db, p);
01143     return TRUE;
01144 }
01145 
01146 gboolean
01147 gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
01148 {
01149     gboolean rc;
01150     if (!db || !p) return FALSE;
01151     ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
01152            db, p, qof_instance_get_dirty_flag(p),
01153            qof_instance_get_destroying(p));
01154 
01155     gnc_price_ref(p);
01156     rc = remove_price (db, p, TRUE);
01157     gnc_pricedb_begin_edit(db);
01158     qof_instance_set_dirty(&db->inst);
01159     gnc_pricedb_commit_edit(db);
01160 
01161     /* invoke the backend to delete this price */
01162     gnc_price_begin_edit (p);
01163     qof_instance_set_destroying(p, TRUE);
01164     gnc_price_commit_edit (p);
01165     p->db = NULL;
01166     gnc_price_unref(p);
01167     LEAVE ("db=%p, pr=%p", db, p);
01168     return rc;
01169 }
01170 
01171 typedef struct
01172 {
01173     GNCPriceDB *db;
01174     Timespec cutoff;
01175     gboolean delete_user;
01176     gboolean delete_last;
01177     GSList *list;
01178 } remove_info;
01179 
01180 static gboolean
01181 check_one_price_date (GNCPrice *price, gpointer user_data)
01182 {
01183     remove_info *data = user_data;
01184     const gchar *source;
01185     Timespec pt;
01186 
01187     ENTER("price %p (%s), data %p", price,
01188           gnc_commodity_get_mnemonic(gnc_price_get_commodity(price)),
01189           user_data);
01190     if (!data->delete_user)
01191     {
01192         source = gnc_price_get_source (price);
01193         if (safe_strcmp(source, "Finance::Quote") != 0)
01194         {
01195             LEAVE("Not an automatic quote");
01196             return TRUE;
01197         }
01198     }
01199 
01200     pt = gnc_price_get_time (price);
01201     {
01202         gchar buf[40];
01203         gnc_timespec_to_iso8601_buff(pt , buf);
01204         DEBUG("checking date %s", buf);
01205     }
01206     if (timespec_cmp (&pt, &data->cutoff) < 0)
01207     {
01208         data->list = g_slist_prepend(data->list, price);
01209         DEBUG("will delete");
01210     }
01211     LEAVE(" ");
01212     return TRUE;
01213 }
01214 
01215 static void
01216 pricedb_remove_foreach_pricelist (gpointer key,
01217                                   gpointer val,
01218                                   gpointer user_data)
01219 {
01220     GList *price_list = (GList *) val;
01221     GList *node = price_list;
01222     remove_info *data = (remove_info *) user_data;
01223 
01224     ENTER("key %p, value %p, data %p", key, val, user_data);
01225 
01226     /* The most recent price is the first in the list */
01227     if (!data->delete_last)
01228         node = g_list_next(node);
01229 
01230     /* now check each item in the list */
01231     g_list_foreach(node, (GFunc)check_one_price_date, data);
01232 
01233     LEAVE(" ");
01234 }
01235 
01236 static void
01237 pricedb_remove_foreach_currencies_hash (gpointer key,
01238                                         gpointer val,
01239                                         gpointer user_data)
01240 {
01241     GHashTable *currencies_hash = (GHashTable *) val;
01242 
01243     ENTER("key %p, value %p, data %p", key, val, user_data);
01244     g_hash_table_foreach(currencies_hash,
01245                          pricedb_remove_foreach_pricelist, user_data);
01246     LEAVE(" ");
01247 }
01248 
01249 
01250 gboolean
01251 gnc_pricedb_remove_old_prices(GNCPriceDB *db,
01252                               Timespec cutoff,
01253                               gboolean delete_user,
01254                               gboolean delete_last)
01255 {
01256     remove_info data;
01257     GSList *item;
01258 
01259     data.db = db;
01260     data.cutoff = cutoff;
01261     data.delete_user = delete_user;
01262     data.delete_last = delete_last;
01263     data.list = NULL;
01264 
01265     ENTER("db %p, delet_user %d, delete_last %d", db, delete_user, delete_last);
01266     {
01267         gchar buf[40];
01268         gnc_timespec_to_iso8601_buff(cutoff, buf);
01269         DEBUG("checking date %s", buf);
01270     }
01271 
01272     /* Traverse the database once building up an external list of prices
01273      * to be deleted */
01274     g_hash_table_foreach(db->commodity_hash,
01275                          pricedb_remove_foreach_currencies_hash,
01276                          &data);
01277 
01278     if (data.list == NULL)
01279         return FALSE;
01280 
01281     /* Now run this external list deleting prices */
01282     for (item = data.list; item; item = g_slist_next(item))
01283     {
01284         gnc_pricedb_remove_price(db, item->data);
01285     }
01286 
01287     g_slist_free(data.list);
01288     LEAVE(" ");
01289     return TRUE;
01290 }
01291 
01292 /* ==================================================================== */
01293 /* lookup/query functions */
01294 
01295 GNCPrice *
01296 gnc_pricedb_lookup_latest(GNCPriceDB *db,
01297                           const gnc_commodity *commodity,
01298                           const gnc_commodity *currency)
01299 {
01300     GList *price_list;
01301     GNCPrice *result;
01302     GHashTable *currency_hash;
01303     QofBook *book;
01304     QofBackend *be;
01305 
01306     if (!db || !commodity || !currency) return NULL;
01307     ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
01308     book = qof_instance_get_book(&db->inst);
01309     be = qof_book_get_backend(book);
01310 #ifdef GNUCASH_MAJOR_VERSION
01311     if (be && be->price_lookup)
01312     {
01313         GNCPriceLookup pl;
01314         pl.type = LOOKUP_LATEST;
01315         pl.prdb = db;
01316         pl.commodity = commodity;
01317         pl.currency = currency;
01318         (be->price_lookup) (be, &pl);
01319     }
01320 #endif
01321 
01322     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01323     if (!currency_hash)
01324     {
01325         LEAVE (" no currency hash");
01326         return NULL;
01327     }
01328 
01329     price_list = g_hash_table_lookup(currency_hash, currency);
01330     if (!price_list)
01331     {
01332         LEAVE (" no price list");
01333         return NULL;
01334     }
01335 
01336     /* This works magically because prices are inserted in date-sorted
01337      * order, and the latest date always comes first. So return the
01338      * first in the list.  */
01339     result = price_list->data;
01340     gnc_price_ref(result);
01341     LEAVE(" ");
01342     return result;
01343 }
01344 
01345 
01346 static void
01347 lookup_latest(gpointer key, gpointer val, gpointer user_data)
01348 {
01349     //gnc_commodity *currency = (gnc_commodity *)key;
01350     GList *price_list = (GList *)val;
01351     GList **return_list = (GList **)user_data;
01352 
01353     if (!price_list) return;
01354 
01355     /* the latest price is the first in list */
01356     gnc_price_list_insert(return_list, price_list->data, FALSE);
01357 }
01358 
01359 PriceList *
01360 gnc_pricedb_lookup_latest_any_currency(GNCPriceDB *db,
01361                                        const gnc_commodity *commodity)
01362 {
01363     GList *result;
01364     GHashTable *currency_hash;
01365     QofBook *book;
01366     QofBackend *be;
01367 
01368     result = NULL;
01369 
01370     if (!db || !commodity) return NULL;
01371     ENTER ("db=%p commodity=%p", db, commodity);
01372     book = qof_instance_get_book(&db->inst);
01373     be = qof_book_get_backend(book);
01374 #ifdef GNUCASH_MAJOR_VERSION
01375     if (be && be->price_lookup)
01376     {
01377         GNCPriceLookup pl;
01378         pl.type = LOOKUP_LATEST;
01379         pl.prdb = db;
01380         pl.commodity = commodity;
01381         pl.currency = NULL;  /* can the backend handle this??? */
01382         (be->price_lookup) (be, &pl);
01383     }
01384 #endif
01385     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01386     if (!currency_hash)
01387     {
01388         LEAVE (" no currency hash");
01389         return NULL;
01390     }
01391 
01392     g_hash_table_foreach(currency_hash, lookup_latest, &result);
01393 
01394     if (!result)
01395     {
01396         LEAVE (" ");
01397         return NULL;
01398     }
01399 
01400     result = g_list_sort(result, compare_prices_by_date);
01401 
01402     LEAVE(" ");
01403     return result;
01404 }
01405 
01406 
01407 static void
01408 hash_values_helper(gpointer key, gpointer value, gpointer data)
01409 {
01410     GList ** l = data;
01411     *l = g_list_concat(*l, g_list_copy (value));
01412 }
01413 
01414 gboolean
01415 gnc_pricedb_has_prices(GNCPriceDB *db,
01416                        const gnc_commodity *commodity,
01417                        const gnc_commodity *currency)
01418 {
01419     GList *price_list;
01420     GHashTable *currency_hash;
01421     gint size;
01422     QofBook *book;
01423     QofBackend *be;
01424 
01425     if (!db || !commodity) return FALSE;
01426     ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
01427     book = qof_instance_get_book(&db->inst);
01428     be = qof_book_get_backend(book);
01429 #ifdef GNUCASH_MAJOR_VERSION
01430     if (book && be && be->price_lookup)
01431     {
01432         GNCPriceLookup pl;
01433         pl.type = LOOKUP_ALL;
01434         pl.prdb = db;
01435         pl.commodity = commodity;
01436         pl.currency = currency;
01437         (be->price_lookup) (be, &pl);
01438     }
01439 #endif
01440     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01441     if (!currency_hash)
01442     {
01443         LEAVE("no, no currency_hash table");
01444         return FALSE;
01445     }
01446 
01447     if (currency)
01448     {
01449         price_list = g_hash_table_lookup(currency_hash, currency);
01450         if (price_list)
01451         {
01452             LEAVE("yes");
01453             return TRUE;
01454         }
01455         LEAVE("no, no price list");
01456         return FALSE;
01457     }
01458 
01459     size = g_hash_table_size (currency_hash);
01460     LEAVE("%s", size > 0 ? "yes" : "no");
01461     return size > 0;
01462 }
01463 
01464 
01465 PriceList *
01466 gnc_pricedb_get_prices(GNCPriceDB *db,
01467                        const gnc_commodity *commodity,
01468                        const gnc_commodity *currency)
01469 {
01470     GList *price_list;
01471     GList *result;
01472     GList *node;
01473     GHashTable *currency_hash;
01474     QofBook *book;
01475     QofBackend *be;
01476 
01477     if (!db || !commodity) return NULL;
01478     ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
01479     book = qof_instance_get_book(&db->inst);
01480     be = qof_book_get_backend(book);
01481 #ifdef GNUCASH_MAJOR_VERSION
01482     if (be && be->price_lookup)
01483     {
01484         GNCPriceLookup pl;
01485         pl.type = LOOKUP_ALL;
01486         pl.prdb = db;
01487         pl.commodity = commodity;
01488         pl.currency = currency;
01489         (be->price_lookup) (be, &pl);
01490     }
01491 #endif
01492     currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01493     if (!currency_hash)
01494     {
01495         LEAVE (" no currency hash");
01496         return NULL;
01497     }
01498 
01499     if (currency)
01500     {
01501         price_list = g_hash_table_lookup(currency_hash, currency);
01502         if (!price_list)
01503         {
01504             LEAVE (" no price list");
01505             return NULL;
01506         }
01507         result = g_list_copy (price_list);
01508     }
01509     else
01510     {
01511         result = NULL;
01512         g_hash_table_foreach(currency_hash, hash_values_helper, (gpointer)&result);
01513     }
01514     for (node = result; node; node = node->next)
01515         gnc_price_ref (node->data);
01516 
01517     LEAVE (" ");
01518     return result;
01519 }
01520 
01521 
01522 PriceList *
01523 gnc_pricedb_lookup_day(GNCPriceDB *db,
01524                        const gnc_commodity *c,
01525                        const gnc_commodity *currency,
01526                        Timespec t)
01527 {
01528     GList *price_list;
01529     GList *result = NULL;
01530     GList *item = NULL;
01531     GHashTable *currency_hash;
01532     QofBook *book;
01533     QofBackend *be;
01534 
01535     if (!db || !c || !currency) return NULL;
01536     ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
01537     book = qof_instance_get_book(&db->inst);
01538     be = qof_book_get_backend(book);
01539     /* Convert to noon local time. */
01540     t = timespecCanonicalDayTime(t);
01541 #ifdef GNUCASH_MAJOR_VERSION
01542     if (be && be->price_lookup)
01543     {
01544         GNCPriceLookup pl;
01545         pl.type = LOOKUP_AT_TIME;
01546         pl.prdb = db;
01547         pl.commodity = c;
01548         pl.currency = currency;
01549         pl.date = t;
01550         (be->price_lookup) (be, &pl);
01551     }
01552 #endif
01553     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
01554     if (!currency_hash)
01555     {
01556         LEAVE (" no currency hash");
01557         return NULL;
01558     }
01559 
01560     price_list = g_hash_table_lookup(currency_hash, currency);
01561     if (!price_list)
01562     {
01563         LEAVE (" no price list");
01564         return NULL;
01565     }
01566 
01567     item = price_list;
01568     while (item)
01569     {
01570         GNCPrice *p = item->data;
01571         Timespec price_time = timespecCanonicalDayTime(gnc_price_get_time(p));
01572         if (timespec_equal(&price_time, &t))
01573         {
01574             result = g_list_prepend(result, p);
01575             gnc_price_ref(p);
01576         }
01577         item = item->next;
01578     }
01579     LEAVE (" ");
01580     return result;
01581 }
01582 
01583 
01584 static void
01585 lookup_day(gpointer key, gpointer val, gpointer user_data)
01586 {
01587     //gnc_commodity *currency = (gnc_commodity *)key;
01588     GList *price_list = (GList *)val;
01589     GList *item = NULL;
01590     GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
01591     GList **return_list = lookup_helper->return_list;
01592     Timespec t = lookup_helper->time;
01593 
01594     item = price_list;
01595     while (item)
01596     {
01597         GNCPrice *p = item->data;
01598         Timespec price_time = timespecCanonicalDayTime(gnc_price_get_time(p));
01599         if (timespec_equal(&price_time, &t))
01600         {
01601             gnc_price_list_insert(return_list, item->data, FALSE);
01602         }
01603         item = item->next;
01604     }
01605 }
01606 
01607 
01608 PriceList *
01609 gnc_pricedb_lookup_at_time(GNCPriceDB *db,
01610                            const gnc_commodity *c,
01611                            const gnc_commodity *currency,
01612                            Timespec t)
01613 {
01614     GList *price_list;
01615     GList *result = NULL;
01616     GList *item = NULL;
01617     GHashTable *currency_hash;
01618     QofBook *book;
01619     QofBackend *be;
01620 
01621     if (!db || !c || !currency) return NULL;
01622     ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
01623     book = qof_instance_get_book(&db->inst);
01624     be = qof_book_get_backend(book);
01625 #ifdef GNUCASH_MAJOR_VERSION
01626     if (be && be->price_lookup)
01627     {
01628         GNCPriceLookup pl;
01629         pl.type = LOOKUP_AT_TIME;
01630         pl.prdb = db;
01631         pl.commodity = c;
01632         pl.currency = currency;
01633         pl.date = t;
01634         (be->price_lookup) (be, &pl);
01635     }
01636 #endif
01637     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
01638     if (!currency_hash)
01639     {
01640         LEAVE (" no currency hash");
01641         return NULL;
01642     }
01643 
01644     price_list = g_hash_table_lookup(currency_hash, currency);
01645     if (!price_list)
01646     {
01647         LEAVE (" no price list");
01648         return NULL;
01649     }
01650 
01651     item = price_list;
01652     while (item)
01653     {
01654         GNCPrice *p = item->data;
01655         Timespec price_time = gnc_price_get_time(p);
01656         if (timespec_equal(&price_time, &t))
01657         {
01658             result = g_list_prepend(result, p);
01659             gnc_price_ref(p);
01660         }
01661         item = item->next;
01662     }
01663     LEAVE (" ");
01664     return result;
01665 }
01666 
01667 static void
01668 lookup_time(gpointer key, gpointer val, gpointer user_data)
01669 {
01670     //gnc_commodity *currency = (gnc_commodity *)key;
01671     GList *price_list = (GList *)val;
01672     GList *item = NULL;
01673     GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
01674     GList **return_list = lookup_helper->return_list;
01675     Timespec t = lookup_helper->time;
01676 
01677     item = price_list;
01678     while (item)
01679     {
01680         GNCPrice *p = item->data;
01681         Timespec price_time = gnc_price_get_time(p);
01682         if (timespec_equal(&price_time, &t))
01683         {
01684             gnc_price_list_insert(return_list, item->data, FALSE);
01685         }
01686         item = item->next;
01687     }
01688 }
01689 
01690 GNCPrice *
01691 gnc_pricedb_lookup_nearest_in_time(GNCPriceDB *db,
01692                                    const gnc_commodity *c,
01693                                    const gnc_commodity *currency,
01694                                    Timespec t)
01695 {
01696     GList *price_list;
01697     GNCPrice *current_price = NULL;
01698     GNCPrice *next_price = NULL;
01699     GNCPrice *result = NULL;
01700     GList *item = NULL;
01701     GHashTable *currency_hash;
01702     QofBook *book;
01703     QofBackend *be;
01704 
01705     if (!db || !c || !currency) return NULL;
01706     ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
01707     book = qof_instance_get_book(&db->inst);
01708     be = qof_book_get_backend(book);
01709 #ifdef GNUCASH_MAJOR_VERSION
01710     if (be && be->price_lookup)
01711     {
01712         GNCPriceLookup pl;
01713         pl.type = LOOKUP_NEAREST_IN_TIME;
01714         pl.prdb = db;
01715         pl.commodity = c;
01716         pl.currency = currency;
01717         pl.date = t;
01718         (be->price_lookup) (be, &pl);
01719     }
01720 #endif
01721     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
01722     if (!currency_hash)
01723     {
01724         LEAVE ("no currency hash");
01725         return NULL;
01726     }
01727 
01728     price_list = g_hash_table_lookup(currency_hash, currency);
01729     if (!price_list)
01730     {
01731         LEAVE ("no price list");
01732         return NULL;
01733     }
01734 
01735     item = price_list;
01736 
01737     /* default answer */
01738     current_price = item->data;
01739 
01740     /* find the first candidate past the one we want.  Remember that
01741        prices are in most-recent-first order. */
01742     while (!next_price && item)
01743     {
01744         GNCPrice *p = item->data;
01745         Timespec price_time = gnc_price_get_time(p);
01746         if (timespec_cmp(&price_time, &t) <= 0)
01747         {
01748             next_price = item->data;
01749             break;
01750         }
01751         current_price = item->data;
01752         item = item->next;
01753     }
01754 
01755     if (current_price)
01756     {
01757         if (!next_price)
01758         {
01759             result = current_price;
01760         }
01761         else
01762         {
01763             Timespec current_t = gnc_price_get_time(current_price);
01764             Timespec next_t = gnc_price_get_time(next_price);
01765             Timespec diff_current = timespec_diff(&current_t, &t);
01766             Timespec diff_next = timespec_diff(&next_t, &t);
01767             Timespec abs_current = timespec_abs(&diff_current);
01768             Timespec abs_next = timespec_abs(&diff_next);
01769 
01770             /* Choose the price that is closest to the given time. In case of
01771              * a tie, prefer the older price since it actually existed at the
01772              * time. (This also fixes bug #541970.) */
01773             if (timespec_cmp(&abs_current, &abs_next) < 0)
01774             {
01775                 result = current_price;
01776             }
01777             else
01778             {
01779                 result = next_price;
01780             }
01781         }
01782     }
01783 
01784     gnc_price_ref(result);
01785     LEAVE (" ");
01786     return result;
01787 }
01788 
01789 GNCPrice *
01790 gnc_pricedb_lookup_latest_before (GNCPriceDB *db,
01791                                   gnc_commodity *c,
01792                                   gnc_commodity *currency,
01793                                   Timespec t)
01794 {
01795     GList *price_list;
01796     GNCPrice *current_price = NULL;
01797     /*  GNCPrice *next_price = NULL;
01798         GNCPrice *result = NULL;*/
01799     GList *item = NULL;
01800     GHashTable *currency_hash;
01801     QofBook *book;
01802     QofBackend *be;
01803     Timespec price_time;
01804 
01805     if (!db || !c || !currency) return NULL;
01806     ENTER ("db=%p commodity=%p currency=%p", db, c, currency);
01807     book = qof_instance_get_book(&db->inst);
01808     be = qof_book_get_backend(book);
01809 #ifdef GNUCASH_MAJOR_VERSION
01810     if (be && be->price_lookup)
01811     {
01812         GNCPriceLookup pl;
01813         pl.type = LOOKUP_LATEST_BEFORE;
01814         pl.prdb = db;
01815         pl.commodity = c;
01816         pl.currency = currency;
01817         pl.date = t;
01818         (be->price_lookup) (be, &pl);
01819     }
01820 #endif
01821     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
01822     if (!currency_hash)
01823     {
01824         LEAVE ("no currency hash");
01825         return NULL;
01826     }
01827 
01828     price_list = g_hash_table_lookup(currency_hash, currency);
01829     if (!price_list)
01830     {
01831         LEAVE ("no price list");
01832         return NULL;
01833     }
01834 
01835     item = price_list;
01836     do
01837     {
01838         price_time = gnc_price_get_time (item->data);
01839         if (timespec_cmp(&price_time, &t) <= 0)
01840             current_price = item->data;
01841         item = item->next;
01842     }
01843     while (timespec_cmp(&price_time, &t) > 0 && item);
01844     gnc_price_ref(current_price);
01845     LEAVE (" ");
01846     return current_price;
01847 }
01848 
01849 
01850 static void
01851 lookup_nearest(gpointer key, gpointer val, gpointer user_data)
01852 {
01853     //gnc_commodity *currency = (gnc_commodity *)key;
01854     GList *price_list = (GList *)val;
01855     GNCPrice *current_price = NULL;
01856     GNCPrice *next_price = NULL;
01857     GNCPrice *result = NULL;
01858     GList *item = NULL;
01859     GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
01860     GList **return_list = lookup_helper->return_list;
01861     Timespec t = lookup_helper->time;
01862 
01863     item = price_list;
01864 
01865     /* default answer */
01866     current_price = item->data;
01867 
01868     /* find the first candidate past the one we want.  Remember that
01869        prices are in most-recent-first order. */
01870     while (!next_price && item)
01871     {
01872         GNCPrice *p = item->data;
01873         Timespec price_time = gnc_price_get_time(p);
01874         if (timespec_cmp(&price_time, &t) <= 0)
01875         {
01876             next_price = item->data;
01877             break;
01878         }
01879         current_price = item->data;
01880         item = item->next;
01881     }
01882 
01883     if (current_price)
01884     {
01885         if (!next_price)
01886         {
01887             result = current_price;
01888         }
01889         else
01890         {
01891             Timespec current_t = gnc_price_get_time(current_price);
01892             Timespec next_t = gnc_price_get_time(next_price);
01893             Timespec diff_current = timespec_diff(&current_t, &t);
01894             Timespec diff_next = timespec_diff(&next_t, &t);
01895             Timespec abs_current = timespec_abs(&diff_current);
01896             Timespec abs_next = timespec_abs(&diff_next);
01897 
01898             if (timespec_cmp(&abs_current, &abs_next) <= 0)
01899             {
01900                 result = current_price;
01901             }
01902             else
01903             {
01904                 result = next_price;
01905             }
01906         }
01907     }
01908 
01909     gnc_price_list_insert(return_list, result, FALSE);
01910 }
01911 
01912 
01913 static void
01914 lookup_latest_before(gpointer key, gpointer val, gpointer user_data)
01915 {
01916     //gnc_commodity *currency = (gnc_commodity *)key;
01917     GList *price_list = (GList *)val;
01918     GNCPrice *current_price = NULL;
01919     /*  GNCPrice *next_price = NULL;
01920         GNCPrice *result = NULL;*/
01921     GList *item = NULL;
01922     GNCPriceLookupHelper *lookup_helper = (GNCPriceLookupHelper *)user_data;
01923     GList **return_list = lookup_helper->return_list;
01924     Timespec t = lookup_helper->time;
01925     Timespec price_time;
01926 
01927     if (price_list)
01928     {
01929         item = price_list;
01930         do
01931         {
01932             price_time = gnc_price_get_time (item->data);
01933             if (timespec_cmp(&price_time, &t) <= 0)
01934                 current_price = item->data;
01935             item = item->next;
01936         }
01937         while (timespec_cmp(&price_time, &t) > 0 && item);
01938     }
01939 
01940     gnc_price_list_insert(return_list, current_price, FALSE);
01941 }
01942 
01943 
01944 PriceList *
01945 gnc_pricedb_lookup_nearest_in_time_any_currency(GNCPriceDB *db,
01946         const gnc_commodity *c,
01947         Timespec t)
01948 {
01949     GList *result = NULL;
01950     GHashTable *currency_hash;
01951     GNCPriceLookupHelper lookup_helper;
01952     QofBook *book;
01953     QofBackend *be;
01954 
01955     if (!db || !c) return NULL;
01956     ENTER ("db=%p commodity=%p", db, c);
01957     book = qof_instance_get_book(&db->inst);
01958     be = qof_book_get_backend(book);
01959 #ifdef GNUCASH_MAJOR_VERSION
01960     if (be && be->price_lookup)
01961     {
01962         GNCPriceLookup pl;
01963         pl.type = LOOKUP_NEAREST_IN_TIME;
01964         pl.prdb = db;
01965         pl.commodity = c;
01966         pl.currency = NULL;  /* can the backend handle this??? */
01967         pl.date = t;
01968         (be->price_lookup) (be, &pl);
01969     }
01970 #endif
01971     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
01972     if (!currency_hash)
01973     {
01974         LEAVE (" no currency hash");
01975         return NULL;
01976     }
01977 
01978     lookup_helper.return_list = &result;
01979     lookup_helper.time = t;
01980     g_hash_table_foreach(currency_hash, lookup_nearest, &lookup_helper);
01981 
01982     if (!result)
01983     {
01984         LEAVE (" ");
01985         return NULL;
01986     }
01987 
01988     result = g_list_sort(result, compare_prices_by_date);
01989 
01990     LEAVE (" ");
01991     return result;
01992 }
01993 
01994 
01995 PriceList *
01996 gnc_pricedb_lookup_latest_before_any_currency(GNCPriceDB *db,
01997         gnc_commodity *c,
01998         Timespec t)
01999 {
02000     GList *result = NULL;
02001     GHashTable *currency_hash;
02002     GNCPriceLookupHelper lookup_helper;
02003     QofBook *book;
02004     QofBackend *be;
02005 
02006     if (!db || !c) return NULL;
02007     ENTER ("db=%p commodity=%p", db, c);
02008     book = qof_instance_get_book(&db->inst);
02009     be = qof_book_get_backend(book);
02010 #ifdef GNUCASH_MAJOR_VERSION
02011     if (be && be->price_lookup)
02012     {
02013         GNCPriceLookup pl;
02014         pl.type = LOOKUP_LATEST_BEFORE;
02015         pl.prdb = db;
02016         pl.commodity = c;
02017         pl.currency = NULL;  /* can the backend handle this??? */
02018         pl.date = t;
02019         (be->price_lookup) (be, &pl);
02020     }
02021 #endif
02022     currency_hash = g_hash_table_lookup(db->commodity_hash, c);
02023     if (!currency_hash)
02024     {
02025         LEAVE (" no currency hash");
02026         return NULL;
02027     }
02028 
02029     lookup_helper.return_list = &result;
02030     lookup_helper.time = t;
02031     g_hash_table_foreach(currency_hash, lookup_latest_before, &lookup_helper);
02032 
02033     if (!result)
02034     {
02035         LEAVE (" ");
02036         return NULL;
02037     }
02038 
02039     result = g_list_sort(result, compare_prices_by_date);
02040 
02041     LEAVE (" ");
02042     return result;
02043 }
02044 
02045 
02046 /*
02047  * Convert a balance from one currency to another.
02048  */
02049 gnc_numeric
02050 gnc_pricedb_convert_balance_latest_price(GNCPriceDB *pdb,
02051         gnc_numeric balance,
02052         const gnc_commodity *balance_currency,
02053         const gnc_commodity *new_currency)
02054 {
02055     GNCPrice *price, *currency_price;
02056     GList *price_list, *list_helper;
02057     gnc_numeric currency_price_value;
02058     gnc_commodity *intermediate_currency;
02059 
02060     if (gnc_numeric_zero_p (balance) ||
02061             gnc_commodity_equiv (balance_currency, new_currency))
02062         return balance;
02063 
02064     /* Look for a direct price. */
02065     price = gnc_pricedb_lookup_latest (pdb, balance_currency, new_currency);
02066     if (price)
02067     {
02068         balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
02069                                    gnc_commodity_get_fraction (new_currency),
02070                                    GNC_HOW_RND_ROUND_HALF_UP);
02071         gnc_price_unref (price);
02072         return balance;
02073     }
02074 
02075     /* Look for a price of the new currency in the balance currency and use
02076      * the reciprocal if we find it
02077      */
02078     price = gnc_pricedb_lookup_latest (pdb, new_currency, balance_currency);
02079     if (price)
02080     {
02081         balance = gnc_numeric_div (balance, gnc_price_get_value (price),
02082                                    gnc_commodity_get_fraction (new_currency),
02083                                    GNC_HOW_RND_ROUND_HALF_UP);
02084         gnc_price_unref (price);
02085         return balance;
02086     }
02087 
02088     /*
02089      * no direct price found, try if we find a price in another currency
02090      * and convert in two stages
02091      */
02092     price_list = gnc_pricedb_lookup_latest_any_currency(pdb, balance_currency);
02093     if (!price_list)
02094     {
02095         balance =  gnc_numeric_zero ();
02096         return balance;
02097     }
02098 
02099     list_helper = price_list;
02100     currency_price_value = gnc_numeric_zero();
02101 
02102     do
02103     {
02104         price = (GNCPrice *)(list_helper->data);
02105 
02106         intermediate_currency = gnc_price_get_currency(price);
02107         currency_price = gnc_pricedb_lookup_latest(pdb, intermediate_currency,
02108                          new_currency);
02109         if (currency_price)
02110         {
02111             currency_price_value = gnc_price_get_value(currency_price);
02112             gnc_price_unref(currency_price);
02113         }
02114         else
02115         {
02116             currency_price = gnc_pricedb_lookup_latest(pdb, new_currency,
02117                              intermediate_currency);
02118             if (currency_price)
02119             {
02120                 /* here we need the reciprocal */
02121                 currency_price_value = gnc_numeric_div(gnc_numeric_create(1, 1),
02122                                                        gnc_price_get_value(currency_price),
02123                                                        GNC_DENOM_AUTO,
02124                                                        GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER);
02125                 gnc_price_unref(currency_price);
02126             }
02127         }
02128 
02129         list_helper = list_helper->next;
02130     }
02131     while ((list_helper != NULL) &&
02132             (gnc_numeric_zero_p(currency_price_value)));
02133 
02134     balance = gnc_numeric_mul (balance, currency_price_value,
02135                                GNC_DENOM_AUTO,
02136                                GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER);
02137     balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
02138                                gnc_commodity_get_fraction (new_currency),
02139                                GNC_HOW_RND_ROUND_HALF_UP);
02140 
02141     gnc_price_list_destroy(price_list);
02142     return balance;
02143 }
02144 
02145 gnc_numeric
02146 gnc_pricedb_convert_balance_nearest_price(GNCPriceDB *pdb,
02147         gnc_numeric balance,
02148         const gnc_commodity *balance_currency,
02149         const gnc_commodity *new_currency,
02150         Timespec t)
02151 {
02152     GNCPrice *price, *currency_price;
02153     GList *price_list, *list_helper;
02154     gnc_numeric currency_price_value;
02155     gnc_commodity *intermediate_currency;
02156 
02157     if (gnc_numeric_zero_p (balance) ||
02158             gnc_commodity_equiv (balance_currency, new_currency))
02159         return balance;
02160 
02161     /* Look for a direct price. */
02162     price = gnc_pricedb_lookup_nearest_in_time (pdb, balance_currency, new_currency, t);
02163     if (price)
02164     {
02165         balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
02166                                    gnc_commodity_get_fraction (new_currency),
02167                                    GNC_HOW_RND_ROUND_HALF_UP);
02168         gnc_price_unref (price);
02169         return balance;
02170     }
02171 
02172     /* Look for a price of the new currency in the balance currency and use
02173      * the reciprocal if we find it
02174      */
02175     price = gnc_pricedb_lookup_nearest_in_time (pdb, new_currency, balance_currency, t);
02176     if (price)
02177     {
02178         balance = gnc_numeric_div (balance, gnc_price_get_value (price),
02179                                    gnc_commodity_get_fraction (new_currency),
02180                                    GNC_HOW_RND_ROUND_HALF_UP);
02181         gnc_price_unref (price);
02182         return balance;
02183     }
02184 
02185     /*
02186      * no direct price found, try if we find a price in another currency
02187      * and convert in two stages
02188      */
02189     price_list = gnc_pricedb_lookup_nearest_in_time_any_currency(pdb, balance_currency, t);
02190     if (!price_list)
02191     {
02192         balance =  gnc_numeric_zero ();
02193         return balance;
02194     }
02195 
02196     list_helper = price_list;
02197     currency_price_value = gnc_numeric_zero();
02198 
02199     do
02200     {
02201         price = (GNCPrice *)(list_helper->data);
02202 
02203         intermediate_currency = gnc_price_get_currency(price);
02204         currency_price = gnc_pricedb_lookup_nearest_in_time(pdb, intermediate_currency,
02205                          new_currency, t);
02206         if (currency_price)
02207         {
02208             currency_price_value = gnc_price_get_value(currency_price);
02209             gnc_price_unref(currency_price);
02210         }
02211         else
02212         {
02213             currency_price = gnc_pricedb_lookup_nearest_in_time(pdb, new_currency,
02214                              intermediate_currency, t);
02215             if (currency_price)
02216             {
02217                 /* here we need the reciprocal */
02218                 currency_price_value = gnc_numeric_div(gnc_numeric_create(1, 1),
02219                                                        gnc_price_get_value(currency_price),
02220                                                        gnc_commodity_get_fraction (new_currency),
02221                                                        GNC_HOW_RND_ROUND_HALF_UP);
02222                 gnc_price_unref(currency_price);
02223             }
02224         }
02225 
02226         list_helper = list_helper->next;
02227     }
02228     while ((list_helper != NULL) &&
02229             (gnc_numeric_zero_p(currency_price_value)));
02230 
02231     balance = gnc_numeric_mul (balance, currency_price_value,
02232                                gnc_commodity_get_fraction (new_currency),
02233                                GNC_HOW_RND_ROUND_HALF_UP);
02234 
02235     balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
02236                                gnc_commodity_get_fraction (new_currency),
02237                                GNC_HOW_RND_ROUND_HALF_UP);
02238 
02239     gnc_price_list_destroy(price_list);
02240     return balance;
02241 }
02242 
02243 
02244 /* ==================================================================== */
02245 /* gnc_pricedb_foreach_price infrastructure
02246  */
02247 
02248 typedef struct
02249 {
02250     gboolean ok;
02251     gboolean (*func)(GNCPrice *p, gpointer user_data);
02252     gpointer user_data;
02253 } GNCPriceDBForeachData;
02254 
02255 static void
02256 pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
02257 {
02258     GList *price_list = (GList *) val;
02259     GList *node = price_list;
02260     GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data;
02261 
02262     /* stop traversal when func returns FALSE */
02263     while (foreach_data->ok && node)
02264     {
02265         GNCPrice *p = (GNCPrice *) node->data;
02266         foreach_data->ok = foreach_data->func(p, foreach_data->user_data);
02267         node = node->next;
02268     }
02269 }
02270 
02271 static void
02272 pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
02273 {
02274     GHashTable *currencies_hash = (GHashTable *) val;
02275     g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data);
02276 }
02277 
02278 static gboolean
02279 unstable_price_traversal(GNCPriceDB *db,
02280                          gboolean (*f)(GNCPrice *p, gpointer user_data),
02281                          gpointer user_data)
02282 {
02283     GNCPriceDBForeachData foreach_data;
02284 
02285     if (!db || !f) return FALSE;
02286     foreach_data.ok = TRUE;
02287     foreach_data.func = f;
02288     foreach_data.user_data = user_data;
02289     if (db->commodity_hash == NULL)
02290     {
02291         return FALSE;
02292     }
02293     g_hash_table_foreach(db->commodity_hash,
02294                          pricedb_foreach_currencies_hash,
02295                          &foreach_data);
02296 
02297     return foreach_data.ok;
02298 }
02299 
02300 static gint
02301 compare_kvpairs_by_commodity_key(gconstpointer a, gconstpointer b)
02302 {
02303     GHashTableKVPair *kvpa = (GHashTableKVPair *) a;
02304     GHashTableKVPair *kvpb = (GHashTableKVPair *) b;
02305     gnc_commodity *ca;
02306     gnc_commodity *cb;
02307     int cmp_result;
02308 
02309     if (a == b) return 0;
02310     if (!a && !b) return 0;
02311     if (!a) return -1;
02312     if (!b) return 1;
02313 
02314     ca = (gnc_commodity *) kvpa->key;
02315     cb = (gnc_commodity *) kvpb->key;
02316 
02317     cmp_result = safe_strcmp(gnc_commodity_get_namespace(ca),
02318                              gnc_commodity_get_namespace(cb));
02319 
02320     if (cmp_result != 0) return cmp_result;
02321 
02322     return safe_strcmp(gnc_commodity_get_mnemonic(ca),
02323                        gnc_commodity_get_mnemonic(cb));
02324 }
02325 
02326 static gboolean
02327 stable_price_traversal(GNCPriceDB *db,
02328                        gboolean (*f)(GNCPrice *p, gpointer user_data),
02329                        gpointer user_data)
02330 {
02331     GSList *currency_hashes = NULL;
02332     gboolean ok = TRUE;
02333     GSList *i = NULL;
02334 
02335     if (!db || !f) return FALSE;
02336 
02337     currency_hashes = g_hash_table_key_value_pairs(db->commodity_hash);
02338     currency_hashes = g_slist_sort(currency_hashes,
02339                                    compare_kvpairs_by_commodity_key);
02340 
02341     for (i = currency_hashes; i; i = i->next)
02342     {
02343         GHashTableKVPair *kv_pair = (GHashTableKVPair *) i->data;
02344         GHashTable *currency_hash = (GHashTable *) kv_pair->value;
02345         GSList *price_lists = g_hash_table_key_value_pairs(currency_hash);
02346         GSList *j;
02347 
02348         price_lists = g_slist_sort(price_lists, compare_kvpairs_by_commodity_key);
02349         for (j = price_lists; j; j = j->next)
02350         {
02351             GHashTableKVPair *pricelist_kvp = (GHashTableKVPair *) j->data;
02352             GList *price_list = (GList *) pricelist_kvp->value;
02353             GList *node;
02354 
02355             for (node = (GList *) price_list; node; node = node->next)
02356             {
02357                 GNCPrice *price = (GNCPrice *) node->data;
02358 
02359                 /* stop traversal when f returns FALSE */
02360                 if (FALSE == ok) break;
02361                 if (!f(price, user_data)) ok = FALSE;
02362             }
02363         }
02364         if (price_lists)
02365         {
02366             g_slist_foreach(price_lists, g_hash_table_kv_pair_free_gfunc, NULL);
02367             g_slist_free(price_lists);
02368             price_lists = NULL;
02369         }
02370     }
02371 
02372     if (currency_hashes)
02373     {
02374         g_slist_foreach(currency_hashes, g_hash_table_kv_pair_free_gfunc, NULL);
02375         g_slist_free(currency_hashes);
02376     }
02377     return ok;
02378 }
02379 
02380 gboolean
02381 gnc_pricedb_foreach_price(GNCPriceDB *db,
02382                           gboolean (*f)(GNCPrice *p, gpointer user_data),
02383                           gpointer user_data,
02384                           gboolean stable_order)
02385 {
02386     ENTER ("db=%p f=%p", db, f);
02387     if (stable_order)
02388     {
02389         LEAVE (" stable order found");
02390         return stable_price_traversal(db, f, user_data);
02391     }
02392     LEAVE (" use unstable order");
02393     return unstable_price_traversal(db, f, user_data);
02394 }
02395 
02396 /* ==================================================================== */
02397 /* commodity substitution */
02398 
02399 typedef struct
02400 {
02401     gnc_commodity *old_c;
02402     gnc_commodity *new_c;
02403 } GNCPriceFixupData;
02404 
02405 static gboolean
02406 add_price_to_list (GNCPrice *p, gpointer data)
02407 {
02408     GList **list = data;
02409 
02410     *list = g_list_prepend (*list, p);
02411 
02412     return TRUE;
02413 }
02414 
02415 static void
02416 gnc_price_fixup_legacy_commods(gpointer data, gpointer user_data)
02417 {
02418     GNCPrice *p = data;
02419     GNCPriceFixupData *fixup_data = user_data;
02420     gnc_commodity *price_c;
02421 
02422     if (!p) return;
02423 
02424     price_c = gnc_price_get_commodity(p);
02425     if (gnc_commodity_equiv(price_c, fixup_data->old_c))
02426     {
02427         gnc_price_set_commodity (p, fixup_data->new_c);
02428     }
02429     price_c = gnc_price_get_currency(p);
02430     if (gnc_commodity_equiv(price_c, fixup_data->old_c))
02431     {
02432         gnc_price_set_currency (p, fixup_data->new_c);
02433     }
02434 }
02435 
02436 void
02437 gnc_pricedb_substitute_commodity(GNCPriceDB *db,
02438                                  gnc_commodity *old_c,
02439                                  gnc_commodity *new_c)
02440 {
02441     GNCPriceFixupData data;
02442     GList *prices = NULL;
02443 
02444     if (!db || !old_c || !new_c) return;
02445 
02446     data.old_c = old_c;
02447     data.new_c = new_c;
02448 
02449     gnc_pricedb_foreach_price (db, add_price_to_list, &prices, FALSE);
02450 
02451     g_list_foreach (prices, gnc_price_fixup_legacy_commods, &data);
02452 
02453     g_list_free (prices);
02454 }
02455 
02456 /***************************************************************************/
02457 
02458 /* Semi-lame debugging code */
02459 
02460 void
02461 gnc_price_print(GNCPrice *p, FILE *f, int indent)
02462 {
02463     gnc_commodity *commodity;
02464     gnc_commodity *currency;
02465     gchar *istr = NULL;           /* indent string */
02466     const char *str;
02467 
02468     if (!p) return;
02469     if (!f) return;
02470 
02471     commodity = gnc_price_get_commodity(p);
02472     currency = gnc_price_get_currency(p);
02473 
02474     if (!commodity) return;
02475     if (!currency) return;
02476 
02477     istr = g_strnfill(indent, ' ');
02478 
02479     fprintf(f, "%s<pdb:price>\n", istr);
02480     fprintf(f, "%s  <pdb:commodity pointer=%p>\n", istr, commodity);
02481     str = gnc_commodity_get_namespace(commodity);
02482     str = str ? str : "(null)";
02483     fprintf(f, "%s    <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
02484     str = gnc_commodity_get_mnemonic(commodity);
02485     str = str ? str : "(null)";
02486     fprintf(f, "%s    <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
02487     fprintf(f, "%s  </pdb:commodity>\n", istr);
02488     fprintf(f, "%s  <pdb:currency pointer=%p>\n", istr, currency);
02489     str = gnc_commodity_get_namespace(currency);
02490     str = str ? str : "(null)";
02491     fprintf(f, "%s    <cmdty:ref-space>%s</gnc:cmdty:ref-space>\n", istr, str);
02492     str = gnc_commodity_get_mnemonic(currency);
02493     str = str ? str : "(null)";
02494     fprintf(f, "%s    <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
02495     fprintf(f, "%s  </pdb:currency>\n", istr);
02496     str = gnc_price_get_source(p);
02497     str = str ? str : "(null)";
02498     fprintf(f, "%s  %s\n", istr, str);
02499     str = gnc_price_get_typestr(p);
02500     str = str ? str : "(null)";
02501     fprintf(f, "%s  %s\n", istr, str);
02502     fprintf(f, "%s  %g\n", istr, gnc_numeric_to_double(gnc_price_get_value(p)));
02503     fprintf(f, "%s</pdb:price>\n", istr);
02504 
02505     g_free(istr);
02506 }
02507 
02508 static gboolean
02509 print_pricedb_adapter(GNCPrice *p, gpointer user_data)
02510 {
02511     FILE *f = (FILE *) user_data;
02512     gnc_price_print(p, f, 1);
02513     return TRUE;
02514 }
02515 
02516 void
02517 gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f)
02518 {
02519     if (!db)
02520     {
02521         PERR("NULL PriceDB\n");
02522         return;
02523     }
02524     if (!f)
02525     {
02526         PERR("NULL FILE*\n");
02527         return;
02528     }
02529 
02530     fprintf(f, "<gnc:pricedb>\n");
02531     gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE);
02532     fprintf(f, "</gnc:pricedb>\n");
02533 }
02534 
02535 /* ==================================================================== */
02536 /* gncObject function implementation and registration */
02537 
02538 static void
02539 pricedb_book_begin (QofBook *book)
02540 {
02541     GNCPriceDB *db;
02542 
02543     db = gnc_pricedb_create(book);
02544 }
02545 
02546 static void
02547 pricedb_book_end (QofBook *book)
02548 {
02549     GNCPriceDB *db;
02550     QofCollection *col;
02551 
02552     if (!book)
02553         return;
02554     col = qof_book_get_collection(book, GNC_ID_PRICEDB);
02555     db = qof_collection_get_data(col);
02556     qof_collection_set_data(col, NULL);
02557     gnc_pricedb_destroy(db);
02558 }
02559 
02560 static gpointer
02561 price_create (QofBook *book)
02562 {
02563     return gnc_price_create(book);
02564 }
02565 
02566 /* ==================================================================== */
02567 /* a non-boolean foreach. Ugh */
02568 
02569 typedef struct
02570 {
02571     void (*func)(GNCPrice *p, gpointer user_data);
02572     gpointer user_data;
02573 }
02574 VoidGNCPriceDBForeachData;
02575 
02576 static void
02577 void_pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
02578 {
02579     GList *price_list = (GList *) val;
02580     GList *node = price_list;
02581     VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
02582 
02583     while (node)
02584     {
02585         GNCPrice *p = (GNCPrice *) node->data;
02586         foreach_data->func(p, foreach_data->user_data);
02587         node = node->next;
02588     }
02589 }
02590 
02591 static void
02592 void_pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data)
02593 {
02594     GHashTable *currencies_hash = (GHashTable *) val;
02595     g_hash_table_foreach(currencies_hash, void_pricedb_foreach_pricelist, user_data);
02596 }
02597 
02598 static void
02599 void_unstable_price_traversal(GNCPriceDB *db,
02600                               void (*f)(GNCPrice *p, gpointer user_data),
02601                               gpointer user_data)
02602 {
02603     VoidGNCPriceDBForeachData foreach_data;
02604 
02605     if (!db || !f) return;
02606     foreach_data.func = f;
02607     foreach_data.user_data = user_data;
02608 
02609     g_hash_table_foreach(db->commodity_hash,
02610                          void_pricedb_foreach_currencies_hash,
02611                          &foreach_data);
02612 }
02613 
02614 static void
02615 price_foreach(const QofCollection *col, QofInstanceForeachCB cb, gpointer data)
02616 {
02617     GNCPriceDB *db;
02618 
02619     db = qof_collection_get_data(col);
02620     void_unstable_price_traversal(db,
02621                                   (void (*)(GNCPrice *, gpointer)) cb,
02622                                   data);
02623 }
02624 
02625 /* ==================================================================== */
02626 
02627 static const char *
02628 price_printable(gpointer obj)
02629 {
02630     GNCPrice *pr = obj;
02631     gnc_commodity *commodity;
02632     gnc_commodity *currency;
02633     static char buff[2048];  /* nasty static OK for printing */
02634     char *val, *da;
02635 
02636     if (!pr) return "";
02637 
02638     val = gnc_numeric_to_string (pr->value);
02639     da = qof_print_date (pr->tmspec.tv_sec);
02640 
02641     commodity = gnc_price_get_commodity(pr);
02642     currency = gnc_price_get_currency(pr);
02643 
02644     g_snprintf (buff, 2048, "%s %s / %s on %s", val,
02645                 gnc_commodity_get_unique_name(commodity),
02646                 gnc_commodity_get_unique_name(currency),
02647                 da);
02648     g_free (val);
02649     g_free (da);
02650     return buff;
02651 }
02652 
02653 #ifdef _MSC_VER
02654 /* MSVC compiler doesn't have C99 "designated initializers"
02655  * so we wrap them in a macro that is empty on MSVC. */
02656 # define DI(x) /* */
02657 #else
02658 # define DI(x) x
02659 #endif
02660 static QofObject price_object_def =
02661 {
02662     DI(.interface_version = ) QOF_OBJECT_VERSION,
02663     DI(.e_type            = ) GNC_ID_PRICE,
02664     DI(.type_label        = ) "Price",
02665     DI(.create            = ) price_create,
02666     DI(.book_begin        = ) NULL,
02667     DI(.book_end          = ) NULL,
02668     DI(.is_dirty          = ) qof_collection_is_dirty,
02669     DI(.mark_clean        = ) qof_collection_mark_clean,
02670     DI(.foreach           = ) price_foreach,
02671     DI(.printable         = ) price_printable,
02672     DI(.version_cmp       = ) NULL,
02673 };
02674 
02675 static QofObject pricedb_object_def =
02676 {
02677     DI(.interface_version = ) QOF_OBJECT_VERSION,
02678     DI(.e_type            = ) GNC_ID_PRICEDB,
02679     DI(.type_label        = ) "PriceDB",
02680     DI(.create            = ) NULL,
02681     DI(.book_begin        = ) pricedb_book_begin,
02682     DI(.book_end          = ) pricedb_book_end,
02683     DI(.is_dirty          = ) qof_collection_is_dirty,
02684     DI(.mark_clean        = ) qof_collection_mark_clean,
02685     DI(.foreach           = ) NULL,
02686     DI(.printable         = ) NULL,
02687     DI(.version_cmp       = ) NULL,
02688 };
02689 
02690 gboolean
02691 gnc_pricedb_register (void)
02692 {
02693     static QofParam params[] =
02694     {
02695         { PRICE_COMMODITY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_commodity, (QofSetterFunc)gnc_price_set_commodity },
02696         { PRICE_CURRENCY, GNC_ID_COMMODITY, (QofAccessFunc)gnc_price_get_currency, (QofSetterFunc)gnc_price_set_currency },
02697         { PRICE_DATE, QOF_TYPE_DATE, (QofAccessFunc)gnc_price_get_time, (QofSetterFunc)gnc_price_set_time },
02698         { PRICE_SOURCE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_source, (QofSetterFunc)gnc_price_set_source },
02699         { PRICE_TYPE, QOF_TYPE_STRING, (QofAccessFunc)gnc_price_get_typestr, (QofSetterFunc)gnc_price_set_typestr },
02700         { PRICE_VALUE, QOF_TYPE_NUMERIC, (QofAccessFunc)gnc_price_get_value, (QofSetterFunc)gnc_price_set_value },
02701         { NULL },
02702     };
02703 
02704     qof_class_register (GNC_ID_PRICE, NULL, params);
02705 
02706     if (!qof_object_register (&price_object_def))
02707         return FALSE;
02708     return qof_object_register (&pricedb_object_def);
02709 }
02710 
02711 /* ========================= END OF FILE ============================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines