@ -27,6 +27,7 @@
# include <string>
# include <iostream>
# include <sstream>
# include <boost/algorithm/string.hpp>
# include <boost/filesystem.hpp>
# include <boost/process.hpp>
# include <boost/property_tree/ptree.hpp>
@ -36,6 +37,8 @@
# include <boost/asio.hpp>
# include <glib.h>
# include "gnc-commodity.hpp"
# include <gnc-datetime.hpp>
# include <gnc-numeric.hpp>
# include "gnc-quotes.hpp"
extern " C " {
@ -48,6 +51,7 @@ extern "C" {
}
namespace bp = boost : : process ;
namespace bfs = boost : : filesystem ;
namespace bpt = boost : : property_tree ;
namespace bio = boost : : iostreams ;
@ -78,8 +82,12 @@ private:
// - one with the contents of stdout
// - one with the contents of stderr
// Will also set m_cmd_result
CmdOutput run_cmd ( std : : string cmd_name , StrVec args , StrVec input_vec ) ;
template < typename BufferT > CmdOutput run_cmd ( const bfs : : path & cmd_name , StrVec args , BufferT input ) ;
void parse_quotes ( const std : : string & quotes ) ;
CommVec m_comm_vec ;
std : : string m_version ;
QuoteSources m_sources ;
int m_cmd_result ;
@ -140,11 +148,13 @@ GncQuotesImpl::sources_as_glist()
void
GncQuotesImpl : : fetch ( const CommVec & commodities )
{
m_comm_vec = commodities ; // Store for later use
auto dflt_curr = gnc_default_currency ( ) ;
bpt : : ptree pt , pt_child ;
pt . put ( " defaultcurrency " , gnc_commodity_get_mnemonic ( dflt_curr ) ) ;
std : : for_each ( commodities. cbegin ( ) , commodities . cend ( ) ,
std : : for_each ( m_comm_vec. cbegin ( ) , m_comm_vec . cend ( ) ,
[ & pt , & dflt_curr ] ( auto comm )
{
auto comm_mnemonic = gnc_commodity_get_mnemonic ( comm ) ;
@ -152,7 +162,7 @@ GncQuotesImpl::fetch (const CommVec& commodities)
if ( gnc_commodity_is_currency ( comm ) )
{
if ( gnc_commodity_equiv ( comm , dflt_curr ) | |
( ! comm_mnemonic | | ( strcmp ( comm_mnemonic , " XXX " ) = = 0 ) ) )
( ! comm_mnemonic | | ( strcmp ( comm_mnemonic , " XXX " ) = = 0 ) ) )
return ;
}
else
@ -165,7 +175,30 @@ GncQuotesImpl::fetch (const CommVec& commodities)
std : : ostringstream result ;
bpt : : write_json ( result , pt ) ;
std : : cerr < < " GncQuotes fetch_all - resulting json object \n " < < result . str ( ) < < std : : endl ;
//std::cerr << "GncQuotes fetch_all - resulting json object\n" << result.str() << std::endl;
auto perl_executable = bp : : search_path ( " perl " ) ;
auto fq_wrapper = std : : string ( gnc_path_get_bindir ( ) ) + " /finance-quote-wrapper " ;
StrVec args { " -w " , fq_wrapper } ;
auto cmd_out = run_cmd ( perl_executable . string ( ) , args , result . str ( ) ) ;
if ( m_cmd_result = = 0 )
{
std : : string resultstr ;
for ( auto line : cmd_out . first )
resultstr . append ( std : : move ( line ) + " \n " ) ;
parse_quotes ( resultstr ) ;
}
else
for ( auto line : cmd_out . second )
m_error_msg . append ( std : : move ( line ) + " \n " ) ;
for ( auto line : cmd_out . first )
std : : cerr < < " Output line retrieved from wrapper: \n " < < line < < std : : endl ;
for ( auto line : cmd_out . second )
std : : cerr < < " Error line retrieved from wrapper: \n " < < line < < std : : endl ;
}
@ -186,22 +219,27 @@ format_quotes (const std::vector<gnc_commodity*>)
}
CmdOutput
GncQuotesImpl : : run_cmd ( std : : string cmd_name , StrVec args , StrVec input_vec )
template < typename BufferT > CmdOutput
GncQuotesImpl : : run_cmd ( const bfs : : path & cmd_name , StrVec args , BufferT input )
{
StrVec out_vec , err_vec ;
auto av_key = gnc_prefs_get_string ( " general.finance-quote " , " alphavantage-api-key " ) ;
if ( ! av_key )
std : : cerr < < " No AlphaVantage API key set, currency quotes and other AlphaVantage based quotes won't work. " < < std : : endl ;
try
{
std : : future < std : : vector < char > > out_buf , err_buf ;
boost : : asio : : io_service svc ;
auto input_buf = bp : : buffer ( input _vec ) ;
auto input_buf = bp : : buffer ( input ) ;
bp : : child process ( cmd_name , args ,
bp : : std_out > out_buf ,
bp : : std_err > err_buf ,
bp : : std_in < input_buf ,
svc ) ;
bp : : std_out > out_buf ,
bp : : std_err > err_buf ,
bp : : std_in < input_buf ,
bp : : env [ " ALPHAVANTAGE_API_KEY " ] = ( av_key ? av_key : " " ) ,
svc ) ;
svc . run ( ) ;
process . wait ( ) ;
@ -233,6 +271,168 @@ GncQuotesImpl::run_cmd (std::string cmd_name, StrVec args, StrVec input_vec)
return CmdOutput ( std : : move ( out_vec ) , std : : move ( err_vec ) ) ;
}
void
GncQuotesImpl : : parse_quotes ( const std : : string & quotes_str )
{
bpt : : ptree pt ;
std : : istringstream ss { quotes_str } ;
try
{
bpt : : read_json ( ss , pt ) ;
}
catch ( bpt : : json_parser_error & e ) {
m_cmd_result = - 1 ;
m_error_msg = m_error_msg +
" Failed to parse quotes results. " + " \n " +
" Error message: " + " \n " +
e . what ( ) + " \n " ;
}
catch ( . . . ) {
m_cmd_result = - 1 ;
m_error_msg = m_error_msg +
" Failed to parse quotes results. " + " \n " ;
}
auto book = m_book ;
auto dflt_curr = gnc_default_currency ( ) ;
auto pricedb = gnc_pricedb_get_db ( m_book ) ;
std : : for_each ( m_comm_vec . begin ( ) , m_comm_vec . end ( ) ,
[ this , & pt , & dflt_curr , & pricedb ] ( gnc_commodity * comm )
{
auto comm_ns = gnc_commodity_get_namespace ( comm ) ;
auto comm_mnemonic = gnc_commodity_get_mnemonic ( comm ) ;
if ( gnc_commodity_equiv ( comm , dflt_curr ) | |
( ! comm_mnemonic | | ( strcmp ( comm_mnemonic , " XXX " ) = = 0 ) ) )
return ;
if ( pt . find ( comm_mnemonic ) = = pt . not_found ( ) )
{
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - Finance::Quote didn't return any data. \n " ;
return ;
}
std : : string key = comm_mnemonic ;
boost : : optional < bool > success = pt . get_optional < bool > ( key + " .success " ) ;
std : : string price_type = " last " ;
boost : : optional < std : : string > price_str = pt . get_optional < std : : string > ( key + " . " + price_type ) ;
if ( ! price_str )
{
price_type = " nav " ;
price_str = pt . get_optional < std : : string > ( key + " . " + price_type ) ;
}
if ( ! price_str )
{
price_type = " price " ;
price_str = pt . get_optional < std : : string > ( key + " . " + price_type ) ;
/* guile wrapper used "unknown" as price type when "price" was found,
* reproducing here to keep same result for users in the pricedb */
price_type = " unknown " ;
}
boost : : optional < bool > inverted_tmp = pt . get_optional < bool > ( key + " .inverted " ) ;
bool inverted = inverted_tmp ? * inverted_tmp : false ;
boost : : optional < std : : string > date_str = pt . get_optional < std : : string > ( key + " .date " ) ;
boost : : optional < std : : string > time_str = pt . get_optional < std : : string > ( key + " .time " ) ;
boost : : optional < std : : string > currency_str = pt . get_optional < std : : string > ( key + " .currency " ) ;
std : : cout < < " Commodity: " < < comm_mnemonic < < " \n " ;
std : : cout < < " Date: " < < ( date_str ? * date_str : " missing " ) < < " \n " ;
std : : cout < < " Time: " < < ( time_str ? * time_str : " missing " ) < < " \n " ;
std : : cout < < " Currency: " < < ( currency_str ? * currency_str : " missing " ) < < " \n " ;
std : : cout < < " Price: " < < ( price_str ? * price_str : " missing " ) < < " \n " ;
std : : cout < < " Inverted: " < < ( inverted ? " yes " : " no " ) < < " \n \n " ;
if ( ! success | | ! * success )
{
boost : : optional < std : : string > errmsg = pt . get_optional < std : : string > ( key + " .errormsg " ) ;
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - Finance::Quote returned fetch failure. \n " ;
std : : cerr < < " Reason: " < < ( errmsg ? * errmsg : " unknown " ) < < " \n " ;
return ;
}
if ( ! price_str )
{
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - Finance::Quote didn't return a valid price \n " ;
return ;
}
GncNumeric price ;
try
{
price = GncNumeric { * price_str } ;
}
catch ( . . . )
{
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - failed to parse returned price ' " < < * price_str < < " ' \n " ;
return ;
}
if ( inverted )
price = price . inv ( ) ;
if ( ! currency_str )
{
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - Finance::Quote didn't return a currency \n " ;
return ;
}
boost : : to_upper ( * currency_str ) ;
auto commodity_table = gnc_commodity_table_get_table ( m_book ) ;
auto currency = gnc_commodity_table_lookup ( commodity_table , " ISO4217 " , currency_str - > c_str ( ) ) ;
if ( ! currency )
{
std : : cerr < < " Skipped " < < comm_ns < < " : " < < comm_mnemonic < < " - failed to parse returned currency ' " < < * currency_str < < " ' \n " ;
return ;
}
std : : string iso_date_str = GncDate ( ) . format ( " %Y-%m-%d " ) ;
if ( date_str )
{
// Returned date is always in MM/DD/YYYY format according to F::Q man page, transform it to simplify conversion to GncDateTime
auto date_tmp = * date_str ;
iso_date_str = date_tmp . substr ( 6 , 4 ) + " - " + date_tmp . substr ( 0 , 2 ) + " - " + date_tmp . substr ( 3 , 2 ) ;
}
else
std : : cerr < < " Info: no date was returned for " < < comm_ns < < " : " < < comm_mnemonic < < " - will use today \n " ;
iso_date_str + = " " + ( time_str ? * time_str : " 12:00:00 " ) ;
auto can_convert = true ;
try
{
GncDateTime testdt { iso_date_str } ;
}
catch ( . . . )
{
std : : cerr < < " Warning: failed to parse quote date and time ' " < < iso_date_str < < " ' for " < < comm_ns < < " : " < < comm_mnemonic < < " - will use today \n " ;
return ;
}
/* Bit of an odd construct: GncDateTimes can't be copied,
which makes it impossible to first create a temporary GncDateTime
based on whether the string is parsable and then assign that temporary
to our final GncDateTime . The creation has to happen in one go , so
below construct will pass a different constructor argument based on
whether a test conversion worked or not .
*/
GncDateTime quotedt { can_convert ? iso_date_str : GncDateTime ( ) } ;
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 ) ;
gnc_price_set_typestr ( gnc_price , price_type . c_str ( ) ) ;
gnc_price_set_value ( gnc_price , price ) ;
gnc_pricedb_add_price ( pricedb , gnc_price ) ;
gnc_price_commit_edit ( gnc_price ) ;
gnc_price_unref ( gnc_price ) ;
} ) ;
}
/********************************************************************
* gnc_quotes_get_quotable_commodities