|
GnuCash 2.4.99
|
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(¤t_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(¤t_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 ============================== */
1.7.4