Merge branch 'price-quotes-cpp'

This commit is contained in:
John Ralls 2022-10-14 11:25:14 -07:00
commit 939a77407c
43 changed files with 2375 additions and 2174 deletions

View File

@ -512,6 +512,14 @@ if (NOT PERL_FOUND)
message(SEND_ERROR "Perl executable not found. Please set PERL_EXECUTABLE.")
endif()
execute_process(COMMAND
${PERL_EXECUTABLE} -MFinance::Quote -e ""
ERROR_QUIET
RESULT_VARIABLE have_f_q)
if (${have_f_q} EQUAL 0)
set(HAVE_F_Q 1)
endif()
get_filename_component(PERL_DIR ${PERL_EXECUTABLE} DIRECTORY)
find_program(POD2MAN_EXECUTABLE pod2man HINTS ${PERL_DIR})

View File

@ -74,22 +74,6 @@ Account * gnc_get_current_root_account (void);
#if defined(SWIGGUILE)
%typemap(out) GncCommodityList * {
SCM list = SCM_EOL;
GList *node;
for (node = $1; node; node = node->next)
list = scm_cons(gnc_quoteinfo2scm(static_cast<gnc_commodity*>(node->data)), list);
$result = scm_reverse(list);
}
%inline %{
typedef GList GncCommodityList;
GncCommodityList *
gnc_commodity_table_get_quotable_commodities(const gnc_commodity_table * table);
%}
gnc_commodity * gnc_default_currency (void);
gnc_commodity * gnc_default_report_currency (void);

View File

@ -90,11 +90,6 @@ gchar * gnc_build_stdreports_path(const gchar *);
%newobject gnc_build_reports_path;
gchar * gnc_build_reports_path(const gchar *);
void gnc_scm_log_warn(const gchar *);
void gnc_scm_log_error(const gchar *);
void gnc_scm_log_msg(const gchar *);
void gnc_scm_log_debug(const gchar *);
%newobject gnc_utf8_strip_invalid_strdup;
gchar * gnc_utf8_strip_invalid_strdup(const gchar *);

View File

@ -111,6 +111,15 @@ engine-common.i */
%include "qoflog.h"
%inline %{
static void gnc_log_warn(const char *msg)
{ g_log("gnc.scm", G_LOG_LEVEL_WARNING, "%s", msg); }
static void gnc_log_error(const char *msg)
{ g_log("gnc.scm", G_LOG_LEVEL_CRITICAL, "%s", msg); }
static void gnc_log_msg(const char *msg)
{ g_log("gnc.scm", G_LOG_LEVEL_MESSAGE, "%s", msg); }
static void gnc_log_debug(const char *msg)
{ g_log("gnc.scm", G_LOG_LEVEL_DEBUG, "%s", msg); }
static const GncGUID * gncPriceGetGUID(GNCPrice *x)
{ return qof_instance_get_guid(QOF_INSTANCE(x)); }
static const GncGUID * gncBudgetGetGUID(GncBudget *x)
@ -248,34 +257,8 @@ SplitList * qof_query_run_subquery (QofQuery *q, const QofQuery *q);
time64 time64CanonicalDayTime(time64 t);
%include <gnc-budget.h>
%typemap(in) GList * {
SCM path_scm = $input;
GList *path = NULL;
while (!scm_is_null (path_scm))
{
SCM key_scm = SCM_CAR (path_scm);
char *key;
if (!scm_is_string (key_scm))
break;
key = scm_to_locale_string (key_scm);
path = g_list_prepend (path, key);
path_scm = SCM_CDR (path_scm);
}
$1 = g_list_reverse (path);
}
%typemap (freearg) GList * "g_list_free_full ($1, g_free);"
void gnc_quote_source_set_fq_installed (const char* version_string,
GList *sources_list);
%clear GList *;
%ignore gnc_quote_source_set_fq_installed;
%ignore gnc_commodity_table_get_quotable_commodities;
%include <gnc-commodity.h>
void gnc_hook_add_scm_dangler (const gchar *name, SCM proc);
@ -475,8 +458,3 @@ void qof_book_set_string_option(QofBook* book, const char* opt_name, const char*
}
$1 = g_list_reverse (path);
}
Process *gnc_spawn_process_async(GList *argl, const gboolean search_path);
%clear GList *;
gint gnc_process_get_fd(const Process *proc, const guint std_fd);
void gnc_detach_process(Process *proc, const gboolean kill_it);

View File

@ -202,139 +202,3 @@ gnc_glist_string_p(SCM list)
{
return scm_is_list(list);
}
struct _Process
{
GPid pid;
gint fd_stdin;
gint fd_stdout;
gint fd_stderr;
gboolean dead;
gboolean detached;
};
static void
on_child_exit (GPid pid, gint status, gpointer data)
{
Process *proc = data;
g_return_if_fail (proc && proc->pid == pid);
g_spawn_close_pid (proc->pid);
/* free if the process is both dead and detached */
if (!proc->detached)
proc->dead = TRUE;
else
g_free (proc);
}
Process *
gnc_spawn_process_async (GList *argl, const gboolean search_path)
{
gboolean retval;
Process *proc;
GList *l_iter;
guint argc;
gchar **argv, **v_iter;
GSpawnFlags flags;
GError *error = NULL;
proc = g_new0 (Process, 1);
argc = g_list_length (argl);
argv = g_malloc ((argc + 1) * sizeof(gchar*));
for (l_iter = argl, v_iter = argv; l_iter; l_iter = l_iter->next, v_iter++)
{
*v_iter = (gchar*) l_iter->data;
}
*v_iter = NULL;
g_list_free (argl);
flags = G_SPAWN_DO_NOT_REAP_CHILD;
if (search_path)
flags |= G_SPAWN_SEARCH_PATH;
retval = g_spawn_async_with_pipes (
NULL, argv, NULL, flags, NULL, NULL, &proc->pid,
&proc->fd_stdin, &proc->fd_stdout, &proc->fd_stderr, &error);
if (retval)
{
g_child_watch_add (proc->pid, on_child_exit, proc);
}
else
{
PWARN ("Could not spawn %s: %s", *argv ? *argv : "(null)",
error->message ? error->message : "(null)");
g_free (proc);
proc = NULL;
}
g_strfreev (argv);
return proc;
}
gint
gnc_process_get_fd (const Process *proc, const gint std_fd)
{
const gint *retptr = NULL;
g_return_val_if_fail (proc, -1);
if (std_fd == 0)
retptr = &proc->fd_stdin;
else if (std_fd == 1)
retptr = &proc->fd_stdout;
else if (std_fd == 2)
retptr = &proc->fd_stderr;
else
g_return_val_if_reached (-1);
if (*retptr == -1)
PWARN ("Pipe to child's file descriptor %d is -1", std_fd);
return *retptr;
}
void
gnc_detach_process (Process *proc, const gboolean kill_it)
{
g_return_if_fail (proc && proc->pid);
errno = 0;
close (proc->fd_stdin);
if (errno)
{
PINFO ("Close of child's stdin (%d) failed: %s", proc->fd_stdin,
g_strerror (errno));
errno = 0;
}
close (proc->fd_stdout);
if (errno)
{
PINFO ("Close of child's stdout (%d) failed: %s", proc->fd_stdout,
g_strerror(errno));
errno = 0;
}
close (proc->fd_stderr);
if (errno)
{
PINFO ("Close of child's stderr (%d) failed: %s", proc->fd_stderr,
g_strerror(errno));
errno = 0;
}
if (kill_it && !proc->dead)
{
/* give it a chance to die */
while (g_main_context_iteration (NULL, FALSE) && !proc->dead)
;
if (!proc->dead)
gnc_gpid_kill (proc->pid);
}
/* free if the process is both dead and detached */
if (!proc->dead)
proc->detached = TRUE;
else
g_free (proc);
}

View File

@ -37,40 +37,4 @@ int gnc_glist_string_p(SCM list);
GSList * gnc_scm_to_gslist_string(SCM list);
/** An opaque process structure returned by gnc_spawn_process_async. */
typedef struct _Process Process;
/** Wraps g_spawn_async_with_pipes minimally. Use gnc_process_get_fd to access
* the file descriptors to the child. To close them and free the memory
* allocated for the process once it has exited, call gnc_detach_process.
*
* @param argl A list of null-terminated strings used as arguments for spawning,
* i.e. "perl" "-w" "my-perl-script". Will be freed inside.
*
* @param search_path Determines whether the first element of argl will be
* looked for in the user's PATH.
*
* @return A pointer to a structure representing the process or NULL on failure.
*/
Process *gnc_spawn_process_async(GList *argl, const gboolean search_path);
/** Accesses a given process structure and returns the file descriptor connected
* to the childs stdin, stdout or stderr.
*
* @param proc A process structure returned by gnc_spawn_process_async.
*
* @param std_fd 0, 1 or 2.
*
* @return The file descriptor to write to the child on 0, or read from the
* childs output or error on 1 or 2, resp. */
gint gnc_process_get_fd(const Process *proc, const gint std_fd);
/** Close the file descriptors to a given process and declare it as detached. If
* it is both dead and detached, the allocated memory will be freed.
*
* @param proc A process structure returned by gnc_spawn_process_async.
*
* @param kill_it If TRUE, kill the process. */
void gnc_detach_process(Process *proc, const gboolean kill_it);
#endif

View File

@ -32,7 +32,6 @@ extern "C"
#include "Account.h"
#include "engine-helpers.h"
#include "gnc-engine-guile.h"
#include "glib-guile.h"
#include "gnc-date.h"
#include "gnc-engine.h"
#include "gnc-session.h"

View File

@ -92,44 +92,3 @@ gnc_scm2printinfo(SCM info_scm)
return info;
}
/* This is a scaled down version of the routine that would be needed
* to fully convert a gnc-commodity to a scheme data structure. In an
* attempt to optimize the speed of price quote retrieval, this
* routine only converts the fields that price-quotes.scm uses. Since
* it converts these fields all at once, it should prevent multiple
* transitions back and forth from Scheme to C to extract
* the data from a pointers to a gnc-commodity (the older method).
* This is *not* a reversible conversion as it drops data.
*
* When this routine was written, gnucash retrieved all quotes into
* the user's default currency. (Did earlier version do any
* different?) This routine inserts that default currency into the
* returned structure as another optimization.
*/
SCM
gnc_quoteinfo2scm(gnc_commodity *comm)
{
gnc_quote_source *source;
const char *name, *tz;
SCM info_scm = SCM_EOL, comm_scm, def_comm_scm;
if (!comm)
return SCM_EOL;
source = gnc_commodity_get_quote_source (comm);
name = gnc_quote_source_get_internal_name (source);
tz = gnc_commodity_get_quote_tz (comm);
comm_scm = SWIG_NewPointerObj(comm, SWIG_TypeQuery("_p_gnc_commodity"), 0);
def_comm_scm = SWIG_NewPointerObj(gnc_default_currency (),
SWIG_TypeQuery("_p_gnc_commodity"), 0);
if (tz)
info_scm = scm_cons (scm_from_utf8_string (tz), info_scm);
else
info_scm = scm_cons (SCM_BOOL_F, info_scm);
info_scm = scm_cons (def_comm_scm, info_scm);
info_scm = scm_cons (comm_scm, info_scm);
info_scm = scm_cons (name ? scm_from_utf8_string (name) : SCM_BOOL_F, info_scm);
return info_scm;
}

View File

@ -31,16 +31,4 @@
SCM gnc_printinfo2scm(GNCPrintAmountInfo info);
GNCPrintAmountInfo gnc_scm2printinfo(SCM info_scm);
/** Given a pointer to a gnc-commodity data structure, build a Scheme
* list containing the data needed by the code in price-quotes.scm.
* This prevents flipping back and forth from Scheme to C while
* extracting values from a pointer.
*
* @param com A pointer to the commodity to convert.
*
* @return A pointer to a Scheme list, or SCM_EOL on error.
*/
SCM gnc_quoteinfo2scm(gnc_commodity *com);
#endif

View File

