/******************************************************************** * gnc-commodity.c -- api for tradable commodities (incl. currency) * * Copyright (C) 2000 Bill Gribble * * Copyright (C) 2001,2003 Linas Vepstas * * Copyright (c) 2006 David Hampton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * *******************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "gnc-commodity.hpp" #include "gnc-commodity.h" #include "gnc-locale-utils.h" #include "gnc-prefs.h" #include "guid.h" #include "qofinstance.h" #include #include static QofLogModule log_module = GNC_MOD_COMMODITY; /* Parts per unit is nominal, i.e. number of 'partname' units in * a 'unitname' unit. fraction is transactional, i.e. how many * of the smallest-transactional-units of the currency are there * in a 'unitname' unit. */ enum { PROP_0, PROP_NAMESPACE, /* Table */ PROP_FULL_NAME, /* Table */ PROP_MNEMONIC, /* Table */ PROP_PRINTNAME, /* Constructed */ PROP_CUSIP, /* Table */ PROP_FRACTION, /* Table */ PROP_UNIQUE_NAME, /* Constructed */ PROP_QUOTE_FLAG, /* Table */ PROP_QUOTE_SOURCE, /* Table */ PROP_QUOTE_TZ, /* Table */ }; struct gnc_commodity_s { QofInstance inst; }; typedef struct gnc_commodityPrivate { gnc_commodity_namespace *name_space; const char *fullname; const char *mnemonic; char *printname; const char *cusip; /* CUSIP or other identifying code */ int fraction; char *unique_name; char *user_symbol; gboolean quote_flag; /* user wants price quotes */ gnc_quote_source *quote_source; /* current/old source of quotes */ const char *quote_tz; /* the number of accounts using this commodity - this field is not * persisted */ int usage_count; /* the default display_symbol, set in iso-4217-currencies at start-up */ const char *default_symbol; } gnc_commodityPrivate; #define GET_PRIVATE(o) \ ((gnc_commodityPrivate*)gnc_commodity_get_instance_private((gnc_commodity*)o)) struct _GncCommodityClass { QofInstanceClass parent_class; }; static void commodity_free(gnc_commodity * cm); static void gnc_commodity_set_default_symbol(gnc_commodity *, const char *); struct gnc_commodity_namespace_s { QofInstance inst; const gchar *name; gboolean iso4217; GHashTable * cm_table; GList * cm_list; }; struct _GncCommodityNamespaceClass { QofInstanceClass parent_class; }; struct gnc_commodity_table_s { GHashTable * ns_table; GList * ns_list; }; static const std::unordered_map gnc_new_iso_codes = { {"RUR", "RUB"}, /* Russian Ruble: RUR through 1997-12, RUB from 1998-01 onwards; see bug #393185 */ {"PLZ", "PLN"}, /* Polish Zloty */ {"UAG", "UAH"}, /* Ukraine Hryvnia */ {"NIS", "ILS"}, /* New Israeli Shekel: The informal abbreviation may be "NIS", but its iso-4217 is clearly ILS and only this! Incorrectly changed due to bug#152755 (Nov 2004) and changed back again by bug#492417 (Oct 2008). */ {"MXP", "MXN"}, /* Mexican (Nuevo) Peso */ {"TRL", "TRY"}, /* New Turkish Lira: changed 2005 */ /* Only add currencies to this table when the old currency no longer * exists in the file iso-4217-currencies.xml */ }; static std::string fq_version; struct gnc_quote_source_s { private: gboolean m_supported; QuoteSourceType m_type; std::string m_user_name; /* User friendly name incl. region code*/ std::string m_internal_name; /* Name used internally and by finance::quote. */ public: bool get_supported () const { return m_supported; } void set_supported (bool supported) { m_supported = supported; } QuoteSourceType get_type () const { return m_type; } const char* get_user_name () const { return m_user_name.c_str(); } const char* get_internal_name () const { return m_internal_name.c_str(); } gnc_quote_source_s (gboolean supported, QuoteSourceType type, const char* username, const char* int_name) : m_supported{supported} , m_type{type} , m_user_name{username ? username : ""} , m_internal_name{int_name ? int_name: ""} { }; }; using QuoteSourceList = std::list; /* To update the following lists scan * from github.com/finance-quote/finance-quote * in lib/Finance/Quote/ all *.pm for "methods" * because many of them have more than one - * ideally after each release of them. * * Apply changes here also to the FQ appendix of help. */ static QuoteSourceList currency_quote_sources = { { true, SOURCE_CURRENCY, "Currency", "currency" } }; /* The single quote method is usually the module name, but * sometimes it gets the suffix "_direct" * and the failover method is without suffix. */ static QuoteSourceList single_quote_sources = { { false, SOURCE_SINGLE, "Alphavantage, US", "alphavantage" }, { false, SOURCE_SINGLE, "Amsterdam Euronext eXchange, NL", "aex" }, { false, SOURCE_SINGLE, "Association of Mutual Funds in India", "amfiindia" }, { false, SOURCE_SINGLE, "Australian Stock Exchange, AU", "asx" }, { false, SOURCE_SINGLE, "Canada Mutual", "canadamutual" }, { false, SOURCE_SINGLE, "Deka Investments, DE", "deka" }, { false, SOURCE_SINGLE, "Dutch", "dutch" }, { false, SOURCE_SINGLE, "DWS, DE", "dwsfunds" }, { false, SOURCE_SINGLE, "Financial Times Funds service, GB", "ftfunds" }, { false, SOURCE_SINGLE, "Finanzpartner, DE", "finanzpartner" }, { false, SOURCE_SINGLE, "GoldMoney spot rates, JE", "goldmoney" }, { false, SOURCE_SINGLE, "Google Web, US Stocks", "googleweb" }, { false, SOURCE_SINGLE, "India Mutual", "indiamutual" }, { false, SOURCE_SINGLE, "Morningstar, GB", "morningstaruk" }, { false, SOURCE_SINGLE, "Morningstar, JP", "morningstarjp" }, { false, SOURCE_SINGLE, "New Zealand stock eXchange, NZ", "nzx" }, { false, SOURCE_SINGLE, "Paris Stock Exchange/Boursorama, FR", "bourso" }, { false, SOURCE_SINGLE, "Romania", "romania" }, { false, SOURCE_SINGLE, "SIX Swiss Exchange shares, CH", "six" }, { false, SOURCE_SINGLE, "Skandinaviska Enskilda Banken, SE", "seb_funds" }, { false, SOURCE_SINGLE, "Sharenet, ZA", "za" }, { false, SOURCE_SINGLE, "TIAA-CREF, US", "tiaacref" }, { false, SOURCE_SINGLE, "Toronto Stock eXchange, CA", "tsx" }, { false, SOURCE_SINGLE, "T. Rowe Price", "troweprice" }, { false, SOURCE_SINGLE, "T. Rowe Price, US", "troweprice_direct" }, { false, SOURCE_SINGLE, "Union Investment, DE", "unionfunds" }, { false, SOURCE_SINGLE, "US Govt. Thrift Savings Plan", "tsp" }, { false, SOURCE_SINGLE, "Yahoo as JSON", "yahoo_json" }, { false, SOURCE_SINGLE, "Yahoo Web", "yahooweb" }, }; static QuoteSourceList multiple_quote_sources = { { false, SOURCE_MULTI, "Australia (ASX, ...)", "australia" }, { false, SOURCE_MULTI, "Canada (Alphavantage, TSX, ...)", "canada" }, { false, SOURCE_MULTI, "Canada Mutual (Fund Library, StockHouse, ...)", "canadamutual" }, { false, SOURCE_MULTI, "Dutch (AEX, ...)", "dutch" }, { false, SOURCE_MULTI, "Europe (asegr,.bsero, hex ...)", "europe" }, { false, SOURCE_MULTI, "India Mutual (AMFI, ...)", "indiamutual" }, { false, SOURCE_MULTI, "France (bourso, ĺerevenu, ...)", "france" }, { false, SOURCE_MULTI, "Nasdaq (alphavantage, yahoo_json, ...)", "nasdaq" }, { false, SOURCE_MULTI, "NYSE (alphavantage, yahoo_json, ...)", "nyse" }, { false, SOURCE_MULTI, "South Africa (Sharenet, ...)", "za" }, { false, SOURCE_MULTI, "Romania (BSE-RO, ...)", "romania" }, { false, SOURCE_MULTI, "T. Rowe Price", "troweprice" }, { false, SOURCE_MULTI, "U.K. Funds (citywire, FTfunds, MStar, tnetuk, ...)", "ukfunds" }, { false, SOURCE_MULTI, "USA (alphavantage, yahoo_json, ...)", "usa" }, }; static QuoteSourceList new_quote_sources; // cannot use map or unordered_map because order must be preserved static const std::vector> quote_sources_map = { { SOURCE_CURRENCY, currency_quote_sources }, { SOURCE_SINGLE, single_quote_sources }, { SOURCE_MULTI, multiple_quote_sources }, { SOURCE_UNKNOWN, new_quote_sources } }; /******************************************************************** * gnc_quote_source_fq_installed * * This function indicates whether or not the Finance::Quote module * is installed on a users computer. ********************************************************************/ gboolean gnc_quote_source_fq_installed (void) { return (!fq_version.empty()); } /******************************************************************** * gnc_quote_source_fq_version * * This function the version of the Finance::Quote module installed * on a user's computer or nullptr if no installation is found. ********************************************************************/ const char* gnc_quote_source_fq_version (void) { return fq_version.c_str(); } static QuoteSourceList& get_quote_source_from_type (QuoteSourceType type) { auto quote_sources_it = std::find_if (quote_sources_map.begin(), quote_sources_map.end(), [type] (const auto& qs) { return type == qs.first; }); if (quote_sources_it != quote_sources_map.end()) return quote_sources_it->second; PWARN ("Invalid Quote Source %d, returning new_quote_sources", type); return new_quote_sources; } /******************************************************************** * gnc_quote_source_num_entries * * Return the number of entries for a given type of price source. ********************************************************************/ gint gnc_quote_source_num_entries(QuoteSourceType type) { auto source{get_quote_source_from_type(type)}; return std::distance(source.begin(), source.end()); } /******************************************************************** * gnc_quote_source_add_new * * Add a new price source. Called when unknown source names are found * either in the F::Q installation (a newly available source) or in * the user's data file (a source that has vanished but needs to be * tracked.) ********************************************************************/ gnc_quote_source * gnc_quote_source_add_new (const char *source_name, gboolean supported) { DEBUG("Creating new source %s", (!source_name ? "(null)" : source_name)); /* This name can be changed if/when support for this price source is * integrated into gnucash. */ /* This name is permanent and must be kept the same if/when support * for this price source is integrated into gnucash (i.e. for a * nice user name). */ return &new_quote_sources.emplace_back (supported, SOURCE_UNKNOWN, source_name, source_name); } /******************************************************************** * gnc_quote_source_lookup_by_xxx * * Lookup a price source data structure based upon various criteria. ********************************************************************/ gnc_quote_source * gnc_quote_source_lookup_by_ti (QuoteSourceType type, gint index) { ENTER("type/index is %d/%d", type, index); auto& sources = get_quote_source_from_type (type); if ((size_t) index < sources.size()) { auto it = std::next(sources.begin(), index); LEAVE("found %s", it->get_user_name()); return &*it; } LEAVE("not found"); return nullptr; } gnc_quote_source * gnc_quote_source_lookup_by_internal(const char * name) { if (!name || !*name) return nullptr; for (const auto& [_, sources] : quote_sources_map) { auto source_it = std::find_if (sources.begin(), sources.end(), [name] (const auto& qs) { return (g_strcmp0(name, qs.get_internal_name()) == 0); }); if (source_it != sources.end()) return &(*source_it); } DEBUG("gnc_quote_source_lookup_by_internal: Unknown source %s", name); return nullptr; } /******************************************************************** * gnc_quote_source_get_xxx * * Accessor functions - get functions only. There are no set functions. ********************************************************************/ QuoteSourceType gnc_quote_source_get_type (const gnc_quote_source *source) { ENTER("%p", source); if (!source) { LEAVE("bad source"); return SOURCE_SINGLE; } LEAVE("type is %d", source->get_type()); return source->get_type(); } gint gnc_quote_source_get_index (const gnc_quote_source *source) { if (!source) { PWARN ("bad source"); return 0; } auto& sources = get_quote_source_from_type (source->get_type()); auto is_source = [&source](const auto& findif_source) { return &findif_source == source; }; auto iter = std::find_if (sources.begin(), sources.end(), is_source); if (iter != sources.end()) return std::distance (sources.begin(), iter); PWARN ("couldn't locate source"); return 0; } gboolean gnc_quote_source_get_supported (const gnc_quote_source *source) { ENTER("%p", source); if (!source) { LEAVE("bad source"); return FALSE; } LEAVE("%s supported", source && source->get_supported() ? "" : "not "); return source->get_supported(); } const char * gnc_quote_source_get_user_name (const gnc_quote_source *source) { ENTER("%p", source); if (!source) { LEAVE("bad source"); return nullptr; } LEAVE("user name %s", source->get_user_name()); return source->get_user_name(); } const char * gnc_quote_source_get_internal_name (const gnc_quote_source *source) { ENTER("%p", source); if (!source) { LEAVE("bad source"); return nullptr; } LEAVE("internal name %s", source->get_internal_name()); return source->get_internal_name(); } /******************************************************************** * gnc_quote_source_set_fq_installed * * Update gnucash internal tables on what Finance::Quote sources are * installed. ********************************************************************/ void gnc_quote_source_set_fq_installed (const char* version_string, const std::vector& sources_list) { ENTER(" "); if (sources_list.empty()) return; if (version_string) fq_version = version_string; else fq_version.clear(); for (const auto& source_name_str : sources_list) { auto source_name = source_name_str.c_str(); auto source = gnc_quote_source_lookup_by_internal(source_name); if (source) { DEBUG("Found source %s: %s", source_name, source->get_user_name()); source->set_supported (true); continue; } gnc_quote_source_add_new(source_name, TRUE); } LEAVE(" "); } /******************************************************************** * QoF Helpers ********************************************************************/ void gnc_commodity_begin_edit (gnc_commodity *cm) { qof_begin_edit(&cm->inst); } static void commit_err (QofInstance *inst, QofBackendError errcode) { PERR ("Failed to commit: %d", errcode); gnc_engine_signal_commit_error( errcode ); } static void noop (QofInstance *inst) {} static void comm_free(QofInstance* inst) { commodity_free( GNC_COMMODITY(inst) ); } void gnc_commodity_commit_edit (gnc_commodity *cm) { if (!qof_commit_edit (QOF_INSTANCE(cm))) return; qof_commit_edit_part2 (&cm->inst, commit_err, noop, comm_free); } /******************************************************************** * gnc_commodity_new ********************************************************************/ static void mark_commodity_dirty (gnc_commodity *cm) { qof_instance_set_dirty(&cm->inst); qof_event_gen (&cm->inst, QOF_EVENT_MODIFY, nullptr); } static void reset_printname(gnc_commodityPrivate *priv) { g_free(priv->printname); priv->printname = g_strdup_printf("%s (%s)", priv->mnemonic ? priv->mnemonic : "", priv->fullname ? priv->fullname : ""); } static void reset_unique_name(gnc_commodityPrivate *priv) { gnc_commodity_namespace *ns; g_free(priv->unique_name); ns = priv->name_space; priv->unique_name = g_strdup_printf("%s::%s", ns ? ns->name : "", priv->mnemonic ? priv->mnemonic : ""); } /* GObject Initialization */ G_DEFINE_TYPE_WITH_PRIVATE(gnc_commodity, gnc_commodity, QOF_TYPE_INSTANCE) static void gnc_commodity_init(gnc_commodity* com) { gnc_commodityPrivate* priv; priv = GET_PRIVATE(com); priv->name_space = nullptr; priv->fullname = CACHE_INSERT(""); priv->mnemonic = CACHE_INSERT(""); priv->cusip = CACHE_INSERT(""); priv->fraction = 10000; priv->quote_flag = 0; priv->quote_source = nullptr; priv->quote_tz = CACHE_INSERT(""); reset_printname(priv); reset_unique_name(priv); } static void gnc_commodity_dispose(GObject *comp) { G_OBJECT_CLASS(gnc_commodity_parent_class)->dispose(comp); } static void gnc_commodity_finalize(GObject* comp) { G_OBJECT_CLASS(gnc_commodity_parent_class)->finalize(comp); } /* Note that g_value_set_object() refs the object, as does * g_object_get(). But g_object_get() only unrefs once when it disgorges * the object, leaving an unbalanced ref, which leaks. So instead of * using g_value_set_object(), use g_value_take_object() which doesn't * ref the object when used in get_property(). */ static void gnc_commodity_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { gnc_commodity *commodity; gnc_commodityPrivate* priv; g_return_if_fail(GNC_IS_COMMODITY(object)); commodity = GNC_COMMODITY(object); priv = GET_PRIVATE(commodity); switch (prop_id) { case PROP_NAMESPACE: g_value_take_object(value, priv->name_space); break; case PROP_FULL_NAME: g_value_set_string(value, priv->fullname); break; case PROP_MNEMONIC: g_value_set_string(value, priv->mnemonic); break; case PROP_PRINTNAME: g_value_set_string(value, priv->printname); break; case PROP_CUSIP: g_value_set_string(value, priv->cusip); break; case PROP_FRACTION: g_value_set_int(value, priv->fraction); break; case PROP_UNIQUE_NAME: g_value_set_string(value, priv->unique_name); break; case PROP_QUOTE_FLAG: g_value_set_boolean(value, priv->quote_flag); break; case PROP_QUOTE_SOURCE: g_value_set_pointer(value, priv->quote_source); break; case PROP_QUOTE_TZ: g_value_set_string(value, priv->quote_tz); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_commodity_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { gnc_commodity *commodity; g_return_if_fail(GNC_IS_COMMODITY(object)); commodity = GNC_COMMODITY(object); g_assert (qof_instance_get_editlevel(commodity)); switch (prop_id) { case PROP_NAMESPACE: gnc_commodity_set_namespace(commodity, static_cast(g_value_get_object(value))); break; case PROP_FULL_NAME: gnc_commodity_set_fullname(commodity, g_value_get_string(value)); break; case PROP_MNEMONIC: gnc_commodity_set_mnemonic(commodity, g_value_get_string(value)); break; case PROP_CUSIP: gnc_commodity_set_cusip(commodity, g_value_get_string(value)); break; case PROP_FRACTION: gnc_commodity_set_fraction(commodity, g_value_get_int(value)); break; case PROP_QUOTE_FLAG: gnc_commodity_set_quote_flag(commodity, g_value_get_boolean(value)); break; case PROP_QUOTE_SOURCE: gnc_commodity_set_quote_source(commodity, static_cast(g_value_get_pointer(value))); break; case PROP_QUOTE_TZ: gnc_commodity_set_quote_tz(commodity, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_commodity_class_init(struct _GncCommodityClass* klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = gnc_commodity_dispose; gobject_class->finalize = gnc_commodity_finalize; gobject_class->set_property = gnc_commodity_set_property; gobject_class->get_property = gnc_commodity_get_property; g_object_class_install_property(gobject_class, PROP_NAMESPACE, g_param_spec_object ("namespace", "Namespace", "The namespace field denotes the " "namespace for this commodity, either " "a currency or symbol from a quote source.", GNC_TYPE_COMMODITY_NAMESPACE, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_FULL_NAME, g_param_spec_string ("fullname", "Full Commodity Name", "The fullname is the official full name of" "the currency.", nullptr, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_MNEMONIC, g_param_spec_string ("mnemonic", "Commodity Mnemonic", "The mnemonic is the official abbreviated" "designation for the currency.", nullptr, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_PRINTNAME, g_param_spec_string ("printname", "Commodity Print Name", "Printable form of the commodity name.", nullptr, G_PARAM_READABLE)); g_object_class_install_property(gobject_class, PROP_CUSIP, g_param_spec_string ("cusip", "Commodity CUSIP Code", "?????", nullptr, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_FRACTION, g_param_spec_int ("fraction", "Fraction", "The fraction is the number of sub-units that " "the basic commodity can be divided into.", 1, GNC_COMMODITY_MAX_FRACTION, 1, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_UNIQUE_NAME, g_param_spec_string ("unique-name", "Commodity Unique Name", "Unique form of the commodity name which combines " "the namespace name and the commodity name.", nullptr, G_PARAM_READABLE)); g_object_class_install_property(gobject_class, PROP_QUOTE_FLAG, g_param_spec_boolean ("quote_flag", "Quote Flag", "TRUE if prices are to be downloaded for this " "commodity from a quote source.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_QUOTE_SOURCE, g_param_spec_pointer("quote-source", "Quote Source", "The quote source from which prices are downloaded.", G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_QUOTE_TZ, g_param_spec_string ("quote-tz", "Commodity Quote Timezone", "?????", nullptr, G_PARAM_READWRITE)); } gnc_commodity * gnc_commodity_new(QofBook *book, const char * fullname, const char * name_space, const char * mnemonic, const char * cusip, int fraction) { auto retval = GNC_COMMODITY(g_object_new(GNC_TYPE_COMMODITY, nullptr)); qof_instance_init_data (&retval->inst, GNC_ID_COMMODITY, book); gnc_commodity_begin_edit(retval); if ( name_space != nullptr ) { /* Prevent setting anything except template in namespace template. */ if (g_strcmp0 (name_space, GNC_COMMODITY_NS_TEMPLATE) == 0 && g_strcmp0 (mnemonic, "template") != 0) { PWARN("Converting commodity %s from namespace template to " "namespace User", mnemonic); name_space = "User"; } gnc_commodity_set_namespace(retval, name_space); if (gnc_commodity_namespace_is_iso(name_space)) { gnc_commodity_set_quote_source(retval, gnc_quote_source_lookup_by_internal("currency") ); } } gnc_commodity_set_fullname(retval, fullname); gnc_commodity_set_mnemonic(retval, mnemonic); gnc_commodity_set_cusip(retval, cusip); gnc_commodity_set_fraction(retval, fraction); mark_commodity_dirty (retval); gnc_commodity_commit_edit(retval); qof_event_gen (&retval->inst, QOF_EVENT_CREATE, nullptr); return retval; } /******************************************************************** * gnc_commodity_destroy ********************************************************************/ static void commodity_free(gnc_commodity * cm) { QofBook *book; gnc_commodity_table *table; gnc_commodityPrivate* priv; if (!cm) return; book = qof_instance_get_book(&cm->inst); table = gnc_commodity_table_get_table(book); gnc_commodity_table_remove(table, cm); priv = GET_PRIVATE(cm); qof_event_gen (&cm->inst, QOF_EVENT_DESTROY, nullptr); /* Set at creation */ CACHE_REMOVE (priv->fullname); CACHE_REMOVE (priv->cusip); CACHE_REMOVE (priv->mnemonic); CACHE_REMOVE (priv->quote_tz); priv->name_space = nullptr; /* Set through accessor functions */ priv->quote_source = nullptr; /* Automatically generated */ g_free(priv->printname); priv->printname = nullptr; g_free(priv->unique_name); priv->unique_name = nullptr; #ifdef ACCOUNTS_CLEANED_UP /* Account objects are not actually cleaned up when a book is closed (in fact * a memory leak), but commodities are, so in currently this warning gets hit * quite frequently. Disable the check until cleaning up of accounts objects * on close is implemented. */ if (priv->usage_count != 0) { PWARN("Destroying commodity (%p) with non-zero usage_count (%d).", cm, priv->usage_count); } #endif /* qof_instance_release (&cm->inst); */ g_object_unref(cm); } void gnc_commodity_destroy(gnc_commodity * cm) { gnc_commodity_begin_edit(cm); qof_instance_set_destroying(cm, TRUE); gnc_commodity_commit_edit(cm); } void gnc_commodity_copy(gnc_commodity * dest, const gnc_commodity *src) { gnc_commodityPrivate* src_priv = GET_PRIVATE(src); gnc_commodityPrivate* dest_priv = GET_PRIVATE(dest); gnc_commodity_set_fullname (dest, src_priv->fullname); gnc_commodity_set_mnemonic (dest, src_priv->mnemonic); dest_priv->name_space = src_priv->name_space; gnc_commodity_set_fraction (dest, src_priv->fraction); gnc_commodity_set_cusip (dest, src_priv->cusip); gnc_commodity_set_quote_flag (dest, src_priv->quote_flag); gnc_commodity_set_quote_source (dest, gnc_commodity_get_quote_source (src)); gnc_commodity_set_quote_tz (dest, src_priv->quote_tz); qof_instance_copy_kvp (QOF_INSTANCE (dest), QOF_INSTANCE (src)); } gnc_commodity * gnc_commodity_clone(const gnc_commodity *src, QofBook *dest_book) { gnc_commodityPrivate* src_priv; gnc_commodityPrivate* dest_priv; auto dest = GNC_COMMODITY (g_object_new(GNC_TYPE_COMMODITY, nullptr)); qof_instance_init_data (&dest->inst, GNC_ID_COMMODITY, dest_book); src_priv = GET_PRIVATE(src); dest_priv = GET_PRIVATE(dest); dest_priv->fullname = CACHE_INSERT(src_priv->fullname); dest_priv->mnemonic = CACHE_INSERT(src_priv->mnemonic); dest_priv->cusip = CACHE_INSERT(src_priv->cusip); dest_priv->quote_tz = CACHE_INSERT(src_priv->quote_tz); dest_priv->name_space = src_priv->name_space; dest_priv->fraction = src_priv->fraction; dest_priv->quote_flag = src_priv->quote_flag; gnc_commodity_set_quote_source (dest, gnc_commodity_get_quote_source (src)); qof_instance_copy_kvp (QOF_INSTANCE (dest), QOF_INSTANCE (src)); reset_printname(dest_priv); reset_unique_name(dest_priv); return dest; } /******************************************************************** * gnc_commodity_get_mnemonic ********************************************************************/ const char * gnc_commodity_get_mnemonic(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->mnemonic; } /******************************************************************** * gnc_commodity_get_printname ********************************************************************/ const char * gnc_commodity_get_printname(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->printname; } /******************************************************************** * gnc_commodity_get_namespace ********************************************************************/ const char * gnc_commodity_get_namespace(const gnc_commodity * cm) { if (!cm) return nullptr; return gnc_commodity_namespace_get_name(GET_PRIVATE(cm)->name_space); } gnc_commodity_namespace * gnc_commodity_get_namespace_ds(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->name_space; } /******************************************************************** * gnc_commodity_get_fullname ********************************************************************/ const char * gnc_commodity_get_fullname(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->fullname; } /******************************************************************** * gnc_commodity_get_unique_name ********************************************************************/ const char * gnc_commodity_get_unique_name(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->unique_name; } /******************************************************************** * gnc_commodity_get_cusip ********************************************************************/ const char * gnc_commodity_get_cusip(const gnc_commodity * cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->cusip; } /******************************************************************** * gnc_commodity_get_fraction ********************************************************************/ int gnc_commodity_get_fraction(const gnc_commodity * cm) { if (!cm) return 0; return GET_PRIVATE(cm)->fraction; } /******************************************************************** * gnc_commodity_get_auto_quote_control_flag ********************************************************************/ static gboolean gnc_commodity_get_auto_quote_control_flag(const gnc_commodity *cm) { GValue v = G_VALUE_INIT; gboolean retval = TRUE; if (!cm) return FALSE; qof_instance_get_kvp (QOF_INSTANCE (cm), &v, 1, "auto_quote_control"); if (G_VALUE_HOLDS_STRING (&v) && strcmp(g_value_get_string (&v), "false") == 0) retval = FALSE; g_value_unset (&v); return retval; } /******************************************************************** * gnc_commodity_get_quote_flag ********************************************************************/ gboolean gnc_commodity_get_quote_flag(const gnc_commodity *cm) { if (!cm) return FALSE; return (GET_PRIVATE(cm)->quote_flag); } /******************************************************************** * gnc_commodity_get_quote_source ********************************************************************/ gnc_quote_source* gnc_commodity_get_quote_source(const gnc_commodity *cm) { gnc_commodityPrivate* priv; if (!cm) return nullptr; priv = GET_PRIVATE(cm); if (!priv->quote_source && gnc_commodity_is_iso(cm)) return ¤cy_quote_sources.front(); return priv->quote_source; } gnc_quote_source* gnc_commodity_get_default_quote_source(const gnc_commodity *cm) { if (cm && gnc_commodity_is_iso(cm)) return ¤cy_quote_sources.front(); /* Should make this a user option at some point. */ return gnc_quote_source_lookup_by_internal("alphavantage"); } /******************************************************************** * gnc_commodity_get_quote_tz ********************************************************************/ const char* gnc_commodity_get_quote_tz(const gnc_commodity *cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->quote_tz; } /******************************************************************** * gnc_commodity_get_user_symbol ********************************************************************/ const char* gnc_commodity_get_user_symbol(const gnc_commodity *cm) { g_return_val_if_fail (GNC_IS_COMMODITY (cm), nullptr); GValue v = G_VALUE_INIT; qof_instance_get_kvp (QOF_INSTANCE(cm), &v, 1, "user_symbol"); const char *rv = G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : nullptr; g_value_unset (&v); return rv; } /******************************************************************** * gnc_commodity_get_default_symbol *******************************************************************/ const char* gnc_commodity_get_default_symbol(const gnc_commodity *cm) { if (!cm) return nullptr; return GET_PRIVATE(cm)->default_symbol; } /******************************************************************** * gnc_commodity_get_nice_symbol *******************************************************************/ const char* gnc_commodity_get_nice_symbol (const gnc_commodity *cm) { const char *nice_symbol; struct lconv *lc; if (!cm) return nullptr; nice_symbol = gnc_commodity_get_user_symbol(cm); if (nice_symbol && *nice_symbol) return nice_symbol; lc = gnc_localeconv(); nice_symbol = lc->currency_symbol; if (!g_strcmp0(gnc_commodity_get_mnemonic(cm), lc->int_curr_symbol)) return nice_symbol; nice_symbol = gnc_commodity_get_default_symbol(cm); if (nice_symbol && *nice_symbol) return nice_symbol; return gnc_commodity_get_mnemonic(cm); } /******************************************************************** * gnc_commodity_set_mnemonic ********************************************************************/ void gnc_commodity_set_mnemonic(gnc_commodity * cm, const char * mnemonic) { gnc_commodityPrivate* priv; if (!cm) return; priv = GET_PRIVATE(cm); if (priv->mnemonic == mnemonic) return; gnc_commodity_begin_edit(cm); CACHE_REMOVE (priv->mnemonic); priv->mnemonic = CACHE_INSERT(mnemonic); mark_commodity_dirty (cm); reset_printname(priv); reset_unique_name(priv); gnc_commodity_commit_edit(cm); } /******************************************************************** * gnc_commodity_set_namespace ********************************************************************/ void gnc_commodity_set_namespace(gnc_commodity * cm, const char * name_space) { QofBook *book; gnc_commodity_table *table; gnc_commodity_namespace *nsp; gnc_commodityPrivate* priv; if (!cm) return; priv = GET_PRIVATE(cm); book = qof_instance_get_book (&cm->inst); table = gnc_commodity_table_get_table(book); nsp = gnc_commodity_table_add_namespace(table, name_space, book); if (priv->name_space == nsp) return; gnc_commodity_begin_edit(cm); priv->name_space = nsp; if (nsp->iso4217) priv->quote_source = gnc_quote_source_lookup_by_internal("currency"); mark_commodity_dirty(cm); reset_printname(priv); reset_unique_name(priv); gnc_commodity_commit_edit(cm); } /******************************************************************** * gnc_commodity_set_fullname ********************************************************************/ void gnc_commodity_set_fullname(gnc_commodity * cm, const char * fullname) { gnc_commodityPrivate* priv; if (!cm) return; priv = GET_PRIVATE(cm); if (priv->fullname == fullname) return; CACHE_REMOVE (priv->fullname); priv->fullname = CACHE_INSERT (fullname); gnc_commodity_begin_edit(cm); mark_commodity_dirty(cm); reset_printname(priv); gnc_commodity_commit_edit(cm); } /******************************************************************** * gnc_commodity_set_cusip ********************************************************************/ void gnc_commodity_set_cusip(gnc_commodity * cm, const char * cusip) { gnc_commodityPrivate* priv; if (!cm) return; priv = GET_PRIVATE(cm); if (priv->cusip == cusip) return; gnc_commodity_begin_edit(cm); CACHE_REMOVE (priv->cusip); priv->cusip = CACHE_INSERT (cusip); mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); } /******************************************************************** * gnc_commodity_set_fraction ********************************************************************/ void gnc_commodity_set_fraction(gnc_commodity * cm, int fraction) { if (!cm) return; gnc_commodity_begin_edit(cm); GET_PRIVATE(cm)->fraction = fraction; mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); } /******************************************************************** * gnc_commodity_set_auto_quote_control_flag ********************************************************************/ static void gnc_commodity_set_auto_quote_control_flag(gnc_commodity *cm, const gboolean flag) { GValue v = G_VALUE_INIT; ENTER ("(cm=%p, flag=%d)", cm, flag); if (!cm) { LEAVE(""); return; } gnc_commodity_begin_edit(cm); if (flag) qof_instance_set_kvp (QOF_INSTANCE (cm), nullptr, 1, "auto_quote_control"); else { g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, "false"); qof_instance_set_kvp (QOF_INSTANCE (cm), &v, 1, "auto_quote_control"); } g_value_unset (&v); mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); LEAVE(""); } /******************************************************************** * gnc_commodity_user_set_quote_flag ********************************************************************/ void gnc_commodity_user_set_quote_flag(gnc_commodity *cm, const gboolean flag) { gnc_commodityPrivate* priv; ENTER ("(cm=%p, flag=%d)", cm, flag); if (!cm) { LEAVE(""); return; } priv = GET_PRIVATE(cm); gnc_commodity_begin_edit(cm); gnc_commodity_set_quote_flag(cm, flag); if (gnc_commodity_is_iso(cm)) { /* For currencies, disable auto quote control if the quote flag is being * changed from its default value and enable it if the quote flag is being * reset to its default value. The defaults for the quote flag are * disabled if no accounts are using the currency, and true otherwise. * Thus enable auto quote control if flag is FALSE and there are not any * accounts using this currency OR flag is TRUE and there are accounts * using this currency; otherwise disable auto quote control */ gnc_commodity_set_auto_quote_control_flag(cm, (!flag && (priv->usage_count == 0)) || (flag && (priv->usage_count != 0))); } gnc_commodity_commit_edit(cm); LEAVE(""); } /******************************************************************** * gnc_commodity_set_quote_flag ********************************************************************/ void gnc_commodity_set_quote_flag(gnc_commodity *cm, const gboolean flag) { ENTER ("(cm=%p, flag=%d)", cm, flag); if (!cm) return; gnc_commodity_begin_edit(cm); GET_PRIVATE(cm)->quote_flag = flag; mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); LEAVE(" "); } /******************************************************************** * gnc_commodity_set_quote_source ********************************************************************/ void gnc_commodity_set_quote_source(gnc_commodity *cm, gnc_quote_source *src) { ENTER ("(cm=%p, src=%p(%s))", cm, src, src ? src->get_internal_name() : "unknown"); if (!cm) return; gnc_commodity_begin_edit(cm); GET_PRIVATE(cm)->quote_source = src; mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); LEAVE(" "); } /******************************************************************** * gnc_commodity_set_quote_tz ********************************************************************/ void gnc_commodity_set_quote_tz(gnc_commodity *cm, const char *tz) { gnc_commodityPrivate* priv; if (!cm) return; ENTER ("(cm=%p, tz=%s)", cm, tz ? tz : "(null)"); priv = GET_PRIVATE(cm); if (tz == priv->quote_tz) { LEAVE("Already correct TZ"); return; } gnc_commodity_begin_edit(cm); CACHE_REMOVE (priv->quote_tz); priv->quote_tz = CACHE_INSERT (tz); mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); LEAVE(" "); } /******************************************************************** * gnc_commodity_set_user_symbol ********************************************************************/ void gnc_commodity_set_user_symbol(gnc_commodity * cm, const char * user_symbol) { struct lconv *lc; if (!cm) return; ENTER ("(cm=%p, symbol=%s)", cm, user_symbol ? user_symbol : "(null)"); lc = gnc_localeconv(); if (!user_symbol || !*user_symbol) user_symbol = nullptr; else if (!g_strcmp0(lc->int_curr_symbol, gnc_commodity_get_mnemonic(cm)) && !g_strcmp0(lc->currency_symbol, user_symbol)) /* if the user gives the ISO symbol for the locale currency or the * default symbol, actually remove the user symbol */ user_symbol = nullptr; else if (!g_strcmp0(user_symbol, gnc_commodity_get_default_symbol(cm))) user_symbol = nullptr; gnc_commodity_begin_edit (cm); if (user_symbol) { GValue v = G_VALUE_INIT; g_value_init (&v, G_TYPE_STRING); g_value_set_static_string (&v, user_symbol); qof_instance_set_kvp (QOF_INSTANCE(cm), &v, 1, "user_symbol"); g_value_unset (&v); } else { qof_instance_set_kvp (QOF_INSTANCE(cm), nullptr, 1, "user_symbol"); } mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); LEAVE(" "); } /******************************************************************** * gnc_commodity_set_default_symbol * Not made visible in gnc-commodity.h, it is only called from * iso-4217-currencies.c at startup. ********************************************************************/ void gnc_commodity_set_default_symbol(gnc_commodity * cm, const char * default_symbol) { GET_PRIVATE(cm)->default_symbol = default_symbol; } /******************************************************************** * gnc_commodity_increment_usage_count ********************************************************************/ void gnc_commodity_increment_usage_count(gnc_commodity *cm) { gnc_commodityPrivate* priv; ENTER("(cm=%p)", cm); if (!cm) { LEAVE(""); return; } priv = GET_PRIVATE(cm); if ((priv->usage_count == 0) && !priv->quote_flag && gnc_commodity_get_auto_quote_control_flag(cm) && gnc_commodity_is_iso(cm)) { /* compatibility hack - Gnucash 1.8 gets currency quotes when a non-default currency is assigned to an account. */ gnc_commodity_begin_edit(cm); gnc_commodity_set_quote_flag(cm, TRUE); gnc_commodity_set_quote_source(cm, gnc_commodity_get_default_quote_source(cm)); gnc_commodity_commit_edit(cm); } priv->usage_count++; LEAVE("(usage_count=%d)", priv->usage_count); } /******************************************************************** * gnc_commodity_decrement_usage_count ********************************************************************/ void gnc_commodity_decrement_usage_count(gnc_commodity *cm) { gnc_commodityPrivate* priv; ENTER("(cm=%p)", cm); if (!cm) { LEAVE(""); return; } priv = GET_PRIVATE(cm); if (priv->usage_count == 0) { PWARN("usage_count already zero"); LEAVE(""); return; } priv->usage_count--; if ((priv->usage_count == 0) && priv->quote_flag && gnc_commodity_get_auto_quote_control_flag(cm) && gnc_commodity_is_iso(cm)) { /* if this is a currency with auto quote control enabled and no more * accounts reference this currency, disable quote retrieval */ gnc_commodity_set_quote_flag(cm, FALSE); } LEAVE("(usage_count=%d)", priv->usage_count); } /********************************************************************\ \********************************************************************/ /******************************************************************** * gnc_commodity_equiv * are two commodities the same? ********************************************************************/ gboolean gnc_commodity_equiv(const gnc_commodity * a, const gnc_commodity * b) { gnc_commodityPrivate* priv_a; gnc_commodityPrivate* priv_b; if (a == b) return TRUE; if (!a || !b) return FALSE; priv_a = GET_PRIVATE(a); priv_b = GET_PRIVATE(b); if (priv_a->name_space != priv_b->name_space) return FALSE; if (g_strcmp0(priv_a->mnemonic, priv_b->mnemonic) != 0) return FALSE; return TRUE; } gboolean gnc_commodity_equal(const gnc_commodity * a, const gnc_commodity * b) { return gnc_commodity_compare(a, b) == 0; } // Used as a sorting callback for deleting old prices, so it needs to be // stable but doesn't need to be in any particular order sensible to humans. int gnc_commodity_compare(const gnc_commodity * a, const gnc_commodity * b) { if (a == b) return 0; if (a && !b) return 1; if (b && !a) return -1; return qof_instance_guid_compare(a, b); } // Used as a callback to g_list_find_custom, it should return 0 // when the commodities match. int gnc_commodity_compare_void(const void * a, const void * b) { return gnc_commodity_compare(GNC_COMMODITY (a), GNC_COMMODITY (b)); } /************************************************************ * Namespace functions * ************************************************************/ const char * gnc_commodity_namespace_get_name (const gnc_commodity_namespace *ns) { if (ns == nullptr) return nullptr; return ns->name; } const char * gnc_commodity_namespace_get_gui_name (const gnc_commodity_namespace *ns) { if (ns == nullptr) return nullptr; if (g_strcmp0 (ns->name, GNC_COMMODITY_NS_CURRENCY) == 0) return GNC_COMMODITY_NS_ISO_GUI; return ns->name; } GList * gnc_commodity_namespace_get_commodity_list(const gnc_commodity_namespace *name_space) { if (!name_space) return nullptr; return name_space->cm_list; } gboolean gnc_commodity_namespace_is_iso(const char *name_space) { return ((g_strcmp0(name_space, GNC_COMMODITY_NS_ISO) == 0) || (g_strcmp0(name_space, GNC_COMMODITY_NS_CURRENCY) == 0)); } static const gchar * gnc_commodity_table_map_namespace(const char * name_space) { if (g_strcmp0(name_space, GNC_COMMODITY_NS_ISO) == 0) return GNC_COMMODITY_NS_CURRENCY; return name_space; } /******************************************************************** * gnc_commodity_table_new * make a new commodity table ********************************************************************/ gnc_commodity_table * gnc_commodity_table_new(void) { gnc_commodity_table * retval = g_new0(gnc_commodity_table, 1); retval->ns_table = g_hash_table_new(&g_str_hash, &g_str_equal); retval->ns_list = nullptr; return retval; } /******************************************************************** * book anchor functions ********************************************************************/ gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book) { if (!book) return nullptr; return static_cast(qof_book_get_data (book, GNC_COMMODITY_TABLE)); } gnc_commodity * gnc_commodity_obtain_twin (const gnc_commodity *from, QofBook *book) { gnc_commodity *twin; const char * ucom; gnc_commodity_table * comtbl; if (!from) return nullptr; comtbl = gnc_commodity_table_get_table (book); if (!comtbl) return nullptr; ucom = gnc_commodity_get_unique_name (from); twin = gnc_commodity_table_lookup_unique (comtbl, ucom); if (!twin) { twin = gnc_commodity_clone (from, book); twin = gnc_commodity_table_insert (comtbl, twin); } return twin; } /******************************************************************** * gnc_commodity_table_get_size * get the size of the commodity table ********************************************************************/ static void count_coms(gpointer key, gpointer value, gpointer user_data) { GHashTable *tbl = ((gnc_commodity_namespace*)value)->cm_table; guint *count = (guint*)user_data; if (g_strcmp0((char*)key, GNC_COMMODITY_NS_CURRENCY) == 0) { /* don't count default commodities */ return; } if (!value) return; *count += g_hash_table_size(tbl); } guint gnc_commodity_table_get_size(const gnc_commodity_table* tbl) { guint count = 0; g_return_val_if_fail(tbl, 0); g_return_val_if_fail(tbl->ns_table, 0); g_hash_table_foreach(tbl->ns_table, count_coms, (gpointer)&count); return count; } /******************************************************************** * gnc_commodity_table_lookup * locate a commodity by namespace and mnemonic. ********************************************************************/ gnc_commodity * gnc_commodity_table_lookup(const gnc_commodity_table * table, const char * name_space, const char * mnemonic) { gnc_commodity_namespace * nsp = nullptr; if (!table || !name_space || !mnemonic) return nullptr; nsp = gnc_commodity_table_find_namespace(table, name_space); if (nsp) { /* * Backward compatibility support for currencies that have * recently changed. */ if (nsp->iso4217) { auto it = gnc_new_iso_codes.find (mnemonic); if (it != gnc_new_iso_codes.end()) mnemonic = it->second.c_str(); } return GNC_COMMODITY(g_hash_table_lookup(nsp->cm_table, (gpointer)mnemonic)); } else { return nullptr; } } /******************************************************************** * gnc_commodity_table_lookup * locate a commodity by unique name. ********************************************************************/ gnc_commodity * gnc_commodity_table_lookup_unique(const gnc_commodity_table *table, const char * unique_name) { char *name_space; char *mnemonic; gnc_commodity *commodity; if (!table || !unique_name) return nullptr; name_space = g_strdup (unique_name); mnemonic = strstr (name_space, "::"); if (!mnemonic) { g_free (name_space); return nullptr; } *mnemonic = '\0'; mnemonic += 2; commodity = gnc_commodity_table_lookup (table, name_space, mnemonic); g_free (name_space); return commodity; } /******************************************************************** * gnc_commodity_table_find_full * locate a commodity by namespace and printable name ********************************************************************/ gnc_commodity * gnc_commodity_table_find_full(const gnc_commodity_table * table, const char * name_space, const char * fullname) { gnc_commodity * retval = nullptr; GList * all; GList * iterator; if (!fullname || (fullname[0] == '\0')) return nullptr; all = gnc_commodity_table_get_commodities(table, name_space); for (iterator = all; iterator; iterator = iterator->next) { auto commodity = GNC_COMMODITY (iterator->data); if (!strcmp(fullname, gnc_commodity_get_printname(commodity))) { retval = commodity; break; } } g_list_free (all); return retval; } /******************************************************************** * gnc_commodity_table_insert * add a commodity to the table. ********************************************************************/ gnc_commodity * gnc_commodity_table_insert(gnc_commodity_table * table, gnc_commodity * comm) { gnc_commodity_namespace * nsp = nullptr; gnc_commodity *c; const char *ns_name; gnc_commodityPrivate* priv; QofBook *book; if (!table) return nullptr; if (!comm) return nullptr; priv = GET_PRIVATE(comm); ENTER ("(table=%p, comm=%p) %s %s", table, comm, (priv->mnemonic == nullptr ? "(null)" : priv->mnemonic), (priv->fullname == nullptr ? "(null)" : priv->fullname)); ns_name = gnc_commodity_namespace_get_name(priv->name_space); c = gnc_commodity_table_lookup (table, ns_name, priv->mnemonic); if (c) { if (c == comm) { LEAVE("already in table"); return c; } /* Backward compatibility support for currencies that have * recently changed. */ if (priv->name_space->iso4217) { auto it = gnc_new_iso_codes.find (priv->mnemonic); if (it != gnc_new_iso_codes.end()) gnc_commodity_set_mnemonic(comm, it->second.c_str()); } gnc_commodity_copy (c, comm); gnc_commodity_destroy (comm); LEAVE("found at %p", c); return c; } /* Prevent setting anything except template in namespace template. */ if (g_strcmp0 (ns_name, GNC_COMMODITY_NS_TEMPLATE) == 0 && g_strcmp0 (priv->mnemonic, "template") != 0) { PWARN("Converting commodity %s from namespace template to " "namespace User", priv->mnemonic); gnc_commodity_set_namespace (comm, "User"); ns_name = "User"; mark_commodity_dirty (comm); } book = qof_instance_get_book (&comm->inst); nsp = gnc_commodity_table_add_namespace(table, ns_name, book); PINFO ("insert %p %s into nsp=%p %s", priv->mnemonic, priv->mnemonic, nsp->cm_table, nsp->name); g_hash_table_insert(nsp->cm_table, (gpointer)CACHE_INSERT(priv->mnemonic), (gpointer)comm); nsp->cm_list = g_list_append(nsp->cm_list, comm); qof_event_gen (&comm->inst, QOF_EVENT_ADD, nullptr); LEAVE ("(table=%p, comm=%p)", table, comm); return comm; } /******************************************************************** * gnc_commodity_table_remove * remove a commodity from the table. ********************************************************************/ void gnc_commodity_table_remove(gnc_commodity_table * table, gnc_commodity * comm) { gnc_commodity_namespace * nsp; gnc_commodity *c; gnc_commodityPrivate* priv; const char *ns_name; if (!table) return; if (!comm) return; priv = GET_PRIVATE(comm); ns_name = gnc_commodity_namespace_get_name(priv->name_space); c = gnc_commodity_table_lookup (table, ns_name, priv->mnemonic); if (c != comm) return; qof_event_gen (&comm->inst, QOF_EVENT_REMOVE, nullptr); nsp = gnc_commodity_table_find_namespace(table, ns_name); if (!nsp) return; nsp->cm_list = g_list_remove(nsp->cm_list, comm); g_hash_table_remove (nsp->cm_table, priv->mnemonic); /* XXX minor mem leak, should remove the key as well */ } /******************************************************************** * gnc_commodity_table_has_namespace * see if the commodities namespace exists. May have zero commodities. ********************************************************************/ int gnc_commodity_table_has_namespace(const gnc_commodity_table * table, const char * name_space) { gnc_commodity_namespace * nsp = nullptr; if (!table || !name_space) { return 0; } nsp = gnc_commodity_table_find_namespace(table, name_space); if (nsp) { return 1; } else { return 0; } } static void hash_keys_helper(gpointer key, gpointer value, gpointer data) { auto l = (GList**)data; *l = g_list_prepend(*l, key); } static GList * g_hash_table_keys(GHashTable * table) { GList * l = nullptr; g_hash_table_foreach(table, &hash_keys_helper, (gpointer) &l); return l; } static void hash_values_helper(gpointer key, gpointer value, gpointer data) { auto l = (GList**)data; *l = g_list_prepend(*l, value); } static GList * g_hash_table_values(GHashTable * table) { GList * l = nullptr; g_hash_table_foreach(table, &hash_values_helper, (gpointer) &l); return l; } /******************************************************************** * gnc_commodity_table_get_namespaces * see if any commodities in the namespace exist ********************************************************************/ GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table * table) { if (!table) return nullptr; return g_hash_table_keys(table->ns_table); } GList * gnc_commodity_table_get_namespaces_list(const gnc_commodity_table * table) { if (!table) return nullptr; return table->ns_list; } /* Because gnc_commodity_table_add_namespace maps GNC_COMMODITY_NS_ISO to GNC_COMMODITY_NS_CURRENCY and then sets iso4217 if the namespace is either of these, the net result is that the iso4217 bit is set only for GNC_COMMODITY_NS_CURRENCY. This means that gnc_commodity_is_iso is a subset of gnc_commodity_is_currency. Most callers seem to use gnc_commodity_is_iso. */ gboolean gnc_commodity_is_iso(const gnc_commodity * cm) { gnc_commodityPrivate* priv; if (!cm) return FALSE; priv = GET_PRIVATE(cm); if ( !priv->name_space) return FALSE; return priv->name_space->iso4217; } gboolean gnc_commodity_is_currency(const gnc_commodity *cm) { const char *ns_name; if (!cm) return FALSE; ns_name = gnc_commodity_namespace_get_name(GET_PRIVATE(cm)->name_space); return (!g_strcmp0(ns_name, GNC_COMMODITY_NS_LEGACY) || !g_strcmp0(ns_name, GNC_COMMODITY_NS_CURRENCY)); } /******************************************************************** * gnc_commodity_table_get_commodities * list commodities in a given namespace ********************************************************************/ static CommodityList* commodity_table_get_all_noncurrency_commodities(const gnc_commodity_table* table) { GList *node = nullptr, *nslist = gnc_commodity_table_get_namespaces(table); CommodityList *retval = nullptr; for (node = nslist; node; node=g_list_next(node)) { gnc_commodity_namespace *ns = nullptr; if (g_strcmp0((char*)(node->data), GNC_COMMODITY_NS_CURRENCY) == 0 || g_strcmp0((char*)(node->data), GNC_COMMODITY_NS_TEMPLATE) == 0) continue; ns = gnc_commodity_table_find_namespace(table, (char*)(node->data)); if (!ns) continue; retval = g_list_concat(g_hash_table_values(ns->cm_table), retval); } g_list_free(nslist); return retval; } CommodityList * gnc_commodity_table_get_commodities(const gnc_commodity_table * table, const char * name_space) { gnc_commodity_namespace * ns = nullptr; if (!table) return nullptr; if (g_strcmp0(name_space, GNC_COMMODITY_NS_NONISO_GUI) == 0) return commodity_table_get_all_noncurrency_commodities(table); ns = gnc_commodity_table_find_namespace(table, name_space); if (!ns) return nullptr; return g_hash_table_values(ns->cm_table); } /******************************************************************** * gnc_commodity_table_get_quotable_commodities * list commodities in a given namespace that get price quotes ********************************************************************/ static void get_quotables_helper1(gpointer key, gpointer value, gpointer data) { auto comm = GNC_COMMODITY(value); gnc_commodityPrivate* priv = GET_PRIVATE(comm); auto l = static_cast(data); if (!priv->quote_flag || !priv->quote_source || !priv->quote_source->get_supported()) return; *l = g_list_prepend(*l, value); } static gboolean get_quotables_helper2 (gnc_commodity *comm, gpointer data) { auto l = static_cast(data); gnc_commodityPrivate* priv = GET_PRIVATE(comm); if (!priv->quote_flag || priv->quote_source || !priv->quote_source->get_supported()) return TRUE; *l = g_list_prepend(*l, comm); return TRUE; } CommodityList * gnc_commodity_table_get_quotable_commodities(const gnc_commodity_table * table) { gnc_commodity_namespace * ns = nullptr; const char *name_space; GList * nslist, * tmp; GList * l = nullptr; regex_t pattern; const char *expression = gnc_prefs_get_namespace_regexp(); ENTER("table=%p, expression=%s", table, expression); if (!table) return nullptr; if (expression && *expression) { if (regcomp(&pattern, expression, REG_EXTENDED | REG_ICASE) != 0) { LEAVE("Cannot compile regex"); return nullptr; } nslist = gnc_commodity_table_get_namespaces(table); for (tmp = nslist; tmp; tmp = tmp->next) { name_space = static_cast(tmp->data); if (regexec(&pattern, name_space, 0, nullptr, 0) == 0) { DEBUG("Running list of %s commodities", name_space); ns = gnc_commodity_table_find_namespace(table, name_space); if (ns) { g_hash_table_foreach(ns->cm_table, &get_quotables_helper1, (gpointer) &l); } } } g_list_free(nslist); regfree(&pattern); } else { gnc_commodity_table_foreach_commodity(table, get_quotables_helper2, (gpointer) &l); } LEAVE("list head %p", l); return l; } /******************************************************************** * gnc_commodity_table_add_namespace * add an empty namespace if it does not exist ********************************************************************/ /* GObject Initialization */ QOF_GOBJECT_IMPL(gnc_commodity_namespace, gnc_commodity_namespace, QOF_TYPE_INSTANCE) static void gnc_commodity_namespace_init(gnc_commodity_namespace* ns) { } static void gnc_commodity_namespace_dispose_real (GObject *nsp) { } static void gnc_commodity_namespace_finalize_real(GObject* nsp) { } gnc_commodity_namespace * gnc_commodity_table_add_namespace(gnc_commodity_table * table, const char * name_space, QofBook *book) { gnc_commodity_namespace * ns = nullptr; if (!table) return nullptr; name_space = gnc_commodity_table_map_namespace(name_space); ns = gnc_commodity_table_find_namespace(table, name_space); if (!ns) { ns = static_cast(g_object_new(GNC_TYPE_COMMODITY_NAMESPACE, nullptr)); ns->cm_table = g_hash_table_new(g_str_hash, g_str_equal); ns->name = CACHE_INSERT(static_cast(name_space)); ns->iso4217 = gnc_commodity_namespace_is_iso(name_space); qof_instance_init_data (&ns->inst, GNC_ID_COMMODITY_NAMESPACE, book); qof_event_gen (&ns->inst, QOF_EVENT_CREATE, nullptr); g_hash_table_insert(table->ns_table, (gpointer) ns->name, (gpointer) ns); table->ns_list = g_list_append(table->ns_list, ns); qof_event_gen (&ns->inst, QOF_EVENT_ADD, nullptr); } return ns; } gnc_commodity_namespace * gnc_commodity_table_find_namespace(const gnc_commodity_table * table, const char * name_space) { if (!table || !name_space) return nullptr; name_space = gnc_commodity_table_map_namespace(name_space); return static_cast(g_hash_table_lookup(table->ns_table, (gpointer)name_space)); } gnc_commodity * gnc_commodity_find_commodity_by_guid(const GncGUID *guid, QofBook *book) { QofCollection *col; if (!guid || !book) return nullptr; col = qof_book_get_collection (book, GNC_ID_COMMODITY); return (gnc_commodity *) qof_collection_lookup_entity (col, guid); } /******************************************************************** * gnc_commodity_table_delete_namespace * delete a namespace ********************************************************************/ static int ns_helper(gpointer key, gpointer value, gpointer user_data) { auto c = GNC_COMMODITY(value); gnc_commodity_destroy(c); CACHE_REMOVE(static_cast(key)); /* key is commodity mnemonic */ return TRUE; } void gnc_commodity_table_delete_namespace(gnc_commodity_table * table, const char * name_space) { gnc_commodity_namespace * ns; if (!table) return; ns = gnc_commodity_table_find_namespace(table, name_space); if (!ns) return; qof_event_gen (&ns->inst, QOF_EVENT_REMOVE, nullptr); g_hash_table_remove(table->ns_table, name_space); table->ns_list = g_list_remove(table->ns_list, ns); g_list_free(ns->cm_list); ns->cm_list = nullptr; g_hash_table_foreach_remove(ns->cm_table, ns_helper, nullptr); g_hash_table_destroy(ns->cm_table); CACHE_REMOVE(ns->name); qof_event_gen (&ns->inst, QOF_EVENT_DESTROY, nullptr); /* qof_instance_release(&ns->inst); */ g_object_unref(ns); } /******************************************************************** * gnc_commodity_table_foreach_commodity * call user-defined function once for every commodity in every * namespace ********************************************************************/ typedef struct { gboolean ok; gboolean (*func)(gnc_commodity *, gpointer); gpointer user_data; } IterData; static void iter_commodity (gpointer key, gpointer value, gpointer user_data) { IterData *iter_data = (IterData *) user_data; gnc_commodity *cm = (gnc_commodity *) value; if (iter_data->ok) { iter_data->ok = (iter_data->func)(cm, iter_data->user_data); } } static void iter_namespace (gpointer key, gpointer value, gpointer user_data) { GHashTable *namespace_hash = ((gnc_commodity_namespace *) value)->cm_table; g_hash_table_foreach (namespace_hash, iter_commodity, user_data); } gboolean gnc_commodity_table_foreach_commodity (const gnc_commodity_table * tbl, gboolean (*f)(gnc_commodity *, gpointer), gpointer user_data) { IterData iter_data; if (!tbl || !f) return FALSE; iter_data.ok = TRUE; iter_data.func = f; iter_data.user_data = user_data; g_hash_table_foreach(tbl->ns_table, iter_namespace, (gpointer)&iter_data); return iter_data.ok; } /******************************************************************** * gnc_commodity_table_destroy * cleanup and free. ********************************************************************/ void gnc_commodity_table_destroy(gnc_commodity_table * t) { gnc_commodity_namespace * ns; GList *item, *next; if (!t) return; ENTER ("table=%p", t); for (item = t->ns_list; item; item = next) { next = g_list_next(item); ns = static_cast(item->data); gnc_commodity_table_delete_namespace(t, ns->name); } g_list_free(t->ns_list); t->ns_list = nullptr; g_hash_table_destroy(t->ns_table); t->ns_table = nullptr; LEAVE ("table=%p", t); g_free(t); } /* =========================================================== */ /******************************************************************** * gnc_commodity_table_add_default_data ********************************************************************/ #define CUR_I18N(String) dgettext ("iso_4217", String) gboolean gnc_commodity_table_add_default_data(gnc_commodity_table *table, QofBook *book) { QofCollection *col; gnc_commodity* c; ENTER ("table=%p", table); gnc_commodity_table_add_namespace(table, GNC_COMMODITY_NS_TEMPLATE, book); c = gnc_commodity_new(book, "template", GNC_COMMODITY_NS_TEMPLATE, "template", "template", 1); gnc_commodity_table_insert(table, c); #include "iso-4217-currencies.c" /* We've just created the default namespaces and currencies. Mark * these collections as clean because there is no USER entered data * in these collections as of yet. */ col = qof_book_get_collection(book, GNC_ID_COMMODITY); qof_collection_mark_clean(col); col = qof_book_get_collection(book, GNC_ID_COMMODITY_NAMESPACE); qof_collection_mark_clean(col); LEAVE ("table=%p", table); return TRUE; } /******************************************************************** ********************************************************************/ /* QofObject function implementation and registration */ #ifdef _MSC_VER /* MSVC compiler doesn't have C99 "designated initializers" * so we wrap them in a macro that is empty on MSVC. */ # define DI(x) /* */ #else # define DI(x) x #endif static QofObject commodity_object_def = { DI(.interface_version = ) QOF_OBJECT_VERSION, DI(.e_type = ) GNC_ID_COMMODITY, DI(.type_label = ) "Commodity", DI(.create = ) nullptr, DI(.book_begin = ) nullptr, DI(.book_end = ) nullptr, DI(.is_dirty = ) qof_collection_is_dirty, DI(.mark_clean = ) qof_collection_mark_clean, DI(.foreach = ) qof_collection_foreach, DI(.printable = ) (const char * (*)(gpointer)) gnc_commodity_get_fullname, }; static QofObject namespace_object_def = { DI(.interface_version = ) QOF_OBJECT_VERSION, DI(.e_type = ) GNC_ID_COMMODITY_NAMESPACE, DI(.type_label = ) "Namespace", DI(.create = ) nullptr, DI(.book_begin = ) nullptr, DI(.book_end = ) nullptr, DI(.is_dirty = ) nullptr, DI(.mark_clean = ) nullptr, DI(.foreach = ) nullptr, DI(.printable = ) nullptr, }; static void commodity_table_book_begin (QofBook *book) { gnc_commodity_table *ct; ENTER ("book=%p", book); if (gnc_commodity_table_get_table(book)) return; ct = gnc_commodity_table_new (); qof_book_set_data (book, GNC_COMMODITY_TABLE, ct); if (!gnc_commodity_table_add_default_data(ct, book)) { PWARN("unable to initialize book's commodity_table"); } LEAVE ("book=%p", book); } static void commodity_table_book_end (QofBook *book) { gnc_commodity_table *ct; ct = gnc_commodity_table_get_table (book); qof_book_set_data (book, GNC_COMMODITY_TABLE, nullptr); gnc_commodity_table_destroy (ct); } static QofObject commodity_table_object_def = { DI(.interface_version = ) QOF_OBJECT_VERSION, DI(.e_type = ) GNC_ID_COMMODITY_TABLE, DI(.type_label = ) "CommodityTable", DI(.create = ) nullptr, DI(.book_begin = ) commodity_table_book_begin, DI(.book_end = ) commodity_table_book_end, DI(.is_dirty = ) qof_collection_is_dirty, DI(.mark_clean = ) qof_collection_mark_clean, DI(.foreach = ) nullptr, DI(.printable = ) nullptr, DI(.version_cmp = ) nullptr, }; gboolean gnc_commodity_table_register (void) { if (!qof_object_register (&commodity_object_def)) return FALSE; if (!qof_object_register (&namespace_object_def)) return FALSE; return qof_object_register (&commodity_table_object_def); } /* ******************************************************************* * gnc_monetary methods ********************************************************************/ /** Add a gnc_monetary to the list */ MonetaryList * gnc_monetary_list_add_monetary(MonetaryList *list, gnc_monetary add_mon) { MonetaryList *l = list, *tmp; for (tmp = list; tmp; tmp = tmp->next) { auto list_mon = static_cast(tmp->data); if (gnc_commodity_equiv(list_mon->commodity, add_mon.commodity)) { list_mon->value = gnc_numeric_add(list_mon->value, add_mon.value, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT); break; } } /* See if we found an entry, and add one if not */ if (tmp == nullptr) { auto new_mon = static_cast(g_new0(gnc_monetary, 1)); *new_mon = add_mon; l = g_list_prepend(l, new_mon); } return l; } /** Delete all entries in the list that have zero value. Return list pointer will be a null pointer if there are no non-zero entries **/ MonetaryList * gnc_monetary_list_delete_zeros(MonetaryList *list) { MonetaryList *node, *next; for (node = list; node; node = next) { auto mon = static_cast(node->data); next = node->next; if (gnc_numeric_zero_p(mon->value)) { g_free(mon); list = g_list_delete_link(list, node); } } return list; } /** Free a MonetaryList and all the monetaries it points to */ void gnc_monetary_list_free(MonetaryList *list) { MonetaryList *tmp; for (tmp = list; tmp; tmp = tmp->next) { g_free(tmp->data); } g_list_free(list); } /* ========================= END OF FILE ============================== */