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 /* GObject Initialization */
00039 QOF_GOBJECT_IMPL(gnc_price, GNCPrice, QOF_TYPE_INSTANCE);
00040 
00041 static void
00042 gnc_price_init(GNCPrice* price)
00043 {
00044 }
00045 
00046 static void
00047 gnc_price_dispose_real (GObject *pricep)
00048 {
00049 }
00050 
00051 static void
00052 gnc_price_finalize_real(GObject* pricep)
00053 {
00054 }
00055 
00056 /* ==================================================================== */
00057 /* GNCPrice functions
00058  */
00059 
00060 /* allocation */
00061 GNCPrice *
00062 gnc_price_create (QofBook *book)
00063 {
00064   GNCPrice *p;
00065 
00066   g_return_val_if_fail (book, NULL);
00067 
00068   p = g_object_new(GNC_TYPE_PRICE, NULL);
00069 
00070   p->refcount = 1;
00071   p->value = gnc_numeric_zero();
00072   p->type = NULL;
00073   p->source = NULL;
00074 
00075   qof_instance_init_data (&p->inst, GNC_ID_PRICE, book);
00076   qof_event_gen (&p->inst, QOF_EVENT_CREATE, NULL);
00077 
00078   return p;
00079 }
00080 
00081 static void
00082 gnc_price_destroy (GNCPrice *p)
00083 {
00084   ENTER(" ");
00085   qof_event_gen (&p->inst, QOF_EVENT_DESTROY, NULL);
00086 
00087   if(p->type) CACHE_REMOVE(p->type);
00088   if(p->source) CACHE_REMOVE(p->source);
00089 
00090   /* qof_instance_release (&p->inst); */
00091   g_object_unref(p);
00092   LEAVE (" ");
00093 }
00094 
00095 void
00096 gnc_price_ref(GNCPrice *p)
00097 {
00098   if(!p) return;
00099   p->refcount++;
00100 }
00101 
00102 void
00103 gnc_price_unref(GNCPrice *p)
00104 {
00105   if(!p) return;
00106   if(p->refcount == 0) {
00107     return;
00108   }
00109 
00110   p->refcount--;
00111 
00112   if(p->refcount <= 0) {
00113     if (NULL != p->db) {
00114        PERR("last unref while price in database");
00115     }
00116     gnc_price_destroy (p);
00117   }
00118 }
00119 
00120 /* ==================================================================== */
00121 
00122 GNCPrice *
00123 gnc_price_clone (GNCPrice* p, QofBook *book)
00124 {
00125   /* the clone doesn't belong to a PriceDB */
00126   GNCPrice *new_p;
00127 
00128   g_return_val_if_fail (book, NULL);
00129 
00130   ENTER ("pr=%p", p);
00131 
00132   if(!p) { LEAVE (" "); return NULL; }
00133 
00134   new_p = gnc_price_create(book);
00135   if(!new_p) { LEAVE (" "); return NULL; }
00136 
00137   qof_instance_copy_version(new_p, p);
00138 
00139   gnc_price_begin_edit(new_p);
00140   /* never ever clone guid's */
00141   gnc_price_set_commodity(new_p, gnc_price_get_commodity(p));
00142   gnc_price_set_time(new_p, gnc_price_get_time(p));
00143   gnc_price_set_source(new_p, gnc_price_get_source(p));
00144   gnc_price_set_typestr(new_p, gnc_price_get_typestr(p));
00145   gnc_price_set_value(new_p, gnc_price_get_value(p));
00146   gnc_price_set_currency(new_p, gnc_price_get_currency(p));
00147   gnc_price_commit_edit(new_p);
00148   LEAVE (" ");
00149   return(new_p);
00150 }
00151 
00152 /* ==================================================================== */
00153 
00154 void
00155 gnc_price_begin_edit (GNCPrice *p)
00156 {
00157   qof_begin_edit(&p->inst);
00158 }
00159 
00160 static void commit_err (QofInstance *inst, QofBackendError errcode)
00161 {
00162   PERR ("Failed to commit: %d", errcode);
00163 }
00164 
00165 static void noop (QofInstance *inst) {}
00166 
00167 void
00168 gnc_price_commit_edit (GNCPrice *p)
00169 {
00170   if (!qof_commit_edit (QOF_INSTANCE(p))) return;
00171   qof_commit_edit_part2 (&p->inst, commit_err, noop, noop);
00172 }
00173 
00174 /* ==================================================================== */
00175 
00176 void
00177 gnc_pricedb_begin_edit (GNCPriceDB *pdb)
00178 {
00179   qof_begin_edit(&pdb->inst);
00180 }
00181 
00182 void
00183 gnc_pricedb_commit_edit (GNCPriceDB *pdb)
00184 {
00185   if (!qof_commit_edit (QOF_INSTANCE(pdb))) return;
00186   qof_commit_edit_part2 (&pdb->inst, commit_err, noop, noop);
00187 }
00188 
00189 /* ==================================================================== */
00190 /* setters */
00191 
00192 static void
00193 gnc_price_set_dirty (GNCPrice *p)
00194 {
00195   qof_instance_set_dirty(&p->inst);
00196   qof_event_gen(&p->inst, QOF_EVENT_MODIFY, NULL);
00197 }
00198 
00199 void
00200 gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c)
00201 {
00202   if(!p) return;
00203 
00204   if(!gnc_commodity_equiv(p->commodity, c))
00205   {
00206     /* Changing the commodity requires the hash table
00207      * position to be modified. The easiest way of doing
00208      * this is to remove and reinsert. */
00209     gnc_price_ref (p);
00210     remove_price (p->db, p, TRUE);
00211     gnc_price_begin_edit (p);
00212     p->commodity = c;
00213     gnc_price_set_dirty(p);
00214     gnc_price_commit_edit (p);
00215     add_price (p->db, p);
00216     gnc_price_unref (p);
00217   }
00218 }
00219 
00220 
00221 void
00222 gnc_price_set_currency(GNCPrice *p, gnc_commodity *c)
00223 {
00224   if(!p) return;
00225 
00226   if(!gnc_commodity_equiv(p->currency, c))
00227   {
00228     /* Changing the currency requires the hash table
00229      * position to be modified. The easiest way of doing
00230      * this is to remove and reinsert. */
00231     gnc_price_ref (p);
00232     remove_price (p->db, p, TRUE);
00233     gnc_price_begin_edit (p);
00234     p->currency = c;
00235     gnc_price_set_dirty(p);
00236     gnc_price_commit_edit (p);
00237     add_price (p->db, p);
00238     gnc_price_unref (p);
00239   }
00240 }
00241 
00242 void
00243 gnc_price_set_time(GNCPrice *p, Timespec t)
00244 {
00245   if(!p) return;
00246   if(!timespec_equal(&(p->tmspec), &t))
00247   {
00248     /* Changing the datestamp requires the hash table
00249      * position to be modified. The easiest way of doing
00250      * this is to remove and reinsert. */
00251     gnc_price_ref (p);
00252     remove_price (p->db, p, FALSE);
00253     gnc_price_begin_edit (p);
00254     p->tmspec = t;
00255     gnc_price_set_dirty(p);
00256     gnc_price_commit_edit (p);
00257     add_price (p->db, p);
00258     gnc_price_unref (p);
00259   }
00260 }
00261 
00262 void
00263 gnc_price_set_source(GNCPrice *p, const char *s)
00264 {
00265   if(!p) return;
00266   if(safe_strcmp(p->source, s) != 0)
00267   {
00268     char *tmp;
00269 
00270     gnc_price_begin_edit (p);
00271     tmp = CACHE_INSERT((gpointer) s);
00272     if(p->source) CACHE_REMOVE(p->source);
00273     p->source = tmp;
00274     gnc_price_set_dirty(p);
00275     gnc_price_commit_edit (p);
00276   }
00277 }
00278 
00279 void
00280 gnc_price_set_typestr(GNCPrice *p, const char* type)
00281 {
00282   if(!p) return;
00283   if(safe_strcmp(p->type, type) != 0)
00284   {
00285     gchar *tmp;
00286 
00287     gnc_price_begin_edit (p);
00288     tmp = CACHE_INSERT((gpointer) type);
00289     if(p->type) CACHE_REMOVE(p->type);
00290     p->type = tmp;
00291     gnc_price_set_dirty(p);
00292     gnc_price_commit_edit (p);
00293   }
00294 }
00295 
00296 void
00297 gnc_price_set_value(GNCPrice *p, gnc_numeric value)
00298 {
00299   if(!p) return;
00300   if(!gnc_numeric_eq(p->value, value))
00301   {
00302     gnc_price_begin_edit (p);
00303     p->value = value;
00304     gnc_price_set_dirty(p);
00305     gnc_price_commit_edit (p);
00306   }
00307 }
00308 
00309 /* ==================================================================== */
00310 /* getters */
00311 
00312 GNCPrice *
00313 gnc_price_lookup (const GUID *guid, QofBook *book)
00314 {
00315   QofCollection *col;
00316 
00317   if (!guid || !book) return NULL;
00318   col = qof_book_get_collection (book, GNC_ID_PRICE);
00319   return (GNCPrice *) qof_collection_lookup_entity (col, guid);
00320 }
00321 
00322 gnc_commodity *
00323 gnc_price_get_commodity(const GNCPrice *p)
00324 {
00325   if(!p) return NULL;
00326   return p->commodity;
00327 }
00328 
00329 Timespec
00330 gnc_price_get_time(const GNCPrice *p)
00331 {
00332   if(!p) {
00333     Timespec result;
00334     result.tv_sec = 0;
00335     result.tv_nsec = 0;
00336     return result;
00337   }
00338   return p->tmspec;
00339 }
00340 
00341 const char *
00342 gnc_price_get_source(const GNCPrice *p)
00343 {
00344   if(!p) return NULL;
00345   return p->source;
00346 }
00347 
00348 const char *
00349 gnc_price_get_typestr(const GNCPrice *p)
00350 {
00351   if(!p) return NULL;
00352   return p->type;
00353 }
00354 
00355 gnc_numeric
00356 gnc_price_get_value(const GNCPrice *p)
00357 {
00358   if(!p) {
00359     PERR("price NULL.\n");
00360     return gnc_numeric_zero();
00361   }
00362   return p->value;
00363 }
00364 
00365 gnc_commodity *
00366 gnc_price_get_currency(const GNCPrice *p)
00367 {
00368   if(!p) return NULL;
00369   return p->currency;
00370 }
00371 
00372 gboolean
00373 gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
00374 {
00375   Timespec ts1;
00376   Timespec ts2;
00377 
00378   if (p1 == p2) return TRUE;
00379   if (!p1 || !p2) return FALSE;
00380 
00381   if (!gnc_commodity_equiv (gnc_price_get_commodity (p1),
00382                             gnc_price_get_commodity (p2)))
00383     return FALSE;
00384 
00385   if (!gnc_commodity_equiv (gnc_price_get_currency (p1),
00386                             gnc_price_get_currency (p2)))
00387     return FALSE;
00388 
00389   ts1 = gnc_price_get_time (p1);
00390   ts2 = gnc_price_get_time (p2);
00391 
00392   if (!timespec_equal (&ts1, &ts2))
00393     return FALSE;
00394 
00395   if (safe_strcmp (gnc_price_get_source (p1),
00396                    gnc_price_get_source (p2)) != 0)
00397     return FALSE;
00398 
00399   if (safe_strcmp (gnc_price_get_typestr (p1),
00400                    gnc_price_get_typestr (p2)) != 0)
00401     return FALSE;
00402 
00403   if (!gnc_numeric_eq (gnc_price_get_value (p1),
00404                        gnc_price_get_value (p2)))
00405     return FALSE;
00406 
00407   return TRUE;
00408 }
00409 
00410 /* ==================================================================== */
00411 /* price list manipulation functions */
00412 
00413 static gint
00414 compare_prices_by_date(gconstpointer a, gconstpointer b)
00415 {
00416   Timespec time_a;
00417   Timespec time_b;
00418   gint result;
00419 
00420   if(!a && !b) return 0;
00421   /* nothing is always less than something */
00422   if(!a) return -1;
00423 
00424   time_a = gnc_price_get_time((GNCPrice *) a);
00425   time_b = gnc_price_get_time((GNCPrice *) b);
00426 
00427   result = -timespec_cmp(&time_a, &time_b);
00428   if (result) return result;
00429 
00430   /* For a stable sort */
00431   return guid_compare (gnc_price_get_guid((GNCPrice *) a),
00432                        gnc_price_get_guid((GNCPrice *) b));
00433 }
00434 
00435 typedef struct {
00436         GNCPrice* pPrice;
00437         gboolean isDupl;
00438 } PriceListIsDuplStruct;
00439 
00440 static void
00441 price_list_is_duplicate( gpointer data, gpointer user_data )
00442 {
00443         GNCPrice* pPrice = (GNCPrice*)data;
00444         PriceListIsDuplStruct* pStruct = (PriceListIsDuplStruct*)user_data;
00445         Timespec time_a, time_b;
00446 
00447     time_a = timespecCanonicalDayTime( gnc_price_get_time( pPrice ) );
00448     time_b = timespecCanonicalDayTime( gnc_price_get_time( pStruct->pPrice ) );
00449 
00450         /* If the date, currency, commodity and price match, it's a duplicate */
00451         if( !gnc_numeric_equal( gnc_price_get_value( pPrice ),  gnc_price_get_value( pStruct->pPrice ) ) ) return;
00452         if( gnc_price_get_commodity( pPrice ) != gnc_price_get_commodity( pStruct->pPrice ) ) return;
00453         if( gnc_price_get_currency( pPrice ) != gnc_price_get_currency( pStruct->pPrice ) ) return;
00454 
00455   if( timespec_cmp( &time_a, &time_b ) != 0 ) return;
00456 
00457         pStruct->isDupl = TRUE;
00458 }
00459 
00460 gboolean
00461 gnc_price_list_insert(PriceList **prices, GNCPrice *p, gboolean check_dupl)
00462 {
00463   GList *result_list;
00464   PriceListIsDuplStruct* pStruct;
00465   gboolean isDupl;
00466 
00467   if(!prices || !p) return FALSE;
00468   gnc_price_ref(p);
00469 
00470   if (check_dupl) {
00471     pStruct = g_new0( PriceListIsDuplStruct, 1 );
00472     pStruct->pPrice = p;
00473     pStruct->isDupl = FALSE;
00474     g_list_foreach( *prices, price_list_is_duplicate, pStruct );
00475     isDupl = pStruct->isDupl;
00476     g_free( pStruct );
00477 
00478     if( isDupl ) {
00479       return TRUE;
00480     }
00481   }
00482 
00483   result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date);
00484   if(!result_list) return FALSE;
00485   *prices = result_list;
00486   return TRUE;
00487 }
00488 
00489 gboolean
00490 gnc_price_list_remove(PriceList **prices, GNCPrice *p)
00491 {
00492   GList *result_list;
00493   GList *found_element;
00494 
00495   if(!prices || !p) return FALSE;
00496 
00497   found_element = g_list_find(*prices, p);
00498   if(!found_element) return TRUE;
00499 
00500   result_list = g_list_remove_link(*prices, found_element);
00501   gnc_price_unref((GNCPrice *) found_element->data);
00502   g_list_free(found_element);
00503 
00504   *prices = result_list;
00505   return TRUE;
00506 }
00507 
00508 static void
00509 price_list_destroy_helper(gpointer data, gpointer user_data)
00510 {
00511   gnc_price_unref((GNCPrice *) data);
00512 }
00513 
00514 void
00515 gnc_price_list_destroy(PriceList *prices)
00516 {
00517   g_list_foreach(prices, price_list_destroy_helper, NULL);
00518   g_list_free(prices);
00519 }
00520 
00521 gboolean
00522 gnc_price_list_equal(PriceList *prices1, PriceList *prices2)
00523 {
00524   GList *n1, *n2;
00525 
00526   if (prices1 == prices2) return TRUE;
00527 
00528   if (g_list_length (prices1) < g_list_length (prices2))
00529   {
00530     PWARN ("prices2 has extra prices");
00531     return FALSE;
00532   }
00533 
00534   if (g_list_length (prices1) > g_list_length (prices2))
00535   {
00536     PWARN ("prices1 has extra prices");
00537     return FALSE;
00538   }
00539 
00540   for (n1 = prices1, n2 = prices2; n1 ; n1 = n1->next, n2 = n2->next)
00541     if (!gnc_price_equal (n1->data, n2->data))
00542       return FALSE;
00543 
00544   return TRUE;
00545 }
00546 
00547 /* ==================================================================== */
00548 /* GNCPriceDB functions
00549 
00550    Structurally a GNCPriceDB contains a hash mapping price commodities
00551    (of type gnc_commodity*) to hashes mapping price currencies (of
00552    type gnc_commodity*) to GNCPrice lists (see gnc-pricedb.h for a
00553    description of GNCPrice lists).  The top-level key is the commodity
00554    you want the prices for, and the second level key is the commodity
00555    that the value is expressed in terms of.
00556  */
00557 
00558 /* GObject Initialization */
00559 QOF_GOBJECT_IMPL(gnc_pricedb, GNCPriceDB, QOF_TYPE_INSTANCE);
00560 
00561 static void
00562 gnc_pricedb_init(GNCPriceDB* pdb)
00563 {
00564 }
00565 
00566 static void
00567 gnc_pricedb_dispose_real (GObject *pdbp)
00568 {
00569 }
00570 
00571 static void
00572 gnc_pricedb_finalize_real(GObject* pdbp)
00573 {
00574 }
00575 
00576 static GNCPriceDB *
00577 gnc_pricedb_create(QofBook * book)
00578 {
00579   GNCPriceDB * result;
00580   QofCollection *col;
00581 
00582   g_return_val_if_fail (book, NULL);
00583 
00584   /* There can only be one pricedb per book.  So if one exits already,
00585    * then use that.  Warn user, they shouldn't be creating two ...
00586    */
00587   col = qof_book_get_collection (book, GNC_ID_PRICEDB);
00588   result = qof_collection_get_data (col);
00589   if (result)
00590   {
00591     PWARN ("A price database already exists for this book!");
00592     return result;
00593   }
00594 
00595   result = g_object_new(GNC_TYPE_PRICEDB, NULL);
00596   qof_instance_init_data (&result->inst, GNC_ID_PRICEDB, book);
00597   qof_collection_mark_clean(col);
00598 
00602   qof_collection_set_data (col, result);
00603 
00604   result->commodity_hash = g_hash_table_new(NULL, NULL);
00605   g_return_val_if_fail (result->commodity_hash, NULL);
00606   return result;
00607 }
00608 
00609 static void
00610 destroy_pricedb_currency_hash_data(gpointer key,
00611                                    gpointer data,
00612                                    gpointer user_data)
00613 {
00614   GList *price_list = (GList *) data;
00615   GList *node;
00616   GNCPrice *p;
00617 
00618   for (node = price_list; node; node = node->next)
00619   {
00620     p = node->data;
00621 
00622     p->db = NULL;
00623   }
00624 
00625   gnc_price_list_destroy(price_list);
00626 }
00627 
00628 static void
00629 destroy_pricedb_commodity_hash_data(gpointer key,
00630                                     gpointer data,
00631                                     gpointer user_data)
00632 {
00633   GHashTable *currency_hash = (GHashTable *) data;
00634   if (!currency_hash) return;
00635   g_hash_table_foreach (currency_hash,
00636                         destroy_pricedb_currency_hash_data,
00637                         NULL);
00638   g_hash_table_destroy(currency_hash);
00639 }
00640 
00641 void
00642 gnc_pricedb_destroy(GNCPriceDB *db)
00643 {
00644   if(!db) return;
00645   if(db->commodity_hash) {
00646   g_hash_table_foreach (db->commodity_hash,
00647                         destroy_pricedb_commodity_hash_data,
00648                         NULL);
00649   }
00650   g_hash_table_destroy (db->commodity_hash);
00651   db->commodity_hash = NULL;
00652   /* qof_instance_release (&db->inst); */
00653   g_object_unref(db);
00654 }
00655 
00656 void
00657 gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
00658 {
00659   db->bulk_update = bulk_update;
00660 }
00661 
00662 /* ==================================================================== */
00663 /* This is kind of weird, the way its done.  Each collection of prices
00664  * for a given commodity should get its own guid, be its own entity, etc.
00665  * We really shouldn't be using the collection data.  But, hey I guess its OK,
00666  * yeah? Umm, possibly not. (NW). See TODO below.
00667 */
00676 GNCPriceDB *
00677 gnc_collection_get_pricedb(QofCollection *col)
00678 {
00679   if (!col) return NULL;
00680   return qof_collection_get_data (col);
00681 }
00682 
00683 GNCPriceDB *
00684 gnc_pricedb_get_db(QofBook *book)
00685 {
00686   QofCollection *col;
00687 
00688   if (!book) return NULL;
00689   col = qof_book_get_collection (book, GNC_ID_PRICEDB);
00690   return gnc_collection_get_pricedb (col);
00691 }
00692 
00693 /* ==================================================================== */
00694 
00695 static gboolean
00696 num_prices_helper (GNCPrice *p, gpointer user_data)
00697 {
00698   guint *count = user_data;
00699 
00700   *count += 1;
00701 
00702   return TRUE;
00703 }
00704 
00705 guint
00706 gnc_pricedb_get_num_prices(GNCPriceDB *db)
00707 {
00708   guint count;
00709 
00710   if (!db) return 0;
00711 
00712   count = 0;
00713 
00714   gnc_pricedb_foreach_price(db, num_prices_helper, &count, FALSE);
00715 
00716   return count;
00717 }
00718 
00719 /* ==================================================================== */
00720 
00721 typedef struct
00722 {
00723   gboolean equal;
00724   GNCPriceDB *db2;
00725   gnc_commodity *commodity;
00726 } GNCPriceDBEqualData;
00727 
00728 static void
00729 pricedb_equal_foreach_pricelist(gpointer key, gpointer val, gpointer user_data)
00730 {
00731   GNCPriceDBEqualData *equal_data = user_data;
00732   gnc_commodity *currency = key;
00733   GList *price_list1 = val;
00734   GList *price_list2;
00735 
00736   price_list2 = gnc_pricedb_get_prices (equal_data->db2,
00737                                         equal_data->commodity,
00738                                         currency);
00739 
00740   if (!gnc_price_list_equal (price_list1, price_list2))
00741     equal_data->equal = FALSE;
00742 
00743   gnc_price_list_destroy (price_list2);
00744 }
00745 
00746 static void
00747 pricedb_equal_foreach_currencies_hash (gpointer key, gpointer val,
00748                                        gpointer user_data)
00749 {
00750   GHashTable *currencies_hash = val;
00751   GNCPriceDBEqualData *equal_data = user_data;
00752 
00753   equal_data->commodity = key;
00754 
00755   g_hash_table_foreach (currencies_hash,
00756                         pricedb_equal_foreach_pricelist,
00757                         equal_data);
00758 }
00759 
00760 gboolean
00761 gnc_pricedb_equal (GNCPriceDB *db1, GNCPriceDB *db2)
00762 {
00763   GNCPriceDBEqualData equal_data;
00764 
00765   if (db1 == db2) return TRUE;
00766 
00767   if (!db1 || !db2)
00768   {
00769     PWARN ("one is NULL");
00770     return FALSE;
00771   }
00772 
00773   equal_data.equal = TRUE;
00774   equal_data.db2 = db2;
00775 
00776   g_hash_table_foreach (db1->commodity_hash,
00777                         pricedb_equal_foreach_currencies_hash,
00778                         &equal_data);
00779 
00780   return equal_data.equal;
00781 }
00782 
00783 /* ==================================================================== */
00784 /* The add_price() function is a utility that only manages the
00785  * dual hash table instertion */
00786 
00787 static gboolean
00788 add_price(GNCPriceDB *db, GNCPrice *p)
00789 {
00790   /* This function will use p, adding a ref, so treat p as read-only
00791      if this function succeeds. */
00792   GList *price_list;
00793   gnc_commodity *commodity;
00794   gnc_commodity *currency;
00795   GHashTable *currency_hash;
00796 
00797   if(!db || !p) return FALSE;
00798   ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
00799          db, p, qof_instance_get_dirty_flag(p),
00800          qof_instance_get_destroying(p));
00801 
00802   if (!qof_instance_books_equal(db, p))
00803   {
00804      PERR ("attempted to mix up prices across different books");
00805          LEAVE (" ");
00806      return FALSE;
00807   }
00808 
00809   commodity = gnc_price_get_commodity(p);
00810   if(!commodity) {
00811     PWARN("no commodity");
00812         LEAVE (" ");
00813     return FALSE;
00814   }
00815   currency = gnc_price_get_currency(p);
00816   if(!currency) {
00817     PWARN("no currency");
00818         LEAVE (" ");
00819     return FALSE;
00820   }
00821   if(!db->commodity_hash) { LEAVE ("no commodity hash found "); return FALSE; }
00822 
00823   currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
00824   if(!currency_hash) {
00825     currency_hash = g_hash_table_new(NULL, NULL);
00826     g_hash_table_insert(db->commodity_hash, commodity, currency_hash);
00827   }
00828 
00829   price_list = g_hash_table_lookup(currency_hash, currency);
00830   if(!gnc_price_list_insert(&price_list, p, !db->bulk_update)) 
00831   {
00832           LEAVE ("gnc_price_list_insert failed");
00833           return FALSE;
00834   }
00835   if(!price_list) 
00836   {
00837         LEAVE (" no price list");
00838         return FALSE;
00839   }
00840   g_hash_table_insert(currency_hash, currency, price_list);
00841   p->db = db;
00842   qof_event_gen (&p->inst, QOF_EVENT_ADD, NULL);
00843 
00844   LEAVE ("db=%p, pr=%p dirty=%d dextroying=%d commodity=%s/%s currency_hash=%p",
00845          db, p, qof_instance_get_dirty_flag(p),
00846          qof_instance_get_destroying(p),
00847          gnc_commodity_get_namespace(p->commodity),
00848          gnc_commodity_get_mnemonic(p->commodity),
00849          currency_hash);
00850   return TRUE;
00851 }
00852 
00853 /* the gnc_pricedb_add_price() function will use p, adding a ref, so
00854    treat p as read-only if this function succeeds. (Huh ???) */
00855 gboolean
00856 gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
00857 {
00858   if(!db || !p) return FALSE;
00859 
00860   ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
00861          db, p, qof_instance_get_dirty_flag(p),
00862          qof_instance_get_destroying(p));
00863 
00864   if (FALSE == add_price(db, p)) 
00865   {
00866           LEAVE (" failed to add price");
00867           return FALSE;
00868   }
00869 
00870   gnc_pricedb_begin_edit(db);
00871   qof_instance_set_dirty(&db->inst);
00872   gnc_pricedb_commit_edit(db);
00873 
00874   LEAVE ("db=%p, pr=%p dirty=%d destroying=%d",
00875          db, p, qof_instance_get_dirty_flag(p),
00876          qof_instance_get_destroying(p));
00877 
00878   return TRUE;
00879 }
00880 
00881 /* remove_price() is a utility; its only function is to remove the price
00882  * from the double-hash tables.
00883  */
00884 
00885 static gboolean
00886 remove_price(GNCPriceDB *db, GNCPrice *p, gboolean cleanup)
00887 {
00888   GList *price_list;
00889   gnc_commodity *commodity;
00890   gnc_commodity *currency;
00891   GHashTable *currency_hash;
00892 
00893   if(!db || !p) return FALSE;
00894   ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
00895          db, p, qof_instance_get_dirty_flag(p),
00896          qof_instance_get_destroying(p));
00897 
00898   commodity = gnc_price_get_commodity(p);
00899   if(!commodity) { LEAVE (" no commodity"); return FALSE; }
00900   currency = gnc_price_get_currency(p);
00901   if(!currency) { LEAVE (" no currency"); return FALSE;}
00902   if(!db->commodity_hash) 
00903   {
00904           LEAVE (" no commodity hash");
00905           return FALSE;
00906   }
00907 
00908   currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
00909   if(!currency_hash) { LEAVE (" no currency hash"); return FALSE; }
00910 
00911   qof_event_gen (&p->inst, QOF_EVENT_REMOVE, NULL);
00912   price_list = g_hash_table_lookup(currency_hash, currency);
00913   gnc_price_ref(p);
00914   if(!gnc_price_list_remove(&price_list, p)) {
00915     gnc_price_unref(p);
00916         LEAVE (" cannot remove price list");
00917     return FALSE;
00918   }
00919 
00920   /* if the price list is empty, then remove this currency from the
00921      commodity hash */
00922   if(price_list) {
00923     g_hash_table_insert(currency_hash, currency, price_list);
00924   } else {
00925     g_hash_table_remove(currency_hash, currency);
00926 
00927     if (cleanup) {
00928       /* chances are good that this commodity had only one currency.
00929        * If there are no currencies, we may as well destroy the
00930        * commodity too. */
00931       guint num_currencies = g_hash_table_size (currency_hash);
00932       if (0 == num_currencies) {
00933         g_hash_table_remove (db->commodity_hash, commodity);
00934         g_hash_table_destroy (currency_hash);
00935       }
00936     }
00937   }
00938 
00939   gnc_price_unref(p);
00940   LEAVE ("db=%p, pr=%p", db, p);
00941   return TRUE;
00942 }
00943 
00944 gboolean
00945 gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p)
00946 {
00947   gboolean rc;
00948   if(!db || !p) return FALSE;
00949   ENTER ("db=%p, pr=%p dirty=%d destroying=%d",
00950          db, p, qof_instance_get_dirty_flag(p),
00951          qof_instance_get_destroying(p));
00952 
00953   gnc_price_ref(p);
00954   rc = remove_price (db, p, TRUE);
00955   gnc_pricedb_begin_edit(db);
00956   qof_instance_set_dirty(&db->inst);
00957   gnc_pricedb_commit_edit(db);
00958 
00959   /* invoke the backend to delete this price */
00960   gnc_price_begin_edit (p);
00961   qof_instance_set_destroying(p, TRUE);
00962   gnc_price_commit_edit (p);
00963   p->db = NULL;
00964   gnc_price_unref(p);
00965   LEAVE ("db=%p, pr=%p", db, p);
00966   return rc;
00967 }
00968 
00969 typedef struct {
00970   GNCPriceDB *db;
00971   Timespec cutoff;
00972   gboolean delete_user;
00973   gboolean delete_last;
00974   GSList *list;
00975 } remove_info;
00976 
00977 static gboolean
00978 check_one_price_date (GNCPrice *price, gpointer user_data)
00979 {
00980   remove_info *data = user_data;
00981   const gchar *source;
00982   Timespec pt;
00983 
00984   ENTER("price %p (%s), data %p", price,
00985         gnc_commodity_get_mnemonic(gnc_price_get_commodity(price)),
00986         user_data);
00987   if (!data->delete_user) {
00988     source = gnc_price_get_source (price);
00989     if (safe_strcmp(source, "Finance::Quote") != 0) {
00990       LEAVE("Not an automatic quote");
00991       return TRUE;
00992     }
00993   }
00994 
00995   pt = gnc_price_get_time (price);
00996   {
00997     gchar buf[40];
00998     gnc_timespec_to_iso8601_buff(pt , buf);
00999     DEBUG("checking date %s", buf);
01000   }
01001   if (timespec_cmp (&pt, &data->cutoff) < 0) {
01002     data->list = g_slist_prepend(data->list, price);
01003     DEBUG("will delete");
01004   }
01005   LEAVE(" ");
01006   return TRUE;
01007 }
01008 
01009 static void
01010 pricedb_remove_foreach_pricelist (gpointer key,
01011                                   gpointer val,
01012                                   gpointer user_data)
01013 {
01014   GList *price_list = (GList *) val;
01015   GList *node = price_list;
01016   remove_info *data = (remove_info *) user_data;
01017 
01018   ENTER("key %p, value %p, data %p", key, val, user_data);
01019 
01020   /* The most recent price is the first in the list */
01021   if (!data->delete_last)
01022     node = g_list_next(node);
01023 
01024   /* now check each item in the list */
01025   g_list_foreach(node, (GFunc)check_one_price_date, data);
01026 
01027   LEAVE(" ");
01028 }
01029 
01030 static void
01031 pricedb_remove_foreach_currencies_hash (gpointer key,
01032                                         gpointer val,
01033                                         gpointer user_data)
01034 {
01035   GHashTable *currencies_hash = (GHashTable *) val;
01036 
01037   ENTER("key %p, value %p, data %p", key, val, user_data);
01038   g_hash_table_foreach(currencies_hash,
01039                        pricedb_remove_foreach_pricelist, user_data);
01040   LEAVE(" ");
01041 }
01042 
01043 
01044 gboolean
01045 gnc_pricedb_remove_old_prices(GNCPriceDB *db,
01046                               Timespec cutoff,
01047                               gboolean delete_user,
01048                               gboolean delete_last)
01049 {
01050   remove_info data;
01051   GSList *item;
01052 
01053   data.db = db;
01054   data.cutoff = cutoff;
01055   data.delete_user = delete_user;
01056   data.delete_last = delete_last;
01057   data.list = NULL;
01058 
01059   ENTER("db %p, delet_user %d, delete_last %d", db, delete_user, delete_last);
01060   {
01061     gchar buf[40];
01062     gnc_timespec_to_iso8601_buff(cutoff, buf);
01063     DEBUG("checking date %s", buf);
01064   }
01065 
01066   /* Traverse the database once building up an external list of prices
01067    * to be deleted */
01068   g_hash_table_foreach(db->commodity_hash,
01069                        pricedb_remove_foreach_currencies_hash,
01070                        &data);
01071 
01072   if (data.list == NULL)
01073     return FALSE;
01074 
01075   /* Now run this external list deleting prices */
01076   for (item = data.list; item; item = g_slist_next(item)) {
01077     gnc_pricedb_remove_price(db, item->data);
01078   }
01079 
01080   g_slist_free(data.list);
01081   LEAVE(" ");
01082   return TRUE;
01083 }
01084 
01085 /* ==================================================================== */
01086 /* lookup/query functions */
01087 
01088 GNCPrice *
01089 gnc_pricedb_lookup_latest(GNCPriceDB *db,
01090                           const gnc_commodity *commodity,
01091                           const gnc_commodity *currency)
01092 {
01093   GList *price_list;
01094   GNCPrice *result;
01095   GHashTable *currency_hash;
01096   QofBook *book;
01097   QofBackend *be;
01098 
01099   if(!db || !commodity || !currency) return NULL;
01100   ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
01101   book = qof_instance_get_book(&db->inst);
01102   be = qof_book_get_backend(book);
01103 #ifdef GNUCASH_MAJOR_VERSION
01104   if (be && be->price_lookup)
01105   {
01106      GNCPriceLookup pl;
01107      pl.type = LOOKUP_LATEST;
01108      pl.prdb = db;
01109      pl.commodity = commodity;
01110      pl.currency = currency;
01111      (be->price_lookup) (be, &pl);
01112   }
01113 #endif
01114 
01115   currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01116   if(!currency_hash) { LEAVE (" no currency hash"); return NULL; }
01117 
01118   price_list = g_hash_table_lookup(currency_hash, currency);
01119   if(!price_list) { LEAVE (" no price list"); return NULL; }
01120 
01121   /* This works magically because prices are inserted in date-sorted
01122    * order, and the latest date always comes first. So return the
01123    * first in the list.  */
01124   result = price_list->data;
01125   gnc_price_ref(result);
01126   LEAVE(" ");
01127   return result;
01128 }
01129 
01130 
01131 static void
01132 lookup_latest(gpointer key, gpointer val, gpointer user_data)
01133 {
01134   //gnc_commodity *currency = (gnc_commodity *)key;
01135   GList *price_list = (GList *)val;
01136   GList **return_list = (GList **)user_data;
01137 
01138   if(!price_list) return;
01139 
01140   /* the latest price is the first in list */
01141   gnc_price_list_insert(return_list, price_list->data, FALSE);
01142 }
01143 
01144 PriceList *
01145 gnc_pricedb_lookup_latest_any_currency(GNCPriceDB *db,
01146                                        const gnc_commodity *commodity)
01147 {
01148   GList *result;
01149   GHashTable *currency_hash;
01150   QofBook *book;
01151   QofBackend *be;
01152 
01153   result = NULL;
01154 
01155   if(!db || !commodity) return NULL;
01156   ENTER ("db=%p commodity=%p", db, commodity);
01157   book = qof_instance_get_book(&db->inst);
01158   be = qof_book_get_backend(book);
01159 #ifdef GNUCASH_MAJOR_VERSION
01160   if (be && be->price_lookup)
01161   {
01162      GNCPriceLookup pl;
01163      pl.type = LOOKUP_LATEST;
01164      pl.prdb = db;
01165      pl.commodity = commodity;
01166      pl.currency = NULL;  /* can the backend handle this??? */
01167                 (be->price_lookup) (be, &pl);
01168   }
01169 #endif
01170   currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01171   if(!currency_hash) { LEAVE (" no currency hash"); return NULL; }
01172 
01173   g_hash_table_foreach(currency_hash, lookup_latest, &result);
01174 
01175   if(!result) { LEAVE (" "); return NULL; }
01176 
01177   result = g_list_sort(result, compare_prices_by_date);
01178 
01179   LEAVE(" ");
01180   return result;
01181 }
01182 
01183 
01184 static void
01185 hash_values_helper(gpointer key, gpointer value, gpointer data)
01186 {
01187   GList ** l = data;
01188   *l = g_list_concat(*l, g_list_copy (value));
01189 }
01190 
01191 gboolean
01192 gnc_pricedb_has_prices(GNCPriceDB *db,
01193                        const gnc_commodity *commodity,
01194                        const gnc_commodity *currency)
01195 {
01196   GList *price_list;
01197   GHashTable *currency_hash;
01198   gint size;
01199   QofBook *book;
01200   QofBackend *be;
01201 
01202   if(!db || !commodity) return FALSE;
01203   ENTER ("db=%p commodity=%p currency=%p", db, commodity, currency);
01204   book = qof_instance_get_book(&db->inst);
01205   be = qof_book_get_backend(book);
01206 #ifdef GNUCASH_MAJOR_VERSION
01207   if (book && be && be->price_lookup)
01208   {
01209      GNCPriceLookup pl;
01210      pl.type = LOOKUP_ALL;
01211      pl.prdb = db;
01212      pl.commodity = commodity;
01213      pl.currency = currency;
01214      (be->price_lookup) (be, &pl);
01215   }
01216 #endif
01217   currency_hash = g_hash_table_lookup(db->commodity_hash, commodity);
01218   if(!currency_hash) {
01219     LEAVE("no, no currency_hash table");
01220     return FALSE;
01221   }
01222 
01223   if (currency) {
01224     price_list = g_hash_table_lookup(currency_hash, currency);
01225     if (price_list) {
01226       LEAVE("yes");
01227       return TRUE;
01228     }
01229     LEAVE("no, no price list");
01230     return FALSE;
01231   }
01232 
01233   size = g_hash_table_size (currency_hash);
01234   LEAVE("%s", size > 0 ? "yes" : "no");
01235   return size > 0;
01236 }
01237 
01238 
01239 PriceList *
01240 gnc_pricedb_get_prices(GNCPriceDB *db,
01241                        const gnc_commodity *commodity,
01242                        const