@ -40,17 +40,17 @@
(string-join (map (lambda (x) (format #f "~A" x)) items) ""))
(define (gnc:warn . items)
(gnc-scm-log-warn (strify items)))
(gnc-log-warn (strify items)))
(define (gnc:error . items)
(gnc-scm-log-error (strify items )))
(gnc-log-error (strify items )))
(define (gnc:msg . items)
(gnc-scm-log-msg (strify items)))
(gnc-log-msg (strify items)))
(define (gnc:debug . items)
(when (qof-log-check "gnc.scm" QOF-LOG-DEBUG)
(gnc-scm-log-debug (strify items))))
(gnc-log-debug (strify items))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the following functions are initialized to log message to tracefile

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Test file for price database stuff
# To update the price database call
# $PATH/gnucash --add-price-quotes $PATHTOFILE
# $PATH/gnucash-cli --quotes get $PATHTOFILE
# before running this.
# Adding to a calling bash script would be better
# Although calling it from here would be even better!

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Another test file for price database stuff
# To update the price database call
# $PATH/gnucash --add-price-quotes $PATHTOFILE
# $PATH/gnucash-cli --quotes get $PATHTOFILE
# before running this.
# Adding to a calling bash script would be better
# Although calling it from here would be even better!

View File

@ -129,6 +129,9 @@
/* Define to 1 if you have the `pthread' library (-lpthread). */
#cmakedefine HAVE_LIBPTHREAD 1
/* Define to 1 if Finance::Quote is installed in perl. */
#cmakedefine HAVE_F_Q 1
/* System has libsecret 0.18 or better */
#cmakedefine HAVE_LIBSECRET 1

View File

@ -267,15 +267,10 @@ foreach(gres_file ${gresource_files})
endforeach()
gnc_add_scheme_targets(price-quotes
SOURCES price-quotes.scm
OUTPUT_DIR gnucash
DEPENDS "scm-engine;scm-app-utils;scm-gnome-utils")
set_local_dist(gnucash_DIST_local CMakeLists.txt environment.in generate-gnc-script
gnucash.cpp gnucash-commands.cpp gnucash-cli.cpp gnucash-core-app.cpp
gnucash-locale-macos.mm gnucash-locale-windows.c gnucash.rc.in gnucash-valgrind.in
gnucash-gresources.xml ${gresource_files} price-quotes.scm
gnucash-gresources.xml ${gresource_files}
${gnucash_noinst_HEADERS} ${gnucash_EXTRA_DIST})
set (gnucash_DIST ${gnucash_DIST_local} ${gnome_DIST} ${gnome_search_DIST} ${gnome_utils_DIST}

View File

@ -43,7 +43,7 @@ set (gnome_utils_SOURCES
dialog-reset-warnings.c
dialog-tax-table.c
dialog-totd.c
dialog-transfer.c
dialog-transfer.cpp
dialog-userpass.c
dialog-utils.c
gnc-account-sel.c

View File

@ -27,7 +27,9 @@
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <gnc-quotes.hpp>
extern "C" {
#include "dialog-transfer.h"
#include "dialog-utils.h"
#include "gnc-amount-edit.h"
@ -43,13 +45,10 @@
#include "gnc-ui.h"
#include "Transaction.h"
#include "Account.h"
#include <libguile.h>
#include "swig-runtime.h"
#include "guile-mappings.h"
#include "engine-helpers.h"
#include "gnc-engine-guile.h"
#include "QuickFill.h"
#include <gnc-commodity.h>
}
#define DIALOG_TRANSFER_CM_CLASS "dialog-transfer"
@ -162,6 +161,7 @@ static void gnc_transfer_dialog_set_selected_account (XferDialog *dialog,
Account *account,
XferDirection direction);
extern "C" {
void gnc_xfer_description_insert_cb(GtkEditable *editable,
const gchar *insert_text,
const gint insert_text_len,
@ -177,6 +177,7 @@ void price_amount_radio_toggled_cb(GtkToggleButton *togglebutton, gpointer data)
void gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data);
void gnc_xfer_dialog_close_cb(GtkDialog *dialog, gpointer data);
}
/** Implementations **********************************************/
@ -337,10 +338,9 @@ gnc_xfer_dialog_update_price (XferDialog *xferData)
static void
gnc_xfer_dialog_toggle_cb(GtkToggleButton *button, gpointer data)
{
AccountTreeFilterInfo* info;
GncTreeViewAccount* treeview = GNC_TREE_VIEW_ACCOUNT (data);
info = g_object_get_data (G_OBJECT(treeview), "filter-info");
auto info = static_cast<AccountTreeFilterInfo*> (g_object_get_data (G_OBJECT(treeview), "filter-info"));
if (info)
{
info->show_inc_exp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
@ -355,11 +355,9 @@ gnc_xfer_dialog_key_press_cb (GtkWidget *widget,
GdkEventKey *event,
gpointer unused)
{
GtkWidget *toplevel;
if ((event->keyval == GDK_KEY_Return) || (event->keyval == GDK_KEY_KP_Enter))
{
toplevel = gtk_widget_get_toplevel (widget);
auto toplevel = gtk_widget_get_toplevel (widget);
if (gtk_widget_is_toplevel(toplevel) && GTK_IS_WINDOW(toplevel))
{
gtk_window_activate_default(GTK_WINDOW(toplevel));
@ -375,15 +373,10 @@ gnc_xfer_dialog_set_price_auto (XferDialog *xferData,
const gnc_commodity *from_currency,
const gnc_commodity *to_currency)
{
gnc_numeric from_rate;
gnc_numeric to_rate;
gnc_numeric price_value;
if (!currency_active)
{
GtkEntry *entry;
gnc_xfer_dialog_set_price_edit(xferData, gnc_numeric_zero());
entry = GTK_ENTRY(gnc_amount_edit_gtk_entry
auto entry = GTK_ENTRY(gnc_amount_edit_gtk_entry
(GNC_AMOUNT_EDIT(xferData->price_edit)));
gtk_entry_set_text(entry, "");
@ -399,13 +392,13 @@ gnc_xfer_dialog_set_price_auto (XferDialog *xferData,
return;
}
from_rate = gnc_euro_currency_get_rate (from_currency);
to_rate = gnc_euro_currency_get_rate (to_currency);
auto from_rate = gnc_euro_currency_get_rate (from_currency);
auto to_rate = gnc_euro_currency_get_rate (to_currency);
if (gnc_numeric_zero_p (from_rate) || gnc_numeric_zero_p (to_rate))
gnc_xfer_dialog_update_price (xferData);
price_value = gnc_numeric_div (to_rate, from_rate, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
auto price_value = gnc_numeric_div (to_rate, from_rate, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(xferData->price_edit), price_value);
@ -415,21 +408,17 @@ gnc_xfer_dialog_set_price_auto (XferDialog *xferData,
static void
gnc_xfer_dialog_curr_acct_activate(XferDialog *xferData)
{
Account *to_account;
Account *from_account;
gboolean curr_active;
g_return_if_fail (xferData != NULL);
from_account =
auto from_account =
gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);
to_account =
auto to_account =
gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
curr_active = (xferData->exch_rate ||
((from_account != NULL) && (to_account != NULL)))
&& !gnc_commodity_equiv(xferData->from_commodity,
xferData->to_commodity);
gboolean curr_active =
(xferData->exch_rate ||
((from_account != NULL) && (to_account != NULL))) &&
!gnc_commodity_equiv(xferData->from_commodity, xferData->to_commodity);
gtk_widget_set_sensitive(xferData->curr_xfer_table, curr_active);
gtk_widget_set_sensitive(xferData->price_edit,
@ -447,12 +436,10 @@ gnc_xfer_dialog_curr_acct_activate(XferDialog *xferData)
if (!curr_active)
{
GtkEntry *entry;
gnc_amount_edit_set_amount(GNC_AMOUNT_EDIT(xferData->to_amount_edit),
gnc_numeric_zero ());
entry = GTK_ENTRY(gnc_amount_edit_gtk_entry
(GNC_AMOUNT_EDIT(xferData->to_amount_edit)));
auto entry = GTK_ENTRY(gnc_amount_edit_gtk_entry
(GNC_AMOUNT_EDIT(xferData->to_amount_edit)));
gtk_entry_set_text(entry, "");
}
}
@ -461,9 +448,9 @@ gnc_xfer_dialog_curr_acct_activate(XferDialog *xferData)
void
price_amount_radio_toggled_cb(GtkToggleButton *togglebutton, gpointer data)
{
XferDialog *xferData = data;
g_return_if_fail (xferData != NULL);
g_return_if_fail (data);
auto xferData = static_cast<XferDialog *> (data);
gtk_widget_set_sensitive(xferData->price_edit, gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON(xferData->price_radio)));
gtk_widget_set_sensitive(xferData->to_amount_edit,
@ -481,23 +468,17 @@ price_amount_radio_toggled_cb(GtkToggleButton *togglebutton, gpointer data)
static void
gnc_xfer_dialog_reload_quickfill( XferDialog *xferData )
{
GList *splitlist, *node;
Split *split;
Transaction *trans;
Account *account;
account = gnc_transfer_dialog_get_selected_account (xferData, xferData->quickfill);
auto account = gnc_transfer_dialog_get_selected_account (xferData, xferData->quickfill);
/* get a new QuickFill to use */
gnc_quickfill_destroy( xferData->qf );
xferData->qf = gnc_quickfill_new();
splitlist = xaccAccountGetSplitList( account );
for ( node = splitlist; node; node = node->next )
auto splitlist = xaccAccountGetSplitList( account );
for ( GList *node = splitlist; node; node = node->next )
{
split = node->data;
trans = xaccSplitGetParent( split );
auto split = static_cast<Split *> (node->data);
auto trans = xaccSplitGetParent (split);
gnc_quickfill_insert( xferData->qf,
xaccTransGetDescription (trans), QUICKFILL_LIFO);
}
@ -508,22 +489,19 @@ static void
gnc_xfer_dialog_from_tree_selection_changed_cb (GtkTreeSelection *selection,
gpointer data)
{
XferDialog *xferData = data;
GNCPrintAmountInfo print_info;
gnc_commodity *commodity;
Account *account;
auto xferData = static_cast<XferDialog *> (data);
account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);
auto account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);
if (!account)
return;
commodity = gnc_account_or_default_currency(account, NULL);
auto commodity = gnc_account_or_default_currency(account, NULL);
gtk_label_set_text(GTK_LABEL(xferData->from_currency_label),
gnc_commodity_get_printname(commodity));
xferData->from_commodity = commodity;
print_info = gnc_account_print_info (account, FALSE);
auto print_info = gnc_account_print_info (account, FALSE);
gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT (xferData->amount_edit),
print_info);
gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT (xferData->amount_edit),
@ -542,22 +520,19 @@ gnc_xfer_dialog_from_tree_selection_changed_cb (GtkTreeSelection *selection,
static void
gnc_xfer_dialog_to_tree_selection_changed_cb (GtkTreeSelection *selection, gpointer data)
{
XferDialog *xferData = data;
GNCPrintAmountInfo print_info;
gnc_commodity *commodity;
Account *account;
auto xferData = static_cast<XferDialog *> (data);
account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
auto account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
if (!account)
return;
commodity = xaccAccountGetCommodity(account);
auto commodity = xaccAccountGetCommodity(account);
gtk_label_set_text(GTK_LABEL(xferData->to_currency_label),
gnc_commodity_get_printname(commodity));
xferData->to_commodity = commodity;
print_info = gnc_account_print_info (account, FALSE);
auto print_info = gnc_account_print_info (account, FALSE);
gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT (xferData->to_amount_edit),
print_info);
gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT (xferData->to_amount_edit),
@ -576,10 +551,7 @@ gboolean
gnc_xfer_dialog_inc_exp_filter_func (Account *account,
gpointer data)
{
AccountTreeFilterInfo* info;
GNCAccountType type;
info = (AccountTreeFilterInfo*)data;
auto info = static_cast<AccountTreeFilterInfo *> (data);
if (!info->show_hidden && xaccAccountIsHidden(account))
{
@ -591,7 +563,7 @@ gnc_xfer_dialog_inc_exp_filter_func (Account *account,
return TRUE;
}
type = xaccAccountGetType(account);
auto type = xaccAccountGetType(account);
return ((type != ACCT_TYPE_INCOME) && (type != ACCT_TYPE_EXPENSE));
}
@ -599,18 +571,14 @@ static void
gnc_xfer_dialog_fill_tree_view(XferDialog *xferData,
XferDirection direction)
{
GtkTreeView *tree_view;
const char *show_inc_exp_message = _("Show the income and expense accounts");
GtkWidget *scroll_win;
GtkWidget *button;
GtkTreeSelection *selection;
gboolean use_accounting_labels;
AccountTreeFilterInfo *info;
GtkBuilder *builder = g_object_get_data (G_OBJECT (xferData->dialog), "builder");
GtkWidget *scroll_win;
auto builder = static_cast<GtkBuilder *> (g_object_get_data (G_OBJECT (xferData->dialog), "builder"));
g_return_if_fail (xferData != NULL);
use_accounting_labels = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL,
GNC_PREF_ACCOUNTING_LABELS);
auto use_accounting_labels = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL,
GNC_PREF_ACCOUNTING_LABELS);
/* In "normal" mode (non accounting terms) the account where the
* money comes from is displayed on the left side and the account
@ -643,12 +611,13 @@ gnc_xfer_dialog_fill_tree_view(XferDialog *xferData,
}
AccountTreeFilterInfo *info;
if (direction == XFER_DIALOG_TO)
info = to_info;
else
info = from_info;
tree_view = GTK_TREE_VIEW(gnc_tree_view_account_new(FALSE));
auto tree_view = GTK_TREE_VIEW(gnc_tree_view_account_new(FALSE));
gtk_container_add(GTK_CONTAINER(scroll_win), GTK_WIDGET(tree_view));
info->show_inc_exp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
info->show_hidden = FALSE;
@ -662,7 +631,7 @@ gnc_xfer_dialog_fill_tree_view(XferDialog *xferData,
g_signal_connect (G_OBJECT (tree_view), "key-press-event",
G_CALLBACK (gnc_xfer_dialog_key_press_cb), NULL);
selection = gtk_tree_view_get_selection (tree_view);
auto selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
@ -824,8 +793,9 @@ gnc_xfer_dialog_quickfill( XferDialog *xferData )
static gboolean
idle_select_region(gpointer data)
{
XferDialog *xferData = data;
g_return_val_if_fail(xferData, FALSE);
g_return_val_if_fail(data, FALSE);
auto xferData = static_cast<XferDialog *> (data);
gtk_editable_select_region(GTK_EDITABLE(xferData->description_entry),
xferData->desc_start_selection,
@ -992,8 +962,9 @@ static gboolean
gnc_xfer_amount_update_cb(GtkWidget *widget, GdkEventFocus *event,
gpointer data)
{
XferDialog * xferData = data;
g_return_val_if_fail (xferData != NULL, FALSE);
g_return_val_if_fail (data, FALSE);
auto xferData = static_cast<XferDialog *> (data);
gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (xferData->amount_edit), NULL);
@ -1052,19 +1023,18 @@ static gboolean
gnc_xfer_price_update_cb(GtkWidget *widget, GdkEventFocus *event,
gpointer data)
{
XferDialog *xferData = data;
auto xferData = static_cast<XferDialog *> (data);
gnc_xfer_update_to_amount (xferData);
xferData->price_type = PRICE_TYPE_TRN;
return FALSE;
}
static gboolean
gnc_xfer_date_changed_cb(GtkWidget *widget, gpointer data)
{
XferDialog *xferData = data;
auto xferData = static_cast<XferDialog *> (data);
if (xferData)
gnc_xfer_dialog_update_price (xferData);
@ -1076,11 +1046,10 @@ static gboolean
gnc_xfer_to_amount_update_cb(GtkWidget *widget, GdkEventFocus *event,
gpointer data)
{
XferDialog *xferData = data;
gnc_numeric price_value;
auto xferData = static_cast<XferDialog *> (data);
gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (xferData->to_amount_edit), NULL);
price_value = gnc_xfer_dialog_compute_price_value(xferData);
auto price_value = gnc_xfer_dialog_compute_price_value (xferData);
gnc_amount_edit_set_amount(GNC_AMOUNT_EDIT(xferData->price_edit),
price_value);
xferData->price_source = PRICE_SOURCE_XFER_DLG_VAL;
@ -1675,14 +1644,9 @@ create_price(XferDialog *xferData, time64 time)
void
gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
{
XferDialog *xferData = data;
Account *to_account;
Account *from_account;
gnc_numeric amount, to_amount;
time64 time;
GDate date;
g_return_if_fail (data);
auto xferData = static_cast<XferDialog *> (data);
g_return_if_fail (xferData != NULL);
ENTER(" ");
if (response == GTK_RESPONSE_APPLY)
@ -1695,7 +1659,7 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
* Remove date changed handler to prevent it from triggering
* on a focus-out event while we're already destroying the widget */
g_signal_handlers_disconnect_by_func (G_OBJECT (xferData->date_entry),
G_CALLBACK (gnc_xfer_date_changed_cb),
(gpointer)gnc_xfer_date_changed_cb,
xferData);
if (response != GTK_RESPONSE_OK)
@ -1705,8 +1669,8 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
return;
}
from_account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);
to_account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
auto from_account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_FROM);
auto to_account = gnc_transfer_dialog_get_selected_account (xferData, XFER_DIALOG_TO);
if (xferData->exch_rate == NULL &&
!check_accounts(xferData, from_account, to_account))
@ -1719,7 +1683,7 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
return;
}
amount = gnc_amount_edit_get_amount(GNC_AMOUNT_EDIT(xferData->amount_edit));
auto amount = gnc_amount_edit_get_amount(GNC_AMOUNT_EDIT(xferData->amount_edit));
if (gnc_numeric_zero_p (amount))
{
@ -1728,10 +1692,13 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
LEAVE("invalid from amount");
return;
}
GDate date;
g_date_clear (&date, 1);
gnc_date_edit_get_gdate (GNC_DATE_EDIT (xferData->date_entry), &date);
time = gdate_to_time64 (date);
auto time = gdate_to_time64 (date);
auto to_amount = amount;
if (!gnc_commodity_equiv(xferData->from_commodity, xferData->to_commodity))
{
if (!check_edit(xferData))
@ -1739,22 +1706,18 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
to_amount = gnc_amount_edit_get_amount
(GNC_AMOUNT_EDIT(xferData->to_amount_edit));
}
else
to_amount = amount;
gnc_suspend_gui_refresh ();
if (xferData->exch_rate)
{
gnc_numeric price_value;
/* If we've got the price-button set, then make sure we update the
* to-amount before we use it.
*/
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(xferData->price_radio)))
gnc_xfer_update_to_amount(xferData);
price_value = gnc_xfer_dialog_compute_price_value(xferData);
auto price_value = gnc_xfer_dialog_compute_price_value(xferData);
gnc_amount_edit_set_amount(GNC_AMOUNT_EDIT(xferData->price_edit),
price_value);
*(xferData->exch_rate) = gnc_numeric_abs(price_value);
@ -1777,14 +1740,13 @@ gnc_xfer_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
void
gnc_xfer_dialog_close_cb(GtkDialog *dialog, gpointer data)
{
XferDialog * xferData = data;
GtkWidget *entry;
auto xferData = static_cast<XferDialog *> (data);
/* Notify transaction callback to unregister here */
if (xferData->transaction_cb)
xferData->transaction_cb(NULL, xferData->transaction_user_data);
entry = gnc_amount_edit_gtk_entry(GNC_AMOUNT_EDIT(xferData->amount_edit));
auto entry = gnc_amount_edit_gtk_entry(GNC_AMOUNT_EDIT(xferData->amount_edit));
g_signal_handlers_disconnect_matched (G_OBJECT (entry), G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, xferData);
@ -1819,44 +1781,26 @@ gnc_xfer_dialog_close_cb(GtkDialog *dialog, gpointer data)
void
gnc_xfer_dialog_fetch (GtkButton *button, XferDialog *xferData)
{
PriceReq pr;
SCM quotes_func;
SCM book_scm;
SCM scm_window;
g_return_if_fail (xferData);
ENTER(" ");
quotes_func = scm_c_eval_string ("gnc:book-add-quotes");
if (!scm_is_procedure (quotes_func))
try
{
LEAVE("quote retrieval failed");
return;
GncQuotes quotes;
gnc_set_busy_cursor(nullptr, TRUE);
quotes.fetch(xferData->book);
gnc_unset_busy_cursor(nullptr);
}
book_scm = gnc_book_to_scm (xferData->book);
if (scm_is_true (scm_not (book_scm)))
catch (const GncQuoteException& err)
{
LEAVE("no book");
return;
gnc_unset_busy_cursor(nullptr);
PERR("Price retrieval failed: %s", err.what());
gnc_error_dialog(GTK_WINDOW(xferData->dialog), _("Price retrieval failed: %s"), err.what());
}
scm_window = SWIG_NewPointerObj(xferData->dialog,
SWIG_TypeQuery("_p_GtkWindow"), 0);
if (scm_is_true (scm_not (book_scm)))
{
LEAVE("no scm window");
return;
}
gnc_set_busy_cursor (NULL, TRUE);
scm_call_2 (quotes_func, scm_window, book_scm);
gnc_unset_busy_cursor (NULL);
/*the results should be in the price db now, but don't crash if not. */
PriceReq pr;
price_request_from_xferData(&pr, xferData);
if (lookup_price(&pr, LATEST))
{
@ -2070,11 +2014,10 @@ gnc_xfer_dialog_create(GtkWidget *parent, XferDialog *xferData)
static void
close_handler (gpointer user_data)
{
XferDialog *xferData = user_data;
GtkWidget *dialog;
auto xferData = static_cast<XferDialog *> (user_data);
ENTER(" ");
dialog = GTK_WIDGET (xferData->dialog);
auto dialog = GTK_WIDGET (xferData->dialog);
gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW (dialog));
gtk_widget_hide (dialog);
@ -2238,10 +2181,10 @@ void gnc_xfer_dialog_add_user_specified_button( XferDialog *xferData,
{
if ( xferData && label && callback )
{
GtkBuilder *builder = g_object_get_data (G_OBJECT (xferData->dialog), "builder");
GtkWidget *button = gtk_button_new_with_label( label );
GtkWidget *box = GTK_WIDGET(gtk_builder_get_object (builder,
"transfermain-vbox" ));
auto builder = static_cast<GtkBuilder *> (g_object_get_data (G_OBJECT (xferData->dialog), "builder"));
auto button = gtk_button_new_with_label( label );
auto box = GTK_WIDGET (gtk_builder_get_object (builder,
"transfermain-vbox" ));
gtk_box_pack_end( GTK_BOX(box), button, FALSE, FALSE, 0 );
g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), user_data);
gtk_widget_show( button );
@ -2292,7 +2235,7 @@ gboolean gnc_xfer_dialog_run_until_done( XferDialog *xferData )
* that's bad mojo whole gtk_dialog_run is still in control.
*/
count = g_signal_handlers_disconnect_by_func(dialog,
gnc_xfer_dialog_response_cb,
(gpointer) gnc_xfer_dialog_response_cb,
xferData);
g_assert(count == 1);

View File

@ -90,7 +90,7 @@ set (gnc_gnome_SOURCES
dialog-order.c
dialog-payment.c
dialog-price-editor.c
dialog-price-edit-db.c
dialog-price-edit-db.cpp
dialog-print-check.c
dialog-progress.c
dialog-report-column-view.cpp
@ -133,6 +133,7 @@ target_link_libraries(gnc-gnome
gnc-register-gnome
gnc-register-core
gnc-gnome-utils
gnc-app-utils
gnc-engine
gnc-expressions
gnc-html
@ -148,6 +149,7 @@ target_compile_options(gnc-gnome PRIVATE -Wno-deprecated-declarations)
target_include_directories(gnc-gnome
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE
${CMAKE_SOURCE_DIR}/libgnucash/app-utils
${CMAKE_SOURCE_DIR}/libgnucash/app-utils/calculation
${CMAKE_SOURCE_DIR}/gnucash/html
${CMAKE_BINARY_DIR}/gnucash/gnome-utils # for gnc-warnings.h

View File

@ -27,9 +27,10 @@
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libguile.h>
#include <time.h>
#include <gnc-quotes.hpp>
extern "C" {
#include "dialog-utils.h"
#include "gnc-accounting-period.h"
#include "gnc-amount-edit.h"
@ -46,10 +47,8 @@
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-warnings.h"
#include "swig-runtime.h"
#include "guile-mappings.h"
#include "gnc-engine-guile.h"
#include <gnc-glib-utils.h>
}
#define DIALOG_PRICE_DB_CM_CLASS "dialog-price-edit-db"
@ -60,6 +59,7 @@
static QofLogModule log_module = GNC_MOD_GUI;
extern "C" {
void gnc_prices_dialog_destroy_cb (GtkWidget *object, gpointer data);
void gnc_prices_dialog_close_cb (GtkDialog *dialog, gpointer data);
void gnc_prices_dialog_help_cb (GtkDialog *dialog, gpointer data);
@ -71,9 +71,10 @@ void gnc_prices_dialog_get_quotes_clicked (GtkWidget *widget, gpointer data);
static gboolean gnc_prices_dialog_key_press_cb (GtkWidget *widget,
GdkEventKey *event,
gpointer data);
}
typedef struct
struct PricesDialog
{
GtkWidget * window;
QofSession *session;
@ -88,14 +89,14 @@ typedef struct
GtkWidget *remove_dialog;
GtkTreeView *remove_view;
gint remove_source;
} PricesDialog;
int remove_source;
};
void
gnc_prices_dialog_destroy_cb (GtkWidget *object, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
gnc_unregister_gui_component_by_data (DIALOG_PRICE_DB_CM_CLASS, pdb_dialog);
@ -116,7 +117,7 @@ gnc_prices_dialog_delete_event_cb (GtkWidget *widget,
GdkEvent *event,
gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
// this cb allows the window size to be saved on closing with the X
gnc_save_window_size (GNC_PREFS_GROUP,
GTK_WINDOW(pdb_dialog->window));
@ -127,7 +128,7 @@ gnc_prices_dialog_delete_event_cb (GtkWidget *widget,
void
gnc_prices_dialog_close_cb (GtkDialog *dialog, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
gnc_close_gui_component_by_data (DIALOG_PRICE_DB_CM_CLASS, pdb_dialog);
@ -138,7 +139,7 @@ gnc_prices_dialog_close_cb (GtkDialog *dialog, gpointer data)
void
gnc_prices_dialog_help_cb (GtkDialog *dialog, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog{static_cast<PricesDialog*>(data)};
gnc_gnome_help (GTK_WINDOW (pdb_dialog->window), HF_HELP, HL_PRICE_DB);
}
@ -147,11 +148,10 @@ gnc_prices_dialog_help_cb (GtkDialog *dialog, gpointer data)
void
gnc_prices_dialog_edit_clicked (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
GList *price_list;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
price_list = gnc_tree_view_price_get_selected_prices(pdb_dialog->price_tree);
auto price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
if (!price_list)
{
LEAVE("no price selected");
@ -164,9 +164,10 @@ gnc_prices_dialog_edit_clicked (GtkWidget *widget, gpointer data)
return;
}
auto price = static_cast<GNCPrice *> (price_list->data);
gnc_price_edit_dialog (pdb_dialog->window, pdb_dialog->session,
price_list->data, GNC_PRICE_EDIT);
g_list_free(price_list);
price, GNC_PRICE_EDIT);
g_list_free (price_list);
LEAVE(" ");
}
@ -181,20 +182,18 @@ remove_helper(GNCPrice *price, GNCPriceDB *pdb)
void
gnc_prices_dialog_remove_clicked (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
GList *price_list;
gint length, response;
GtkWidget *dialog;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
price_list = gnc_tree_view_price_get_selected_prices(pdb_dialog->price_tree);
auto price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
if (!price_list)
{
LEAVE("no price selected");
return;
}
length = g_list_length(price_list);
gint response;
auto length = g_list_length(price_list);
if (length > 0)
{
gchar *message;
@ -205,11 +204,11 @@ gnc_prices_dialog_remove_clicked (GtkWidget *widget, gpointer data)
"Are you sure you want to delete the %d selected prices?",
length),
length);
dialog = gtk_message_dialog_new(GTK_WINDOW(pdb_dialog->window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"%s", _("Delete prices?"));
auto dialog = gtk_message_dialog_new (GTK_WINDOW(pdb_dialog->window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"%s", _("Delete prices?"));
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
"%s", message);
g_free(message);
@ -242,46 +241,38 @@ enum GncPriceColumn {PRICED_FULL_NAME, PRICED_COMM, PRICED_DATE, PRICED_COUNT};
static time64
gnc_prices_dialog_load_view (GtkTreeView *view, GNCPriceDB *pdb)
{
GtkTreeModel *model = gtk_tree_view_get_model (view);
const gnc_commodity_table *commodity_table = gnc_get_current_commodities ();
GList *namespace_list = gnc_commodity_table_get_namespaces (commodity_table);
gnc_commodity *tmp_commodity = NULL;
char *tmp_namespace = NULL;
GList *commodity_list = NULL;
GtkTreeIter iter;
auto oldest = gnc_time (nullptr);
auto model = gtk_tree_view_get_model (view);
const auto commodity_table = gnc_get_current_commodities ();
auto namespace_list = gnc_commodity_table_get_namespaces (commodity_table);
time64 oldest = gnc_time (NULL);
namespace_list = g_list_first (namespace_list);
while (namespace_list != NULL)
while (namespace_list)
{
tmp_namespace = namespace_list->data;
auto tmp_namespace = static_cast<char *> (namespace_list->data);
DEBUG("Looking at namespace %s", tmp_namespace);
commodity_list = gnc_commodity_table_get_commodities (commodity_table, tmp_namespace);
commodity_list = g_list_first (commodity_list);
while (commodity_list != NULL)
auto commodity_list = gnc_commodity_table_get_commodities (commodity_table, tmp_namespace);
while (commodity_list)
{
gint num = 0;
tmp_commodity = commodity_list->data;
num = gnc_pricedb_num_prices (pdb, tmp_commodity);
auto tmp_commodity = static_cast<gnc_commodity *> (commodity_list->data);
auto num = gnc_pricedb_num_prices (pdb, tmp_commodity);
DEBUG("Looking at commodity %s, Number of prices %d", gnc_commodity_get_fullname (tmp_commodity), num);
if (num > 0)
{
PriceList *list = gnc_pricedb_get_prices (pdb, tmp_commodity, NULL);
GList *node = g_list_last (list);
GNCPrice *price = (GNCPrice*)node->data;
time64 price_time = gnc_price_get_time64 (price);
const gchar *name_str = gnc_commodity_get_printname (tmp_commodity);
gchar *date_str, *num_str;
auto list = gnc_pricedb_get_prices (pdb, tmp_commodity, NULL);
auto node = g_list_last (list);
auto price = static_cast<GNCPrice*> (node->data);
auto price_time = gnc_price_get_time64 (price);
auto name_str = gnc_commodity_get_printname (tmp_commodity);
if (oldest > price_time)
oldest = price_time;
date_str = qof_print_date (price_time);
num_str = g_strdup_printf ("%d", num);
auto date_str = qof_print_date (price_time);
auto num_str = g_strdup_printf ("%d", num);
GtkTreeIter iter;
gtk_list_store_append (GTK_LIST_STORE(model), &iter);
gtk_list_store_set (GTK_LIST_STORE(model), &iter, PRICED_FULL_NAME, name_str,
PRICED_COMM, tmp_commodity, PRICED_DATE, date_str, PRICED_COUNT, num_str, -1);
@ -291,9 +282,9 @@ gnc_prices_dialog_load_view (GtkTreeView *view, GNCPriceDB *pdb)
}
commodity_list = g_list_next (commodity_list);
}
g_list_free (commodity_list);
namespace_list = g_list_next (namespace_list);
}
g_list_free (commodity_list);
g_list_free (namespace_list);
return oldest;
@ -302,25 +293,24 @@ gnc_prices_dialog_load_view (GtkTreeView *view, GNCPriceDB *pdb)
static GList *
gnc_prices_dialog_get_commodities (GtkTreeView *view)
{
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
GList *list = gtk_tree_selection_get_selected_rows (selection, &model);
GList *row;
GList *comm_list = NULL;
GtkTreeIter iter;
gnc_commodity *comm;
auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
auto selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
auto list = gtk_tree_selection_get_selected_rows (selection, &model);
GList *comm_list = nullptr;
// Walk the list
for (row = g_list_first (list); row; row = g_list_next (row))
for (auto row = g_list_first (list); row; row = g_list_next (row))
{
if (gtk_tree_model_get_iter (model, &iter, row->data))
auto path = static_cast<GtkTreePath *> (row->data);
GtkTreeIter iter;
if (gtk_tree_model_get_iter (model, &iter, path))
{
gnc_commodity *comm;
gtk_tree_model_get (model, &iter, PRICED_COMM, &comm, -1);
comm_list = g_list_prepend (comm_list, comm);
}
}
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
g_list_free (list);
g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
return g_list_reverse (comm_list);
}
@ -328,7 +318,7 @@ gnc_prices_dialog_get_commodities (GtkTreeView *view)
static void
change_source_flag (PriceRemoveSourceFlags source, gboolean set, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
GtkWidget *w = gtk_dialog_get_widget_for_response (GTK_DIALOG(pdb_dialog->remove_dialog), GTK_RESPONSE_OK);
gboolean enable_button;
@ -347,7 +337,7 @@ change_source_flag (PriceRemoveSourceFlags source, gboolean set, gpointer data)
static void
check_event_fq_cb (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget));
change_source_flag (PRICE_REMOVE_SOURCE_FQ, active, pdb_dialog);
@ -356,7 +346,7 @@ check_event_fq_cb (GtkWidget *widget, gpointer data)
static void
check_event_user_cb (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget));
change_source_flag (PRICE_REMOVE_SOURCE_USER, active, pdb_dialog);
@ -365,7 +355,7 @@ check_event_user_cb (GtkWidget *widget, gpointer data)
static void
check_event_app_cb (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget));
change_source_flag (PRICE_REMOVE_SOURCE_APP, active, pdb_dialog);
@ -374,14 +364,13 @@ check_event_app_cb (GtkWidget *widget, gpointer data)
static void
selection_changed_cb (GtkTreeSelection *selection, gpointer data)
{
PricesDialog *pdb_dialog = data;
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->remove_view));
GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
auto pdb_dialog = static_cast<PricesDialog *> (data);
auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->remove_view));
auto rows = gtk_tree_selection_get_selected_rows (selection, &model);
gboolean have_rows = (gnc_list_length_cmp (rows, 0));
change_source_flag (PRICE_REMOVE_SOURCE_COMM, have_rows, pdb_dialog);
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
}
static GDate
@ -401,45 +390,36 @@ get_fiscal_end_date (void)
void
gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
GtkBuilder *builder;
GtkTreeModel *model;
GtkWidget *date, *label, *box;
GtkWidget *button;
GtkTreeSelection *selection;
GtkTreeViewColumn *tree_column;
GtkCellRenderer *cr;
time64 first;
gint result;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
builder = gtk_builder_new();
auto builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "dialog-price.glade", "liststore4");
gnc_builder_add_from_file (builder, "dialog-price.glade", "deletion_date_dialog");
pdb_dialog->remove_dialog = GTK_WIDGET(gtk_builder_get_object (builder, "deletion_date_dialog"));
box = GTK_WIDGET(gtk_builder_get_object (builder, "date_hbox"));
date = gnc_date_edit_new (time (NULL), FALSE, FALSE);
auto box = GTK_WIDGET(gtk_builder_get_object (builder, "date_hbox"));
auto date = gnc_date_edit_new (time (NULL), FALSE, FALSE);
gtk_box_pack_start (GTK_BOX (box), date, FALSE, FALSE, 0);
gtk_widget_show (date);
gtk_entry_set_activates_default(GTK_ENTRY(GNC_DATE_EDIT(date)->date_entry), TRUE);
label = GTK_WIDGET(gtk_builder_get_object (builder, "date_label"));
auto label = GTK_WIDGET(gtk_builder_get_object (builder, "date_label"));
gnc_date_make_mnemonic_target (GNC_DATE_EDIT(date), label);
// Setup the commodity view
pdb_dialog->remove_view = GTK_TREE_VIEW(gtk_builder_get_object (builder, "commodty_treeview"));
selection = gtk_tree_view_get_selection (pdb_dialog->remove_view);
auto selection = gtk_tree_view_get_selection (pdb_dialog->remove_view);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
// Add Entries column this way as align does not seem to work from builder
tree_column = gtk_tree_view_column_new();
auto tree_column = gtk_tree_view_column_new();
gtk_tree_view_column_set_title (tree_column, _("Entries"));
gtk_tree_view_append_column (GTK_TREE_VIEW(pdb_dialog->remove_view), tree_column);
gtk_tree_view_column_set_alignment (tree_column, 0.5);
gtk_tree_view_column_set_expand (tree_column, TRUE);
cr = gtk_cell_renderer_text_new();
auto cr = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start (tree_column, cr, TRUE);
// set 'xalign' property of the cell renderer
gtk_tree_view_column_set_attributes (tree_column, cr, "text", PRICED_COUNT, NULL);
@ -454,19 +434,19 @@ gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
gtk_window_set_transient_for (GTK_WINDOW (pdb_dialog->remove_dialog), GTK_WINDOW (pdb_dialog->window));
pdb_dialog->remove_source = 9; // FQ and Commodities highlighted
button = GTK_WIDGET(gtk_builder_get_object (builder, "checkbutton_fq"));
pdb_dialog->remove_source = PRICE_REMOVE_SOURCE_FQ + PRICE_REMOVE_SOURCE_COMM; // FQ and Commodities highlighted
auto button = GTK_WIDGET(gtk_builder_get_object (builder, "checkbutton_fq"));
g_signal_connect (button, "toggled", G_CALLBACK (check_event_fq_cb), pdb_dialog);
button = GTK_WIDGET(gtk_builder_get_object (builder, "checkbutton_user"));
g_signal_connect (button, "toggled", G_CALLBACK (check_event_user_cb), pdb_dialog);
button = GTK_WIDGET(gtk_builder_get_object (builder, "checkbutton_app"));
g_signal_connect (button, "toggled", G_CALLBACK (check_event_app_cb), pdb_dialog);
result = gtk_dialog_run (GTK_DIALOG (pdb_dialog->remove_dialog));
auto result = gtk_dialog_run (GTK_DIALOG (pdb_dialog->remove_dialog));
if (result == GTK_RESPONSE_OK)
{
const char *fmt = _("Are you sure you want to delete these prices?");
GList *comm_list = gnc_prices_dialog_get_commodities (pdb_dialog->remove_view);
auto comm_list = gnc_prices_dialog_get_commodities (pdb_dialog->remove_view);
// Are you sure you want to delete the entries and we have commodities
if ((g_list_length (comm_list) != 0) && (gnc_verify_dialog (GTK_WINDOW (pdb_dialog->remove_dialog), FALSE, fmt, NULL)))
@ -477,7 +457,7 @@ gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
PriceRemoveKeepOptions keep = PRICE_REMOVE_KEEP_NONE;
// disconnect the model to the price treeview
model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->price_tree));
auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->price_tree));
g_object_ref (G_OBJECT(model));
gtk_tree_view_set_model (GTK_TREE_VIEW(pdb_dialog->price_tree), NULL);
@ -502,19 +482,18 @@ gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
if (keep != PRICE_REMOVE_KEEP_SCALED)
gnc_pricedb_remove_old_prices (pdb_dialog->price_db, comm_list,
&fiscal_end_date,
last, pdb_dialog->remove_source,
&fiscal_end_date, last,
static_cast<PriceRemoveSourceFlags> (pdb_dialog->remove_source),
keep);
else
{
time64 tmp;
GDate tmp_date = time64_to_gdate (last);
auto tmp_date = time64_to_gdate (last);
g_date_subtract_months (&tmp_date, 6);
tmp = gdate_to_time64 (tmp_date);
auto tmp = gdate_to_time64 (tmp_date);
gnc_pricedb_remove_old_prices (pdb_dialog->price_db, comm_list,
&fiscal_end_date, tmp,
pdb_dialog->remove_source,
static_cast<PriceRemoveSourceFlags> (pdb_dialog->remove_source),
PRICE_REMOVE_KEEP_LAST_WEEKLY);
g_date_subtract_months (&tmp_date, 6);
@ -522,7 +501,7 @@ gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
gnc_pricedb_remove_old_prices (pdb_dialog->price_db, comm_list,
&fiscal_end_date, tmp,
pdb_dialog->remove_source,
static_cast<PriceRemoveSourceFlags> (pdb_dialog->remove_source),
PRICE_REMOVE_KEEP_LAST_MONTHLY);
}
// reconnect the model to the price treeview
@ -541,19 +520,17 @@ gnc_prices_dialog_remove_old_clicked (GtkWidget *widget, gpointer data)
void
gnc_prices_dialog_add_clicked (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
GNCPrice *price = NULL;
GList *price_list;
GList *comm_list;
auto pdb_dialog = static_cast<PricesDialog *> (data);
GNCPrice *price = nullptr;
gboolean unref_price = FALSE;
ENTER(" ");
price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
comm_list = gnc_tree_view_price_get_selected_commodities (pdb_dialog->price_tree);
auto price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
auto comm_list = gnc_tree_view_price_get_selected_commodities (pdb_dialog->price_tree);
if (price_list) // selected row is on a price
{
price = price_list->data;
price = static_cast<GNCPrice *> (price_list->data);
g_list_free (price_list);
}
else if (comm_list) // selection contains price parent rows
@ -561,7 +538,8 @@ gnc_prices_dialog_add_clicked (GtkWidget *widget, gpointer data)
if (!gnc_list_length_cmp (comm_list, 1)) // make sure it is only one parent
{
price = gnc_price_create (pdb_dialog->book);
gnc_price_set_commodity (price, comm_list->data);
auto comm = static_cast<gnc_commodity *> (comm_list->data);
gnc_price_set_commodity (price, comm);
unref_price = TRUE;
}
g_list_free (comm_list);
@ -578,33 +556,25 @@ gnc_prices_dialog_add_clicked (GtkWidget *widget, gpointer data)
void
gnc_prices_dialog_get_quotes_clicked (GtkWidget *widget, gpointer data)
{
PricesDialog *pdb_dialog = data;
SCM quotes_func;
SCM book_scm;
SCM scm_window;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
quotes_func = scm_c_eval_string ("gnc:book-add-quotes");
if (!scm_is_procedure (quotes_func))
{
LEAVE(" no procedure");
return;
try {
GncQuotes quotes;
gnc_set_busy_cursor (NULL, TRUE);
quotes.fetch (pdb_dialog->book);
gnc_unset_busy_cursor (NULL);
if (quotes.had_failures())
gnc_warning_dialog(GTK_WINDOW(pdb_dialog->window), "%s",
quotes.report_failures().c_str());
}
book_scm = gnc_book_to_scm (pdb_dialog->book);
if (scm_is_true (scm_not (book_scm)))
catch (const GncQuoteException& err)
{
LEAVE("no book");
return;
gnc_unset_busy_cursor(nullptr);
PERR("Price retrieval failed: %s", err.what());
gnc_error_dialog(GTK_WINDOW(pdb_dialog), _("Price retrieval failed: %s"), err.what());
}
scm_window = SWIG_NewPointerObj(pdb_dialog->window,
SWIG_TypeQuery("_p_GtkWindow"), 0);
gnc_set_busy_cursor (NULL, TRUE);
scm_call_2 (quotes_func, scm_window, book_scm);
gnc_unset_busy_cursor (NULL);
/* Without this, the summary bar on the accounts tab
* won't reflect the new prices (bug #522095). */
gnc_gui_refresh_all ();
@ -617,26 +587,21 @@ static void
gnc_prices_dialog_selection_changed (GtkTreeSelection *treeselection,
gpointer data)
{
PricesDialog *pdb_dialog = data;
GtkTreeModel *model;
GList *price_list;
GList *rows;
gint length;
auto pdb_dialog = static_cast<PricesDialog *> (data);
ENTER(" ");
price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
length = g_list_length (price_list);
auto price_list = gnc_tree_view_price_get_selected_prices (pdb_dialog->price_tree);
auto length = g_list_length (price_list);
g_list_free (price_list);
model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->price_tree));
rows = gtk_tree_selection_get_selected_rows (treeselection, &model);
auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(pdb_dialog->price_tree));
auto rows = gtk_tree_selection_get_selected_rows (treeselection, &model);
// if selected rows greater than length, parents must of been selected also
if (g_list_length (rows) > length)
length = 0;
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
gtk_widget_set_sensitive (pdb_dialog->edit_button,
length == 1);
@ -652,29 +617,23 @@ static gboolean
gnc_price_dialog_filter_ns_func (gnc_commodity_namespace *name_space,
gpointer data)
{
PricesDialog *pdb_dialog = data;
const gchar *name;
static GList *cm_list;
GList *item;
auto pdb_dialog = static_cast<PricesDialog *> (data);
/* Never show the template list */
name = gnc_commodity_namespace_get_name (name_space);
auto name = gnc_commodity_namespace_get_name (name_space);
if (g_strcmp0 (name, GNC_COMMODITY_NS_TEMPLATE) == 0)
return FALSE;
/* See if this namespace has commodities */
cm_list = gnc_commodity_namespace_get_commodity_list(name_space);
for (item = cm_list; item; item = g_list_next(item))
auto cm_list = gnc_commodity_namespace_get_commodity_list (name_space);
for (auto item = cm_list; item; item = g_list_next (item))
{
/* For each commodity, see if there are prices */
if (gnc_pricedb_has_prices(pdb_dialog->price_db, item->data, NULL))
{
auto comm = static_cast<gnc_commodity *> (item->data);
if (gnc_pricedb_has_prices (pdb_dialog->price_db, comm, nullptr))
return TRUE;
}
}
// printf("Namespace %s not visible\n", name);
return FALSE;
}
@ -683,7 +642,7 @@ static gboolean
gnc_price_dialog_filter_cm_func (gnc_commodity *commodity,
gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
/* Show any commodity that has prices */
return gnc_pricedb_has_prices(pdb_dialog->price_db, commodity, NULL);
@ -808,11 +767,10 @@ gnc_prices_dialog_create (GtkWidget * parent, PricesDialog *pdb_dialog)
static void
close_handler (gpointer user_data)
{
PricesDialog *pdb_dialog = user_data;
auto pdb_dialog = static_cast<PricesDialog *> (user_data);
ENTER(" ");
gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(pdb_dialog->window));
gtk_widget_destroy (GTK_WIDGET (pdb_dialog->window));
LEAVE(" ");
}
@ -830,7 +788,7 @@ static gboolean
show_handler (const char *klass, gint component_id,
gpointer user_data, gpointer iter_data)
{
PricesDialog *pdb_dialog = user_data;
auto pdb_dialog = static_cast<PricesDialog *> (user_data);
ENTER(" ");
if (!pdb_dialog)
@ -849,7 +807,7 @@ gboolean
gnc_prices_dialog_key_press_cb (GtkWidget *widget, GdkEventKey *event,
gpointer data)
{
PricesDialog *pdb_dialog = data;
auto pdb_dialog = static_cast<PricesDialog *> (data);
if (event->keyval == GDK_KEY_Escape)
{

View File

@ -42,6 +42,7 @@ extern "C" {
#include <boost/nowide/args.hpp>
#endif
#include <iostream>
#include <gnc-quotes.hpp>
namespace bl = boost::locale;
@ -59,8 +60,9 @@ namespace Gnucash {
private:
void configure_program_options (void);
boost::optional <std::string> m_quotes_cmd;
std::vector<std::string> m_quotes_cmd;
boost::optional <std::string> m_namespace;
bool m_verbose = false;
boost::optional <std::string> m_report_cmd;
boost::optional <std::string> m_report_name;
@ -93,11 +95,17 @@ Gnucash::GnucashCli::configure_program_options (void)
{
bpo::options_description quotes_options(_("Price Quotes Retrieval Options"));
quotes_options.add_options()
("quotes,Q", bpo::value (&m_quotes_cmd),
_("Execute price quote related commands. Currently only one command is supported.\n\n"
" get: \tFetch current quotes for all foreign currencies and stocks in the given GnuCash datafile.\n"))
("quotes,Q", bpo::value<std::vector<std::string>> (&m_quotes_cmd)->multitoken(),
_("Execute price quote related commands. The following commands are supported.\n\n"
" info: \tShow Finance::Quote version and exposed quote sources.\n"
" get: \tFetch current quotes for all foreign currencies and stocks in the given GnuCash datafile.\n"
" dump: \tFetch current quotes for specified currencies or stocks from a specified namespace and print the results to the console.\n"
" \tThis must be followed with a source and one or more symbols, unless the source is \"currency\" in which case it must be followed with two or more symbols, the first of which is the currency in which exchange rates for the rest will be quoted.\n"))
("namespace", bpo::value (&m_namespace),
_("Regular expression determining which namespace commodities will be retrieved for"));
_("Regular expression determining which namespace commodities will be retrieved for when using the get command"))
("verbose,V", bpo::bool_switch (&m_verbose),
_("When using the dump command list all of the parameters Finance::Quote returns for the symbol instead of the ones that Gnucash requires."));
m_opt_desc_display->add (quotes_options);
m_opt_desc_all.add (quotes_options);
@ -125,23 +133,44 @@ Gnucash::GnucashCli::start ([[maybe_unused]] int argc, [[maybe_unused]] char **a
{
Gnucash::CoreApp::start();
if (m_quotes_cmd)
if (!m_quotes_cmd.empty())
{
if (*m_quotes_cmd != "get")
if (m_quotes_cmd.front() == "info")
{
std::cerr << bl::format (bl::translate("Unknown quotes command '{1}'")) % *m_quotes_cmd << "\n\n"
<< *m_opt_desc_display.get();
return 1;
return Gnucash::check_finance_quote ();
}
if (!m_file_to_load || m_file_to_load->empty())
else if (m_quotes_cmd.front() == "get")
{
std::cerr << bl::translate("Missing data file parameter") << "\n\n"
<< *m_opt_desc_display.get();
return 1;
if (!m_file_to_load || m_file_to_load->empty())
{
std::cerr << bl::translate("Missing data file parameter") << "\n\n"
<< *m_opt_desc_display.get() << std::endl;
return 1;
}
else
return Gnucash::add_quotes (m_file_to_load);
}
else if (m_quotes_cmd.front() == "dump")
{
if (m_quotes_cmd.size() < 3 ||
(m_quotes_cmd[1] == "currency" &&
m_quotes_cmd.size() < 4))
{
std::cerr << bl::translate("Not enough information for quotes dump") << std::endl;
return 1;
}
auto source = m_quotes_cmd[1];
m_quotes_cmd.erase(m_quotes_cmd.begin(), m_quotes_cmd.begin() + 2);
return Gnucash::report_quotes(source.c_str(), m_quotes_cmd,
m_verbose);
}
else
return Gnucash::add_quotes (m_file_to_load);
{
std::cerr << bl::format (bl::translate("Unknown quotes command '{1}'")) % m_quotes_cmd.front() << "\n\n"
<< *m_opt_desc_display.get() << std::endl;
return 1;
}
}
if (m_report_cmd)
@ -151,7 +180,7 @@ Gnucash::GnucashCli::start ([[maybe_unused]] int argc, [[maybe_unused]] char **a
if (!m_file_to_load || m_file_to_load->empty())
{
std::cerr << bl::translate("Missing data file parameter") << "\n\n"
<< *m_opt_desc_display.get();
<< *m_opt_desc_display.get() << std::endl;
return 1;
}
else
@ -175,7 +204,7 @@ Gnucash::GnucashCli::start ([[maybe_unused]] int argc, [[maybe_unused]] char **a
if (!m_report_name || m_report_name->empty())
{
std::cerr << bl::translate("Missing --name parameter") << "\n\n"
<< *m_opt_desc_display.get();
<< *m_opt_desc_display.get() << std::endl;
return 1;
}
else
@ -183,13 +212,13 @@ Gnucash::GnucashCli::start ([[maybe_unused]] int argc, [[maybe_unused]] char **a
else
{
std::cerr << bl::format (bl::translate("Unknown report command '{1}'")) % *m_report_cmd << "\n\n"
<< *m_opt_desc_display.get();
<< *m_opt_desc_display.get() << std::endl;
return 1;
}
}
std::cerr << bl::translate("Missing command or option") << "\n\n"
<< *m_opt_desc_display.get();
<< *m_opt_desc_display.get() << std::endl;
return 1;
}

View File

@ -45,6 +45,7 @@ extern "C" {
#include <fstream>
#include <iostream>
#include <gnc-report.h>
#include <gnc-quotes.hpp>
namespace bl = boost::locale;
@ -53,8 +54,8 @@ static std::string empty_string{};
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_GUI;
static void
scm_cleanup_and_exit_with_failure (QofSession *session)
static int
cleanup_and_exit_with_failure (QofSession *session)
{
if (session)
{
@ -70,60 +71,15 @@ scm_cleanup_and_exit_with_failure (QofSession *session)
qof_session_destroy (session);
}
qof_event_resume();
gnc_shutdown (1);
return 1;
}
/* scm_boot_guile doesn't expect to return, so call shutdown ourselves here */
static void
scm_add_quotes(void *data, [[maybe_unused]] int argc, [[maybe_unused]] char **argv)
scm_cleanup_and_exit_with_failure (QofSession *session)
{
auto add_quotes_file = static_cast<const std::string*>(data);
scm_c_eval_string("(debug-set! stack 200000)");
auto mod = scm_c_resolve_module("gnucash price-quotes");
scm_set_current_module(mod);
gnc_prefs_init ();
qof_event_suspend();
scm_c_eval_string("(gnc:price-quotes-install-sources)");
if (!gnc_quote_source_fq_installed())
{
std::cerr << bl::translate ("No quotes retrieved. Finance::Quote isn't "
"installed properly.") << "\n";
scm_cleanup_and_exit_with_failure (nullptr);
}
auto add_quotes = scm_c_eval_string("gnc:book-add-quotes");
auto session = gnc_get_current_session();
if (!session)
scm_cleanup_and_exit_with_failure (session);
qof_session_begin(session, add_quotes_file->c_str(), SESSION_NORMAL_OPEN);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
scm_cleanup_and_exit_with_failure (session);
qof_session_load(session, NULL);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
scm_cleanup_and_exit_with_failure (session);
auto scm_book = gnc_book_to_scm(qof_session_get_book(session));
auto scm_result = scm_call_2(add_quotes, SCM_BOOL_F, scm_book);
qof_session_save(session, NULL);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
scm_cleanup_and_exit_with_failure (session);
qof_session_destroy(session);
if (!scm_is_true(scm_result))
{
PERR ("Failed to add quotes to %s.", add_quotes_file->c_str());
scm_cleanup_and_exit_with_failure (session);
}
qof_event_resume();
gnc_shutdown(0);
return;
cleanup_and_exit_with_failure (session);
gnc_shutdown (1);
}
static void
@ -343,12 +299,84 @@ scm_report_list ([[maybe_unused]] void *data,
return;
}
int
Gnucash::check_finance_quote (void)
{
gnc_prefs_init ();
try
{
GncQuotes quotes;
std::cout << bl::format (bl::translate ("Found Finance::Quote version {1}.")) % quotes.version() << "\n";
std::cout << bl::translate ("Finance::Quote sources: ");
for (auto source : quotes.sources())
std::cout << source << " ";
std::cout << std::endl;
return 0;
}
catch (const GncQuoteException& err)
{
std::cout << err.what() << std::endl;
return 1;
}
}
int
Gnucash::add_quotes (const bo_str& uri)
{
if (uri && !uri->empty())
scm_boot_guile (0, nullptr, scm_add_quotes, (void *)&(*uri));
gnc_prefs_init ();
qof_event_suspend();
auto session = gnc_get_current_session();
if (!session)
return 1;
qof_session_begin(session, uri->c_str(), SESSION_NORMAL_OPEN);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
cleanup_and_exit_with_failure (session);
qof_session_load(session, NULL);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
cleanup_and_exit_with_failure (session);
try
{
GncQuotes quotes;
std::cout << bl::format (bl::translate ("Found Finance::Quote version {1}.")) % quotes.version() << std::endl;
auto quote_sources = quotes.sources_as_glist();
gnc_quote_source_set_fq_installed (quotes.version().c_str(), quote_sources);
g_list_free_full (quote_sources, g_free);
quotes.fetch(qof_session_get_book(session));
if (quotes.had_failures())
std::cerr << quotes.report_failures() << std::endl;
}
catch (const GncQuoteException& err)
{
std::cerr << bl::translate("Price retrieval failed: ") << err.what() << std::endl;
}
qof_session_save(session, NULL);
if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
cleanup_and_exit_with_failure (session);
qof_session_destroy(session);
qof_event_resume();
return 0;
}
int
Gnucash::report_quotes (const char* source, const StrVec& commodities, bool verbose)
{
try
{
GncQuotes quotes;
quotes.report(source, commodities, verbose);
if (quotes.had_failures())
std::cerr << quotes.report_failures() << std::endl;
}
catch (const GncQuoteException& err)
{
std::cerr << bl::translate("Price retrieval failed: ") << err.what() << std::endl;
return -1;
}
return 0;
}

View File

@ -26,13 +26,19 @@
#define GNUCASH_COMMANDS_HPP
#include <string>
#include <vector>
#include <boost/optional.hpp>
using bo_str = boost::optional <std::string>;
using StrVec = std::vector<std::string>;
namespace Gnucash {
int check_finance_quote (void);
int add_quotes (const bo_str& uri);
int report_quotes (const char* source,
const StrVec& commodities,
bool verbose);
int run_report (const bo_str& file_to_load,
const bo_str& run_report,
const bo_str& export_type,

View File

@ -244,7 +244,7 @@ Gnucash::CoreApp::parse_command_line (int argc, char **argv)
catch (std::exception &e)
{
std::cerr << e.what() << "\n\n";
std::cerr << *m_opt_desc_display.get() << "\n";
std::cerr << *m_opt_desc_display.get() << std::endl;
exit(1);
}
@ -275,13 +275,13 @@ Gnucash::CoreApp::parse_command_line (int argc, char **argv)
else
std::cout << rel_fmt % gnc_version () << "\n";
std::cout << bl::translate ("Build ID") << ": " << gnc_build_id () << "\n";
std::cout << bl::translate ("Build ID") << ": " << gnc_build_id () << std::endl;
exit(0);
}
if (m_show_help)
{
std::cout << *m_opt_desc_display.get() << "\n";
std::cout << *m_opt_desc_display.get() << std::endl;
exit(0);
}

View File

@ -69,6 +69,7 @@ extern "C" {
#include <iostream>
#include <gnc-report.h>
#include <gnc-locale-utils.hpp>
#include <gnc-quotes.hpp>
namespace bl = boost::locale;
@ -173,10 +174,24 @@ scm_run_gnucash (void *data, [[maybe_unused]] int argc, [[maybe_unused]] char **
gnc_hook_add_dangler(HOOK_UI_SHUTDOWN, (GFunc)gnc_file_quit, NULL, NULL);
/* Install Price Quote Sources */
auto msg = bl::translate ("Checking Finance::Quote...").str(gnc_get_boost_locale());
gnc_update_splash_screen (msg.c_str(), GNC_SPLASH_PERCENTAGE_UNKNOWN);
scm_c_use_module("gnucash price-quotes");
scm_c_eval_string("(gnc:price-quotes-install-sources)");
try
{
auto msg = bl::translate ("Checking Finance::Quote...").str(gnc_get_boost_locale());
GncQuotes quotes;
msg = (bl::format (bl::translate("Found Finance::Quote version {1}.")) % quotes.version()).str(gnc_get_boost_locale());
auto quote_sources = quotes.sources_as_glist();
gnc_quote_source_set_fq_installed (quotes.version().c_str(), quote_sources);
g_list_free (quote_sources);
gnc_update_splash_screen (msg.c_str(), GNC_SPLASH_PERCENTAGE_UNKNOWN);
}
catch (const GncQuoteException& err)
{
auto msg = bl::translate("Unable to load Finance::Quote.").str(gnc_get_boost_locale());
PINFO ("Attempt to load Finance::Quote returned this error message:\n");
PINFO ("%s", err.what());
gnc_update_splash_screen (msg.c_str(), GNC_SPLASH_PERCENTAGE_UNKNOWN);
}
gnc_hook_run(HOOK_STARTUP, NULL);

View File

@ -1,544 +0,0 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; price-quotes.scm - manage sub-processes.
;;; Copyright 2001 Rob Browning <rlb@cs.utexas.edu>
;;;
;;; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-module (gnucash price-quotes))
(export gnc:book-add-quotes) ;; called from gnome/dialog-price-edit-db.c
(export gnc:price-quotes-install-sources)
(use-modules (gnucash engine))
(use-modules (gnucash utilities))
(use-modules (gnucash core-utils))
(use-modules (gnucash app-utils))
(use-modules (gnucash gnome-utils))
(use-modules (srfi srfi-11)
(srfi srfi-1))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define gnc:*finance-quote-check*
(string-append (gnc-path-get-bindir) "/gnc-fq-check"))
(define (gnc:fq-check-sources)
(let ((program #f))
(define (start-program)
(set! program
(gnc-spawn-process-async
(list "perl" "-w" gnc:*finance-quote-check*) #t)))
(define (get-sources)
(when program
(catch #t
(lambda ()
(let ((results (read (fdes->inport (gnc-process-get-fd program 1)))))
(gnc:debug "gnc:fq-check-sources results: " results)
results))
(lambda (key . args) key))))
(define (kill-program)
(when program
(gnc-detach-process program #t)
(set! program #f)))
(dynamic-wind start-program get-sources kill-program)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Finance::Quote based instantaneous quotes -- used by the
;; --add-price-quotes command line option, etc.
;;
;; Note From: Dave Peticolas <dave@krondo.com> Date: Sun, 01 Apr 2001
;; Those aren't pricedb functions, those are online quote functions,
;; i.e., low-level functions for getting online-quotes and putting
;; them into the price db. Reports should not be using those
;; functions, they should be using the price db. See
;; src/engine/gnc-pricedb.h
(define gnc:*finance-quote-helper*
(string-append (gnc-path-get-bindir) "/gnc-fq-helper"))
(define (gnc:fq-get-quotes requests)
;; requests should be a list where each item is of the form
;;
;; (<fq-method> sym sym ...)
;;
;; i.e. (alphavantage "RHAT" "LNUX" "IBM")
;;
;; for currencies, we have
;;
;; (currency "USD" "AUD") for the conversion from USD to AUD,
;; i.e., the price of USD in AUD.
;;
;; This function will return #f on catastrophic failure or a list
;; where, for each element in requests, the output list will contain
;; a quote-result element. This element will be #f or an error
;; symbol if the corresponding method call fails, or a list
;; otherwise. A quote-result list will contain the symbol
;; representing the item being quoted, followed by an alist
;; detailing the quote data from gnc-fq-helper.
;;
;; Possible error symbols and their meanings are:
;; missing-lib One of the required perl libs is missing
;;
;; So for the example method call above, the resulting item in the
;; output list might look like this:
;;
;; (("RHAT" (symbol . "RHAT") (gnc:time-no-zone . "...")
;; (last . 6.59375) (currency . "USD"))
;; ("LNUX" (symbol . "LNUX") (gnc:time-no-zone . "...")
;; (last . 3.5) (currency . "USD"))
;; ("IBM" (symbol . "IBM") (gnc:time-no-zone . "...")
;; (last . 104.42) (currency . "USD")))
;;
;; Also note that any given value in the alist might be
;; 'failed-conversion if the Finance::Quote result for that field
;; was unparsable. See the gnc-fq-helper for more details
;; about it's output.
(let ((quoter #f))
(define (start-quoter)
(set! quoter
(gnc-spawn-process-async (list "perl" "-w" gnc:*finance-quote-helper*) #t)))
(define (get-quotes)
(when quoter
(map
(lambda (request)
(catch #t
(lambda ()
(gnc:debug "handling-request: " request)
(and (member (car request) '("currency" "alphavantage" "vanguard"))
(not (getenv "ALPHAVANTAGE_API_KEY"))
(throw 'need-alphavantage-key))
;; we need to display the first element (the method,
;; so it won't be quoted) and then write the rest
(with-output-to-port (fdes->outport (gnc-process-get-fd quoter 0))
(lambda ()
(display #\()
(display (car request))
(display " ")
(for-each write (cdr request))
(display #\))
(newline)
(force-output)))
(let ((results (read (fdes->inport (gnc-process-get-fd quoter 1)))))
(gnc:debug "results: " results)
results))
(lambda (key . args) key)))
requests)))
(define (kill-quoter)
(when quoter
(gnc-detach-process quoter #t)
(set! quoter #f)))
(dynamic-wind start-quoter get-quotes kill-quoter)))
(define (gnc:book-add-quotes window book)
(define (book->commodity->fq-call-data book)
;; Call helper that walks all of the defined commodities to see if
;; any are marked for quote retrieval. This function returns a
;; list of info needed for the relevant Finance::Quote calls, and
;; a list of the corresponding commodities. Also perform a bit of
;; optimization, merging calls for symbols to the same
;; Finance::Quote method.
;;
;; Returns a list of the info needed for a set of calls to
;; gnc-fq-helper. Each item will of the list will be of the
;; form:
;;
;; (("alphavantage" (commodity-1 currency-1 tz-1)
;; (commodity-2 currency-2 tz-2) ...)
;; ("fidelity_direct" (commodity-3 currency-3 tz-3)
;; (commodity-4 currency-4 tz-4) ...)
;; ("currency" curr-1 curr-2 tz)
;; ("currency" curr-3 curr-4 tz) ...)
(let-values (((currency-list commodity-list)
(partition (lambda (a) (string=? (car a) "currency"))
(gnc-commodity-table-get-quotable-commodities
(gnc-commodity-table-get-table book)))))
(let ((commodity-hash (make-hash-table))
(currency-list-filtered
(filter
(lambda (a)
(and (not (gnc-commodity-equiv (cadr a) (caddr a)))
(not (string=? (gnc-commodity-get-mnemonic (cadr a)) "XXX"))))
currency-list)))
;; Now collect symbols going to the same backend.
(for-each
(lambda (item)
(let ((key (car item))
(val (cdr item)))
(hash-set! commodity-hash key
(cons val (hash-ref commodity-hash key '())))))
commodity-list)
;; Now translate to just what gnc-fq-helper expects.
(and (or (pair? currency-list-filtered) (pair? commodity-list))
(append
(hash-map->list cons commodity-hash)
(map (lambda (cmd) (cons (car cmd) (list (cdr cmd))))
currency-list-filtered))))))
(define (fq-call-data->fq-calls fq-call-data)
;; take an output element from book->commodity->fq-call-data and
;; return a list where the gnc_commodities have been converted to
;; their fq-suitable symbol strings. i.e. turn the former into
;; the latter:
;;
;; ("alphavantage" (commodity-1 currency-1 tz-1)
;; (commodity-2 currency-2 tz-2) ...)
;;
;; ("alphavantage" "IBM" "AMD" ...)
;;
(if (equal? (car fq-call-data) "currency")
(map (lambda (quote-item-info)
(list (car fq-call-data)
(gnc-commodity-get-mnemonic (car quote-item-info))
(gnc-commodity-get-mnemonic (cadr quote-item-info))))
(cdr fq-call-data))
(list
(cons (car fq-call-data)
(map
(lambda (quote-item-info)
(gnc-commodity-get-mnemonic (car quote-item-info)))
(cdr fq-call-data))))))
(define (fq-results->commod-tz-quote-triples fq-call-data fq-results)
;; Change output of gnc:fq-get-quotes to a list of (commod
;; timezone quote) triples using the matching commodity items from
;; fq-call-data.
;;
;; This function presumes that fq-call-data is "correct" -- it
;; contains the correct number of calls, and has the commodity
;; pointers in all the right places. If not, then the results of
;; this function are undefined.
;;
;; If there's a catatstrophic error, this function might return
;; #f. If there's an error for any given input element, there
;; will be a pair like this in the output (#f . <commodity>)
;; indicating the commodity for which the quote failed.
;;
;; If this function doesn't return #f, it will return a list with
;; as many elements as there were commodities in the fq-call-data.
;;
;; We might want more sophisticated error handling later, but this
;; will do for now .
(let ((result-list '()))
(define (process-a-quote call-data call-result)
;; data -> (commod-1 currency-1 tz-1)
;; result -> (commod-1-sym . result-alist) or some kind of garbage.
(if (and (list? call-result)
(not (null? call-result))
(list? (cdr call-result))
(every
(lambda (alist-item)
(and (pair? alist-item)
(not (eq? 'failed-conversion (cdr alist-item)))))
(cdr call-result)))
;; OK, data is good (as far as we can tell).
(set! result-list
(cons (list (car call-data)
(caddr call-data)
(cdr call-result))
result-list))
(set! result-list
(cons (cons #f (car call-data))
result-list))))
(define (process-call-result-pair call-data call-result)
(if (and (list? call-result)
(= (length call-data) (+ 1 (length call-result))))
;; OK, continue.
(for-each
(lambda (call-data-item call-result-item)
(if (and (list? call-result-item) (list? (car call-result-item)))
(for-each
(lambda (result-subitem)
(gnc:debug "call-data-item: " call-data-item)
(gnc:debug "result-subitem: " result-subitem)
(process-a-quote call-data-item result-subitem))
call-result-item)
(process-a-quote call-data-item call-result-item)))
(cdr call-data) call-result)
;; else badly formed result, must assume all garbage.
(for-each
(lambda (call-item)
(set! result-list (cons (cons #f (car call-item)) result-list)))
(cdr call-data))))
(and (list? fq-call-data)
(list? fq-results)
(= (length fq-call-data) (length fq-results))
(begin
(for-each process-call-result-pair
fq-call-data
fq-results)
(reverse result-list)))))
(define (timestr->time64 timestr time-zone)
;; time-zone is ignored currently
(gnc-parse-time-to-time64 timestr "%Y-%m-%d %H:%M:%S"))
(define (commodity-tz-quote-triple->price book c-tz-quote-triple)
;; return a string like "NASDAQ:CSCO" on error, or a price on
;; success. Presume that F::Q currencies are ISO4217 currencies.
(let* ((commodity (first c-tz-quote-triple))
(time-zone (second c-tz-quote-triple))
(quote-data (third c-tz-quote-triple))
(gnc-time (assq-ref quote-data 'gnc:time-no-zone))
(price #f)
(price-type #f)
(currency-str (assq-ref quote-data 'currency))
(commodity-table (gnc-commodity-table-get-table book))
(currency
(and commodity-table
(string? currency-str)
(gnc-commodity-table-lookup commodity-table
"ISO4217"
(string-upcase currency-str))))
(pricedb (gnc-pricedb-get-db book))
(saved-price #f)
(commodity-str (gnc-commodity-get-printname commodity))
)
(if (equal? (gnc-commodity-get-printname currency) commodity-str)
(let* ((symbol (assq-ref quote-data 'symbol))
(other-curr
(and commodity-table
(string? symbol)
(gnc-commodity-table-lookup commodity-table "ISO4217"
(string-upcase symbol)))))
(set! commodity other-curr)))
(let lp ((price-syms '(last nav price))
(price-types '("last" "nav" "unknown")))
(unless (null? price-syms)
(cond
((assq-ref quote-data (car price-syms)) =>
(lambda (p)
;; The OpenExchange exchange rate source in Finance::Quote produces
;; some ridiculously precise prices like #e6.95253159056541e-5 which
;; produce a denominator greater than INT64_MAX. Use the rationalize
;; function to bring them back to reality. The precision parameter is
;; chosen empirically to give the best results.
(set! price (gnc-scm-to-numeric
(rationalize p 1/100000000000000)))
(set! price-type (car price-types))))
(else (lp (cdr price-syms) (cdr price-types))))))
(if gnc-time
(set! gnc-time (timestr->time64 gnc-time time-zone))
(set! gnc-time (gnc:get-today)))
(if (not (and commodity currency gnc-time price price-type))
(string-append
currency-str ":" (gnc-commodity-get-mnemonic commodity))
(begin
(set! saved-price (gnc-pricedb-lookup-day-t64 pricedb
commodity currency
gnc-time))
(if (not (null? saved-price))
(begin
(if (gnc-commodity-equiv (gnc-price-get-currency saved-price)
commodity)
(set! price (gnc-numeric-invert price)))
(if (>= (gnc-price-get-source saved-price) PRICE-SOURCE-FQ)
(begin
(gnc-price-begin-edit saved-price)
(gnc-price-set-time64 saved-price gnc-time)
(gnc-price-set-source saved-price PRICE-SOURCE-FQ)
(gnc-price-set-typestr saved-price price-type)
(gnc-price-set-value saved-price price)
(gnc-price-commit-edit saved-price)
#f)
#f))
(let ((gnc-price (gnc-price-create book)))
(if (not gnc-price)
(string-append
currency-str ":" (gnc-commodity-get-mnemonic commodity))
(begin
(gnc-price-begin-edit gnc-price)
(gnc-price-set-commodity gnc-price commodity)
(gnc-price-set-currency gnc-price currency)
(gnc-price-set-time64 gnc-price gnc-time)
(gnc-price-set-source gnc-price PRICE-SOURCE-FQ)
(gnc-price-set-typestr gnc-price price-type)
(gnc-price-set-value gnc-price price)
(gnc-price-commit-edit gnc-price)
gnc-price))))
))
))
(define (book-add-prices! book prices)
(let ((pricedb (gnc-pricedb-get-db book)))
(for-each
(lambda (price)
(when price
(gnc-pricedb-add-price pricedb price)
(gnc-price-unref price)))
prices)))
(define (show-error msg)
(gnc:gui-error msg (G_ msg)))
;; Add the alphavantage api key to the environment. This value is taken from
;; the Online Quotes preference tab
(let ((alphavantage-api-key
(gnc-prefs-get-string "general.finance-quote" "alphavantage-api-key")))
(gnc:debug "ALPHAVANTAGE_API_KEY=" alphavantage-api-key)
(unless (string-null? alphavantage-api-key)
(setenv "ALPHAVANTAGE_API_KEY" alphavantage-api-key)))
(let* ((fq-call-data (book->commodity->fq-call-data book))
(fq-calls (and fq-call-data
(append-map fq-call-data->fq-calls fq-call-data)))
(fq-results (and fq-calls (gnc:fq-get-quotes fq-calls)))
(commod-tz-quote-triples (and fq-results (list? (car fq-results))
(fq-results->commod-tz-quote-triples
fq-call-data fq-results)))
;; At this point commod-tz-quote-triples will either be #f or a
;; list of items. Each item will either be (commodity
;; timezone quote-data) or (#f . problem-commodity)
(problem-syms (and commod-tz-quote-triples
(filter-map
(lambda (cq-pair)
(and (not (car cq-pair))
(string-append
(gnc-commodity-get-namespace (cdr cq-pair))
":"
(gnc-commodity-get-mnemonic (cdr cq-pair)))))
commod-tz-quote-triples)))
;; strip out the "bad" ones from above.
(ok-syms (and commod-tz-quote-triples (filter car commod-tz-quote-triples)))
(keep-going? #t))
(cond
((not fq-call-data)
(set! keep-going? #f)
(show-error (N_ "No commodities marked for quote retrieval.")))
((not fq-results)
(set! keep-going? #f)
(show-error (N_ "Unable to get quotes or diagnose the problem.")))
((memq 'missing-lib fq-results)
(set! keep-going? #f)
(show-error (N_ "You are missing some needed Perl libraries.
Run 'gnc-fq-update' as root to install them.")))
((memq 'need-alphavantage-key fq-results)
(set! keep-going? #f)
(show-error (format #f (G_ "ERROR: ALPHAVANTAGE_API_KEY must be set for currency and quotes; see ~A")
"https://wiki.gnucash.org/wiki/Online_Quotes#Source_Alphavantage.2C_US")))
((memq 'system-error fq-results)
(set! keep-going? #f)
(show-error (N_ "There was a system error while retrieving the price quotes.")))
((not (list? (car fq-results)))
(set! keep-going? #f)
(show-error (N_ "There was an unknown error while retrieving the price quotes.")))
((not commod-tz-quote-triples)
(set! keep-going? #f)
(show-error (N_ "Unable to get quotes or diagnose the problem.")))
((pair? problem-syms)
(cond
((not (gnucash-ui-is-running))
(gnc:warn
(with-output-to-string
(lambda ()
(display "Unable to retrieve quotes for these items:\n")
(display (string-join problem-syms "\n "))
(newline)
(display "Continuing with good quotes.")
(newline)))))
((and ok-syms (not (null? ok-syms)))
(set! keep-going?
(gnc-verify-dialog
window #t (with-output-to-string
(lambda ()
(display (G_ "Unable to retrieve quotes for these items:"))
(display "\n ")
(display (string-join problem-syms "\n "))
(newline)
(display (G_ "Continue using only the good quotes?")))))))
(else
(set! keep-going? #f)
(gnc-error-dialog
window (with-output-to-string
(lambda ()
(display (G_ "Unable to retrieve quotes for these items:"))
(display "\n ")
(display (string-join problem-syms "\n ")))))))))
(when keep-going?
(let ((prices (map (lambda (triple)
(commodity-tz-quote-triple->price book triple))
ok-syms)))
(when (any string? prices)
(if (gnucash-ui-is-running)
(set! keep-going?
(gnc-verify-dialog
window #t
(with-output-to-string
(lambda ()
(display (G_ "Unable to create prices for these items:"))
(display "\n ")
(display (string-join (filter string? prices) "\n "))
(newline)
(display (G_ "Add remaining good quotes?"))))))
(gnc:warn
(with-output-to-string
(lambda ()
(display "Unable to create prices for these items:\n ")
(display (string-join (filter string? prices) "\n "))
(newline)
(display "Adding remaining good quotes.")
(newline))))))
(when keep-going?
(book-add-prices! book (filter (negate string?) prices)))))))
(define (gnc:price-quotes-install-sources)
(let ((sources (gnc:fq-check-sources)))
(cond
((list? sources)
;; Translators: ~A is the version string
(format #t (G_ "Found Finance::Quote version ~A.") (car sources))
(newline)
(gnc:msg "Found Finance::Quote version " (car sources))
(gnc-quote-source-set-fq-installed (car sources) (cdr sources))))))

View File

@ -17,6 +17,7 @@ set (app_utils_HEADERS
gnc-gsettings.h
gnc-help-utils.h
gnc-prefs-utils.h
gnc-quotes.hpp
gnc-state.h
gnc-ui-util.h
gnc-ui-balances.h
@ -31,6 +32,7 @@ set (app_utils_SOURCES
gnc-euro.c
gnc-gsettings.cpp
gnc-prefs-utils.c
gnc-quotes.cpp
gnc-state.c
gnc-ui-util.c
gnc-ui-balances.c
@ -46,7 +48,9 @@ endif()
set(app_utils_ALL_SOURCES ${app_utils_SOURCES} ${app_utils_HEADERS})
set(app_utils_ALL_LIBRARIES
gnc-engine
${GLIB_LDFLAGS}
${Boost_FILESYSTEM_LIBRARY}
${Boost_PROPERTY_TREE_LIBRARY}
${Boost_LOCALE_LIBRARY}
${GIO_LDFLAGS}
${LIBXML2_LDFLAGS}
${LIBXSLT_LDFLAGS}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
/********************************************************************\
* 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 *
\********************************************************************/
#ifndef GNC_QUOTES_HPP
#define GNC_QUOTES_HPP
#include <memory>
#include <string>
#include <vector>
#include <gnc-commodity.hpp> // For CommVec alias
#include <glib.h>
#include <stdexcept>
extern "C" {
#include <qofbook.h>
}
using StrVec = std::vector<std::string>;
using QuoteSources = StrVec;
enum class GncQuoteError
{
SUCCESS,
NO_RESULT,
QUOTE_FAILED,
NO_CURRENCY,
UNKNOWN_CURRENCY,
NO_PRICE,
UNKNOWN_PRICE_TYPE,
PRICE_PARSE_FAILURE,
};
/** QuoteFailure elements are namespace, mnemonic, error code, and
* F::Q errormsg if there is one.
*/
using QuoteFailure = std::tuple<std::string, std::string,
GncQuoteError, std::string>;
using QFVec = std::vector<QuoteFailure>;
struct GncQuoteException : public std::runtime_error
{
GncQuoteException(const std::string& msg) : std::runtime_error(msg) {}
};
class GncQuotesImpl;
class GncQuotes
{
public:
/** Create a GncQuotes object.
*
* Throws a GncQuoteException if Finance::Quote is not installed or fails to initialize.
*/
GncQuotes ();
~GncQuotes ();
/** Fetch quotes for all commodities in our db that have a quote source set
*
* @param book The current book.
*/
void fetch (QofBook *book);
/** Fetch quotes for a vector of commodities
*
* @param commodities std::vector of the gnc_commodity* to get quotes for.
* @note Commodities without a quote source will be silently ignored.
*/
void fetch (CommVec& commodities);
/** Fetch quote for a single commodity
*
* @param comm Commodity for which to retrieve a quote
* @note Commodity must have a quote source set or the call will silently fail.
*/
void fetch (gnc_commodity *comm);
/** Report quote results from Finance::Quote to std::cout.
*
* @param source A valid quote source
* @param commodities A std::vector of symbols to request quotes for.
* @note If requesting currency rates the first symbol is the to-currency and the rest are from-currencies. For example, {"USD", "EUR", "CAD"} will print the price of 1 Euro and 1 Canadian Dollar in US Dollars.
* @param verbose Ignored for currency queries. If false it will print the six fields GnuCash uses regardless of whether a value was returned; if true it will print all of the fields for which Finanace::Quote returned values.
*/
void report (const char* source, const StrVec& commodities, bool verbose = false);
/** Get the installed Finance::Quote version
*
* @return the Finance::Quote version string
*/
const std::string& version() noexcept;
/** Get the available Finance::Quote sources as a std::vector
*
* @return The quote sources configured in Finance::Quote
*/
const QuoteSources& sources() noexcept;
/** Get the available Finance::Quote sources as a GList
*
* @return A double-linked list containing the names of the installed quote sources.
* @note the list and its contents are owned by the caller and should be freed with `g_list_free_full(list, g_free)`.
*/
GList* sources_as_glist () ;
/** Report if there were quotes requested but not retrieved.
*
* @returns True if there were quote failures.
*/
bool had_failures() noexcept;
/** Report the commodities for which quotes were requested but not successfully retrieved.
*
* This does not include requested commodities that didn't have a quote source.
*
* @return a reference to a vector of QuoteFailure tuples.
* @note The vector and its contents belong to the GncQuotes object and will be destroyed with it.
*/
const QFVec& failures() noexcept;
/* Report the commodities for which quotes were requested but not successfully retrieved.
*
* This does not include requested commodities that didn't have a quote source.
*
* @return A localized std::string with an intro and a list of the quote failures with a cause. The string is owned by the caller.
*/
const std::string report_failures() noexcept;
private:
std::unique_ptr<GncQuotesImpl> m_impl;
};
#endif /* GNC_QUOTES_HPP */

View File

@ -31,6 +31,24 @@ gnc_add_test_with_guile(test-sx test-sx.cpp
APP_UTILS_TEST_INCLUDE_DIRS APP_UTILS_TEST_LIBS
)
set(test_gnc_quotes_SOURCES
gtest-gnc-quotes.cpp
)
set(test_gnc_quotes_INCLUDES
${CMAKE_BINARY_DIR}/common # for config.h
${MODULEPATH}
)
set(test_gnc_quotes_LIBS
gnc-engine
gtest
${Boost_FILESYSTEM_LIBRARY}
${Boost_LOCALE_LIBRARY}
${Boost_PROPERTY_TREE_LIBRARY}
${Boost_SYSTEM_LIBRARY}
)
gnc_add_test(test-gnc-quotes "${test_gnc_quotes_SOURCES}" test_gnc_quotes_INCLUDES test_gnc_quotes_LIBS)
set(GUILE_DEPENDS
scm-test-engine
@ -43,6 +61,7 @@ set(GUILE_DEPENDS
set_dist_list(test_app_utils_DIST
CMakeLists.txt
gtest-gnc-quotes.cpp
test-exp-parser.c
test-print-parse-amount.cpp
test-sx.cpp

View File

@ -0,0 +1,411 @@
/********************************************************************\
* test-gnc-price-quotes.cpp -- Unit tests for GncQuotes *
* *
* Copyright 2022 John Ralls <jralls@ceridwen.us> *
* *
* 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 *
* *
\********************************************************************/
extern "C"
{
#include <config.h>
#include <gnc-session.h>
#include <gnc-commodity.h>
#include <gnc-pricedb-p.h>
#include <qof.h>
/* gnc-quotes normally gets this from gnc-ui-util, but let's avoid the dependency. */
static gnc_commodity*
gnc_default_currency(void)
{
auto book{qof_session_get_book(gnc_get_current_session())};
auto table{gnc_commodity_table_get_table(book)};
return gnc_commodity_table_lookup(table, GNC_COMMODITY_NS_CURRENCY, "USD");
}
} // extern "C"
#include <gtest/gtest.h>
#include "../gnc-quotes.cpp"
class GncMockQuoteSource final : public GncQuoteSource
{
const std::string m_version{"9.99"};
const StrVec m_sources{"currency", "yahoo_json"};
const StrVec m_quotes;
const StrVec m_errors;
public:
GncMockQuoteSource(StrVec&& quotes, StrVec&& errors) :
m_quotes{std::move(quotes)}, m_errors{std::move(errors)}{}
~GncMockQuoteSource() override = default;
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;
};
class GncFailedQuoteSource final : public GncQuoteSource
{
const std::string m_version{"0"};
const StrVec m_sources;
public:
GncFailedQuoteSource()
{
std::string err{"Failed to initialize Finance::Quote: "};
err += "missing_modules Mozilla::CA Try::Tiny";
throw GncQuoteSourceError (err);
}
~GncFailedQuoteSource() override = default;
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 {return {0, {}, {}}; }
};
QuoteResult
GncMockQuoteSource::get_quotes(const std::string& json_string) const
{
if (m_errors.empty())
return {0, m_quotes, m_errors};
return {1, m_quotes, m_errors};
}
class GncQuotesTest : public ::testing::Test
{
protected:
GncQuotesTest() : m_session{gnc_get_current_session()},
m_book{qof_session_get_book(gnc_get_current_session())}
{
qof_init();
gnc_commodity_table_register();
gnc_pricedb_register();
auto comm_table{gnc_commodity_table_new()};
qof_book_set_data(m_book, GNC_COMMODITY_TABLE, comm_table);
auto eur = gnc_commodity_new(m_book, "Euro", "ISO4217", "EUR", NULL, 100);
auto source{gnc_quote_source_lookup_by_internal("currency")};
gnc_commodity_begin_edit(eur);
gnc_commodity_set_quote_flag(eur, TRUE);
gnc_commodity_set_quote_source(eur, source);
gnc_commodity_commit_edit(eur);
gnc_commodity_table_insert(comm_table, eur);
auto usd = gnc_commodity_new(m_book, "United States Dollar", "CURRENCY",
"USD", NULL, 100);
gnc_commodity_table_insert(comm_table, usd);
source = gnc_quote_source_lookup_by_internal("yahoo_json");
auto aapl = gnc_commodity_new(m_book, "Apple", "NASDAQ", "AAPL", NULL, 1);
gnc_commodity_begin_edit(aapl);
gnc_commodity_set_quote_flag(aapl, TRUE);
gnc_commodity_set_quote_source(aapl, source);
gnc_commodity_commit_edit(aapl);
gnc_commodity_table_insert(comm_table, aapl);
auto hpe = gnc_commodity_new(m_book, "Hewlett Packard", "NYSE", "HPE",
NULL, 1);
gnc_commodity_begin_edit(hpe);
gnc_commodity_set_quote_flag(hpe, TRUE);
gnc_commodity_set_quote_source(hpe, source);
gnc_commodity_commit_edit(hpe);
gnc_commodity_table_insert(comm_table, hpe);
auto fkcm = gnc_commodity_new(m_book, "Fake Company", "NASDAQ", "FKCM", NULL, 1);
gnc_commodity_begin_edit(fkcm);
gnc_commodity_set_quote_flag(fkcm, TRUE);
gnc_commodity_set_quote_source(fkcm, source);
gnc_commodity_commit_edit(fkcm);
gnc_commodity_table_insert(comm_table, fkcm);
gnc_quote_source_set_fq_installed("TestSuite", g_list_prepend(nullptr, (void*)"yahoo_json"));
}
~GncQuotesTest() {
gnc_clear_current_session();
}
QofSession* m_session;
QofBook* m_book;
};
TEST_F(GncQuotesTest, quote_sources)
{
auto qs_cur{gnc_quote_source_lookup_by_internal("currency")};
auto qs_yahoo{gnc_quote_source_lookup_by_internal("yahoo_json")};
auto qs_alpha{gnc_quote_source_lookup_by_internal("alphavantage")};
EXPECT_TRUE(qs_cur != nullptr);
EXPECT_TRUE(qs_yahoo != nullptr);
EXPECT_TRUE(qs_alpha != nullptr);
EXPECT_TRUE(gnc_quote_source_get_supported(qs_cur));
EXPECT_TRUE(gnc_quote_source_get_supported(qs_yahoo));
EXPECT_FALSE(gnc_quote_source_get_supported(qs_alpha));
}
TEST_F(GncQuotesTest, quotable_commodities)
{
auto commodities{gnc_quotes_get_quotable_commodities(gnc_commodity_table_get_table(m_book))};
EXPECT_EQ(4u, commodities.size());
}
#ifdef HAVE_F_Q
TEST_F(GncQuotesTest, online_wiggle)
{
GncQuotes quotes;
quotes.fetch(m_book);
auto pricedb{gnc_pricedb_get_db(m_book)};
auto failures{quotes.failures()};
ASSERT_EQ(1u, failures.size());
EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[0]));
// EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[1]));
EXPECT_EQ(3u, gnc_pricedb_get_num_prices(pricedb));
}
#else
TEST_F(GncQuotesTest, fq_failure)
{
EXPECT_THROW(GncQuotes quotes;, GncQuoteException);
}
#endif
TEST_F(GncQuotesTest, offline_wiggle)
{
StrVec quote_vec{
"{"
"\"EUR\":{\"symbol\":\"EUR\",\"currency\":\"USD\",\"success\":\"1\",\"inverted\":0,\"last\":1.0004},"
"\"AAPL\":{\"eps\":6.05,\"success\":1,\"year_range\":\" 129.04 - 182.94\",\"currency\":\"USD\",\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"volume\":73539475,\"close\":157.22,\"high\":158.39,\"open\":156.64,\"div_yield\":0.5660857,\"last\":157.96,\"isodate\":\"2022-09-01\",\"method\":\"yahoo_json\",\"name\":\"AAPL (Apple Inc.)\",\"pe\":26.10909,\"low\":154.67,\"type\":\"EQUITY\",\"symbol\":\"AAPL\",\"date\":\"09/01/2022\"},"
"\"HPE\":{\"symbol\":\"HPE\",\"date\":\"09/01/2022\",\"low\":13.13,\"type\":\"EQUITY\",\"method\":\"yahoo_json\",\"name\":\"HPE (Hewlett Packard Enterprise Comp)\",\"isodate\":\"2022-09-01\",\"pe\":4.7921147,\"last\":13.37,\"high\":13.535,\"close\":13.6,\"open\":13.5,\"div_yield\":3.5294116,\"volume\":16370483,\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"currency\":\"USD\",\"year_range\":\" 12.4 - 17.76\",\"eps\":2.79,\"success\":1},"
"\"FKCM\":{\"success\":0,\"symbol\":\"FKCM\",\"errormsg\":\"Error retrieving quote for FKCM - no listing for this name found. Please check symbol and the two letter extension (if any)\"}"
"}"
};
StrVec err_vec;
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(m_book);
auto failures{quotes.failures()};
ASSERT_EQ(1u, failures.size());
EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[0]));
auto pricedb{gnc_pricedb_get_db(m_book)};
EXPECT_EQ(3u, gnc_pricedb_get_num_prices(pricedb));
}
TEST_F(GncQuotesTest, offline_report)
{
StrVec quote_vec{
"{"
"\"AAPL\":{\"eps\":6.05,\"success\":1,\"year_range\":\" 129.04 - 182.94\",\"currency\":\"USD\",\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"volume\":73539475,\"close\":157.22,\"high\":158.39,\"open\":156.64,\"div_yield\":0.5660857,\"last\":157.96,\"isodate\":\"2022-09-01\",\"method\":\"yahoo_json\",\"name\":\"AAPL (Apple Inc.)\",\"pe\":26.10909,\"low\":154.67,\"type\":\"EQUITY\",\"symbol\":\"AAPL\",\"date\":\"09/01/2022\"},"
"\"HPE\":{\"symbol\":\"HPE\",\"date\":\"09/01/2022\",\"low\":13.13,\"type\":\"EQUITY\",\"method\":\"yahoo_json\",\"name\":\"HPE (Hewlett Packard Enterprise Comp)\",\"isodate\":\"2022-09-01\",\"pe\":4.7921147,\"last\":13.37,\"high\":13.535,\"close\":13.6,\"open\":13.5,\"div_yield\":3.5294116,\"volume\":16370483,\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"currency\":\"USD\",\"year_range\":\" 12.4 - 17.76\",\"eps\":2.79,\"success\":1},"
"\"FKCM\":{\"success\":0,\"symbol\":\"FKCM\",\"errormsg\":\"Error retrieving quote for FKCM - no listing for this name found. Please check symbol and the two letter extension (if any)\"}"
"}"
};
StrVec commodities{"AAPL", "HPE", "FKCM"};
StrVec err_vec;
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.report("yahoo_json", commodities, false);
quotes.report("yahoo_json", commodities, true);
}
TEST_F(GncQuotesTest, offline_currency_report)
{
StrVec quote_vec{
"{"
"\"EUR\":{\"symbol\":\"EUR\",\"currency\":\"USD\",\"success\":\"1\",\"inverted\":0,\"last\":1.0004}"
"}"
};
StrVec commodities{"USD", "EUR"};
StrVec err_vec;
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.report("currency", commodities, false);
quotes.report("currency", commodities, true);
}
TEST_F(GncQuotesTest, comvec_fetch)
{
StrVec quote_vec{
"{"
"\"AAPL\":{\"eps\":6.05,\"success\":1,\"year_range\":\" 129.04 - 182.94\",\"currency\":\"USD\",\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"volume\":73539475,\"close\":157.22,\"high\":158.39,\"open\":156.64,\"div_yield\":0.5660857,\"last\":157.96,\"isodate\":\"2022-09-01\",\"method\":\"yahoo_json\",\"name\":\"AAPL (Apple Inc.)\",\"pe\":26.10909,\"low\":154.67,\"type\":\"EQUITY\",\"symbol\":\"AAPL\",\"date\":\"09/01/2022\"},"
"\"HPE\":{\"symbol\":\"HPE\",\"date\":\"09/01/2022\",\"low\":13.13,\"type\":\"EQUITY\",\"method\":\"yahoo_json\",\"name\":\"HPE (Hewlett Packard Enterprise Comp)\",\"isodate\":\"2022-09-01\",\"pe\":4.7921147,\"last\":13.37,\"high\":13.535,\"close\":13.6,\"open\":13.5,\"div_yield\":3.5294116,\"volume\":16370483,\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"currency\":\"USD\",\"year_range\":\" 12.4 - 17.76\",\"eps\":2.79,\"success\":1}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto hpe{gnc_commodity_table_lookup(commtable, "NYSE", "HPE")};
auto aapl{gnc_commodity_table_lookup(commtable, "NASDAQ", "AAPL")};
CommVec comms{hpe, aapl};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(comms);
auto failures{quotes.failures()};
EXPECT_TRUE(failures.empty());
auto pricedb{gnc_pricedb_get_db(m_book)};
EXPECT_EQ(2u, gnc_pricedb_get_num_prices(pricedb));
}
TEST_F(GncQuotesTest, fetch_one_commodity)
{
StrVec quote_vec{
"{"
"\"HPE\":{\"date\":\"09/01/2022\",\"last\":13.37,\"currency\":\"USD\",\"success\":1}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto hpe{gnc_commodity_table_lookup(commtable, "NYSE", "HPE")};
auto usd{gnc_commodity_table_lookup(commtable, "ISO4217", "USD")};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(hpe);
auto failures{quotes.failures()};
EXPECT_TRUE(failures.empty());
auto pricedb{gnc_pricedb_get_db(m_book)};
auto price{gnc_pricedb_lookup_latest(pricedb, hpe, usd)};
auto datetime{static_cast<time64>(GncDateTime("20220901160000"))};
EXPECT_EQ(usd, gnc_price_get_currency(price));
EXPECT_EQ(datetime, gnc_price_get_time64(price));
EXPECT_EQ(PRICE_SOURCE_FQ, gnc_price_get_source(price));
EXPECT_TRUE(gnc_numeric_equal(GncNumeric{1337, 100},
gnc_price_get_value(price)));
EXPECT_STREQ("Finance::Quote", gnc_price_get_source_string(price));
EXPECT_STREQ("last", gnc_price_get_typestr(price));
}
TEST_F(GncQuotesTest, fetch_one_currency)
{
StrVec quote_vec{
"{"
"\"EUR\":{\"symbol\":\"EUR\",\"currency\":\"USD\",\"success\":\"1\",\"inverted\":0,\"last\":1.0004}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto eur{gnc_commodity_table_lookup(commtable, "ISO4217", "EUR")};
auto usd{gnc_commodity_table_lookup(commtable, "ISO4217", "USD")};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(eur);
auto failures{quotes.failures()};
EXPECT_TRUE(failures.empty());
auto pricedb{gnc_pricedb_get_db(m_book)};
auto price{gnc_pricedb_lookup_latest(pricedb, eur, usd)};
EXPECT_EQ(1u, gnc_pricedb_get_num_prices(pricedb));
auto datetime{static_cast<time64>(GncDateTime())};
EXPECT_EQ(usd, gnc_price_get_currency(price));
EXPECT_EQ(datetime, gnc_price_get_time64(price));
EXPECT_EQ(PRICE_SOURCE_FQ, gnc_price_get_source(price));
EXPECT_EQ(10004, gnc_price_get_value(price).num);
EXPECT_TRUE(gnc_numeric_equal(GncNumeric{10004, 10000},
gnc_price_get_value(price)));
EXPECT_STREQ("Finance::Quote", gnc_price_get_source_string(price));
EXPECT_STREQ("last", gnc_price_get_typestr(price));
}
TEST_F(GncQuotesTest, no_currency)
{
StrVec quote_vec{
"{"
"\"HPE\":{\"date\":\"09/01/2022\",\"last\":13.37,\"success\":1}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto hpe{gnc_commodity_table_lookup(commtable, "NYSE", "HPE")};
auto usd{gnc_commodity_table_lookup(commtable, "ISO4217", "USD")};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(hpe);
auto failures{quotes.failures()};
ASSERT_EQ(1u, failures.size());
EXPECT_EQ(GncQuoteError::NO_CURRENCY, std::get<2>(failures[0]));
auto pricedb{gnc_pricedb_get_db(m_book)};
EXPECT_EQ(0u, gnc_pricedb_get_num_prices(pricedb));
}
TEST_F(GncQuotesTest, bad_currency)
{
StrVec quote_vec{
"{"
"\"HPE\":{\"date\":\"09/01/2022\",\"last\":13.37,\"currency\":\"BTC\",\"success\":1}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto hpe{gnc_commodity_table_lookup(commtable, "NYSE", "HPE")};
auto usd{gnc_commodity_table_lookup(commtable, "ISO4217", "USD")};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(hpe);
auto failures{quotes.failures()};
ASSERT_EQ(1u, failures.size());
EXPECT_EQ(GncQuoteError::UNKNOWN_CURRENCY, std::get<2>(failures[0]));
auto pricedb{gnc_pricedb_get_db(m_book)};
EXPECT_EQ(0u, gnc_pricedb_get_num_prices(pricedb));
}
TEST_F(GncQuotesTest, no_date)
{
StrVec quote_vec{
"{"
"\"HPE\":{\"last\":13.37,\"currency\":\"USD\",\"success\":1}"
"}"
};
StrVec err_vec;
auto commtable{gnc_commodity_table_get_table(m_book)};
auto hpe{gnc_commodity_table_lookup(commtable, "NYSE", "HPE")};
auto usd{gnc_commodity_table_lookup(commtable, "ISO4217", "USD")};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
quotes.fetch(hpe);
auto failures{quotes.failures()};
EXPECT_TRUE(failures.empty());
auto pricedb{gnc_pricedb_get_db(m_book)};
auto price{gnc_pricedb_lookup_latest(pricedb, hpe, usd)};
auto datetime{static_cast<time64>(GncDateTime())};
EXPECT_EQ(usd, gnc_price_get_currency(price));
EXPECT_EQ(datetime, gnc_price_get_time64(price));
EXPECT_EQ(PRICE_SOURCE_FQ, gnc_price_get_source(price));
EXPECT_TRUE(gnc_numeric_equal(GncNumeric{1337, 100},
gnc_price_get_value(price)));
EXPECT_STREQ("Finance::Quote", gnc_price_get_source_string(price));
EXPECT_STREQ("last", gnc_price_get_typestr(price));
}
TEST_F(GncQuotesTest, test_version)
{
StrVec quote_vec, err_vec;
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
EXPECT_STREQ("9.99", quotes.version().c_str());
}
TEST_F(GncQuotesTest, test_failure_invalid_json)
{
StrVec quote_vec, err_vec{"invalid_json\n"};
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
EXPECT_THROW(quotes.fetch(m_book), GncQuoteException);
try
{
quotes.fetch(m_book);
}
catch (const GncQuoteException& err)
{
EXPECT_STREQ("GnuCash submitted invalid json to Finance::Quote. The details were logged.\n",
err.what());
}
}
TEST_F(GncQuotesTest, test_failure_missing_modules)
{
EXPECT_THROW(GncQuotesImpl quotes(m_book, std::make_unique<GncFailedQuoteSource>()),
GncQuoteSourceError);
try
{
GncQuotesImpl quotes(m_book, std::make_unique<GncFailedQuoteSource>());
}
catch (const GncQuoteSourceError& err)
{
EXPECT_STREQ("Failed to initialize Finance::Quote: missing_modules Mozilla::CA Try::Tiny",
err.what());
}
}

View File

@ -287,46 +287,6 @@ gnc_g_list_cut(GList **list, GList *cut_point)
cut_point->prev = NULL;
}
void
gnc_scm_log_warn(const gchar *msg)
{
g_log("gnc.scm", G_LOG_LEVEL_WARNING, "%s", msg);
}
void
gnc_scm_log_error(const gchar *msg)
{
g_log("gnc.scm", G_LOG_LEVEL_CRITICAL, "%s", msg);
}
void
gnc_scm_log_msg(const gchar *msg)
{
g_log("gnc.scm", G_LOG_LEVEL_MESSAGE, "%s", msg);
}
void
gnc_scm_log_debug(const gchar *msg)
{
g_log("gnc.scm", G_LOG_LEVEL_DEBUG, "%s", msg);
}
void gnc_gpid_kill(GPid pid)
{
#ifdef G_OS_WIN32
if (!TerminateProcess((HANDLE) pid, 0))
{
gchar *msg = g_win32_error_message(GetLastError());
g_warning("Could not kill child process: %s", msg ? msg : "(null)");
g_free(msg);
}
#else /* !G_OS_WIN32 */
if (kill(pid, SIGKILL))
{
g_warning("Could not kill child process: %s", g_strerror(errno));
}
#endif /* G_OS_WIN32 */
}
gchar *
gnc_g_list_stringjoin (GList *list_of_strings, const gchar *sep)

View File

@ -169,20 +169,6 @@ void gnc_g_list_cut(GList **list, GList *cut_point);
/** @} */
/** @name Message Logging
@{
*/
void gnc_scm_log_warn(const gchar *msg);
void gnc_scm_log_error(const gchar *msg);
void gnc_scm_log_msg(const gchar *msg);
void gnc_scm_log_debug(const gchar *msg);
/** @} */
/** @name glib Miscellaneous Functions
@{
*/
/**
* @brief Return a string joining a GList whose elements are gchar*
@ -198,7 +184,6 @@ void gnc_scm_log_debug(const gchar *msg);
* caller.
**/
gchar * gnc_g_list_stringjoin (GList *list_of_strings, const gchar *sep);
/**
* @brief Scans the GList elements the minimum number of iterations
* required to test it against a specified size. Returns -1, 0 or 1
@ -213,12 +198,6 @@ gchar * gnc_g_list_stringjoin (GList *list_of_strings, const gchar *sep);
**/
gint gnc_list_length_cmp (const GList *list, size_t len);
/** Kill a process. On UNIX send a SIGKILL, on Windows call TerminateProcess.
*
* @param pid The process ID. */
void gnc_gpid_kill(GPid pid);
/** @} */
#ifdef __cplusplus
} /* extern "C" */

View File

@ -53,6 +53,7 @@ set (engine_HEADERS
gnc-aqbanking-templates.h
gnc-budget.h
gnc-commodity.h
gnc-commodity.hpp
gnc-date.h
gnc-datetime.hpp
gnc-engine.h

View File

@ -0,0 +1,46 @@
/**********************************************************************
* gnc-commodity.hpp -- API for tradable commodities (incl. currency) *
* *
* 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 *
* *
*********************************************************************/
/** @addtogroup Engine
@{ */
/** @addtogroup Commodity Commodities
@{ */
/** @file gnc-commodity.hpp
* @brief Commodity handling public routines (C++ api)
* @author Copyright (C) 2021 Geert Janssens
*/
#ifndef GNC_COMMODITY_HPP
#define GNC_COMMODITY_HPP
#include <vector>
extern "C" {
#include <gnc-commodity.h>
}
using CommVec = std::vector<gnc_commodity*>;
#endif /* GNC_COMMODITY_HPP */
/** @} */
/** @} */

View File

@ -118,7 +118,6 @@ static const char* source_names[(size_t)PRICE_SOURCE_INVALID + 1] =
{
/* sync with price_to_gui in dialog-price-editor.c */
"user:price-editor",
/* sync with commidity-tz-quote->price in price-quotes.scm */
"Finance::Quote",
"user:price",
/* String retained for backwards compatibility. */

View File

@ -1,6 +1,6 @@
set(_BIN_FILES "")
foreach(file gnc-fq-check.in gnc-fq-helper.in gnc-fq-update.in gnc-fq-dump.in)
foreach(file gnc-fq-update.in finance-quote-wrapper.in)
string(REPLACE ".in" "" _OUTPUT_FILE_NAME ${file})
set(_ABS_OUTPUT_FILE ${BINDIR_BUILD}/${_OUTPUT_FILE_NAME})
configure_file( ${file} ${_ABS_OUTPUT_FILE} @ONLY)
@ -9,7 +9,7 @@ endforeach(file)
set(_MAN_FILES "")
foreach(file gnc-fq-dump gnc-fq-helper)
foreach(file finance-quote-wrapper)
set(_POD_INPUT ${BINDIR_BUILD}/${file})
set(_MAN_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${file}.1)
list(APPEND _MAN_FILES ${_MAN_OUTPUT})
@ -26,4 +26,4 @@ add_custom_target(quotes-bin ALL DEPENDS ${_BIN_FILES})
install(FILES ${_MAN_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
install(PROGRAMS ${_BIN_FILES} DESTINATION ${CMAKE_INSTALL_BINDIR})
set_dist_list(quotes_DIST CMakeLists.txt gnc-fq-check.in gnc-fq-dump.in gnc-fq-helper.in gnc-fq-update.in Quote_example.pl README)
set_dist_list(quotes_DIST CMakeLists.txt gnc-fq-update.in finance-quote-wrapper.in README)

View File

@ -1,90 +0,0 @@
#!/usr/bin/perl -w
##@file
# @brief
# example script showing how to use the Quote perl module.
# gets prices for some stocks, for some mutual funds
#
# Note that this example uses the meta-level "fetch" command. We do
# NOT used that in Gnucash because it's behavior is unpredictable If
# the given method/exchange doesn't work, it'll fall back to other
# methods, and I've seen no guarantee that all exchanges treat all
# symbols the same. So in Gnucash, we use the backend methods
# directly, i.e. $quoter->fidelity_direct("IBM", "LNUX");, etc. The
# documentation page for each Finance::Quote sub-module describes how
# to call it directly without fallbacks.
#
# @cond PERL
use Finance::Quote;
# Create a quote object.
my $quoter = Finance::Quote->new();
# -----------------------------------
# get quotes for two stocks ...
%quotes = $quoter->fetch("alphavantage","IBM", "SGI");
# print some selected values
print "NYSE by Alphavantage: ", $quotes {"IBM", "name"},
" last price: ", $quotes {"IBM", "last"}, "\n";
print "NYSE by Alphavantage: ", $quotes {"SGI", "name"},
" last price: ", $quotes {"SGI", "last"}, "\n";
# loop over and print all values.
# Notes that values are stored ion a multi-dimensional associative array
foreach $k (sort (keys %quotes)) {
($sym, $attr) = split ($;, $k, 2);
$val = $quotes {$sym, $attr};
# $val = $quotes {$k}; # this also works, if desired ...
print "\t$sym $attr =\t $val\n";
}
print "\n\n";
# -----------------------------------
# get quotes from Fidelity Investments
@funds = ("FGRIX", "FNMIX", "FASGX", "FCONX");
%quotes = $quoter->fetch("fidelity",@funds);
foreach $f (@funds) {
$name = $quotes {$f, "name"};
$nav = $quotes {$f, "nav"};
print "Fidelity Fund $f $name \tNAV = $nav\n";
}
print "\n\n";
# -----------------------------------
@funds = ("FGRXX");
%quotes = $quoter->fetch("fidelity",@funds);
print "Not all funds have a NAV; some have Yields:\n";
foreach $f (@funds) {
$name = $quotes {$f, "name"};
$yield = $quotes {$f, "yield"};
print "\tFidelity $f $name 30-day Yield = $yield percent\n";
}
print "\n\n";
# -----------------------------------
# demo T. Rowe Price -- same as above
@funds = ("PRFDX", "PRIDX");
%quotes = $quoter->fetch("troweprice",@funds);
foreach $f (@funds) {
$nav = $quotes {$f, "nav"};
$dayte = $quotes {$f, "date"};
print "T. Rowe Price $f NAV = $nav as of $dayte\n";
}
print "\n\n";
# -----------------------------------
# demo for ASX. Grab the price of Coles-Myer and Telstra
@funds = ("CML","TLS");
%quotes = $quoter->fetch("australia",@funds);
foreach $f (@funds) {
print "ASX Price of $f is ".$quotes{$f,"last"}." at ".
$quotes{$f,"date"}."\n";
}
print "\n\n";
##@endcond Perl

View File

@ -2,23 +2,11 @@
This directory contains assorted stock quote scripts.
gnc-fq-check.in:
finance-quote-wrapper.in:
Source file for gnc-fq-check which is a perl script that allows
gnucash to determine if Finance::Quote is installed properly. The
responses is a scheme form.
gnc-fq-dump.in:
Source file for gnc-fq-dump which is a perl script that retrieves
a quote from Finance::Quote and dumps the response to the terminal.
Its useful for determining problems with F::Q.
gnc-fq-helper.in:
Source file for gnc-fq-helper which is a perl script that
allows gnucash to communicate with Finance::Quote over pipes from
guile. The requests and responses are scheme forms.
Source file for finance-quote-wrapper which is a perl script that
allows gnucash to communicate with Finance::Quote.
The requests and responses are in json format.
gnc-fq-update.in:

View File

@ -0,0 +1,286 @@
#!@PERL@ -w
######################################################################
### finance-quote-wrapper - interface file between gnucash and
### Finanace::Quote. Only intended to be used
### from gnucash code.
### Based on code taken from gnc-fq-helper.
### Copyright 2001 Rob Browning <rlb@cs.utexas.edu>
### Copyright 2021 Geert Janssens
###
### 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
######################################################################
use strict;
use English;
=head1 NAME
finance-quote-wrapper - internal interface between gnucash and Finance::Quote
=head1 SYNOPSIS
finance-quote-wrapper
=head1 DESCRIPTION
Input: a JSON encoded hash of namespaces and commodities to query prices for.
Currencies all go under the "currency' namespace, other commodities are
grouped according to the quotes source they should be queried from
There should also be a "defaultcurrency" key with the currency to be used as
base currency for currency quotes.
{
"defaultcurrency": "EUR",
"currency": {
"XAG": "",
"HKD": "",
"USD": ""
},
"yahoo_json": {
"CSCO": ""
}
}
Output (on standard output):
The retrieved quotes in JSON format for further processing. These are
the raw values returned by Finance::Quote. The caller is responsible for
parsing and interpreting the results.
If there are program failures, an error message will be printed on standard error.
Exit status
0 - success
non-zero - failure
=cut
sub check_modules {
my @modules = qw(Finance::Quote JSON::Parse Getopt::Std);
my @missing;
foreach my $mod (@modules) {
if (eval "require $mod") {
$mod->import();
}
else {
push (@missing, $mod);
}
}
return unless @missing;
# Test for STDERR being a tty and output a detailed message if it is
# and a short message if it isn't; in the latter case we're probably
# being called from GnuCash and it will emit its own localized error.
if (-t STDERR)
{
print STDERR "\n";
print STDERR "You need to install the following Perl modules:\n";
foreach my $mod (@missing) {
print STDERR " ".$mod."\n";
}
print STDERR "\n";
print STDERR "Please see https://wiki.gnucash.org/wiki/Online_Quotes#Finance::Quote for detailed corrective action.\n";
print "missing-lib\n";
}
else
{
print STDERR "missing_modules ", join(" ", @missing), "\n";
}
exit 1;
}
sub print_version {
my $quoter = Finance::Quote->new();
my @sources = $quoter->sources();
print "$Finance::Quote::VERSION\n";
foreach my $source (@sources) {
print "$source\n";
}
exit 0;
}
sub print_usage {
if (-t STDERR)
{
my $message = << 'END';
Usage:
Check proper installation and version:
finance-quote-wrapper -v
Fetch quotes (input should be passed as JSON via stdin):
finance-quote-wrapper -f
END
print STDERR $message;
}
}
sub sanitize_hash {
my (%quotehash) = @_;
my %newhash;
my @oldkeys = sort keys %quotehash;
foreach my $singlekey (@oldkeys) {
my ($symbol, $newkey) = split /\x1c/, $singlekey, 2;
$newhash{$symbol}{$newkey} = $quotehash{$singlekey};
}
return %newhash;
}
sub parse_currencies {
my($quoter, $currencies, $to_currency) = @_;
return unless $to_currency;
my %results;
foreach my $from_currency (keys %$currencies) {
next unless $from_currency;
my $price = $quoter->currency($from_currency, $to_currency);
my $inv_price = undef;
my $inverted = 0;
# Sometimes price quotes are available in only one direction.
unless (defined($price)) {
$inv_price = $quoter->currency($to_currency, $from_currency);
if (defined($inv_price)) {
$price = $inv_price;
$inverted = 1;
}
}
$results{$from_currency}{"success"} = defined($price);
$results{$from_currency}{"inverted"} = $inverted;
$results{$from_currency}{"symbol"} = $from_currency;
$results{$from_currency}{"currency"} = $to_currency;
$results{$from_currency}{"last"} = $price;
}
return %results;
}
sub parse_commodities {
my($quoter, $quote_method_name, $commodities) = @_;
my %quote_data = $quoter->fetch($quote_method_name, keys %$commodities);
my %normalized_quote_data = sanitize_hash(%quote_data);
return %normalized_quote_data;
}
#---------------------------------------------------------------------------
# Runtime.
# Check for and load non-standard modules
check_modules ();
my %opts;
my $status = getopts('hvf', \%opts);
if (!$status)
{
print_usage();
exit 1;
}
if (exists $opts{'v'})
{
print_version();
}
elsif (exists $opts{'h'})
{
print_usage();
exit 0;
}
elsif (!exists $opts{'f'})
{
print_usage();
exit 1;
}
JSON::Parse->import(qw(valid_json parse_json));
my $json_input = do { local $/; <STDIN> };
if (!valid_json($json_input)) {
if (-t STDERR)
{
print STDERR "Could not parse input as valid JSON.\n";
print STDERR "Received input:\n$json_input\n";
}
else
{
print STDERR "invalid_json\n";
}
exit 1;
}
my $requests = parse_json ($json_input);
my $defaultcurrency = $$requests{'defaultcurrency'};
# This shouldn't be possible if we're called from GnuCash, so only warn in interactive use.
if (!$defaultcurrency) {
$defaultcurrency = "USD";
if (-t STDERR)
{
print STDERR "Warning: no default currency was specified, assuming 'USD'\n";
}
}
# Create a stockquote object.
my $quoter = Finance::Quote->new();
my $prgnam = "gnc-fq-helper";
# Disable default currency conversions.
$quoter->set_currency();
my $key;
my $values;
my %results;
while (($key, $values) = each %$requests)
{
next if ($key eq "defaultcurrency");
if ($key eq "currency") {
my %curr_results = parse_currencies ($quoter, $values, $defaultcurrency, %results);
if (%curr_results) {
%results = (%results, %curr_results);
}
}
else
{
my %comm_results = parse_commodities ($quoter, $key, $values, %results);
if (%comm_results) {
%results = (%results, %comm_results);
}
}
}
if (%results) {
use JSON;
my $jsonval = encode_json \%results;
print "$jsonval\n";
}
STDOUT->flush();
## Local Variables:
## mode: perl
## End:

View File

@ -1,103 +0,0 @@
#!@PERL@ -w
######################################################################
### gnc-fq-check - check for the presence of Finance::Quote
### From gnc-fq-helper.
### Copyright 2001 Rob Browning <rlb@cs.utexas.edu>
###
### 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
######################################################################
use strict;
use English;
use FileHandle;
=head1 NAME
gnc-fq-check - check for the presence of Finance::Quote
From gnc-fq-helper
=head1 SYNOPSIS
gnc-fq-check
=head1 DESCRIPTION
Input: <none>
Output (on standard output, one output form per input line):
A list of quote sources supported by Finance::Quote, or the single
term missing-lib if finance quote could not be executed.
Exit status
0 - success
non-zero - failure
=cut
sub check_modules {
my @modules = qw(Finance::Quote);
my @missing;
foreach my $mod (@modules) {
if (eval "require $mod") {
$mod->import();
}
else {
push (@missing, $mod);
}
}
return unless @missing;
print STDERR "\n";
print STDERR "You need to install the following Perl modules:\n";
foreach my $mod (@missing) {
print STDERR " ".$mod."\n";
}
print STDERR "\n";
print STDERR "Use your system's package manager to install them,\n";
print STDERR "or run 'gnc-fq-update' as root.\n";
print "missing-lib\n";
exit 1;
}
#---------------------------------------------------------------------------
# Runtime.
# Check for and load non-standard modules
check_modules ();
# Create a stockquote object.
my $quoter = Finance::Quote->new();
my $prgnam = "gnc-fq-check";
my @qsources;
my @sources = $quoter->sources();
foreach my $source (@sources) {
push(@qsources, "\"$source\"");
}
printf "(\"%s\" %s)\n", $Finance::Quote::VERSION, join(" ", sort(@qsources));
## Local Variables:
## mode: perl
## End:

View File

@ -1,242 +0,0 @@
#!@PERL@ -w
#
# Copyright (C) 2003, David Hampton <hampton@employees.org>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
use strict;
sub check_modules {
my @modules = qw(Finance::Quote);
my @missing;
foreach my $mod (@modules) {
if (eval "require $mod") {
$mod->import();
}
else {
push (@missing, $mod);
}
}
return unless @missing;
print STDERR "$0 cannot find all the Perl modules needed to run.\n";
print STDERR "You need to install the following Perl modules:\n";
foreach my $mod (@missing) {
print STDERR " ".$mod."\n";
}
print STDERR "Use your system's package manager to install them,\n";
print STDERR "or run 'gnc-fq-update' as root.\n";
exit 1;
}
sub report {
my($itemname, $qh, $verbose) = @_;
my ($symbol, $date, $currency, $last, $nav, $price, $timezone, $keyname);
my($gccanuse, $gcshoulduse) = (1, 1);
# Sanity check returned results
if ((keys %$qh) < 1) {
printf("No results found for stock $itemname.\n");
return;
} else {
my ($stock, $attribute, %seen, $first);
foreach $keyname (sort keys %$qh) {
($stock, $attribute) = split('\034', $keyname);
last if $stock eq $itemname;
$first = $stock if !defined $first;
$seen{$stock} = 1;
}
if ($stock ne $itemname) {
printf "\nNo results found for stock $itemname, but results were returned for\n";
printf "the stock(s) %s. ", join(", ", keys(%seen));
printf "Printing data for the first stock returned.\n\n";
# Print stats for the first stock returned.
$itemname = $first;
}
}
# Parse the quote fields and put warnings where necessary.
if (defined($$qh{$itemname, "symbol"})) {
$symbol = $$qh{$itemname, "symbol"};
} else {
$symbol = "$itemname (deduced)";
$gccanuse = 0;
}
if (defined($$qh{$itemname, "date"})) {
$date = $$qh{$itemname, "date"};
} else {
$date = "** missing **";
$gcshoulduse = 0;
}
if (defined($$qh{$itemname, "currency"})) {
$currency = $$qh{$itemname, "currency"};
} else {
$currency = "** missing **";
$gccanuse = 0;
}
if ((!defined($$qh{$itemname, "last"})) &&
(!defined($$qh{$itemname, "nav" })) &&
(!defined($$qh{$itemname, "price"}))) {
$$qh{$itemname, "last"} = "**missing**";
$$qh{$itemname, "nav"} = "**missing**";
$$qh{$itemname, "price"} = "**missing**";
$gccanuse = 0;
}
$last = defined($$qh{$itemname, "last"})
? $$qh{$itemname, "last"} : "";
$nav = defined($$qh{$itemname, "nav"})
? $$qh{$itemname, "nav"} : "";
$price = defined($$qh{$itemname, "price"})
? $$qh{$itemname, "price"} : "";
$timezone = defined($$qh{$itemname, "timezone"})
? $$qh{$itemname, "timezone"} : "";
# Dump gnucash recognized fields
printf "Finance::Quote fields Gnucash uses:\n";
printf " symbol: %-20s <=== required\n", $symbol;
printf " date: %-20s <=== recommended\n", $date;
printf " currency: %-20s <=== required\n", $currency;
printf " last: %-20s <=\\\n", $last;
printf " nav: %-20s <=== one of these\n", $nav;
printf " price: %-20s <=/\n", $price;
printf " timezone: %-20s <=== optional\n", $timezone;
# Report failure
if ($gccanuse == 0) {
printf "\n** This stock quote cannot be used by GnuCash!\n\n";
} elsif ($gcshoulduse == 0) {
printf "\n** This quote will have today's date, which might be incorrect.\n";
printf " GnuCash will use it, but you might prefer that it doesn't.\n\n";
}
# Dump all fields if requested
if ($verbose) {
printf "\nAll fields returned by Finance::Quote for stock $itemname\n\n";
printf "%-10s %10s %s\n", "stock", "field", "value";
printf "%-10s %10s %s\n", "-----", "-----", "-----";
foreach $keyname (sort keys %$qh) {
my ($stock, $key) = split('\034', $keyname);
printf "%-10s %10s: %s\n", $stock, $key, $$qh{$stock, $key};
}
print "\n";
}
}
sub chk_api_key {
my $exch = $_[0];
my $url = " https://wiki.gnucash.org/wiki/Online_Quotes#Source_Alphavantage.2C_US\n";
if (($exch eq "currency") || ($exch eq "alphavantage")
|| ($exch eq "vanguard")) {
die "ERROR: ALPHAVANTAGE_API_KEY *must* be set for currency quotes and\n" .
"stock quotes with source 'alphavantage' or 'vanguard'; see\n" . $url
unless (defined ($ENV{'ALPHAVANTAGE_API_KEY'}));
}
if (($exch eq "canada") || ($exch eq "nasdaq")
|| ($exch eq "nyse") || ($exch eq "usa")) {
printf("WARNING: Multiple Source '%s' will not be able to use alphavantage " .
"unless ALPHAVANTAGE_API_KEY is set; see\n%s", $exch, $url)
unless (defined ($ENV{'ALPHAVANTAGE_API_KEY'}));
}
}
############## end of functions - start mainline #########################
# Check for and load non-standard modules
check_modules ();
my $q = Finance::Quote->new;
$q->timeout(60);
if ($#ARGV < 1) {
my @sources = sort $q->sources();
printf "\nUsage: $0 [-v] <quote-source> <stock> [<stock> ...]\n\n";
printf "-v: verbose\n";
printf "Available sources are:\n %s\n\n", join(' ', @sources);
exit 0;
}
my $verbose = 0;
if ($ARGV[0] eq "-v") {
$verbose = 1;
shift;
}
my $exchange = shift;
chk_api_key ($exchange);
if ($exchange eq "currency") {
my $from = shift;
while ($#ARGV >= 0) {
my $to = shift;
my $result = $q->currency($from, $to);
# Sometimes quotes are available in only one direction.
# If we didn't get the one we wanted try the reverse quote
unless (defined($result)) {
my $inv_res = $q->currency($to, $from);
if (defined($inv_res)) {
my $tmp = $to;
$to = $from;
$from = $tmp;
$result = $inv_res;
}
}
if (defined($result)) {
printf "1 $from = $result $to\n";
} else {
printf "1 $from = <unknown> $to\n";
}
}
} else {
while ($#ARGV >= 0) {
my $stock = shift;
my %quotes = $q->fetch($exchange, $stock);
report($stock, \%quotes, $verbose);
if ($#ARGV >= 0) {
printf "=====\n\n";
}
}
}
=head1 NAME
gnc-fq-dump - Print out data from the F::Q module
=head1 SYNOPSIS
Currency Exchange Rates
gnc-fq-dump currency USD AUD
gnc-fq-dump [-v] yahoo_json USDEUR=X
Stock Quotes
gnc-fq-dump [-v] alphavantage CSCO JNPR
gnc-fq-dump [-v] alphavantage BAESY.PK
gnc-fq-dump [-v] yahoo_json CBA.AX
gnc-fq-dump [-v] europe 48406.PA 13000.PA
gnc-fq-dump [-v] vwd 632034
gnc-fq-dump [-v] ftportfolios FKYGTX
=head1 DESCRIPTION
This program obtains information from Finance::Quote about any
specified stock, and then dumps it to the screen in annotated form.
This will allow someone to see what is returned, and whether it
provides all the information needed by Gnucash.
=cut

View File

@ -1,435 +0,0 @@
#!@PERL@ -w
######################################################################
### gnc-fq-helper - present a scheme interface to Finance::Quote
### Copyright 2001 Rob Browning <rlb@cs.utexas.edu>
###
### 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
######################################################################
use strict;
use English;
=head1 NAME
gnc-fq-helper - allows gnucash to communicate with Finance::Quote
over pipes from guile. The requests and responses
are scheme forms.
=head1 SYNOPSIS
gnc-fq-helper
=head1 DESCRIPTION
Input: (on standard input - one entry per line and one line per
entry, and double quotes must only be delimiters, not string
content -- remember, we don't have a real scheme parser on the perl
side :>).
(<method-name> symbol symbol symbol ...)
where <method-name> indicates the desired Finance::Quote method.
One can list the many methods by running gnc-fq-check.
For currency quotes, the symbols alternate between the 'from'
and 'to' currencies.
For example:
(alphavantage "IBM" "LNUX")
(fidelity_direct "FBIOX" "FSELX")
(currency "USD" "AUD")
Output (on standard output, one output form per input line):
Schemified version of gnc-fq's output, basically an alist of
alists, as in the example below. Right now, only the fields that
this script knows about (and knows how to convert to scheme) are
returned, so the conversion function will have to be updated
whenever Finance::Quote changes. Currently you'll get symbol,
gnc:time-no-zone, and currency, and either last, nav, or price.
Fields with gnc: prefixes are non-Finance::Quote fields.
gnc:time-no-zone is returned as a string of the form "YYYY-MM-DD
HH:MM:SS", basically the unmolested (and underspecified) output of
the quote source. It's up to you to know what it's proper timezone
really is. i.e. if you know the time was in America/Chicago, you'll
need to convert it to that.
For example:
$ echo '(alphavantage "CSCO" "JDSU" "^IXIC")' | ./gnc-fq-helper
(("CSCO" (symbol . "CSCO")
(gnc:time-no-zone . "2001-03-13 19:27:00")
(last . 20.375)
(currency . "USD"))
("JDSU" (symbol . "JDSU")
(gnc:time-no-zone . "2001-03-13 19:27:00")
(last . 23.5625)
(currency . "USD"))
("^IXIC" (symbol . ^IXIC)
(gnc:time-no-zone . 2002-12-04 17:16:00)
(last . 1430.35)
(currency . failed-conversion)))
On error, the overall result may be #f, or on individual errors, the
list sub-item for a given symbol may be #f, like this:
$ echo '(alphavantage "CSCO" "JDSU")' | ./gnc-fq-helper
(#f
("JDSU" (symbol . "JDSU")
(gnc:time-no-zone . "2001-03-13 19:27:00")
(last . 23.5625)
(currency . "USD")))
further, errors may be stored with each quote as indicated in
Finance::Quote, and whenever the conversion to scheme data fails,
the field will have the value 'failed-conversion, and accordingly
this symbol will never be a legitimate conversion.
Exit status
0 - success
non-zero - failure
=cut
# The methods we know about. For now we assume they all have the same
# signature so this works OK.
sub check_modules {
# Date::Manip provides ParseDate, ParseDateString, and UnixTime.
my @modules = qw(FileHandle Finance::Quote Date::Manip);
my @missing;
foreach my $mod (@modules) {
if (eval "require $mod") {
$mod->import();
}
else {
push (@missing, $mod);
}
}
return unless @missing;
print STDERR "\n";
print STDERR "You need to install the following Perl modules:\n";
foreach my $mod (@missing) {
print STDERR " ".$mod."\n";
}
print STDERR "\n";
print STDERR "Use your system's package manager to install them,\n";
print STDERR "or run 'gnc-fq-update' as root.\n";
print "missing-lib";
exit 1;
}
# Check for and load non-standard modules
check_modules ();
# Set a base date with the current time in the current TZ:
my $base_date = new Date::Manip::Date;
$base_date->parse("now");
sub schemify_string {
my($str) = @_;
if(!$str) { return "failed-conversion"; }
# FIXME: Is this safe? Can we just double all backslashes and backslash
# escape all double quotes and get the right answer?
# double all backslashes.
my $bs = "\\";
$str =~ s/$bs$bs/$bs$bs/gmo;
# escape all double quotes.
# Have to do this because the perl-mode parser freaks out otherwise.
my $dq = '"';
$str =~ s/$dq/$bs$dq/gmo;
return '"' . $str . '"';
}
sub schemify_boolean {
my($bool) = @_;
if($bool) {
return "#t";
} else {
return "#f";
}
}
sub schemify_num {
my($numstr) = @_;
# This is for normal numbers, not the funny ones like "2.346B".
# For now we don't need to do anything.
if(!$numstr) { return "failed-conversion"; }
if($numstr =~ /^\s*(\d+(\.\d+)?([eE][+-]?\d+)?)$/o) {
return "#e" . $1;
} else {
return "failed-conversion";
}
}
# sub schemify_range {
# #convert range in form ``num1 - num2'' to ``(num1 num2)''.
# }
sub get_quote_time {
# return the date.
my ($item, $quotehash) = @_;
my $datestr = $$quotehash{$item, 'date'};
my $timestr = $$quotehash{$item, 'time'};
my $format = "%Y-%m-%d %H:%M:%S";
my $result;
if ($datestr) {
my $parsestr = $datestr . " " . ($timestr ? $timestr : "12:00:00");
my $date = $base_date->new();
my $err = $date->parse($parsestr);
if ($err) {
print $date->err(), " $parsestr\n";
$result = $base_date->printf($format);
}
else {
$result = $date->printf($format);
}
} else {
$result = $base_date->printf($format);
}
return("\"$result\"");
}
sub schemify_quote {
my($itemname, $quotehash, $indentlevel) = @_;
my $scmname = schemify_string($itemname);
my $quotedata = "";
my $field;
my $data;
if (!$$quotehash{$itemname, "success"}) {
return schemify_boolean(0);
}
$field = 'symbol';
if (($$quotehash{$itemname, $field})) {
$data = schemify_string($$quotehash{$itemname, $field});
} else {
# VWD and a few others don't set the symbol field
$data = schemify_string($itemname);
}
$quotedata .= "($field . $data)";
$field = 'gnc:time-no-zone';
$data = get_quote_time($itemname, $quotehash);
$quotedata .= " ($field . $data)" if $data;
$field = 'last';
if (!($$quotehash{$itemname, $field})) {
$field = 'nav';
}
if (!($$quotehash{$itemname, $field})) {
$field = 'price';
}
$data = schemify_num($$quotehash{$itemname, $field});
$quotedata .= " ($field . $data)";
$field = 'currency';
$data = schemify_string($$quotehash{$itemname, $field});
$quotedata .= " ($field . $data)";
return "($scmname $quotedata)";
}
sub schemify_quotes {
my($symbols, $quotehash) = @_;
my $resultstr = "";
my $sym;
my $separator = "";
# we have to pass in @$items because Finance::Quote just uses the
# mangled "$name$field string as the key, so there's no way (I know
# of) to find out which stocks are in a given quotehash, just given
# the quotehash.
foreach $sym (@$symbols) {
$resultstr .= $separator . schemify_quote($sym, $quotehash, 2);
if(!$separator) { $separator = "\n "; }
}
return "($resultstr)\n";
}
sub parse_input_line {
# FIXME: we need to rewrite parsing to handle commands modularly.
# Right now all we do is hard-code "fetch".
my($input) = @_;
# Have to do this because the perl-mode parser freaks out otherwise.
my $dq = '"';
my @symbols;
# Make sure we have an opening ( preceded only by whitespace.
# and followed by a one word method name composed of [a-z_]+.
# Also allow the '.' and '^' characters for stock indices.
# Kill off the whitespace if we do and grab the command.
if($input !~ s/^\s*\(\s*([\.\^a-z_]+)\s+//o) { return 0; }
my $quote_method_name = $1;
# Make sure we have an ending ) followed only by whitespace
# and kill it off if we do...
if($input !~ s/\s*\)\s*$//o) { return 0; }
while($input) {
# Items should look like "RHAT"
# Grab RHAT and delete "RHAT"\s*
if($input !~ s/^$dq([^$dq]+)$dq\s*//o) { return 0; }
my $symbol = $1;
push @symbols, $symbol;
}
my @result = ($quote_method_name, \@symbols);
return \@result;
}
#---------------------------------------------------------------------------
# Runtime.
# Create a stockquote object.
my $quoter = Finance::Quote->new();
my $prgnam = "gnc-fq-helper";
# Disable default currency conversions.
$quoter->set_currency();
while(<>) {
my $result = parse_input_line($_);
if(!$result) {
print STDERR "$prgnam: bad input line ($_)\n";
exit 1;
}
my($quote_method_name, $symbols) = @$result;
my %quote_data;
if($quote_method_name =~ m/^currency$/) {
my ($from_currency, $to_currency) = @$symbols;
last unless $from_currency;
last unless $to_currency;
my $price = $quoter->currency($from_currency, $to_currency);
my $inv_price = undef;
# Sometimes price quotes are available in only one direction.
unless (defined($price)) {
$inv_price = $quoter->currency($to_currency, $from_currency);
if (defined($inv_price)) {
my $tmp = $to_currency;
$to_currency = $from_currency;
$from_currency = $tmp;
$price = $inv_price;
}
}
$quote_data{$from_currency, "success"} = defined($price);
$quote_data{$from_currency, "symbol"} = $from_currency;
$quote_data{$from_currency, "currency"} = $to_currency;
$quote_data{$from_currency, "last"} = $price;
my @new_symbols = ($from_currency);
$symbols = \@new_symbols;
} else {
%quote_data = $quoter->fetch($quote_method_name, @$symbols);
}
if (%quote_data) {
print schemify_quotes($symbols, \%quote_data);
} else {
print "#f\n";
}
STDOUT->flush();
}
exit 0;
__END__
# Keep this around in case we need to go back to complex per-symbol args.
#
# while($input) {
# # Items should look like "RHAT" "EST")
# # Grab RHAT and delete ("RHAT"\s*
# if($input !~ s/^\(\s*$dq([^$dq]+)$dq\s*//o) { return 0; }
# my $symbol = $1;
# my $timezone;
# # Now grab EST or #f and delete \s*"EST") or #f)
# if($input =~ s/^\s*$dq([^$dq]+)$dq\)\s*//o) {
# $timezone = $1;
# } else {
# if($input =~ s/^\s*(\#f)\)\s*//o) {
# $timezone = 0;
# } else {
# return 0;
# }
# }
# sub get_quote_utc {
# # return the date in utc epoch seconds, using $timezone if specified.
# my ($item, $timezone, $quotehash) = @_;
# if(!defined($timezone)) { return "failed-conversion"; }
# my $datestr = $$quotehash{$item, 'date'};
# my $timestr = $$quotehash{$item, 'time'};
# if(!$datestr) {
# return "failed-conversion";
# }
# my $parsestr = $datestr;
# if($timestr) {
# $parsestr .= " $timestr";
# }
# if($timezone) {
# # Perform a conversion.
# $parsestr = Date_ConvTZ(ParseDate($parsestr), $timezone, 'UTC');
# }
# my $result = UnixDate($parsestr, "%s");
# if($result !~ /^(\+|-)?\d+$/) {
# $result = "failed-conversion";
# }
# return $result;
# }
## Local Variables:
## mode: perl
## End:

View File

@ -37,7 +37,6 @@ if ($( != 0) {
}
CPAN::Shell->install('Test2'); #Required by an F::Q dependency but cpan doesn't notice.
CPAN::Shell->install('Date::Manip'); #Required by gnc-fq-helper
CPAN::Shell->install('Finance::Quote');
## Local Variables:

View File

@ -78,7 +78,7 @@ gnucash/gnome/dialog-lot-viewer.c
gnucash/gnome/dialog-new-user.c
gnucash/gnome/dialog-order.c
gnucash/gnome/dialog-payment.c
gnucash/gnome/dialog-price-edit-db.c
gnucash/gnome/dialog-price-edit-db.cpp
gnucash/gnome/dialog-price-editor.c
gnucash/gnome/dialog-print-check.c
gnucash/gnome/dialog-progress.c
@ -141,7 +141,7 @@ gnucash/gnome-utils/dialog-query-view.c
gnucash/gnome-utils/dialog-reset-warnings.c
gnucash/gnome-utils/dialog-tax-table.c
gnucash/gnome-utils/dialog-totd.c
gnucash/gnome-utils/dialog-transfer.c
gnucash/gnome-utils/dialog-transfer.cpp
gnucash/gnome-utils/dialog-userpass.c
gnucash/gnome-utils/dialog-utils.c
gnucash/gnome-utils/gnc-account-sel.c
@ -374,7 +374,6 @@ gnucash/import-export/qif-imp/qif-parse.scm
gnucash/import-export/qif-imp/qif-to-gnc.scm
gnucash/import-export/qif-imp/qif-utils.scm
gnucash/import-export/qif-imp/string.scm
gnucash/price-quotes.scm
gnucash/python/gncmod-python.c
gnucash/python/init.py
gnucash/python/pycons/console.py
@ -516,6 +515,7 @@ libgnucash/app-utils/gnc-exp-parser.c
libgnucash/app-utils/gnc-gsettings.cpp
libgnucash/app-utils/gnc-help-utils.c
libgnucash/app-utils/gnc-prefs-utils.c
libgnucash/app-utils/gnc-quotes.cpp
libgnucash/app-utils/gnc-state.c
libgnucash/app-utils/gnc-sx-instance-model.c
libgnucash/app-utils/gnc-ui-balances.c