2021-01-28 10:34:19 -06:00
/********************************************************************\
* gnc - quotes . hpp - - proxy for Finance : : Quote *
* Copyright ( C ) 2021 Geert Janssens < geert @ kobaltwit . be > *
* *
* 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 *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-02-04 08:10:55 -06:00
# include <config.h>
2023-04-09 17:20:07 -05:00
# include <qoflog.h>
2021-02-04 08:10:55 -06:00
2021-01-28 10:34:19 -06:00
# include <algorithm>
2022-09-10 18:18:32 -05:00
# include <stdexcept>
2021-01-28 10:34:19 -06:00
# include <vector>
# include <string>
# include <iostream>
2023-01-22 11:50:34 -06:00
# include <boost/version.hpp>
# if BOOST_VERSION < 107600
// json_parser uses a deprecated version of bind.hpp
# define BOOST_BIND_GLOBAL_PLACEHOLDERS
# endif
2021-02-28 15:36:22 -06:00
# include <boost/algorithm/string.hpp>
2021-01-28 10:34:19 -06:00
# include <boost/filesystem.hpp>
2023-09-22 15:18:08 -05:00
# ifdef BOOST_WINDOWS_API
# include <boost/process/windows.hpp>
# endif
2021-01-28 10:34:19 -06:00
# include <boost/process.hpp>
2022-09-01 17:07:44 -05:00
# include <boost/regex.hpp>
2021-02-04 08:10:55 -06:00
# include <boost/property_tree/ptree.hpp>
# include <boost/property_tree/json_parser.hpp>
2021-02-11 08:05:17 -06:00
# include <boost/iostreams/device/array.hpp>
# include <boost/iostreams/stream_buffer.hpp>
2022-09-10 18:18:32 -05:00
# include <boost/locale.hpp>
2021-02-11 08:05:17 -06:00
# include <boost/asio.hpp>
2021-01-28 10:34:19 -06:00
# include <glib.h>
2021-02-04 08:10:55 -06:00
# include "gnc-commodity.hpp"
2021-02-28 15:36:22 -06:00
# include <gnc-datetime.hpp>
# include <gnc-numeric.hpp>
2021-01-28 10:34:19 -06:00
# include "gnc-quotes.hpp"
2022-09-01 17:07:44 -05:00
# include <gnc-commodity.h>
# include <gnc-path.h>
2021-02-04 08:10:55 -06:00
# include "gnc-ui-util.h"
2021-02-05 10:48:06 -06:00
# include <gnc-prefs.h>
2022-09-01 17:07:44 -05:00
# include <gnc-session.h>
2021-02-05 10:48:06 -06:00
# include <regex.h>
2021-02-04 08:10:55 -06:00
# include <qofbook.h>
2021-01-28 10:34:19 -06:00
2022-09-01 17:07:44 -05:00
static const QofLogModule log_module = " gnc.price-quotes " ;
2022-09-10 18:18:32 -05:00
namespace bl = boost : : locale ;
2021-01-28 10:34:19 -06:00
namespace bp = boost : : process ;
2021-02-28 15:36:22 -06:00
namespace bfs = boost : : filesystem ;
2021-02-04 08:10:55 -06:00
namespace bpt = boost : : property_tree ;
2021-02-11 08:05:17 -06:00
namespace bio = boost : : iostreams ;
2021-01-28 10:34:19 -06:00
2022-09-01 17:07:44 -05:00
using QuoteResult = std : : tuple < int , StrVec , StrVec > ;
2021-01-28 10:34:19 -06:00
2022-09-10 18:18:32 -05:00
struct GncQuoteSourceError : public std : : runtime_error
{
GncQuoteSourceError ( const std : : string & err ) : std : : runtime_error ( err ) { }
} ;
2021-02-05 10:48:06 -06:00
CommVec
gnc_quotes_get_quotable_commodities ( const gnc_commodity_table * table ) ;
2022-09-01 17:07:44 -05:00
class GncQuoteSource
{
public :
virtual ~ GncQuoteSource ( ) = default ;
virtual const StrVec & get_sources ( ) const noexcept = 0 ;
virtual const std : : string & get_version ( ) const noexcept = 0 ;
virtual QuoteResult get_quotes ( const std : : string & json_str ) const = 0 ;
} ;
2022-10-13 14:01:36 -05:00
2021-02-12 11:18:20 -06:00
class GncQuotesImpl
{
public :
// Constructor - checks for presence of Finance::Quote and import version and quote sources
GncQuotesImpl ( ) ;
2022-09-01 17:07:44 -05:00
explicit GncQuotesImpl ( QofBook * book ) ;
GncQuotesImpl ( QofBook * , std : : unique_ptr < GncQuoteSource > ) ;
2021-02-12 11:18:20 -06:00
2021-03-18 13:12:23 -05:00
void fetch ( QofBook * book ) ;
2021-03-18 10:33:16 -05:00
void fetch ( CommVec & commodities ) ;
void fetch ( gnc_commodity * comm ) ;
2022-09-27 17:05:04 -05:00
void report ( const char * source , const StrVec & commodities , bool verbose ) ;
2021-02-12 11:18:20 -06:00
2022-10-13 14:01:36 -05:00
const std : : string & version ( ) noexcept { return m_quotesource - > get_version ( ) ; }
2021-02-12 11:18:20 -06:00
const QuoteSources & sources ( ) noexcept { return m_sources ; }
2022-09-18 17:06:59 -05:00
bool had_failures ( ) noexcept { return ! m_failures . empty ( ) ; }
2022-09-17 13:52:15 -05:00
const QFVec & failures ( ) noexcept ;
std : : string report_failures ( ) noexcept ;
2021-02-12 11:18:20 -06:00
private :
2022-09-27 17:05:04 -05:00
std : : string query_fq ( const char * source , const StrVec & commoditites ) ;
2022-09-10 19:29:23 -05:00
std : : string query_fq ( const CommVec & ) ;
2022-09-27 17:05:04 -05:00
bpt : : ptree parse_quotes ( const std : : string & quote_str ) ;
void create_quotes ( const bpt : : ptree & pt , const CommVec & comm_vec ) ;
2022-09-10 19:29:23 -05:00
std : : string comm_vec_to_json_string ( const CommVec & ) const ;
2022-09-02 13:16:06 -05:00
GNCPrice * parse_one_quote ( const bpt : : ptree & , gnc_commodity * ) ;
2021-02-28 15:36:22 -06:00
2022-09-01 17:07:44 -05:00
std : : unique_ptr < GncQuoteSource > m_quotesource ;
2021-02-12 11:18:20 -06:00
QuoteSources m_sources ;
2022-09-17 13:52:15 -05:00
QFVec m_failures ;
2021-02-12 11:18:20 -06:00
QofBook * m_book ;
2021-03-15 12:38:24 -05:00
gnc_commodity * m_dflt_curr ;
2021-02-12 11:18:20 -06:00
} ;
2022-09-01 17:07:44 -05:00
class GncFQQuoteSource final : public GncQuoteSource
{
const bfs : : path c_cmd ;
2023-03-17 15:39:26 -05:00
std : : string c_fq_wrapper ;
2022-09-01 17:07:44 -05:00
std : : string m_version ;
StrVec m_sources ;
2022-10-01 19:01:48 -05:00
std : : string m_api_key ;
2022-09-01 17:07:44 -05:00
public :
GncFQQuoteSource ( ) ;
~ GncFQQuoteSource ( ) = default ;
2022-10-13 14:52:36 -05:00
const std : : string & get_version ( ) const noexcept override { return m_version ; }
const StrVec & get_sources ( ) const noexcept override { return m_sources ; }
QuoteResult get_quotes ( const std : : string & ) const override ;
2022-09-01 17:07:44 -05:00
private :
QuoteResult run_cmd ( const StrVec & args , const std : : string & json_string ) const ;
2021-02-12 11:18:20 -06:00
2022-09-01 17:07:44 -05:00
} ;
2022-09-27 17:05:04 -05:00
static void show_quotes ( const bpt : : ptree & pt , const StrVec & commodities , bool verbose ) ;
static void show_currency_quotes ( const bpt : : ptree & pt , const StrVec & commodities , bool verbose ) ;
2022-10-13 19:59:14 -05:00
static std : : string parse_quotesource_error ( const std : : string & line ) ;
2022-09-27 17:05:04 -05:00
2022-09-17 13:52:15 -05:00
static const std : : string empty_string { } ;
2022-09-01 17:07:44 -05:00
GncFQQuoteSource : : GncFQQuoteSource ( ) :
c_cmd { bp : : search_path ( " perl " ) } ,
2022-10-13 16:28:17 -05:00
m_version { } , m_sources { } , m_api_key { }
2021-01-28 10:34:19 -06:00
{
2023-03-17 15:39:26 -05:00
char * bindir = gnc_path_get_bindir ( ) ;
c_fq_wrapper = std : : string ( bindir ) + " /finance-quote-wrapper " ;
g_free ( bindir ) ;
2022-09-01 17:07:44 -05:00
StrVec args { " -w " , c_fq_wrapper , " -v " } ;
auto [ rv , sources , errors ] = run_cmd ( args , empty_string ) ;
if ( rv )
{
2022-09-10 18:18:32 -05:00
std : : string err { bl : : translate ( " Failed to initialize Finance::Quote: " ) } ;
2023-01-31 08:43:20 -06:00
for ( const auto & err_line : errors )
2022-09-10 18:18:32 -05:00
err + = err_line . empty ( ) ? " " : err_line + " \n " ;
throw ( GncQuoteSourceError ( err ) ) ;
2022-09-01 17:07:44 -05:00
}
if ( ! errors . empty ( ) )
{
2022-09-10 18:18:32 -05:00
std : : string err { bl : : translate ( " Finance::Quote check returned error " ) } ;
for ( const auto & err_line : errors )
err + = err . empty ( ) ? " " : err_line + " \n " ;
throw ( GncQuoteSourceError ( err ) ) ;
2022-09-01 17:07:44 -05:00
}
auto version { sources . front ( ) } ;
2022-10-13 14:58:09 -05:00
if ( version . empty ( ) )
2022-09-01 17:07:44 -05:00
{
2022-10-13 14:58:09 -05:00
std : : string err { bl : : translate ( " No Finance::Quote Version " ) } ;
2022-09-10 18:18:32 -05:00
throw ( GncQuoteSourceError ( err ) ) ;
2022-09-01 17:07:44 -05:00
}
2022-10-13 14:01:36 -05:00
m_version = std : : move ( version ) ;
2022-09-01 17:07:44 -05:00
sources . erase ( sources . begin ( ) ) ;
m_sources = std : : move ( sources ) ;
2022-10-17 13:13:55 -05:00
std : : sort ( m_sources . begin ( ) , m_sources . end ( ) ) ;
2022-10-01 19:01:48 -05:00
auto av_key = gnc_prefs_get_string ( " general.finance-quote " , " alphavantage-api-key " ) ;
2022-10-17 12:06:21 -05:00
if ( ! ( av_key & & * av_key ) )
2023-03-19 11:38:15 -05:00
{
g_free ( av_key ) ;
2023-03-19 12:09:01 -05:00
av_key = g_strdup ( getenv ( " ALPHAVANTAGE_API_KEY " ) ) ;
2023-03-19 11:38:15 -05:00
}
2022-10-01 19:01:48 -05:00
if ( av_key )
2023-03-19 11:38:15 -05:00
{
2022-10-01 19:01:48 -05:00
m_api_key = std : : string ( av_key ) ;
2023-03-19 11:38:15 -05:00
g_free ( av_key ) ;
}
2022-10-01 19:01:48 -05:00
else
PWARN ( " No Alpha Vantage API key set, currency quotes and other AlphaVantage based quotes won't work. " ) ;
2022-09-01 17:07:44 -05:00
}
2021-01-28 10:34:19 -06:00
2022-09-01 17:07:44 -05:00
QuoteResult
GncFQQuoteSource : : get_quotes ( const std : : string & json_str ) const
{
StrVec args { " -w " , c_fq_wrapper , " -f " } ;
return run_cmd ( args , json_str ) ;
}
2021-01-28 10:34:19 -06:00
2022-09-01 17:07:44 -05:00
QuoteResult
GncFQQuoteSource : : run_cmd ( const StrVec & args , const std : : string & json_string ) const
{
StrVec out_vec , err_vec ;
int cmd_result ;
2021-01-29 05:13:46 -06:00
2022-09-01 17:07:44 -05:00
try
{
std : : future < std : : vector < char > > out_buf , err_buf ;
boost : : asio : : io_service svc ;
2021-01-28 10:34:19 -06:00
2022-09-01 17:07:44 -05:00
auto input_buf = bp : : buffer ( json_string ) ;
2023-03-19 12:28:30 -05:00
bp : : child process ;
if ( m_api_key . empty ( ) )
process = bp : : child ( c_cmd , args ,
bp : : std_out > out_buf ,
bp : : std_err > err_buf ,
bp : : std_in < input_buf ,
2023-09-22 15:18:08 -05:00
# ifdef BOOST_WINDOWS_API
bp : : windows : : create_no_window ,
# endif
2023-03-19 12:28:30 -05:00
svc ) ;
else
process = bp : : child ( c_cmd , args ,
bp : : std_out > out_buf ,
bp : : std_err > err_buf ,
bp : : std_in < input_buf ,
2023-09-22 15:18:08 -05:00
# ifdef BOOST_WINDOWS_API
bp : : windows : : create_no_window ,
# endif
2023-03-19 12:28:30 -05:00
bp : : env [ " ALPHAVANTAGE_API_KEY " ] = m_api_key ,
svc ) ;
svc . run ( ) ;
process . wait ( ) ;
2022-09-01 17:07:44 -05:00
{
auto raw = out_buf . get ( ) ;
std : : vector < std : : string > data ;
std : : string line ;
bio : : stream_buffer < bio : : array_source > sb ( raw . data ( ) , raw . size ( ) ) ;
std : : istream is ( & sb ) ;
while ( std : : getline ( is , line ) & & ! line . empty ( ) )
2023-03-30 18:41:25 -05:00
{
# ifdef __WIN32
if ( line . back ( ) = = ' \r ' )
line . pop_back ( ) ;
# endif
2022-09-01 17:07:44 -05:00
out_vec . push_back ( std : : move ( line ) ) ;
2023-03-30 18:41:25 -05:00
}
2022-09-01 17:07:44 -05:00
raw = err_buf . get ( ) ;
bio : : stream_buffer < bio : : array_source > eb ( raw . data ( ) , raw . size ( ) ) ;
std : : istream es ( & eb ) ;
while ( std : : getline ( es , line ) & & ! line . empty ( ) )
err_vec . push_back ( std : : move ( line ) ) ;
}
cmd_result = process . exit_code ( ) ;
}
catch ( std : : exception & e )
{
cmd_result = - 1 ;
err_vec . push_back ( e . what ( ) ) ;
} ;
return QuoteResult ( cmd_result , std : : move ( out_vec ) , std : : move ( err_vec ) ) ;
}
/* GncQuotes implementation */
GncQuotesImpl : : GncQuotesImpl ( ) : m_quotesource { new GncFQQuoteSource } ,
2022-10-13 14:01:36 -05:00
m_sources { } , m_failures { } ,
2022-09-17 13:52:15 -05:00
m_book { qof_session_get_book ( gnc_get_current_session ( ) ) } ,
m_dflt_curr { gnc_default_currency ( ) }
2022-09-01 17:07:44 -05:00
{
m_sources = m_quotesource - > get_sources ( ) ;
}
GncQuotesImpl : : GncQuotesImpl ( QofBook * book ) : m_quotesource { new GncFQQuoteSource } ,
2022-10-13 14:01:36 -05:00
m_sources { } , m_book { book } ,
2022-09-01 17:07:44 -05:00
m_dflt_curr { gnc_default_currency ( ) }
{
m_sources = m_quotesource - > get_sources ( ) ;
}
GncQuotesImpl : : GncQuotesImpl ( QofBook * book , std : : unique_ptr < GncQuoteSource > quote_source ) :
m_quotesource { std : : move ( quote_source ) } ,
2022-10-13 14:01:36 -05:00
m_sources { } , m_book { book } , m_dflt_curr { gnc_default_currency ( ) }
2022-09-01 17:07:44 -05:00
{
m_sources = m_quotesource - > get_sources ( ) ;
2021-01-28 10:34:19 -06:00
}
2021-02-04 08:10:55 -06:00
void
2021-03-18 13:12:23 -05:00
GncQuotesImpl : : fetch ( QofBook * book )
2021-02-04 08:10:55 -06:00
{
2021-03-18 13:12:23 -05:00
if ( ! book )
2022-09-10 18:18:32 -05:00
throw ( GncQuoteException ( bl : : translate ( " GncQuotes::Fetch called with no book. " ) ) ) ;
2021-02-04 08:10:55 -06:00
auto commodities = gnc_quotes_get_quotable_commodities (
2021-03-18 13:12:23 -05:00
gnc_commodity_table_get_table ( book ) ) ;
2021-02-04 08:10:55 -06:00
fetch ( commodities ) ;
}
2021-03-18 10:33:16 -05:00
void
GncQuotesImpl : : fetch ( gnc_commodity * comm )
{
auto commodities = CommVec { comm } ;
fetch ( commodities ) ;
}
2021-03-19 11:46:46 -05:00
void
GncQuotesImpl : : fetch ( CommVec & commodities )
{
2022-09-17 13:52:15 -05:00
m_failures . clear ( ) ;
2021-03-19 11:46:46 -05:00
if ( commodities . empty ( ) )
2022-10-13 15:51:37 -05:00
throw ( GncQuoteException ( bl : : translate ( " GncQuotes::Fetch called with no commodities. " ) ) ) ;
2022-10-13 19:59:14 -05:00
auto quote_str { query_fq ( commodities ) } ;
auto ptree { parse_quotes ( quote_str ) } ;
create_quotes ( ptree , commodities ) ;
2022-09-27 17:05:04 -05:00
}
2021-03-19 11:46:46 -05:00
2022-09-27 17:05:04 -05:00
void
GncQuotesImpl : : report ( const char * source , const StrVec & commodities ,
bool verbose )
{
2023-06-24 05:49:29 -05:00
if ( ! source )
throw ( GncQuoteException ( bl : : translate ( " GncQuotes::Report called with no source. " ) ) ) ;
bool is_currency { strcmp ( source , " currency " ) = = 0 } ;
2022-09-27 17:05:04 -05:00
m_failures . clear ( ) ;
if ( commodities . empty ( ) )
{
std : : cerr < < _ ( " There were no commodities for which to retrieve quotes. " ) < < std : : endl ;
return ;
}
try
{
auto quote_str { query_fq ( source , commodities ) } ;
auto ptree { parse_quotes ( quote_str ) } ;
if ( is_currency )
show_currency_quotes ( ptree , commodities , verbose ) ;
else
show_quotes ( ptree , commodities , verbose ) ;
}
catch ( const GncQuoteException & err )
{
std : : cerr < < _ ( " Finance::Quote retrieval failed with error " ) < < err . what ( ) < < std : : endl ;
}
2021-02-04 08:10:55 -06:00
}
2022-09-17 13:52:15 -05:00
const QFVec &
GncQuotesImpl : : failures ( ) noexcept
{
return m_failures ;
}
static std : : string
explain ( GncQuoteError err , const std : : string & errmsg )
{
std : : string retval ;
switch ( err )
{
case GncQuoteError : : NO_RESULT :
if ( errmsg . empty ( ) )
retval + = _ ( " Finance::Quote returned no data and set no error. " ) ;
else
retval + = _ ( " Finance::Quote returned an error: " ) + errmsg ;
break ;
case GncQuoteError : : QUOTE_FAILED :
if ( errmsg . empty ( ) )
retval + = _ ( " Finance::Quote reported failure set no error. " ) ;
else
2023-03-24 16:28:24 -05:00
retval + = _ ( " Finance::Quote reported failure with error: " ) + errmsg ;
2022-09-17 13:52:15 -05:00
break ;
case GncQuoteError : : NO_CURRENCY :
retval + = _ ( " Finance::Quote returned a quote with no currency. " ) ;
break ;
case GncQuoteError : : UNKNOWN_CURRENCY :
retval + = _ ( " Finance::Quote returned a quote with a currency GnuCash doesn't know about. " ) ;
break ;
case GncQuoteError : : NO_PRICE :
retval + = _ ( " Finance::Quote returned a quote with no price element. " ) ;
break ;
case GncQuoteError : : PRICE_PARSE_FAILURE :
retval + = _ ( " Finance::Quote returned a quote with a price that GnuCash was unable to covert to a number. " ) ;
break ;
case GncQuoteError : : SUCCESS :
default :
retval + = _ ( " The quote has no error set. " ) ;
break ;
}
return retval ;
}
std : : string
GncQuotesImpl : : report_failures ( ) noexcept
{
std : : string retval { _ ( " Quotes for the following commodities were unavailable or unusable: \n " ) } ;
std : : for_each ( m_failures . begin ( ) , m_failures . end ( ) ,
[ & retval ] ( auto failure )
{
auto [ ns , sym , reason , err ] = failure ;
retval + = " * " + ns + " : " + sym + " " +
explain ( reason , err ) + " \n " ;
} ) ;
return retval ;
}
/* **** Private function implementations ****/
2023-04-09 17:20:07 -05:00
using Path = bpt : : ptree : : path_type ;
static inline Path make_quote_path ( const std : : string & name_space ,
const std : : string & symbol )
{
using Path = bpt : : ptree : : path_type ;
Path key { name_space , ' | ' } ;
key / = Path { symbol , ' | ' } ;
return key ;
} ;
2022-09-02 12:13:09 -05:00
std : : string
2023-04-09 17:20:07 -05:00
GncQuotesImpl : : comm_vec_to_json_string ( const CommVec & comm_vec ) const
2021-03-19 11:46:46 -05:00
{
bpt : : ptree pt , pt_child ;
2023-04-09 17:20:07 -05:00
pt . put ( " defaultcurrency " , gnc_commodity_get_mnemonic ( m_dflt_curr ) ) ;
2021-03-19 11:46:46 -05:00
2022-09-10 19:29:23 -05:00
std : : for_each ( comm_vec . cbegin ( ) , comm_vec . cend ( ) ,
2021-03-19 11:46:46 -05:00
[ this , & pt ] ( auto comm )
{
auto comm_mnemonic = gnc_commodity_get_mnemonic ( comm ) ;
auto comm_ns = std : : string ( " currency " ) ;
if ( gnc_commodity_is_currency ( comm ) )
{
if ( gnc_commodity_equiv ( comm , m_dflt_curr ) | |
2023-04-09 17:20:07 -05:00
( ! comm_mnemonic | | ( strcmp ( comm_mnemonic , " XXX " ) = = 0 ) ) )
2021-03-19 11:46:46 -05:00
return ;
}
else
2023-04-09 17:20:07 -05:00
comm_ns = gnc_quote_source_get_internal_name ( gnc_commodity_get_quote_source ( comm ) ) ;
2021-03-19 11:46:46 -05:00
2023-04-09 17:20:07 -05:00
pt . put ( make_quote_path ( comm_ns , comm_mnemonic ) , " " ) ;
2021-03-19 11:46:46 -05:00
}
) ;
std : : ostringstream result ;
bpt : : write_json ( result , pt ) ;
2022-09-02 12:13:09 -05:00
return result . str ( ) ;
}
2021-03-19 11:46:46 -05:00
2022-09-27 17:05:04 -05:00
static inline std : : string
get_quotes ( const std : : string & json_str , const std : : unique_ptr < GncQuoteSource > & qs )
2022-09-02 12:13:09 -05:00
{
2022-09-27 17:05:04 -05:00
auto [ rv , quotes , errors ] = qs - > get_quotes ( json_str ) ;
2022-09-10 19:29:23 -05:00
std : : string answer ;
2022-09-10 18:18:32 -05:00
2022-09-01 17:07:44 -05:00
if ( rv = = 0 )
2022-09-10 18:18:32 -05:00
{
2023-01-31 08:43:20 -06:00
for ( const auto & line : quotes )
2022-09-10 19:29:23 -05:00
answer . append ( line + " \n " ) ;
2022-09-10 18:18:32 -05:00
}
2021-03-19 11:46:46 -05:00
else
2022-09-10 18:18:32 -05:00
{
std : : string err_str ;
2023-01-31 08:43:20 -06:00
for ( const auto & line : errors )
2022-10-13 19:59:14 -05:00
{
if ( line = = " invalid_json \n " )
PERR ( " Finanace Quote Wrapper was unable to parse %s " ,
json_str . c_str ( ) ) ;
err_str + = parse_quotesource_error ( line ) ;
}
2022-09-10 18:18:32 -05:00
throw ( GncQuoteException ( err_str ) ) ;
}
2022-09-10 19:29:23 -05:00
2022-09-27 17:05:04 -05:00
return answer ;
}
std : : string
GncQuotesImpl : : query_fq ( const char * source , const StrVec & commodities )
{
bpt : : ptree pt ;
auto is_currency { strcmp ( source , " currency " ) = = 0 } ;
if ( is_currency & & commodities . size ( ) < 2 )
throw ( GncQuoteException ( _ ( " Currency quotes requires at least two currencies " ) ) ) ;
if ( is_currency )
pt . put ( " defaultcurrency " , commodities [ 0 ] . c_str ( ) ) ;
else
pt . put ( " defaultcurrency " , gnc_commodity_get_mnemonic ( m_dflt_curr ) ) ;
std : : for_each ( is_currency ? + + commodities . cbegin ( ) : commodities . cbegin ( ) ,
commodities . cend ( ) ,
2023-01-26 12:27:39 -06:00
[ source , & pt ] ( auto sym )
2022-09-27 17:05:04 -05:00
{
2023-04-09 17:20:07 -05:00
pt . put ( make_quote_path ( source , sym ) , " " ) ;
2022-09-27 17:05:04 -05:00
} ) ;
std : : ostringstream result ;
bpt : : write_json ( result , pt ) ;
2023-04-09 17:20:07 -05:00
auto result_str { result . str ( ) } ;
PINFO ( " Query JSON: %s \n " , result_str . c_str ( ) ) ;
2022-09-27 17:05:04 -05:00
return get_quotes ( result . str ( ) , m_quotesource ) ;
}
std : : string
GncQuotesImpl : : query_fq ( const CommVec & comm_vec )
{
auto json_str { comm_vec_to_json_string ( comm_vec ) } ;
2023-04-09 17:20:07 -05:00
PINFO ( " Query JSON: %s \n " , json_str . c_str ( ) ) ;
2022-09-27 17:05:04 -05:00
return get_quotes ( json_str , m_quotesource ) ;
2021-03-19 11:46:46 -05:00
}
2022-09-09 18:31:45 -05:00
struct PriceParams
2022-09-02 13:16:06 -05:00
{
2022-09-09 18:31:45 -05:00
const char * ns ;
const char * mnemonic ;
bool success ;
std : : string type ;
boost : : optional < std : : string > price ;
bool inverted ;
boost : : optional < std : : string > date ;
boost : : optional < std : : string > time ;
boost : : optional < std : : string > currency ;
boost : : optional < std : : string > errormsg ;
} ;
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
static void
get_price_and_type ( PriceParams & p , const bpt : : ptree & comm_pt )
{
p . type = " last " ;
p . price = comm_pt . get_optional < std : : string > ( p . type ) ;
2022-09-17 13:52:15 -05:00
2022-09-09 18:31:45 -05:00
if ( ! p . price )
2022-09-02 13:16:06 -05:00
{
2022-09-09 18:31:45 -05:00
p . type = " nav " ;
p . price = comm_pt . get_optional < std : : string > ( p . type ) ;
2022-09-02 13:16:06 -05:00
}
2022-09-17 13:52:15 -05:00
2022-09-09 18:31:45 -05:00
if ( ! p . price )
2022-09-02 13:16:06 -05:00
{
2022-09-09 18:31:45 -05:00
p . type = " price " ;
p . price = comm_pt . get_optional < std : : string > ( p . type ) ;
2022-09-02 13:16:06 -05:00
/* guile wrapper used "unknown" as price type when "price" was found,
* reproducing here to keep same result for users in the pricedb */
2022-09-09 18:31:45 -05:00
p . type = p . price ? " unknown " : " missing " ;
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
}
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
static void
parse_quote_json ( PriceParams & p , const bpt : : ptree & comm_pt )
{
auto success = comm_pt . get_optional < bool > ( " success " ) ;
p . success = success & & * success ;
if ( ! p . success )
p . errormsg = comm_pt . get_optional < std : : string > ( " errormsg " ) ;
get_price_and_type ( p , comm_pt ) ;
auto inverted = comm_pt . get_optional < bool > ( " inverted " ) ;
p . inverted = inverted & & * inverted ;
p . date = comm_pt . get_optional < std : : string > ( " date " ) ;
p . time = comm_pt . get_optional < std : : string > ( " time " ) ;
p . currency = comm_pt . get_optional < std : : string > ( " currency " ) ;
PINFO ( " Commodity: %s " , p . mnemonic ) ;
2023-08-15 23:07:56 -05:00
PINFO ( " Success: %s " , ( p . success ? " yes " : " no " ) ) ;
2022-09-09 18:31:45 -05:00
PINFO ( " Date: %s " , ( p . date ? p . date - > c_str ( ) : " missing " ) ) ;
PINFO ( " Time: %s " , ( p . time ? p . time - > c_str ( ) : " missing " ) ) ;
PINFO ( " Currency: %s " , ( p . currency ? p . currency - > c_str ( ) : " missing " ) ) ;
PINFO ( " Price: %s " , ( p . price ? p . price - > c_str ( ) : " missing " ) ) ;
2023-05-18 16:34:05 -05:00
PINFO ( " Inverted: %s \n " , ( p . inverted ? " yes " : " no " ) ) ;
2022-09-09 18:31:45 -05:00
}
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
static time64
calc_price_time ( const PriceParams & p )
{
2022-09-12 20:17:12 -05:00
/* Note that as of F::Q v. 1.52 the only sources that provide
* quote times are ftfunds ( aka ukfunds ) , morningstarch , and
* mstaruk_fund , but it ' s faked with a comment " Set a dummy time
* as gnucash insists on having a valid format " . It's also wrong,
* as it lacks seconds . Best ignored .
*/
2023-07-08 18:47:14 -05:00
if ( p . date & & ! p . date - > empty ( ) )
2022-09-02 13:16:06 -05:00
{
2022-09-12 20:17:12 -05:00
try
{
2023-07-08 18:07:09 -05:00
auto quote_time { GncDateTime ( GncDate ( * p . date , " m-d-y " ) ) } ;
2022-09-12 20:17:12 -05:00
PINFO ( " Quote date included, using %s for %s:%s " ,
2023-07-08 18:59:16 -05:00
quote_time . format ( " %Y-%m-%d %H:%M:%S %z " ) . c_str ( ) , p . ns , p . mnemonic ) ;
2023-07-08 18:07:09 -05:00
return static_cast < time64 > ( quote_time ) ;
}
catch ( const std : : exception & err )
2022-09-12 20:17:12 -05:00
{
auto now { GncDateTime ( ) } ;
2023-07-08 18:07:09 -05:00
PWARN ( " Warning: failed to parse quote date '%s' for %s:%s because %s - will use %s " ,
2023-07-08 18:59:16 -05:00
p . date - > c_str ( ) , p . ns , p . mnemonic , err . what ( ) , now . format ( " %Y-%m-%d %H:%M:%S %z " ) . c_str ( ) ) ;
2022-09-12 20:17:12 -05:00
return static_cast < time64 > ( now ) ;
}
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
2022-10-13 14:52:36 -05:00
auto now { GncDateTime ( ) } ;
PINFO ( " No date was returned for %s:%s - will use %s " ,
2023-07-08 18:59:16 -05:00
p . ns , p . mnemonic , now . format ( " %Y-%m-%d %H:%M:%S %z " ) . c_str ( ) ) ;
2022-10-13 14:52:36 -05:00
return static_cast < time64 > ( now ) ;
2022-09-09 18:31:45 -05:00
}
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
static boost : : optional < GncNumeric >
get_price ( const PriceParams & p )
{
boost : : optional < GncNumeric > price ;
2022-09-02 13:16:06 -05:00
try
{
2022-09-09 18:31:45 -05:00
price = GncNumeric { * p . price } ;
2022-09-02 13:16:06 -05:00
}
catch ( . . . )
{
PWARN ( " Skipped %s:%s - failed to parse returned price '%s' " ,
2022-09-09 18:31:45 -05:00
p . ns , p . mnemonic , p . price - > c_str ( ) ) ;
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
if ( price & & p . inverted )
* price = price - > inv ( ) ;
return price ;
}
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
static gnc_commodity *
2022-09-17 13:52:15 -05:00
get_currency ( const PriceParams & p , QofBook * book , QFVec & failures )
2022-09-09 18:31:45 -05:00
{
if ( ! p . currency )
2022-09-02 13:16:06 -05:00
{
2022-09-17 13:52:15 -05:00
failures . emplace_back ( p . ns , p . mnemonic , GncQuoteError : : NO_CURRENCY ,
empty_string ) ;
PWARN ( " Skipped %s:%s - Finance::Quote returned a quote with no currency " ,
2022-09-09 18:31:45 -05:00
p . ns , p . mnemonic ) ;
2022-09-02 13:16:06 -05:00
return nullptr ;
}
2022-09-09 18:31:45 -05:00
std : : string curr_str = * p . currency ;
boost : : to_upper ( curr_str ) ;
auto commodity_table = gnc_commodity_table_get_table ( book ) ;
auto currency = gnc_commodity_table_lookup ( commodity_table , " ISO4217 " , curr_str . c_str ( ) ) ;
2022-09-02 13:16:06 -05:00
if ( ! currency )
{
2022-09-17 13:52:15 -05:00
failures . emplace_back ( p . ns , p . mnemonic ,
GncQuoteError : : UNKNOWN_CURRENCY , empty_string ) ;
2022-09-02 13:16:06 -05:00
PWARN ( " Skipped %s:%s - failed to parse returned currency '%s' " ,
2022-09-09 18:31:45 -05:00
p . ns , p . mnemonic , p . currency - > c_str ( ) ) ;
2022-09-02 13:16:06 -05:00
return nullptr ;
}
2022-09-09 18:31:45 -05:00
return currency ;
}
GNCPrice *
GncQuotesImpl : : parse_one_quote ( const bpt : : ptree & pt , gnc_commodity * comm )
{
PriceParams p ;
p . ns = gnc_commodity_get_namespace ( comm ) ;
p . mnemonic = gnc_commodity_get_mnemonic ( comm ) ;
if ( gnc_commodity_equiv ( comm , m_dflt_curr ) | |
( ! p . mnemonic | | ( strcmp ( p . mnemonic , " XXX " ) = = 0 ) ) )
return nullptr ;
auto comm_pt_ai { pt . find ( p . mnemonic ) } ;
if ( comm_pt_ai = = pt . not_found ( ) )
2022-09-02 13:16:06 -05:00
{
2022-09-17 13:52:15 -05:00
m_failures . emplace_back ( p . ns , p . mnemonic , GncQuoteError : : NO_RESULT ,
empty_string ) ;
2022-09-09 18:31:45 -05:00
PINFO ( " Skipped %s:%s - Finance::Quote didn't return any data. " ,
p . ns , p . mnemonic ) ;
return nullptr ;
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
auto comm_pt { comm_pt_ai - > second } ;
parse_quote_json ( p , comm_pt ) ;
if ( ! p . success )
2022-09-02 13:16:06 -05:00
{
2022-09-17 13:52:15 -05:00
m_failures . emplace_back ( p . ns , p . mnemonic , GncQuoteError : : QUOTE_FAILED ,
p . errormsg ? * p . errormsg : empty_string ) ;
2022-09-09 18:31:45 -05:00
PWARN ( " Skipped %s:%s - Finance::Quote returned fetch failure. \n Reason %s " ,
p . ns , p . mnemonic ,
( p . errormsg ? p . errormsg - > c_str ( ) : " unknown " ) ) ;
return nullptr ;
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
if ( ! p . price )
2022-09-02 13:16:06 -05:00
{
2022-09-17 13:52:15 -05:00
m_failures . emplace_back ( p . ns , p . mnemonic ,
GncQuoteError : : NO_PRICE , empty_string ) ;
2022-09-09 18:31:45 -05:00
PWARN ( " Skipped %s:%s - Finance::Quote didn't return a valid price " ,
p . ns , p . mnemonic ) ;
return nullptr ;
2022-09-02 13:16:06 -05:00
}
2022-09-09 18:31:45 -05:00
auto price { get_price ( p ) } ;
if ( ! price )
2022-09-17 13:52:15 -05:00
{
m_failures . emplace_back ( p . ns , p . mnemonic ,
GncQuoteError : : PRICE_PARSE_FAILURE ,
empty_string ) ;
2022-09-09 18:31:45 -05:00
return nullptr ;
2022-09-17 13:52:15 -05:00
}
2022-09-09 18:31:45 -05:00
2022-09-17 13:52:15 -05:00
auto currency { get_currency ( p , m_book , m_failures ) } ;
2022-09-09 18:31:45 -05:00
if ( ! currency )
2022-09-17 13:52:15 -05:00
return nullptr ;
2022-09-02 13:16:06 -05:00
2022-09-09 18:31:45 -05:00
auto quotedt { calc_price_time ( p ) } ;
2022-09-02 13:16:06 -05:00
auto gnc_price = gnc_price_create ( m_book ) ;
gnc_price_begin_edit ( gnc_price ) ;
gnc_price_set_commodity ( gnc_price , comm ) ;
gnc_price_set_currency ( gnc_price , currency ) ;
gnc_price_set_time64 ( gnc_price , static_cast < time64 > ( quotedt ) ) ;
gnc_price_set_source ( gnc_price , PRICE_SOURCE_FQ ) ;
2022-09-09 18:31:45 -05:00
gnc_price_set_typestr ( gnc_price , p . type . c_str ( ) ) ;
gnc_price_set_value ( gnc_price , * price ) ;
2022-09-02 13:16:06 -05:00
gnc_price_commit_edit ( gnc_price ) ;
return gnc_price ;
}
2022-09-27 17:05:04 -05:00
bpt : : ptree
GncQuotesImpl : : parse_quotes ( const std : : string & quote_str )
2021-02-28 15:36:22 -06:00
{
bpt : : ptree pt ;
2022-09-10 19:29:23 -05:00
std : : istringstream ss { quote_str } ;
2023-08-08 17:37:49 -05:00
std : : string what ;
2021-02-28 15:36:22 -06:00
try
{
bpt : : read_json ( ss , pt ) ;
}
catch ( bpt : : json_parser_error & e ) {
2022-09-10 18:18:32 -05:00
what = e . what ( ) ;
}
catch ( const std : : runtime_error & e )
{
what = e . what ( ) ;
}
catch ( const std : : logic_error & e )
{
what = e . what ( ) ;
2021-02-28 15:36:22 -06:00
}
catch ( . . . ) {
2022-09-10 18:18:32 -05:00
std : : string error_msg { _ ( " Failed to parse result returned by Finance::Quote. " ) } ;
2023-08-08 17:37:49 -05:00
error_msg + = " \n " ;
//Translators: This labels the return value of a query to Finance::Quote written in an error.
error_msg + = _ ( " Result: " ) ;
error_msg + = " \n " ;
error_msg + = quote_str ;
2022-09-10 18:18:32 -05:00
throw ( GncQuoteException ( error_msg ) ) ;
}
2023-08-08 17:37:49 -05:00
if ( ! what . empty ( ) )
2022-09-10 18:18:32 -05:00
{
std : : string error_msg { _ ( " Failed to parse result returned by Finance::Quote. " ) } ;
error_msg + = " \n " ;
2023-08-08 17:37:49 -05:00
//Translators: This is the error message reported by the Online Quotes processing code.
2022-09-10 18:18:32 -05:00
error_msg + = _ ( " Error message: " ) ;
error_msg + = " \n " ;
error_msg + = what ;
2023-08-08 17:37:49 -05:00
error_msg + = " \n " ;
//Translators: This labels the return value of a query to Finance::Quote written in an error.
error_msg + = _ ( " Result: " ) ;
error_msg + = " \n " ;
error_msg + = quote_str ;
2022-09-10 18:18:32 -05:00
throw ( GncQuoteException ( error_msg ) ) ;
2021-02-28 15:36:22 -06:00
}
2022-09-27 17:05:04 -05:00
return pt ;
}
2021-02-28 15:36:22 -06:00
2022-09-27 17:05:04 -05:00
void
GncQuotesImpl : : create_quotes ( const bpt : : ptree & pt , const CommVec & comm_vec )
{
2022-09-02 13:16:06 -05:00
auto pricedb { gnc_pricedb_get_db ( m_book ) } ;
2022-09-10 19:29:23 -05:00
for ( auto comm : comm_vec )
2022-09-02 13:16:06 -05:00
{
auto price { parse_one_quote ( pt , comm ) } ;
if ( ! price )
continue ;
2023-10-20 10:46:57 -05:00
// See the comment at gnc_pricedb_add_price
2022-09-02 13:16:06 -05:00
gnc_pricedb_add_price ( pricedb , price ) ;
}
2021-02-28 15:36:22 -06:00
}
2022-09-27 17:05:04 -05:00
static void
show_verbose_quote ( const bpt : : ptree & comm_pt )
{
std : : for_each ( comm_pt . begin ( ) , comm_pt . end ( ) ,
[ ] ( auto elem ) {
std : : cout < < std : : setw ( 12 ) < < std : : right < < elem . first < < " => " < <
std : : left < < elem . second . data ( ) < < " \n " ;
} ) ;
std : : cout < < std : : endl ;
}
static void
show_gnucash_quote ( const bpt : : ptree & comm_pt )
{
constexpr const char * ptr { " <=== " } ;
constexpr const char * dptr { " <= \\ " } ;
constexpr const char * uptr { " <=/ " } ;
//Translators: Means that the preceding element is required
const char * reqd { C_ ( " Finance::Quote " , " required " ) } ;
//Translators: Means that the quote will work best if the preceding element is provided
const char * rec { C_ ( " Finance::Quote " , " recommended " ) } ;
//Translators: Means that one of the indicated elements is required
const char * oot { C_ ( " Finance::Quote " , " one of these " ) } ;
//Translators: Means that a required element wasn't reported. The *s are for emphasis.
const char * miss { C_ ( " Finance::Quote " , " **missing** " ) } ;
const std : : string miss_str { miss } ;
auto outline { [ ] ( const char * label , std : : string value , const char * pointer , const char * req ) {
std : : cout < < std : : setw ( 12 ) < < std : : right < < label < < std : : setw ( 16 ) < < std : : left < <
value < < pointer < < req < < " \n " ;
} } ;
std : : cout < < _ ( " Finance::Quote fields GnuCash uses: " ) < < " \n " ;
//Translators: The stock or Mutual Fund symbol, ISIN, CUSIP, etc.
outline ( C_ ( " Finance::Quote " , " symbol: " ) , comm_pt . get < char > ( " symbol " , miss ) , ptr , reqd ) ;
//Translators: The date of the quote.
outline ( C_ ( " Finance::Quote " , " date: " ) , comm_pt . get < char > ( " date " , miss ) , ptr , rec ) ;
//Translators: The quote currency
outline ( C_ ( " Finance::Quote " , " currency: " ) , comm_pt . get < char > ( " currency " , miss ) , ptr , reqd ) ;
auto last { comm_pt . get < char > ( " last " , " " ) } ;
auto nav { comm_pt . get < char > ( " nav " , " " ) } ;
auto price { comm_pt . get < char > ( " nav " , " " ) } ;
auto no_price { last . empty ( ) & & nav . empty ( ) & & price . empty ( ) } ;
//Translators: The quote is for the most recent trade on the exchange
outline ( C_ ( " Finance::Quote " , " last: " ) , no_price ? miss_str : last , dptr , " " ) ;
//Translators: The quote is for an open-ended mutual fund and represents the net asset value of one unit of the fund at the previous close of trading.
outline ( C_ ( " Finance::Quote " , " nav: " ) , no_price ? miss_str : nav , ptr , oot ) ;
//Translators: The quote is neither a last trade nor an NAV.
outline ( C_ ( " Finance::Quote " , " price: " ) , no_price ? miss_str : price , uptr , " " ) ;
std : : cout < < std : : endl ;
}
static const bpt : : ptree empty_tree { } ;
static inline const bpt : : ptree &
get_commodity_data ( const bpt : : ptree & pt , const std : : string & comm )
{
auto commdata { pt . find ( comm ) } ;
if ( commdata = = pt . not_found ( ) )
{
std : : cout < < comm < < " " < < _ ( " Finance::Quote returned no data and set no error. " ) < < std : : endl ;
return empty_tree ;
}
auto & comm_pt { commdata - > second } ;
auto success = comm_pt . get_optional < bool > ( " success " ) ;
if ( ! ( success & & * success ) )
{
auto errormsg = comm_pt . get_optional < std : : string > ( " errormsg " ) ;
if ( errormsg & & ! errormsg - > empty ( ) )
std : : cout < < _ ( " Finance::Quote reported a failure for symbol " ) < <
comm < < " : " < < * errormsg < < std : : endl ;
else
std : : cout < < _ ( " Finance::Quote failed silently to retrieve a quote for symbol " ) < <
comm < < std : : endl ;
return empty_tree ;
}
return comm_pt ;
}
static void
show_quotes ( const bpt : : ptree & pt , const StrVec & commodities , bool verbose )
{
2023-01-31 08:43:20 -06:00
for ( const auto & comm : commodities )
2022-09-27 17:05:04 -05:00
{
auto comm_pt { get_commodity_data ( pt , comm ) } ;
if ( comm_pt = = empty_tree )
continue ;
if ( verbose )
{
std : : cout < < comm < < " : \n " ;
show_verbose_quote ( comm_pt ) ;
}
else
{
show_gnucash_quote ( comm_pt ) ;
}
}
}
2021-02-28 15:36:22 -06:00
2022-09-27 17:05:04 -05:00
static void
show_currency_quotes ( const bpt : : ptree & pt , const StrVec & commodities , bool verbose )
{
auto to_cur { commodities . front ( ) } ;
2023-01-31 08:43:20 -06:00
for ( const auto & comm : commodities )
2022-09-27 17:05:04 -05:00
{
if ( comm = = to_cur )
continue ;
auto comm_pt { get_commodity_data ( pt , comm ) } ;
2021-02-11 09:05:05 -06:00
2022-09-27 17:05:04 -05:00
if ( comm_pt = = empty_tree )
continue ;
if ( verbose )
{
std : : cout < < comm < < " : \n " ;
show_verbose_quote ( comm_pt ) ;
}
else
{
std : : cout < < " 1 " < < comm < < " = " < <
comm_pt . get < char > ( " last " , " Not Found " ) < < " " < < to_cur < < " \n " ;
}
std : : cout < < std : : endl ;
}
}
2022-10-13 19:59:14 -05:00
static std : : string
parse_quotesource_error ( const std : : string & line )
{
std : : string err_str ;
if ( line = = " invalid_json \n " )
{
err_str + = _ ( " GnuCash submitted invalid json to Finance::Quote. The details were logged. " ) ;
}
else if ( line . substr ( 0 , 15 ) = = " missing_modules " )
{
PERR ( " Missing Finance::Quote Dependencies: %s " ,
line . substr ( 17 ) . c_str ( ) ) ;
err_str + = _ ( " Perl is missing the following modules. Please see https://wiki.gnucash.org/wiki/Online_Quotes#Finance::Quote for detailed corrective action. " ) ;
err_str + = line . substr ( 17 ) ;
}
else
{
PERR ( " Unrecognized Finance::Quote Error %s " , line . c_str ( ) ) ;
err_str + = _ ( " Unrecognized Finance::Quote Error: " ) ;
err_str + = line ;
}
err_str + = " \n " ;
return err_str ;
}
2021-02-05 10:48:06 -06:00
/********************************************************************
* gnc_quotes_get_quotable_commodities
* list commodities in a given namespace that get price quotes
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-09-01 17:07:44 -05:00
/* Helper function to be passed to g_list_for_each applied to the result
* of gnc_commodity_namespace_get_commodity_list .
*/
2021-02-05 10:48:06 -06:00
static void
get_quotables_helper1 ( gpointer value , gpointer data )
{
auto l = static_cast < CommVec * > ( data ) ;
auto comm = static_cast < gnc_commodity * > ( value ) ;
auto quote_flag = gnc_commodity_get_quote_flag ( comm ) ;
auto quote_source = gnc_commodity_get_quote_source ( comm ) ;
auto quote_source_supported = gnc_quote_source_get_supported ( quote_source ) ;
if ( ! quote_flag | |
! quote_source | | ! quote_source_supported )
return ;
l - > push_back ( comm ) ;
}
2022-09-01 17:07:44 -05:00
// Helper function to be passed to gnc_commodity_table_for_each
2021-02-05 10:48:06 -06:00
static gboolean
get_quotables_helper2 ( gnc_commodity * comm , gpointer data )
{
auto l = static_cast < CommVec * > ( data ) ;
auto quote_flag = gnc_commodity_get_quote_flag ( comm ) ;
auto quote_source = gnc_commodity_get_quote_source ( comm ) ;
auto quote_source_supported = gnc_quote_source_get_supported ( quote_source ) ;
if ( ! quote_flag | |
! quote_source | | ! quote_source_supported )
return TRUE ;
l - > push_back ( comm ) ;
return TRUE ;
}
CommVec
gnc_quotes_get_quotable_commodities ( const gnc_commodity_table * table )
{
gnc_commodity_namespace * ns = NULL ;
const char * name_space ;
GList * nslist , * tmp ;
CommVec l ;
regex_t pattern ;
const char * expression = gnc_prefs_get_namespace_regexp ( ) ;
// ENTER("table=%p, expression=%s", table, expression);
if ( ! table )
return CommVec ( ) ;
if ( expression & & * expression )
{
if ( regcomp ( & pattern , expression , REG_EXTENDED | REG_ICASE ) ! = 0 )
{
// LEAVE ("Cannot compile regex");
return CommVec ( ) ;
}
nslist = gnc_commodity_table_get_namespaces ( table ) ;
for ( tmp = nslist ; tmp ; tmp = tmp - > next )
{
name_space = static_cast < const char * > ( tmp - > data ) ;
if ( regexec ( & pattern , name_space , 0 , NULL , 0 ) = = 0 )
{
// DEBUG ("Running list of %s commodities", name_space);
ns = gnc_commodity_table_find_namespace ( table , name_space ) ;
if ( ns )
{
auto cm_list = gnc_commodity_namespace_get_commodity_list ( ns ) ;
g_list_foreach ( cm_list , & 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 ;
}
2021-02-12 11:18:20 -06:00
/* Public interface functions */
// Constructor - checks for presence of Finance::Quote and import version and quote sources
GncQuotes : : GncQuotes ( )
{
2022-09-10 18:18:32 -05:00
try
{
m_impl = std : : make_unique < GncQuotesImpl > ( ) ;
}
catch ( const GncQuoteSourceError & err )
{
throw ( GncQuoteException ( err . what ( ) ) ) ;
}
2021-02-12 11:18:20 -06:00
}
2022-09-10 18:18:32 -05:00
2021-02-12 11:18:20 -06:00
void
2021-03-18 13:12:23 -05:00
GncQuotes : : fetch ( QofBook * book )
2021-02-12 11:18:20 -06:00
{
2021-03-18 13:12:23 -05:00
m_impl - > fetch ( book ) ;
2021-02-12 11:18:20 -06:00
}
2021-03-18 10:33:16 -05:00
void GncQuotes : : fetch ( CommVec & commodities )
2021-02-12 11:18:20 -06:00
{
m_impl - > fetch ( commodities ) ;
}
2021-03-18 10:33:16 -05:00
void GncQuotes : : fetch ( gnc_commodity * comm )
{
m_impl - > fetch ( comm ) ;
}
2022-09-27 17:05:04 -05:00
void GncQuotes : : report ( const char * source , const StrVec & commodities ,
bool verbose )
{
m_impl - > report ( source , commodities , verbose ) ;
}
2021-02-12 11:18:20 -06:00
const std : : string & GncQuotes : : version ( ) noexcept
{
return m_impl - > version ( ) ;
}
const QuoteSources & GncQuotes : : sources ( ) noexcept
{
return m_impl - > sources ( ) ;
}
GncQuotes : : ~ GncQuotes ( ) = default ;
2022-09-18 17:06:59 -05:00
bool
GncQuotes : : had_failures ( ) noexcept
{
return m_impl - > had_failures ( ) ;
}
2022-09-17 13:52:15 -05:00
const QFVec &
GncQuotes : : failures ( ) noexcept
{
return m_impl - > failures ( ) ;
}
const std : : string
GncQuotes : : report_failures ( ) noexcept
{
return m_impl - > report_failures ( ) ;
}