diff --git a/ChangeLog b/ChangeLog index 1bf5d14fe0..948e4bd36c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,241 @@ +2001-02-24 Rob Browning + + * configure.in (AC_ARG_ENABLE): add --enable-error-on-warning. + Enables -Werror and whatever else is needed for super-strict + checks. I'm compiling with this now, and I urge everyone else to + as well. We do have a few aggravating things we still ignore, + like unused variables, but as of today, the whole codebase + compiles successfully with this turned on. + (AC_CHECK_PROG): Add a check for etags. + (AC_ARG_WITH): --with-perl-includes - don't die if CORE not found. + I install modules in a local directory and use --perl-includes to + enable gnucash to find Finance::Quote at runtime, but there won't + be a CORE file. + (AC_CHECK_LIB): popt - note required Debian -dev package. + + * macros/autogen.sh (conf_flags): omit --enable-compile-warnings. + This was adding flags that overrode our configure.in settings; + specifically, this kept --enable-error-on-warning from working. + + * Makefile.am (noinst_DATA): add optional TAGS target. + (DISTCLEANFILES): add tags cleanup bits. + (TAGS): Add fancy TAGS handling. TAGS file is only built if you + have etags available, and now it takes into account + additions/deletions of files in addition to modifications. + + * src/engine/gnc-engine-util.c + (g_hash_table_key_value_pairs): new - returns a GSList of all the + key value pairs in a given hash table so you can manipulate them. + (g_hash_table_kv_pair_free_gfunc): new - g_slist_foreach helper + for deleting key value hash pairs when you're finished with the + results from g_hash_table_key_value_pairs. You'll still need to + call g_slist_free as well to delete the spine of the list. + + * src/engine/NetIO.c: comment out various bits of the code that's + incompatible with the recent gnc-book changes. Linas said he'd + fix it later. + + * src/scm/price-quotes.scm: new file - not used yet. + + * src/scm/price-quotes.scm: new file - launch a sub-process you + can talk to bidirectionally from scheme read/write. + + * src/scm/command-line.scm: (disabled) support for getting prices. + + * src/scm/command-line.scm + (gnc:group-map-accounts): implementation simplified dramatically, + probably should be dropped entirely, in favor of just calling (map + thunk (gnc:group-get-subaccounts group)) -- which is what's in the + replacement code. + + * src/quotes/price-quote-helper.in: new file. + + * src/Backend.h: Delete ERR_FILEIO_MISC (use ERR_BACKEND_MISC). + Replace ERR_FILEIO_ALLOC with ERR_BACKEND_ALLOC. + + * src/engine/gnc-book.c: add support for GNCPriceDB element. Move + still relevant FileIO bits here. Add support for gnc-book + clean/dirty checking instead of just Group checking. + + * src/engine/gnc-book.h: fixes for gnc-book marking, private + header, and addition of pricedb. + + * src/engine/gnc-book-p.h: new file - private book functions. + + * src/engine/Ledger-xml-parser-v1.c + (ledger_data_after_child_handler): new function to handle grabbing + the pricedb when we hit it. + + * src/FileDialog.c: Various fixes to replace uses of the top level + AccountGroup with the parent GNCBook. Also add support for + reading/writing the GNCPriceDB. This file still needs a lot of + work. Much of it should become a proper file IO Backend. + + * src/guile/gnc.gwp: Add . Add gnc:get-current-book. + Add gnc:book-get-group. Add gnc:account-get-price-src. + + * src/gnome/druid-commodity.c (finish_helper): add support for + GNCPriceDB. + + * src/engine/sixtp-writers.h: add support for GNCPriceDB. + + * src/engine/sixtp-to-dom-parser.c + (sixtp_dom_parser_new): change signature to add fail and result + cleanup functions. These can be NULL, in which case they're + ignored. Also modify overall scheme to clean internal garbage up + properly in case of failure. + + * src/engine/sixtp-parsers.h + (sixtp_dom_parser_new): change signature. + + * src/engine/sixtp-dom-parsers.c (dom_tree_to_text): minor fixes. + + * src/engine/sixtp-dom-generators.c + (text_to_dom_tree): new function. + + * src/engine/sixtp-dom-generators.h + (text_to_dom_tree): new prototype. + + * src/engine/io-gncxml-w.c: add support for pricedb and using + GNCBook at top level rather than Group. + + * src/engine/io-gncxml.h: support migration from top level Group + to top level GNCBook. + + * src/engine/io-gncbin-r.c: add support for detecting legacy + prices and migrating them to the new pricedb. Replace top level + uses of a Group with a GNCBook. + + * src/engine/io-gncbin.h: support pricedb and gnc-book changes. + + * src/engine/io-gncxml-p.h: new file - private header. + + * src/engine/io-gncxml-r.c: move bits to private header. Make + changes to support move from top level to Group to top level + GNCBook. + + * src/gnome/druid-qif-import.c: -Werror fixes. + + * src/engine/gnc-pricedb.h: new file. + + * src/engine/gnc-pricedb-p.h: new file. + + * src/engine/gnc-pricedb.c: new file. + + * src/engine/gnc-pricedb-xml-v1.c: new file. + + * src/engine/gnc-engine-util.h: accomodate new functions. + + * src/engine/gnc-commodity-xml-v2.c + (gnc_commodity_sixtp_parser_create): fix sixtp_dom_parser_new call. + + * src/engine/gnc-account-xml-v2.c + (gnc_account_end_handler): -Werror fixes. + (gnc_account_sixtp_parser_create): fix sixtp_dom_parser_new call. + + * src/engine/date.h (timespec_equal): add back. + + * src/engine/date.c (timespec_equal): add back. + + * src/engine/Commodity-xml-parser-v1.c + (xml_add_commodity_ref): cleanup. + + * src/FileIO.h: deleted. + + * src/FileIOP.h: deleted. + + * src/FileIO.c: deleted. + + * src/test/test-dom-parser1.c: fixes for new sixtp_dom_parser_new. + + * src/test/test-xml-account.c: fixes for new sixtp_dom_parser_new. + + * src/test/test-xml-commodity.c: fixes for new sixtp_dom_parser_new. + + * src/guile/option-util.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/guile/option-util.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/guile/guile-util.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/guile/guile-util.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/guile/global-options.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/guile/global-options.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/window-register.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/window-main.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/gnc-currency-edit.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/gnc-currency-edit.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/gnc-commodity-edit.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/gnc-commodity-edit.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/druid-commodity.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/dialog-account.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/gnome/dialog-options.c: remove consts from gnc_commoditities + (after consultation with Bill). + + * src/gnome/dialog-commodity.h: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnome/dialog-commodity.c: remove consts from + gnc_commoditities (after consultation with Bill). + + * src/gnc-ui-util.h: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/gnc-ui-util.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/engine/Transaction.h: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/engine/Transaction.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/engine/Scrub.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/AccountP.h: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/Account.h: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/Account.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/SplitLedger.c: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/EuroUtils.h: remove consts from gnc_commoditities (after + consultation with Bill). + + * src/EuroUtils.c: remove consts from gnc_commoditities (after + consultation with Bill). + 2001-02-23 Bill Gribble * src/scm/qif-import/qif-dialog-utils.scm: Be more flexible diff --git a/Makefile.am b/Makefile.am index bf1f510343..e01d55d12d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS = macros debian doc-tools doc intl lib src po rpm accounts docdir = ${GNC_DOC_INSTALL_DIR} -noinst_DATA = make-gnucash-patch +noinst_DATA = make-gnucash-patch @GNC_TAGS_FILE@ doc_DATA = \ AUTHORS \ @@ -43,7 +43,8 @@ make-gnucash-patch: make-gnucash-patch.in chmod +x $@.tmp mv $@.tmp $@ -DISTCLEANFILES += cscope.files cscope.out etags.files make-gnucash-patch +DISTCLEANFILES += \ + TAGS.stamp cscope.files cscope.out etags.files make-gnucash-patch cscope.files: find . -name '*.[ch]' > cscope.files @@ -51,8 +52,24 @@ cscope.files: cscope.out: cscope.files cscope -b -TAGS: etags.files - etags `cat etags.files` +if GNC_TAGS_FILE -etags.files: - find . -name '*.[ch]' -o -name '*.scm' > etags.files +TAGS.stamp: etags.files $(shell cat etags.files) + etags `cat etags.files` + touch TAGS.stamp + +TAGS: + find . -name '*.[ch]' -o -name '*.scm' | sort > etags.files.tmp + @if cmp --quiet etags.files etags.files.tmp; \ + then \ + echo "TAGS file list hasn't changed."; \ + rm -f etags.files.tmp; \ + else \ + echo "TAGS file list has changed."; \ + mv etags.files.tmp etags.files; \ + fi + ${MAKE} TAGS.stamp + +.PHONY: TAGS + +endif diff --git a/configure.in b/configure.in index e3364eb4f4..5ccdfb9f62 100644 --- a/configure.in +++ b/configure.in @@ -126,9 +126,18 @@ AC_SUBST(GNC_ACCOUNTS_DIR) # We should always see these errors... CFLAGS="${CFLAGS} -Wall" +AC_ARG_ENABLE(error-on-warning, + [ --enable-error-on-warning treat compile warnings as errors], + [case "${enableval}" in + yes) CFLAGS="${CFLAGS} -Werror"; enable_compile_warnings=no ;; + no) ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-error-on-warning) ;; + esac]) + # This has to come after AC_PROG_CC if test ${GCC}x = yesx then + CFLAGS="${CFLAGS} -Wno-unused" CFLAGS="${CFLAGS} -Werror-implicit-function-declaration" fi @@ -173,11 +182,15 @@ AC_ARG_WITH( help-prefix, AC_SUBST(GNC_HELPDIR) +### -------------------------------------------------------------------------- +### Check for etags + +AC_CHECK_PROG(GNC_TAGS_FILE, etags, TAGS) +AM_CONDITIONAL(GNC_TAGS_FILE, test x${GNC_TAGS_FILE} = xTAGS) ### -------------------------------------------------------------------------- ### Check for glade -# Check for glade AC_ARG_WITH(glade, [ --with-glade=FILE which glade executable to use ], GLADE="${with_glade}") @@ -233,10 +246,6 @@ PERLINCL=`$PERL -MConfig -e 'print $Config{"archlibexp"}'` AC_ARG_WITH( perl-includes, [ --with-perl-includes=DIR specify where to look for perl includes], PERLINCL="$with_perl_includes" ) - -if test ! -d ${PERLINCL}/CORE; then - AC_MSG_ERROR([Missing directory ${PERLINCL}/CORE in the perl include directory]) -fi AC_SUBST(PERLINCL) @@ -390,11 +399,12 @@ AC_SUBST(GTK_XIM_FLAGS) ### -------------------------------------------------------------------------- ### popt -AC_CHECK_LIB(popt, poptStrippedArgv,, AC_MSG_ERROR([ +AC_CHECK_LIB(popt, poptStrippedArgv,, [AC_MSG_ERROR([ popt 1.5 or newer is required to build gnucash. You can download - the latest version from ftp://people.redhat.com/sopwith/popt/ -])) + the latest version from ftp://people.redhat.com/sopwith/popt/, or if + you're running Debian, install the libpopt-dev pacakge. +])]) ### -------------------------------------------------------------------------- ## For now, we just presume you're using the GNOME version. The other diff --git a/macros/autogen.sh b/macros/autogen.sh index 74b8047d1e..845d5555b5 100644 --- a/macros/autogen.sh +++ b/macros/autogen.sh @@ -168,7 +168,7 @@ do fi done -conf_flags="--enable-maintainer-mode --enable-compile-warnings" #--enable-iso-c +conf_flags="--enable-maintainer-mode" # --enable-compile-warnings --enable-iso-c if test x$NOCONFIGURE = x; then echo Running $srcdir/configure $conf_flags "$@" ... diff --git a/src/EuroUtils.c b/src/EuroUtils.c index e2ff019f17..6955b2cc72 100644 --- a/src/EuroUtils.c +++ b/src/EuroUtils.c @@ -67,8 +67,7 @@ static gnc_euro_rate_struct _gnc_euro_rate_[] = }; static int -_gnc_euro_rate_compare_(const void * key, - const void * value) +_gnc_euro_rate_compare_(const void * key, const void * value) { const gnc_commodity * curr = key; const gnc_euro_rate_struct * euro = value; @@ -83,7 +82,8 @@ _gnc_euro_rate_compare_(const void * key, /* ------------------------------------------------------ */ gboolean -gnc_is_euro_currency(const gnc_commodity * currency) { +gnc_is_euro_currency(gnc_commodity * currency) +{ gnc_euro_rate_struct *result; const char *namespace; @@ -113,7 +113,7 @@ gnc_is_euro_currency(const gnc_commodity * currency) { /* ------------------------------------------------------ */ gnc_numeric -gnc_convert_to_euro(const gnc_commodity * currency, gnc_numeric value) { +gnc_convert_to_euro(gnc_commodity * currency, gnc_numeric value) { gnc_euro_rate_struct *result; const char *namespace; @@ -150,7 +150,7 @@ gnc_convert_to_euro(const gnc_commodity * currency, gnc_numeric value) { /* ------------------------------------------------------ */ gnc_numeric -gnc_convert_from_euro(const gnc_commodity * currency, gnc_numeric value) { +gnc_convert_from_euro(gnc_commodity * currency, gnc_numeric value) { gnc_euro_rate_struct * result; const char *namespace; @@ -186,7 +186,7 @@ gnc_convert_from_euro(const gnc_commodity * currency, gnc_numeric value) { /* ------------------------------------------------------ */ -const gnc_commodity * +gnc_commodity * gnc_get_euro (void) { return gnc_commodity_table_lookup (gnc_engine_commodities (), diff --git a/src/EuroUtils.h b/src/EuroUtils.h index 954515922b..a86dd0786c 100644 --- a/src/EuroUtils.h +++ b/src/EuroUtils.h @@ -27,13 +27,13 @@ #include "gnc-commodity.h" #include "gnc-numeric.h" -gboolean gnc_is_euro_currency (const gnc_commodity * currency); -gnc_numeric gnc_convert_to_euro (const gnc_commodity * currency, +gboolean gnc_is_euro_currency (gnc_commodity * currency); +gnc_numeric gnc_convert_to_euro (gnc_commodity * currency, gnc_numeric value); -gnc_numeric gnc_convert_from_euro (const gnc_commodity * currency, +gnc_numeric gnc_convert_from_euro (gnc_commodity * currency, gnc_numeric value); -const gnc_commodity * gnc_get_euro (void); +gnc_commodity * gnc_get_euro (void); #endif /* __EURO_UTILS_H__ */ diff --git a/src/FileDialog.c b/src/FileDialog.c index 52f0559f0a..70a21a0eb4 100644 --- a/src/FileDialog.c +++ b/src/FileDialog.c @@ -28,7 +28,6 @@ #include "Backend.h" #include "FileBox.h" #include "FileDialog.h" -#include "FileIO.h" #include "Group.h" #include "file-history.h" #include "gnc-component-manager.h" @@ -37,6 +36,8 @@ #include "gnc-ui.h" #include "messages.h" +/* FIXME: this is wrong. This file should not need this include. */ +#include "gnc-book-p.h" /** GLOBALS *********************************************************/ /* This static indicates the debugging module that this .o belongs to. */ @@ -113,11 +114,6 @@ show_book_error (GNCBackendError io_error, const char *newfile) if (gnc_verify_dialog (fmt, TRUE)) { uh_oh = FALSE; } break; - case ERR_FILEIO_MISC: - fmt = _("There was an error during file I/O."); - gnc_error_dialog (fmt); - break; - case ERR_SQL_BAD_LOCATION: fmt = _("Can't parse the database URL\n %s\n"); buf = g_strdup_printf (fmt, newfile); @@ -192,7 +188,6 @@ void gncFileNew (void) { GNCBook *book; - AccountGroup *group; /* If user attempts to start a new session before saving results of * the last one, prompt them to clean up their act. */ @@ -200,7 +195,6 @@ gncFileNew (void) return; book = gncGetCurrentBook (); - group = gnc_book_get_group (book); /* close any ongoing file sessions, and free the accounts. * disable events so we don't get spammed by redraws. */ @@ -221,14 +215,8 @@ gncFileNew (void) gboolean gncFileQuerySave (void) { - GNCBook *book; - AccountGroup *group; - gncUIWidget app; - - book = gncGetCurrentBook (); - group = gnc_book_get_group (book); - - app = gnc_get_ui_data (); + GNCBook *book = gncGetCurrentBook(); + gncUIWidget app = gnc_get_ui_data(); /* If user wants to mess around before finishing business with * the old file, give em a chance to figure out what's up. @@ -236,8 +224,7 @@ gncFileQuerySave (void) * up the file-selection dialog, we don't blow em out of the water; * instead, give them another chance to say "no" to the verify box. */ - while (xaccGroupNotSaved (group)) - { + while (gnc_book_not_saved(book)) { GNCVerifyResult result; const char *message = _("Changes have been made since the last " "Save. Save the data to file?"); @@ -386,7 +373,6 @@ gncPostFileOpen (const char * filename) gh_eval_str("gnc:*file-opened-hook*"), gh_str02scm(newfile)); } - g_free (newfile); gnc_engine_resume_events (); @@ -469,7 +455,7 @@ gncFileSave (void) gnc_history_add_file (newfile); - xaccGroupMarkSaved (gnc_book_get_group (book)); + gnc_book_mark_saved(book); LEAVE (" "); } @@ -479,6 +465,7 @@ void gncFileSaveAs (void) { AccountGroup *group; + GNCPriceDB *pdb; GNCBook *new_book; GNCBook *book; const char *filename; @@ -510,7 +497,8 @@ gncFileSaveAs (void) } /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */ - group = gnc_book_get_group (book); + group = gnc_book_get_group(book); + pdb = gnc_book_get_pricedb(book); new_book = gnc_book_new (); gnc_book_begin (new_book, newfile, FALSE, FALSE); @@ -552,7 +540,8 @@ gncFileSaveAs (void) /* if we got to here, then we've successfully gotten a new session */ /* close up the old file session (if any) */ - gnc_book_set_group (book, NULL); + gnc_book_set_group(book, NULL); + gnc_book_set_pricedb(book, NULL); gnc_book_destroy (book); current_book = new_book; @@ -581,7 +570,8 @@ gncFileSaveAs (void) } /* OK, save the data to the file ... */ - gnc_book_set_group (new_book, group); + gnc_book_set_group(new_book, group); + gnc_book_set_pricedb(new_book, pdb); gncFileSave (); g_free (newfile); diff --git a/src/SplitLedger.c b/src/SplitLedger.c index 296d8c57cd..ef2c33a244 100644 --- a/src/SplitLedger.c +++ b/src/SplitLedger.c @@ -426,7 +426,7 @@ gnc_find_split_in_trans_by_memo (Transaction *trans, const char *memo, if (safe_strcmp(memo, xaccSplitGetMemo(split)) == 0) { Account *account = xaccSplitGetAccount(split); - const gnc_commodity *currency, *security; + gnc_commodity *currency, *security; if (account == NULL) return split; @@ -611,7 +611,7 @@ gnc_split_get_value_denom (Split *split) denom = xaccAccountGetCurrencySCU (xaccSplitGetAccount (split)); if (denom == 0) { - const gnc_commodity *commodity = gnc_locale_default_currency (); + gnc_commodity *commodity = gnc_locale_default_currency (); denom = gnc_commodity_get_fraction (commodity); if (denom == 0) denom = 100; @@ -628,7 +628,7 @@ gnc_split_get_quantity_denom (Split *split) denom = xaccAccountGetSecuritySCU (xaccSplitGetAccount (split)); if (denom == 0) { - const gnc_commodity *commodity = gnc_locale_default_currency (); + gnc_commodity *commodity = gnc_locale_default_currency (); denom = gnc_commodity_get_fraction (commodity); if (denom == 0) denom = 100; @@ -660,7 +660,7 @@ sr_set_cell_fractions (SplitRegister *reg, Split *split) } { - const gnc_commodity *commodity; + gnc_commodity *commodity; int fraction; xaccSetPriceCellFraction (reg->sharesCell, 10000); @@ -3245,8 +3245,8 @@ xaccSRSaveChangedCells (SplitRegister *reg, Transaction *trans, Split *split) if ((new_acc != NULL) && (old_acc != new_acc)) { - const gnc_commodity * currency = NULL; - const gnc_commodity * security = NULL; + gnc_commodity * currency = NULL; + gnc_commodity * security = NULL; currency = xaccAccountGetCurrency(new_acc); currency = xaccTransIsCommonExclSCurrency(trans, currency, split); @@ -3319,8 +3319,8 @@ xaccSRSaveChangedCells (SplitRegister *reg, Transaction *trans, Split *split) if ((new_acc != NULL) && (old_acc != new_acc)) { - const gnc_commodity * currency = NULL; - const gnc_commodity * security = NULL; + gnc_commodity * currency = NULL; + gnc_commodity * security = NULL; currency = xaccAccountGetCurrency(new_acc); currency = xaccTransIsCommonExclSCurrency(trans, @@ -4831,8 +4831,8 @@ xaccSRLoadRegister (SplitRegister *reg, GList * slist, static void LoadXferCell (ComboCell * cell, AccountGroup * grp, - const gnc_commodity * base_currency, - const gnc_commodity * base_security) + gnc_commodity * base_currency, + gnc_commodity * base_security) { gboolean load_everything; GList *list; @@ -4853,8 +4853,8 @@ LoadXferCell (ComboCell * cell, for (node = list; node; node = node->next) { Account *account = node->data; - const gnc_commodity * curr; - const gnc_commodity * secu; + gnc_commodity * curr; + gnc_commodity * secu; char *name; curr = xaccAccountGetCurrency (account); @@ -4891,7 +4891,7 @@ xaccLoadXferCell (ComboCell *cell, AccountGroup *grp, Account *base_account) { - const gnc_commodity * curr, * secu; + gnc_commodity * curr, * secu; curr = xaccAccountGetCurrency (base_account); secu = xaccAccountGetSecurity (base_account); diff --git a/src/engine/Account-xml-parser-v1.c b/src/engine/Account-xml-parser-v1.c index 95c2911b0a..bad8e7830e 100644 --- a/src/engine/Account-xml-parser-v1.c +++ b/src/engine/Account-xml-parser-v1.c @@ -132,14 +132,14 @@ account_restore_end_handler(gpointer data_for_children, static gboolean account_restore_after_child_handler(gpointer data_for_children, - GSList* data_from_children, - GSList* sibling_data, - gpointer parent_data, - gpointer global_data, - gpointer *result, - const gchar *tag, - const gchar *child_tag, - sixtp_child_result *child_result) + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag, + const gchar *child_tag, + sixtp_child_result *child_result) { Account *a = (Account *) data_for_children; g_return_val_if_fail(a, FALSE); @@ -172,12 +172,12 @@ account_restore_after_child_handler(gpointer data_for_children, static void account_restore_fail_handler(gpointer data_for_children, - GSList* data_from_children, - GSList* sibling_data, - gpointer parent_data, - gpointer global_data, - gpointer *result, - const gchar *tag) + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag) { Account *acc = (Account *) *result; if(acc) xaccFreeAccount(acc); diff --git a/src/engine/Account.c b/src/engine/Account.c index 086b966dd0..a48364975d 100644 --- a/src/engine/Account.c +++ b/src/engine/Account.c @@ -1147,7 +1147,7 @@ update_split_currency(Account * acc) */ void -xaccAccountSetCommodity (Account * acc, const gnc_commodity * com) +xaccAccountSetCommodity (Account * acc, gnc_commodity * com) { if ((!acc) || (!com)) return; @@ -1180,7 +1180,7 @@ xaccAccountSetCommodity (Account * acc, const gnc_commodity * com) /* below follow the old, deprecated currency/security routines. */ void -xaccAccountSetCurrency (Account * acc, const gnc_commodity * currency) { +xaccAccountSetCurrency (Account * acc, gnc_commodity * currency) { if ((!acc) || (!currency)) return; @@ -1200,7 +1200,7 @@ xaccAccountSetCurrency (Account * acc, const gnc_commodity * currency) { } void -xaccAccountSetSecurity (Account *acc, const gnc_commodity * security) { +xaccAccountSetSecurity (Account *acc, gnc_commodity * security) { if ((!acc) || (!security)) return; @@ -1383,14 +1383,14 @@ xaccAccountGetNotes (Account *acc) return(NULL); } -const gnc_commodity * +gnc_commodity * xaccAccountGetCurrency (Account *acc) { if (!acc) return NULL; return (acc->currency); } -const gnc_commodity * +gnc_commodity * xaccAccountGetEffectiveSecurity (Account *acc) { if (!acc) return NULL; @@ -1401,7 +1401,7 @@ xaccAccountGetEffectiveSecurity (Account *acc) return (acc->security); } -const gnc_commodity * +gnc_commodity * xaccAccountGetSecurity (Account *account) { if (!account) return NULL; diff --git a/src/engine/Account.h b/src/engine/Account.h index b8cef57b16..38aa3bf091 100644 --- a/src/engine/Account.h +++ b/src/engine/Account.h @@ -215,23 +215,23 @@ const char * xaccAccountGetNotes (Account *account); * amount of the Account's commodity involved) into the Transaction's * balancing currency. */ #define xaccAccountGetCommodity xaccAccountGetEffectiveSecurity -void xaccAccountSetCommodity (Account *account, const gnc_commodity *comm); +void xaccAccountSetCommodity (Account *account, gnc_commodity *comm); /* Soon-to-be-deprecated currency/security access routines. * The future API will associate only one thing with an account: * the 'commodity'. Use xaccAccountGetCommodity() to fetch it. */ /* these two funcs take control of thier gnc_commodity args. Don't free */ -void xaccAccountSetCurrency (Account *account, const gnc_commodity *currency); -void xaccAccountSetSecurity (Account *account, const gnc_commodity *security); +void xaccAccountSetCurrency (Account *account, gnc_commodity *currency); +void xaccAccountSetSecurity (Account *account, gnc_commodity *security); void xaccAccountSetCurrencySCU (Account *account, int frac); void xaccAccountSetSecuritySCU (Account *account, int frac); int xaccAccountGetCurrencySCU (Account *account); int xaccAccountGetSecuritySCU (Account *account); -const gnc_commodity * xaccAccountGetCurrency (Account *account); -const gnc_commodity * xaccAccountGetSecurity (Account *account); -const gnc_commodity * xaccAccountGetEffectiveSecurity (Account *account); +gnc_commodity * xaccAccountGetCurrency (Account *account); +gnc_commodity * xaccAccountGetSecurity (Account *account); +gnc_commodity * xaccAccountGetEffectiveSecurity (Account *account); AccountGroup * xaccAccountGetChildren (Account *account); AccountGroup * xaccAccountGetParent (Account *account); diff --git a/src/engine/AccountP.h b/src/engine/AccountP.h index 2cd124948d..0b4e41e490 100644 --- a/src/engine/AccountP.h +++ b/src/engine/AccountP.h @@ -111,8 +111,8 @@ struct _account { * messages will print to the screen if things don't go well. */ - const gnc_commodity * currency; - const gnc_commodity * security; + gnc_commodity * currency; + gnc_commodity * security; int currency_scu; int security_scu; diff --git a/src/engine/Backend.h b/src/engine/Backend.h index 9ae3c0a5c6..197253ea7d 100644 --- a/src/engine/Backend.h +++ b/src/engine/Backend.h @@ -25,6 +25,7 @@ typedef enum { /* or no backend handler (ENOSYS) */ ERR_BACKEND_LOCKED, /* in use by another user (ETXTBSY) */ ERR_BACKEND_NO_SUCH_DB, /* the named database doesn't exist */ + ERR_BACKEND_ALLOC, /* internal memory allocation failure */ ERR_BACKEND_MISC, /* undetermined error */ /* fileio errors */ @@ -34,8 +35,6 @@ typedef enum { ERR_FILEIO_FILE_NOT_FOUND, /* not found / no such file */ ERR_FILEIO_FILE_TOO_NEW, /* file version newer than what we can read */ ERR_FILEIO_FILE_TOO_OLD, /* file version so old we can't read it */ - ERR_FILEIO_ALLOC, /* ?? */ - ERR_FILEIO_MISC, /* unknown weird error */ /* network errors */ ERR_NETIO_NO_CONNECTION, /* network failure, can't connect to server */ diff --git a/src/engine/Commodity-xml-parser-v1.c b/src/engine/Commodity-xml-parser-v1.c index a744552db7..8952607710 100644 --- a/src/engine/Commodity-xml-parser-v1.c +++ b/src/engine/Commodity-xml-parser-v1.c @@ -95,14 +95,14 @@ commodity_restore_start_handler(GSList* sibling_data, gpointer parent_data, static gboolean commodity_restore_after_child_handler(gpointer data_for_children, - GSList* data_from_children, - GSList* sibling_data, - gpointer parent_data, - gpointer global_data, - gpointer *result, - const gchar *tag, - const gchar *child_tag, - sixtp_child_result *child_result) + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag, + const gchar *child_tag, + sixtp_child_result *child_result) { CommodityParseInfo *cpi = (CommodityParseInfo *) data_for_children; @@ -367,31 +367,37 @@ generic_gnc_commodity_lookup_parser_new(void) /* WRITING */ gboolean -xml_add_commodity_ref(xmlNodePtr p, const char *tag, const gnc_commodity *c) { +xml_add_commodity_ref(xmlNodePtr p, const char *tag, const gnc_commodity *c) +{ xmlNodePtr c_xml = NULL; - gboolean ok = FALSE; + xmlNodePtr tmp_xml = NULL; + const gchar *namestr; + const gchar *idstr; - if(p && tag) { - if(!c) { - ok = TRUE; - } else { - c_xml= xmlNewTextChild(p, NULL, tag, NULL); - if(c_xml) { - const gchar *namestr = gnc_commodity_get_namespace(c); - if(namestr) { - xmlNodePtr namespace_xml = xmlNewTextChild(c_xml, NULL, "space", namestr); - if(namespace_xml) { - const gchar *idstr = gnc_commodity_get_mnemonic(c); - xmlNodePtr id_xml = xmlNewTextChild(c_xml, NULL, "id", idstr); - if(id_xml) ok = TRUE; - } - } - } - } - } + if (!(p && tag)) return FALSE; + if (!c) return TRUE; + + namestr = gnc_commodity_get_namespace(c); + idstr = gnc_commodity_get_mnemonic(c); - if(!ok && c_xml) xmlFreeNode(c_xml); - return(TRUE); + if(!(namestr && idstr)) return FALSE; + + c_xml= xmlNewNode(NULL, tag); + if(!c_xml) return FALSE; + + tmp_xml = xmlNewTextChild(c_xml, NULL, "space", namestr); + if(!tmp_xml) { + xmlFreeNode(c_xml); + return FALSE; + } + tmp_xml = xmlNewTextChild(c_xml, NULL, "id", idstr); + if(!tmp_xml) { + xmlFreeNode(c_xml); + return FALSE; + } + + xmlAddChild(p, c_xml); + return TRUE; } /* ============================================================== */ diff --git a/src/engine/FileIO.c b/src/engine/FileIO.c deleted file mode 100644 index 9ea247c08f..0000000000 --- a/src/engine/FileIO.c +++ /dev/null @@ -1,115 +0,0 @@ -/********************************************************************\ - * FileIO.c -- read and write file wrappers (old and new format) * - * Copyright (C) 1997-2000 Linas Vepstas * - * Copyright (C) 1999-2000 Rob Browning * - * * - * 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 * - * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * - * Boston, MA 02111-1307, USA gnu@gnu.org * - * * -\********************************************************************/ - - -#include -#include - -#include "DateUtils.h" -#include "FileIO.h" -#include "io-gncxml.h" -#include "io-gncbin.h" - -AccountGroup * -xaccReadAccountGroupFile(const gchar *name, GNCBackendError *error_result) -{ - AccountGroup *result_grp; - - if(is_gncxml_file(name)) { - if(gncxml_read(name, &result_grp)) { - if(error_result) *error_result = ERR_BACKEND_NO_ERR; - return result_grp; - } else { - if(error_result) *error_result = ERR_FILEIO_MISC; - return NULL; - } - } else { - /* presume it's an old-style binary file */ - result_grp = xaccReadGncBinAccountGroupFile(name); - - if(result_grp) { - if(error_result) *error_result = ERR_BACKEND_NO_ERR; - return result_grp; - } else { - if(error_result) *error_result = xaccGetGncBinFileIOError(); - return NULL; - } - } - /* Should never get here */ - if(error_result) *error_result = ERR_FILEIO_MISC; - return NULL; -} - -gboolean -xaccWriteAccountGroupFile(const char *datafile, - AccountGroup *grp, - gboolean make_backup, - GNCBackendError *error_result) -{ - - if(!datafile) { - if(error_result) *error_result = ERR_FILEIO_FILE_NOT_FOUND; - return FALSE; - } - - if(!gncxml_write(grp, datafile)) { - if(error_result) *error_result = ERR_FILEIO_MISC; - return FALSE; - } - - if(!make_backup) { - if(error_result) *error_result = ERR_BACKEND_NO_ERR; - return TRUE; - } else { - char * timestamp; - int filenamelen; - char * backup; - - /* also, write a time-stamped backup file */ - /* tag each filename with a timestamp */ - timestamp = xaccDateUtilGetStampNow (); - - filenamelen = strlen (datafile) + strlen (timestamp) + 6; - - backup = (char *) malloc (filenamelen); - strcpy (backup, datafile); - strcat (backup, "."); - strcat (backup, timestamp); - strcat (backup, ".xac"); - free (timestamp); - - if(gncxml_write(grp, backup)) { - if(error_result) *error_result = ERR_BACKEND_NO_ERR; - free (backup); - return TRUE; - } else { - if(error_result) *error_result = ERR_FILEIO_MISC; - free (backup); - return FALSE; - } - } - /* Should never get here */ - if(error_result) *error_result = ERR_FILEIO_MISC; - return FALSE; -} diff --git a/src/engine/FileIO.h b/src/engine/FileIO.h deleted file mode 100644 index e9fc228660..0000000000 --- a/src/engine/FileIO.h +++ /dev/null @@ -1,67 +0,0 @@ -/********************************************************************\ - * FileIO.h -- read and write binary format file for gnucash * - * Copyright (C) 1997 Robin D. Clark * - * Copyright (C) 1998, 1999 Linas Vepstas * - * Copyright (C) 1999, 2000 Rob Browning * - * * - * 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 * - * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * - * Boston, MA 02111-1307, USA gnu@gnu.org * - * * - * Author: Rob Clark * - * Internet: rclark@cs.hmc.edu * - * Address: 609 8th Street * - * Huntington Beach, CA 92648-4632 * -\********************************************************************/ - -#ifndef __FILE_IO_H__ -#define __FILE_IO_H__ - -#include "Backend.h" -#include "Group.h" - -/* - - These function read/write the data in an AccountGroup to a file. - The read functions will automatically detect the format of the file - if possible. The write functions write the file in the "current" - format. These days, that means XML. - - The read functions return NULL on error, and set the error parameter - (if it's not NULL) to indicate what went wrong. - - The write functions return FALSE on error and set the error - parameter similarly. - - In most cases, these functions should not be used directly. They - are not "safe" against file-locking errors. Use the Session object - instead. - -*/ - -AccountGroup *xaccReadAccountGroupFile(const char *datafile, - GNCBackendError *error); - -gboolean xaccWriteAccountGroupFile(const char *datafile, - AccountGroup *grp, - gboolean make_backup, - GNCBackendError *error); -/* If make_backup is true, write out a time-stamped copy of the file - into the same directory as the indicated file, with a filename of - "file.YYYYMMDDHHMMSS.xac" where YYYYMMDDHHMMSS is replaced with the - current year/month/day/hour/minute/second. */ - -#endif /* __XACC_FILEIO_H__ */ diff --git a/src/engine/Group.h b/src/engine/Group.h index f9cc3bc349..9db2f90b0e 100644 --- a/src/engine/Group.h +++ b/src/engine/Group.h @@ -70,6 +70,7 @@ void xaccGroupMergeAccounts (AccountGroup *grp); gboolean xaccGroupNotSaved (AccountGroup *grp); void xaccGroupMarkSaved (AccountGroup *grp); void xaccGroupMarkNotSaved (AccountGroup *grp); + void xaccGroupMarkDoFree (AccountGroup *grp); /* diff --git a/src/engine/Ledger-xml-parser-v1.c b/src/engine/Ledger-xml-parser-v1.c index 1bbaa9401b..2ca184133c 100644 --- a/src/engine/Ledger-xml-parser-v1.c +++ b/src/engine/Ledger-xml-parser-v1.c @@ -1,12 +1,21 @@ +#include "config.h" +#include + +#include "gnc-engine-util.h" +#include "gnc-pricedb.h" +#include "io-gncxml-p.h" #include "sixtp.h" - #include "sixtp-parsers.h" #include "sixtp-utils.h" #include "Group.h" #include "TransLog.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_IO; + /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ @@ -47,6 +56,38 @@ ledger_data_start_handler(GSList* sibling_data, gpointer parent_data, return(ag != NULL); } +static gboolean +ledger_data_after_child_handler(gpointer data_for_children, + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag, + const gchar *child_tag, + sixtp_child_result *child_result) +{ + if(!child_result) return(TRUE); + + /* if we see the pricedb, deal with it */ + if(child_result->type != SIXTP_CHILD_RESULT_NODE) return(TRUE); + if(strcmp(child_result->tag, "pricedb") == 0) { + GNCPriceDB *pdb = (GNCPriceDB *) child_result->data; + GNCParseStatus *status = (GNCParseStatus *) global_data; + + g_return_val_if_fail(pdb, FALSE); + g_return_val_if_fail(status, FALSE); + + if(status->pricedb) { + PERR("hit pricedb twice in data file."); + return FALSE; + } + status->pricedb = pdb; + child_result->should_cleanup = FALSE; + } + return(TRUE); +} + static gboolean ledger_data_end_handler(gpointer data_for_children, GSList *data_from_children, GSList *sibling_data, @@ -111,6 +152,7 @@ ledger_data_parser_new(void) sixtp_new(), FALSE, SIXTP_START_HANDLER_ID, ledger_data_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, + SIXTP_AFTER_CHILD_HANDLER_ID, ledger_data_after_child_handler, SIXTP_END_HANDLER_ID, ledger_data_end_handler, SIXTP_CLEANUP_RESULT_ID, ledger_data_result_cleanup, SIXTP_FAIL_HANDLER_ID, ledger_data_fail_handler, @@ -123,6 +165,7 @@ ledger_data_parser_new(void) if(!sixtp_add_some_sub_parsers( top_level, TRUE, "commodity", commodity_restore_parser_new(), + "pricedb", gnc_pricedb_parser_new(), "account", gnc_account_parser_new(), "transaction", gnc_transaction_parser_new(), 0)) diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am index 7d43cec94b..edcef516b4 100644 --- a/src/engine/Makefile.am +++ b/src/engine/Makefile.am @@ -11,7 +11,6 @@ libgncengine_la_SOURCES = \ Account.c \ Backend.c \ DateUtils.c \ - FileIO.c \ Group.c \ NetIO.c \ Query.c \ @@ -35,6 +34,8 @@ libgncengine_la_SOURCES = \ gnc-engine-util.c \ gnc-event.c \ gnc-numeric.c \ + gnc-pricedb.c \ + gnc-pricedb-xml-v1.c \ sixtp-dom-generators.c \ sixtp-dom-parsers.c \ sixtp-kvp-parser.c \ @@ -56,8 +57,6 @@ noinst_HEADERS = \ AccountP.h \ BackendP.h \ DateUtils.h \ - FileIO.h \ - FileIOP.h \ GNCId.h \ GNCIdP.h \ Group.h \ @@ -83,6 +82,7 @@ noinst_HEADERS = \ gnc-event.h \ gnc-event-p.h \ gnc-numeric.h \ + gnc-pricedb.h \ gnc-xml-helper.h \ gnc-xml.h \ sixtp-dom-generators.h \ diff --git a/src/engine/NetIO.c b/src/engine/NetIO.c index 05a0c33ef2..fc5a009ee4 100644 --- a/src/engine/NetIO.c +++ b/src/engine/NetIO.c @@ -59,8 +59,10 @@ struct _xmlend { Backend *xmlendNew (void); +#if 0 + /* ==================================================================== */ -/* Perform vaious validty checks on the reply: +/* Perform various validty checks on the reply: * -- was the content type text/gnc-xml ? * -- was there a reply body, of positive length? * -- did the body appear to contain gnc xml data? @@ -202,7 +204,8 @@ xmlbeBookLoad (Backend *bend) if (0 >= len) return NULL; bufp = ghttp_get_body(request); - grp = gncxml_read_from_buf ((char *)bufp, len); + + grp = gnc_book_load_from_buf ((char *)bufp, len); LEAVE(" "); return grp; @@ -272,9 +275,12 @@ xmlbeBookEnd (Backend *b) /* ==================================================================== */ +#endif + Backend * xmlendNew (void) { +#if 0 XMLBackend *be; be = (XMLBackend *) malloc (sizeof (XMLBackend)); @@ -300,7 +306,8 @@ xmlendNew (void) be->query_url = NULL; return (Backend *) be; +#endif + return NULL; } - /* ============================== END OF FILE ======================== */ diff --git a/src/engine/Scrub.c b/src/engine/Scrub.c index 64e5347f58..f0af425e19 100644 --- a/src/engine/Scrub.c +++ b/src/engine/Scrub.c @@ -375,7 +375,7 @@ static Account * GetOrMakeAccount (AccountGroup *root, Transaction *trans, const char *name_root) { - const gnc_commodity * currency; + gnc_commodity * currency; char * accname; Account * acc; diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c index a15231f5af..6b319e56c8 100644 --- a/src/engine/Transaction.c +++ b/src/engine/Transaction.c @@ -1049,9 +1049,10 @@ xaccIsCommonCurrency(const gnc_commodity * currency_1, return (c1c2 == 1) || (c1s2 == 1) || (s1c2 == 1) || (s1s2 == 1); } -static const gnc_commodity * -FindCommonExclSCurrency (GList *splits, const gnc_commodity * ra, - const gnc_commodity * rb, Split *excl_split) +static gnc_commodity * +FindCommonExclSCurrency (GList *splits, + gnc_commodity * ra, gnc_commodity * rb, + Split *excl_split) { GList *node; @@ -1060,7 +1061,7 @@ FindCommonExclSCurrency (GList *splits, const gnc_commodity * ra, for (node = splits; node; node = node->next) { Split *s = node->data; - const gnc_commodity * sa, * sb; + gnc_commodity * sa, * sb; if (s == excl_split) continue; @@ -1113,17 +1114,16 @@ FindCommonExclSCurrency (GList *splits, const gnc_commodity * ra, * don't exclude one split from the splitlist when looking for a * common currency. */ -static const gnc_commodity * -FindCommonCurrency (GList *splits, - const gnc_commodity * ra, const gnc_commodity * rb) +static gnc_commodity * +FindCommonCurrency (GList *splits, gnc_commodity * ra, gnc_commodity * rb) { return FindCommonExclSCurrency(splits, ra, rb, NULL); } -const gnc_commodity * +gnc_commodity * xaccTransFindCommonCurrency (Transaction *trans) { - const gnc_commodity *ra, *rb, *retval; + gnc_commodity *ra, *rb, *retval; Split *split; if (!trans) return NULL; @@ -1161,16 +1161,16 @@ xaccTransFindCommonCurrency (Transaction *trans) return retval; } -const gnc_commodity * -xaccTransIsCommonCurrency (Transaction *trans, const gnc_commodity * ra) +gnc_commodity * +xaccTransIsCommonCurrency (Transaction *trans, gnc_commodity * ra) { if (!trans) return NULL; return FindCommonCurrency (trans->splits, ra, NULL); } -const gnc_commodity * +gnc_commodity * xaccTransIsCommonExclSCurrency (Transaction *trans, - const gnc_commodity * ra, + gnc_commodity * ra, Split *excl_split) { if (!trans) return NULL; @@ -1182,7 +1182,7 @@ xaccTransIsCommonExclSCurrency (Transaction *trans, /* The new routine for setting the common currency */ void -xaccTransSetCurrency (Transaction *trans, const gnc_commodity *curr) +xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr) { if (!trans || !curr) return; diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h index 3458f626d6..91ef2abbc2 100644 --- a/src/engine/Transaction.h +++ b/src/engine/Transaction.h @@ -253,8 +253,7 @@ int xaccTransCountSplits (Transaction *trans); * */ #define xaccTransGetCurrency xaccTransFindCommonCurrency -void xaccTransSetCurrency (Transaction *trans, - const gnc_commodity *curr); +void xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr); /* --------- * and now for the 'old' routines @@ -281,7 +280,7 @@ gboolean xaccIsCommonCurrency(const gnc_commodity * currency_1, * If all of the splits share both a common security and a common currency, * then the string name for the currency is returned. */ -const gnc_commodity * xaccTransFindCommonCurrency (Transaction *trans); +gnc_commodity * xaccTransFindCommonCurrency (Transaction *trans); /* The xaccTransIsCommonCurrency () method compares the input commodity * to the currency/security denominations of all splits in the @@ -299,8 +298,8 @@ const gnc_commodity * xaccTransFindCommonCurrency (Transaction *trans); * common. This routine is useful in dealing with securities of * differing types as they are moved across accounts. */ -const gnc_commodity * xaccTransIsCommonCurrency(Transaction *trans, - const gnc_commodity * curr); +gnc_commodity * xaccTransIsCommonCurrency(Transaction *trans, + gnc_commodity *curr); /* The xaccTransIsCommonExclSCurrency () method compares the input * string to the currency/security denominations of all splits in @@ -312,9 +311,9 @@ const gnc_commodity * xaccTransIsCommonCurrency(Transaction *trans, * that split is of no relevance when determining whether the new * entry has a common currency with the other splits. */ -const gnc_commodity * +gnc_commodity * xaccTransIsCommonExclSCurrency (Transaction *trans, - const gnc_commodity * currency, + gnc_commodity * currency, Split *excl_split); /* The xaccTransGetImbalance() method returns the total value of the diff --git a/src/engine/date.c b/src/engine/date.c index f186d469c0..3e0d694724 100644 --- a/src/engine/date.c +++ b/src/engine/date.c @@ -51,13 +51,23 @@ static short module = MOD_ENGINE; /********************************************************************\ \********************************************************************/ -int -timespec_cmp (const Timespec *ta, const Timespec *tb) +gboolean +timespec_equal (const Timespec *ta, const Timespec *tb) { + if(ta == tb) return TRUE; + if(ta->tv_sec != tb->tv_sec) return FALSE; + if(ta->tv_nsec != tb->tv_nsec) return FALSE; + return TRUE; +} + +gint +timespec_cmp(const Timespec *ta, const Timespec *tb) +{ + if(ta == tb) return 0; if(ta->tv_sec < tb->tv_sec) return -1; - if(ta->tv_sec > tb->tv_sec) return +1; + if(ta->tv_sec > tb->tv_sec) return 1; if(ta->tv_nsec < tb->tv_nsec) return -1; - if(ta->tv_nsec > tb->tv_nsec) return +1; + if(ta->tv_nsec > tb->tv_nsec) return 1; return 0; } diff --git a/src/engine/date.h b/src/engine/date.h index 68fc0abe49..5d9b57d194 100644 --- a/src/engine/date.h +++ b/src/engine/date.h @@ -77,10 +77,10 @@ typedef struct timespec64 Timespec; /** Prototypes ******************************************************/ -/* The timespec_cmp() routine returns 0 if ta equals tb, - * -1 if tatb - */ -int timespec_cmp (const Timespec *ta, const Timespec *tb); +/* strict equality */ +gboolean timespec_equal(const Timespec *ta, const Timespec *tb); +/* comparison: if (ta < tb) -1; else if (ta > tb) 1; else 0; */ +int timespec_cmp(const Timespec *ta, const Timespec *tb); void setDateFormat(DateFormat df); diff --git a/src/engine/gnc-account-xml-v2.c b/src/engine/gnc-account-xml-v2.c index b69cd545eb..a05c0b4b89 100644 --- a/src/engine/gnc-account-xml-v2.c +++ b/src/engine/gnc-account-xml-v2.c @@ -272,7 +272,7 @@ gnc_account_end_handler(gpointer data_for_children, } } - xmlFreeNode(result); + xmlFreeNode((xmlNodePtr) result); return successful; } @@ -280,5 +280,5 @@ gnc_account_end_handler(gpointer data_for_children, sixtp* gnc_account_sixtp_parser_create(void) { - return sixtp_dom_parser_new(gnc_account_end_handler); + return sixtp_dom_parser_new(gnc_account_end_handler, NULL, NULL); } diff --git a/src/engine/FileIOP.h b/src/engine/gnc-book-p.h similarity index 73% rename from src/engine/FileIOP.h rename to src/engine/gnc-book-p.h index bac379ca13..4014bea5ea 100644 --- a/src/engine/FileIOP.h +++ b/src/engine/gnc-book-p.h @@ -1,8 +1,5 @@ /********************************************************************\ - * FileIOP.h -- private header for binary file i/o * - * datafile for gnucash (X-Accountant) * - * Copyright (C) 1997 Robin D. Clark * - * Copyright (C) 1998, 1999 Linas Vepstas * + * gnc-book-p.h -- private functions for gnc books. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -20,11 +17,25 @@ * Free Software Foundation Voice: +1-617-542-5942 * * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * * Boston, MA 02111-1307, USA gnu@gnu.org * + * * \********************************************************************/ -#ifndef __XACC_FILEIO_P_H__ -#define __XACC_FILEIO_P_H__ +/* + * HISTORY: + * Created 2001 by Rob Browning + * Copyright (c) 2001 Rob Browning + */ +#ifndef __GNC_BOOK_P_H__ +#define __GNC_BOOK_P_H__ + +#include "gnc-book.h" +#include "gnc-pricedb.h" #include "Group.h" -#endif /* __XACC_FILEIO_P_H__ */ +void gnc_book_set_group(GNCBook *book, AccountGroup *grp); +void gnc_book_set_pricedb(GNCBook *book, GNCPriceDB *db); + +void gnc_book_mark_saved(GNCBook *book); + +#endif /* __GNC_BOOK_P_H__ */ diff --git a/src/engine/gnc-book.c b/src/engine/gnc-book.c index 189d9d4dc7..552b99da50 100644 --- a/src/engine/gnc-book.c +++ b/src/engine/gnc-book.c @@ -44,12 +44,19 @@ #include "Backend.h" #include "BackendP.h" -#include "FileIO.h" #include "Group.h" #include "NetIO.h" #include "Scrub.h" #include "TransLog.h" +#include "gnc-engine-util.h" +#include "gnc-pricedb-p.h" +#include "DateUtils.h" +#include "io-gncxml.h" +#include "io-gncbin.h" + + #include "gnc-book.h" +#include "gnc-book-p.h" #include "gnc-engine.h" #include "gnc-engine-util.h" @@ -58,6 +65,7 @@ static short module = MOD_IO; struct _gnc_book { AccountGroup *topgroup; + GNCPriceDB *pricedb; /* the requested book id, in the form or a URI, such as * file:/some/where, or sql:server.host.com:555 @@ -88,7 +96,7 @@ struct _gnc_book Backend *backend; }; -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ static void gnc_book_clear_error (GNCBook *book) @@ -102,7 +110,7 @@ gnc_book_push_error (GNCBook *book, GNCBackendError err) book->last_err = err; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ GNCBackendError gnc_book_get_error (GNCBook * book) @@ -121,31 +129,28 @@ gnc_book_pop_error (GNCBook * book) return err; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ static void gnc_book_init (GNCBook *book) { - if (!book) return; + if(!book) return; - book->topgroup = xaccMallocAccountGroup (); + book->topgroup = xaccMallocAccountGroup(); + book->pricedb = gnc_pricedb_create(); gnc_book_clear_error (book); book->lockfd = -1; -}; +} GNCBook * gnc_book_new (void) { - GNCBook *book; - - book = g_new0(GNCBook, 1); - - gnc_book_init (book); - + GNCBook *book = g_new0(GNCBook, 1); + gnc_book_init(book); return book; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ gnc_commodity_table* gnc_book_get_commodity_table(GNCBook *book) @@ -167,7 +172,23 @@ gnc_book_set_group (GNCBook *book, AccountGroup *grp) book->topgroup = grp; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ + +GNCPriceDB * +gnc_book_get_pricedb(GNCBook *book) +{ + if(!book) return NULL; + return book->pricedb; +} + +void +gnc_book_set_pricedb(GNCBook *book, GNCPriceDB *db) +{ + if(!book) return; + book->pricedb = db; +} + +/* ---------------------------------------------------------------------- */ Backend * xaccGNCBookGetBackend (GNCBook *book) @@ -176,7 +197,7 @@ xaccGNCBookGetBackend (GNCBook *book) return book->backend; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ const char * gnc_book_get_file_path (GNCBook *book) @@ -185,7 +206,7 @@ gnc_book_get_file_path (GNCBook *book) return book->fullpath; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ const char * gnc_book_get_url (GNCBook *book) @@ -194,8 +215,16 @@ gnc_book_get_url (GNCBook *book) return book->book_id; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ +void +gnc_book_mark_saved(GNCBook *book) +{ + xaccGroupMarkSaved(gnc_book_get_group(book)); + gnc_pricedb_mark_clean(gnc_book_get_pricedb(book)); +} + +/* ---------------------------------------------------------------------- */ static gboolean gnc_book_get_file_lock (GNCBook *book) { @@ -266,7 +295,104 @@ gnc_book_get_file_lock (GNCBook *book) return TRUE; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ + +/* Load financial data from a file into the book, automtically + detecting the format of the file, if possible. Return FALSE on + error, and set the error parameter to indicate what went wrong if + it's not NULL. This function does not manage file locks in any + way. */ + +static gboolean +gnc_book_load_from_file(GNCBook *book) +{ + const gchar *name = gnc_book_get_file_path(book); + if(!name) return FALSE; + + if(gnc_is_xml_data_file(name)) { + if(gnc_book_load_from_xml_file(book)) { + return TRUE; + } else { + gnc_book_push_error(book, ERR_BACKEND_MISC); + return FALSE; + } + } else { + /* presume it's an old-style binary file */ + GNCBackendError error; + + gnc_book_load_from_binfile(book); + error = gnc_book_get_binfile_io_error(); + + if(error == ERR_BACKEND_NO_ERR) { + return TRUE; + } else { + gnc_book_push_error(book, error); + return FALSE; + } + } + /* Should never get here */ + gnc_book_push_error(book, ERR_BACKEND_MISC); + return FALSE; +} + +/* ---------------------------------------------------------------------- */ + +/* Write the financial data in a book to a file, returning FALSE on + error and setting the error_result to indicate what went wrong if + it's not NULL. This function does not manage file locks in any + way. + + If make_backup is true, write out a time-stamped copy of the file + into the same directory as the indicated file, with a filename of + "file.YYYYMMDDHHMMSS.xac" where YYYYMMDDHHMMSS is replaced with the + current year/month/day/hour/minute/second. */ + +static gboolean +gnc_book_write_to_file(GNCBook *book, + gboolean make_backup) +{ + const gchar *datafile = gnc_book_get_file_path(book); + + if(!gnc_book_write_to_xml_file(book, datafile)) { + gnc_book_push_error(book, ERR_BACKEND_MISC); + return FALSE; + } + + if(!make_backup) { + return TRUE; + } else { + char * timestamp; + int filenamelen; + char * backup; + + /* also, write a time-stamped backup file */ + /* tag each filename with a timestamp */ + timestamp = xaccDateUtilGetStampNow (); + + filenamelen = strlen (datafile) + strlen (timestamp) + 6; + + backup = (char *) malloc (filenamelen); + strcpy (backup, datafile); + strcat (backup, "."); + strcat (backup, timestamp); + strcat (backup, ".xac"); + free (timestamp); + + if(gnc_book_write_to_xml_file(book, backup)) { + free (backup); + return TRUE; + } else { + gnc_book_push_error(book, ERR_BACKEND_MISC); + free (backup); + return FALSE; + } + } + /* Should never get here */ + gnc_book_push_error(book, ERR_BACKEND_MISC); + return FALSE; +} + +/* ---------------------------------------------------------------------- */ static gboolean gnc_book_begin_file (GNCBook *book, const char * filefrag, @@ -305,7 +431,7 @@ gnc_book_begin_file (GNCBook *book, const char * filefrag, return TRUE; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ gboolean gnc_book_begin (GNCBook *book, const char * book_id, @@ -450,12 +576,12 @@ gnc_book_begin (GNCBook *book, const char * book_id, return rc; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ gboolean gnc_book_load (GNCBook *book) { - GNCBackendError retval; + GNCBackendError backend_err; if (!book) return FALSE; if (!book->book_id) return FALSE; @@ -479,25 +605,25 @@ gnc_book_load (GNCBook *book) xaccGroupMarkDoFree (book->topgroup); xaccFreeAccountGroup (book->topgroup); book->topgroup = NULL; + gnc_pricedb_destroy(book->pricedb); + book->pricedb = NULL; xaccLogSetBaseName(book->fullpath); + gnc_book_clear_error (book); - book->topgroup = xaccReadAccountGroupFile (book->fullpath, - &retval); - if (ERR_BACKEND_NO_ERR != retval) gnc_book_push_error (book, retval); + gnc_book_load_from_file(book); + xaccLogEnable(); - if (!book->topgroup || (gnc_book_get_error(book) != ERR_BACKEND_NO_ERR)) - { - return FALSE; - } + if (!book->topgroup) return FALSE; + if (!book->pricedb) return FALSE; + if (gnc_book_get_error(book) != ERR_BACKEND_NO_ERR) return FALSE; xaccGroupScrubSplits (book->topgroup); LEAVE("book_id=%s", book->book_id); return TRUE; } - else if ((strncmp(book->book_id, "http://", 7) == 0) || (strncmp(book->book_id, "https://", 8) == 0) || (strncmp(book->book_id, "postgres://", 11) == 0)) @@ -545,7 +671,19 @@ gnc_book_load (GNCBook *book) } } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ + +gboolean +gnc_book_not_saved(GNCBook *book) +{ + if(!book) return FALSE; + + return(xaccGroupNotSaved(book->topgroup) + || + gnc_pricedb_dirty(book->pricedb)); +} + +/* ---------------------------------------------------------------------- */ gboolean gnc_book_save_may_clobber_data (GNCBook *book) @@ -562,12 +700,11 @@ gnc_book_save_may_clobber_data (GNCBook *book) return FALSE; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ void gnc_book_save (GNCBook *book) { - GNCBackendError retval; Backend *be; if (!book) return; @@ -579,27 +716,26 @@ gnc_book_save (GNCBook *book) * then give the user the option to save to disk. */ be = book->backend; - if (be && be->sync && book->topgroup) - { - /* if invoked as SaveAs(), then backend not yet set */ - xaccGroupSetBackend (book->topgroup, be); + if (be && be->sync && book->topgroup) { + GNCBackendError err; + + /* if invoked as SaveAs(), then backend not yet set */ + xaccGroupSetBackend (book->topgroup, be); - (be->sync)(be, book->topgroup); - retval = xaccBackendGetError(be); - - if (ERR_BACKEND_NO_ERR != retval) - { - gnc_book_push_error (book, retval); - - /* we close the backend here ... isn't this a bit harsh ??? */ - if (be->book_end) - { - (be->book_end)(be); - } - } - return; + (be->sync)(be, book->topgroup); + err = xaccBackendGetError(be); + + if (ERR_BACKEND_NO_ERR != err) { + gnc_book_push_error (book, err); + + /* we close the backend here ... isn't this a bit harsh ??? */ + if (be->book_end) { + (be->book_end)(be); + } + } + return; } - + /* if the fullpath doesn't exist, either the user failed to initialize, * or the lockfile was never obtained. Either way, we can't write. */ gnc_book_clear_error (book); @@ -610,16 +746,15 @@ gnc_book_save (GNCBook *book) return; } - if (book->topgroup) - { - xaccWriteAccountGroupFile (book->fullpath, book->topgroup, - TRUE, &retval); - if (ERR_BACKEND_NO_ERR != retval) gnc_book_push_error (book, retval); + if (book->topgroup) { + if(!gnc_book_write_to_file(book, TRUE)) { + gnc_book_push_error (book, ERR_BACKEND_MISC); + } } LEAVE(" "); } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ void gnc_book_end (GNCBook *book) @@ -677,6 +812,10 @@ gnc_book_destroy (GNCBook *book) xaccFreeAccountGroup (book->topgroup); book->topgroup = NULL; + + gnc_pricedb_destroy (book->pricedb); + book->pricedb = NULL; + xaccLogEnable(); g_free (book); @@ -684,7 +823,7 @@ gnc_book_destroy (GNCBook *book) } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ /* * If $HOME/.gnucash/data directory doesn't exist, then create it. */ @@ -723,7 +862,7 @@ MakeHomeDir (void) g_free (data); } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ /* XXX hack alert -- we should be yanking this out of some config file */ static char * searchpaths[] = { @@ -858,7 +997,7 @@ xaccResolveFilePath (const char * filefrag) return NULL; } -/* ============================================================== */ +/* ---------------------------------------------------------------------- */ char * xaccResolveURL (const char * pathfrag) @@ -886,5 +1025,3 @@ xaccResolveURL (const char * pathfrag) return (xaccResolveFilePath (pathfrag)); } - -/* ==================== END OF FILE ================== */ diff --git a/src/engine/gnc-book.h b/src/engine/gnc-book.h index 2a36f5b3b0..47dce38fd6 100644 --- a/src/engine/gnc-book.h +++ b/src/engine/gnc-book.h @@ -54,10 +54,11 @@ #define __GNC_BOOK_H__ #include "Group.h" -#include "FileIO.h" +#include "Backend.h" +#include "gnc-pricedb.h" +/** TYPES **********************************************************/ -/** TYPEDEFS ********************************************************/ typedef struct _gnc_book GNCBook; /** PROTOTYPES ******************************************************/ @@ -116,14 +117,9 @@ gboolean gnc_book_load (GNCBook *book); GNCBackendError gnc_book_get_error (GNCBook *book); GNCBackendError gnc_book_pop_error (GNCBook *book); -/* The gnc_book_get_group() method will return the current top-level - * account group. - * - * The gnc_book_set_group() method will set the topgroup to a new value. - */ -AccountGroup * gnc_book_get_group (GNCBook *book); -void gnc_book_set_group (GNCBook *book, AccountGroup *topgroup); +AccountGroup *gnc_book_get_group (GNCBook *book); +GNCPriceDB *gnc_book_get_pricedb (GNCBook *book); /* * gnc_book_get_commodity_table returns the commodity table associated with @@ -148,6 +144,12 @@ gnc_commodity_table* gnc_book_get_commodity_table(GNCBook *book); const char * gnc_book_get_file_path (GNCBook *book); const char * gnc_book_get_url (GNCBook *book); +/* + * The gnc_book_not_saved() subroutine will return TRUE + * if any data in the book hasn't been saved to long-term storage. + */ +gboolean gnc_book_not_saved(GNCBook *book); + /* FIXME: This isn't as thorough as we might want it to be... */ gboolean gnc_book_save_may_clobber_data (GNCBook *book); diff --git a/src/engine/gnc-commodity-xml-v2.c b/src/engine/gnc-commodity-xml-v2.c index 3bd887ba86..e1b91bb59d 100644 --- a/src/engine/gnc-commodity-xml-v2.c +++ b/src/engine/gnc-commodity-xml-v2.c @@ -141,5 +141,5 @@ gnc_commodity_end_handler(gpointer data_for_children, sixtp* gnc_commodity_sixtp_parser_create(void) { - return sixtp_dom_parser_new(gnc_commodity_end_handler); + return sixtp_dom_parser_new(gnc_commodity_end_handler, NULL, NULL); } diff --git a/src/engine/gnc-engine-util.c b/src/engine/gnc-engine-util.c index 6e1c71de63..a2340dfc5a 100644 --- a/src/engine/gnc-engine-util.c +++ b/src/engine/gnc-engine-util.c @@ -261,5 +261,36 @@ gnc_strisnum(const char *s) return FALSE; } +/********************************************************************\ + See header for docs. +\********************************************************************/ + +static void +kv_pair_helper(gpointer key, gpointer val, gpointer user_data) +{ + GSList **result = (GSList **) user_data; + GHashTableKVPair *kvp = g_new(GHashTableKVPair, 1); + + kvp->key = key; + kvp->value = val; + *result = g_slist_prepend(*result, kvp); +} + +GSList * +g_hash_table_key_value_pairs(GHashTable *table) +{ + GSList *result_list = NULL; + g_hash_table_foreach(table, kv_pair_helper, &result_list); + return result_list; +} + +void +g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data) +{ + GHashTableKVPair *kvp = (GHashTableKVPair *) data; + g_free(kvp); +} + + /************************* END OF FILE ******************************\ \********************************************************************/ diff --git a/src/engine/gnc-engine-util.h b/src/engine/gnc-engine-util.h index 060f7379f8..6c04e8815e 100644 --- a/src/engine/gnc-engine-util.h +++ b/src/engine/gnc-engine-util.h @@ -171,4 +171,30 @@ char * ultostr (unsigned long val, int base); * whitespace. */ gboolean gnc_strisnum(const char *s); + +/***********************************************************************\ + + g_hash_table_key_value_pairs(hash): Returns a GSList* of all the + keys and values in a given hash table. Data elements of lists are + actual hash elements, so be careful, and deallocation of the + GHashTableKVPairs in the result list are the caller's + responsibility. A typical sequence might look like this: + + GSList *kvps = g_hash_table_key_value_pairs(hash); + ... use kvps->data->key and kvps->data->val, etc. here ... + g_slist_foreach(kvps, g_hash_table_kv_pair_free_gfunc, NULL); + g_slist_free(kvps); + +*/ + +typedef struct { + gpointer key; + gpointer value; +} GHashTableKVPair; + +GSList *g_hash_table_key_value_pairs(GHashTable *table); +void g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data); + +/***********************************************************************/ + #endif diff --git a/src/engine/gnc-pricedb-p.h b/src/engine/gnc-pricedb-p.h new file mode 100644 index 0000000000..46fda6acb0 --- /dev/null +++ b/src/engine/gnc-pricedb-p.h @@ -0,0 +1,34 @@ +/******************************************************************** + * gnc-pricedb-p.h -- a simple price database for gnucash. * + * Copyright (C) 2001 Rob Browning * + * * + * 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 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * + *******************************************************************/ + +#ifndef __GNC_PRICEDB_P_H__ +#define __GNC_PRICEDB_P_H__ + +#include "gnc-pricedb.h" + +void gnc_pricedb_mark_clean(GNCPriceDB *db); +void gnc_pricedb_substitute_commodity(GNCPriceDB *db, + gnc_commodity *old_c, + gnc_commodity *new_c); + +#endif diff --git a/src/engine/gnc-pricedb-xml-v1.c b/src/engine/gnc-pricedb-xml-v1.c new file mode 100644 index 0000000000..c002d9cddc --- /dev/null +++ b/src/engine/gnc-pricedb-xml-v1.c @@ -0,0 +1,373 @@ +#include "config.h" + +#include + +#include "gnc-engine-util.h" + +#include "sixtp.h" +#include "sixtp-utils.h" +#include "sixtp-parsers.h" +#include "sixtp-dom-parsers.h" +#include "sixtp-dom-generators.h" +#include "sixtp-writers.h" +#include "sixtp-xml-write-utils.h" + +#include "gnc-pricedb.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_ENGINE; + +/* Read and Write the pricedb as XML -- something like this: + + + price-1 + price-2 + ... + + + where each price should look roughly like this: + + + + NASDAQ + RHAT + + + ISO? + USD + + Mon ...12 + Finance::Quote + bid + 11011/100 + + +*/ + +/***********************************************************************/ +/* READING */ +/***********************************************************************/ + +/****************************************************************************/ +/* + + restores a price. Does so via a walk of the XML tree in memory. + Returns a GNCPrice * in result. + + Right now, a price is legitimate even if all of it's fields are not + set. We may need to change that later, but at the moment. + +*/ + +static gboolean +price_parse_xml_sub_node(GNCPrice *p, xmlNodePtr sub_node) +{ + if(!p || !sub_node) return FALSE; + + if(safe_strcmp("price:commodity", sub_node->name) == 0) { + gnc_commodity *c = dom_tree_to_commodity_ref(sub_node); + if(!c) return FALSE; + gnc_price_set_commodity(p, c); + } else if(safe_strcmp("price:currency", sub_node->name) == 0) { + gnc_commodity *c = dom_tree_to_commodity_ref(sub_node); + if(!c) return FALSE; + gnc_price_set_currency(p, c); + } else if(safe_strcmp("price:time", sub_node->name) == 0) { + Timespec *t = dom_tree_to_timespec(sub_node); + if(!t) return FALSE; + gnc_price_set_time(p, t); + g_free(t); + } else if(safe_strcmp("price:source", sub_node->name) == 0) { + char *text = dom_tree_to_text(sub_node->xmlChildrenNode); + if(!text) return FALSE; + gnc_price_set_source(p, text); + g_free(text); + } else if(safe_strcmp("price:type", sub_node->name) == 0) { + char *text = dom_tree_to_text(sub_node->xmlChildrenNode); + if(!text) return FALSE; + gnc_price_set_type(p, text); + g_free(text); + } else if(safe_strcmp("price:value", sub_node->name) == 0) { + gnc_numeric *value = dom_tree_to_gnc_numeric(sub_node); + if(!value) return FALSE; + gnc_price_set_value(p, *value); + g_free(value); + } + return TRUE; +} + +static gboolean +price_parse_xml_end_handler(gpointer data_for_children, + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag) +{ + gboolean ok = TRUE; + xmlNodePtr price_xml = (xmlNodePtr) data_for_children; + xmlNodePtr child; + GNCPrice *p = NULL; + + /* we haven't been handed the *top* level node yet... */ + if(parent_data) return TRUE; + + *result = NULL; + + if(!price_xml) return FALSE; + if(price_xml->next) { ok = FALSE; goto cleanup_and_exit; } + if(price_xml->prev) { ok = FALSE; goto cleanup_and_exit; } + if(!price_xml->xmlChildrenNode) { ok = FALSE; goto cleanup_and_exit; } + + p = gnc_price_create(); + if(!p) { ok = FALSE; goto cleanup_and_exit; } + + for(child = price_xml->xmlChildrenNode; child; child = child->next) { + switch(child->type) { + case XML_COMMENT_NODE: + break; + case XML_ELEMENT_NODE: + if(!price_parse_xml_sub_node(p, child)) { + ok = FALSE; + goto cleanup_and_exit; + } + break; + default: + PERR("Unknown node type (%d) while parsing gnc-price xml.", child->type); + child = NULL; + ok = FALSE; + goto cleanup_and_exit; + break; + } + } + + cleanup_and_exit: + if(ok) { + *result = p; + } else { + *result = NULL; + gnc_price_unref(p); + } + xmlFreeNode(price_xml); + return ok; +} + +static void +cleanup_gnc_price(sixtp_child_result *result) +{ + if(result->data) gnc_price_unref((GNCPrice *) result->data); +} + +static sixtp * +gnc_price_parser_new (void) +{ + return sixtp_dom_parser_new(price_parse_xml_end_handler, + cleanup_gnc_price, + cleanup_gnc_price); +} + + +/****************************************************************************/ +/* (lineage ) + + restores a pricedb. We allocate the new db in the start block, the + children add to it, and it gets returned in result. Note that the + cleanup handler will destroy the pricedb, so the parent needs to + stop that if desired. + + result: GNCPriceDB* + + start: create new GNCPriceDB*, and leave in *data_for_children. + cleanup-result: destroy GNCPriceDB* + result-fail: destroy GNCPriceDB* + +*/ + +static gboolean +pricedb_start_handler(GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *data_for_children, + gpointer *result, + const gchar *tag, + gchar **attrs) +{ + GNCPriceDB *db = gnc_pricedb_create(); + g_return_val_if_fail(db, FALSE); + *result = db; + return(TRUE); +} + +static gboolean +pricedb_after_child_handler(gpointer data_for_children, + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag, + const gchar *child_tag, + sixtp_child_result *child_result) +{ + GNCPriceDB *db = (GNCPriceDB *) *result; + + g_return_val_if_fail(db, FALSE); + + /* right now children have to produce results :> */ + if(!child_result) return(FALSE); + if(child_result->type != SIXTP_CHILD_RESULT_NODE) return(FALSE); + + if(strcmp(child_result->tag, "price") == 0) { + GNCPrice *p = (GNCPrice *) child_result->data; + + g_return_val_if_fail(p, FALSE); + gnc_pricedb_add_price(db, p); + return TRUE; + } else { + return FALSE; + } + return FALSE; +} + +static void +pricedb_cleanup_result_handler(sixtp_child_result *result) +{ + if(result->data) { + GNCPriceDB *db = (GNCPriceDB *) result->data; + if(db) gnc_pricedb_destroy(db); + result->data = NULL; + } +} + +sixtp* +gnc_pricedb_parser_new(void) +{ + sixtp *top_level; + sixtp *price_parser; + + top_level = + sixtp_set_any(sixtp_new(), TRUE, + SIXTP_START_HANDLER_ID, pricedb_start_handler, + SIXTP_AFTER_CHILD_HANDLER_ID, pricedb_after_child_handler, + SIXTP_CHARACTERS_HANDLER_ID, + allow_and_ignore_only_whitespace, + SIXTP_RESULT_FAIL_ID, pricedb_cleanup_result_handler, + SIXTP_CLEANUP_RESULT_ID, pricedb_cleanup_result_handler, + SIXTP_NO_MORE_HANDLERS); + + if(!top_level) return NULL; + + price_parser = gnc_price_parser_new(); + + if(!price_parser) { + sixtp_destroy(top_level); + return NULL; + } + + sixtp_add_sub_parser(top_level, "price", price_parser); + return top_level; +} + +/***********************************************************************/ +/* WRITING */ +/***********************************************************************/ + +static gboolean +add_child_or_kill_parent(xmlNodePtr parent, xmlNodePtr child) +{ + if(!parent) return FALSE; + if(!child) { + xmlFreeNode(parent); + return FALSE; + } + xmlAddChild(parent, child); + return TRUE; +} + +static xmlNodePtr +gnc_price_to_dom_tree(const char *tag, GNCPrice *price) +{ + xmlNodePtr price_xml; + const gchar *typestr, *sourcestr; + xmlNodePtr tmpnode; + gnc_commodity *commodity; + gnc_commodity *currency; + Timespec *timesp; + gnc_numeric value; + + if (!(tag && price)) return NULL; + + price_xml = xmlNewNode(NULL, tag); + if(!price_xml) return NULL; + + commodity = gnc_price_get_commodity(price); + currency = gnc_price_get_currency(price); + + if(!(commodity && currency)) return NULL; + + tmpnode = commodity_ref_to_dom_tree("price:commodity", commodity); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + + tmpnode = commodity_ref_to_dom_tree("price:currency", currency); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + + timesp = gnc_price_get_time(price); + tmpnode = timespec_to_dom_tree("price:time", timesp); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + + sourcestr = gnc_price_get_source(price); + if(sourcestr && (strlen(sourcestr) != 0)) { + tmpnode = text_to_dom_tree("price:source", sourcestr); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + } + + typestr = gnc_price_get_type(price); + if(typestr && (strlen(typestr) != 0)) { + tmpnode = text_to_dom_tree("price:type", typestr); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + } + + value = gnc_price_get_value(price); + tmpnode = gnc_numeric_to_dom_tree("price:value", &value); + if(!add_child_or_kill_parent(price_xml, tmpnode)) return NULL; + + return price_xml; +} + +static gboolean +xml_add_gnc_price_adapter(GNCPrice *p, gpointer data) +{ + xmlNodePtr xml_node = (xmlNodePtr) data; + + if(p) { + xmlNodePtr price_xml = gnc_price_to_dom_tree("price", p); + if(!price_xml) return FALSE; + xmlAddChild(xml_node, price_xml); + return TRUE; + } else { + return TRUE; + } +} + +gboolean +xml_add_gnc_pricedb(xmlNodePtr p, const char *tag, GNCPriceDB *db) +{ + xmlNodePtr db_xml = NULL; + + if(!p || !tag) return FALSE; + if(!db) return TRUE; + + db_xml= xmlNewTextChild(p, NULL, tag, NULL); + if(!db_xml) return FALSE; + + xmlSetProp(db_xml, "version", "1"); + + if(!gnc_pricedb_foreach_price(db, xml_add_gnc_price_adapter, db_xml, TRUE)) + { + xmlFreeNode(db_xml); + return FALSE; + } + + return TRUE; +} diff --git a/src/engine/gnc-pricedb.c b/src/engine/gnc-pricedb.c new file mode 100644 index 0000000000..d11d1ce6ae --- /dev/null +++ b/src/engine/gnc-pricedb.c @@ -0,0 +1,733 @@ +/******************************************************************** + * gnc-pricedb.c -- a simple price database for gnucash. * + * Copyright (C) 2001 Rob Browning * + * * + * 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 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * + *******************************************************************/ + +#include "config.h" + +#include +#include + +#include "gnc-pricedb.h" +#include "gnc-pricedb-p.h" +#include "gnc-engine.h" +#include "gnc-engine-util.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_ENGINE; + +struct _GNCPriceDB { + GHashTable *commodity_hash; + gboolean dirty; +}; + +struct _GNCPrice { + guint32 refcount; + GNCPriceDB *db; + gnc_commodity *commodity; + gnc_commodity *currency; + Timespec time; + char *source; + char *type; + gnc_numeric value; +}; + +/****************************************************************************/ +/* GNCPrice functions + */ + +/* allocation */ +GNCPrice * +gnc_price_create() +{ + GNCPrice *p = g_new0(GNCPrice, 1); + p->refcount = 1; + return p; +} + +void +gnc_price_ref(GNCPrice *p) +{ + if(!p) return; + p->refcount++; +} + +void +gnc_price_unref(GNCPrice *p) +{ + if(!p) return; + if(p->refcount == 0) { + PERR("gnc_price_unref: refcount == 0!"); + assert(p->refcount != 0); + } + p->refcount--; + if(p->refcount == 0) { + if(p->type) g_cache_remove(gnc_engine_get_string_cache(), p->type); + memset(p, 0, sizeof(GNCPrice)); + g_free(p); + } +} + +GNCPrice * +gnc_price_clone(GNCPrice* p) +{ + /* the clone doesn't belong to a PriceDB */ + GNCPrice *new_p; + + if(!p) return NULL; + new_p = gnc_price_create(); + if(!new_p) return NULL; + gnc_price_set_commodity(new_p, gnc_price_get_commodity(p)); + gnc_price_set_time(new_p, gnc_price_get_time(p)); + gnc_price_set_source(new_p, gnc_price_get_source(p)); + gnc_price_set_type(new_p, gnc_price_get_type(p)); + gnc_price_set_value(new_p, gnc_price_get_value(p)); + gnc_price_set_currency(new_p, gnc_price_get_currency(p)); + return(new_p); +} + +/* setters */ +void +gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c) +{ + if(!p) return; + if(!gnc_commodity_equiv(p->commodity, c)) { + p->commodity = c; + if(p->db) p->db->dirty = TRUE; + } +} + +void +gnc_price_set_currency(GNCPrice *p, gnc_commodity *c) +{ + if(!p) return; + if(!gnc_commodity_equiv(p->currency, c)) { + p->currency = c; + if(p->db) p->db->dirty = TRUE; + } +} + +void +gnc_price_set_time(GNCPrice *p, Timespec *t) +{ + if(!p) return; + if(!timespec_equal(&(p->time), t)) { + p->time = *t; + if(p->db) p->db->dirty = TRUE; + } +} + +void +gnc_price_set_source(GNCPrice *p, const char *s) +{ + if(!p) return; + if(safe_strcmp(p->source, s) != 0) { + GCache *cache = gnc_engine_get_string_cache(); + char *tmp = g_cache_insert(cache, (gpointer) s); + if(p->source) g_cache_remove(cache, p->source); + p->source = tmp; + if(p->db) p->db->dirty = TRUE; + } +} + +void +gnc_price_set_type(GNCPrice *p, const char* type) +{ + if(!p) return; + if(safe_strcmp(p->type, type) != 0) { + GCache *cache = gnc_engine_get_string_cache(); + gchar *tmp = g_cache_insert(cache, (gpointer) type); + if(p->type) g_cache_remove(cache, p->type); + p->type = tmp; + if(p->db) p->db->dirty = TRUE; + } +} + +void +gnc_price_set_value(GNCPrice *p, gnc_numeric value) +{ + if(!p) return; + if(!gnc_numeric_eq(p->value, value)) { + p->value = value; + if(p->db) p->db->dirty = TRUE; + } +} + + +/***********/ +/* getters */ +gnc_commodity * +gnc_price_get_commodity(GNCPrice *p) +{ + if(!p) return NULL; + return p->commodity; +} + +Timespec * +gnc_price_get_time(GNCPrice *p) +{ + if(!p) return NULL; + return &(p->time); +} + +const char * +gnc_price_get_source(GNCPrice *p) +{ + if(!p) return NULL; + return p->source; +} + +const char * +gnc_price_get_type(GNCPrice *p) +{ + if(!p) return NULL; + return p->type; +} + +gnc_numeric +gnc_price_get_value(GNCPrice *p) +{ + if(!p) { + PERR("gnc_price_get_value: price NULL.\n"); + return gnc_numeric_zero(); + } + return p->value; +} + +gnc_commodity * +gnc_price_get_currency(GNCPrice *p) +{ + if(!p) return NULL; + return p->currency; +} + +/* setters */ + +static gint +compare_prices_by_date(gconstpointer a, gconstpointer b) +{ + Timespec *time_a; + Timespec *time_b; + if(!a && !b) return 0; + /* nothing is always less than something */ + if(!a) return -1; + + time_a = gnc_price_get_time((GNCPrice *) a); + time_b = gnc_price_get_time((GNCPrice *) b); + return timespec_cmp(time_a, time_b); +} + +gboolean +gnc_price_list_insert(GList **prices, GNCPrice *p) +{ + GList *result_list; + + if(!prices) return FALSE; + if(!p) return FALSE; + gnc_price_ref(p); + result_list = g_list_insert_sorted(*prices, p, compare_prices_by_date); + if(!result_list) return FALSE; + *prices = result_list; + return TRUE; +} + +gboolean +gnc_price_list_remove(GList **prices, GNCPrice *p) +{ + GList *result_list; + GList *found_element; + + if(!prices) return FALSE; + if(!p) return FALSE; + + found_element = g_list_find(*prices, p); + if(!found_element) return TRUE; + + result_list = g_list_remove_link(*prices, found_element); + gnc_price_unref((GNCPrice *) found_element->data); + g_list_free(found_element); + + *prices = result_list; + return TRUE; +} + +static void +price_list_destroy_helper(gpointer data, gpointer user_data) +{ + gnc_price_unref((GNCPrice *) data); +} + +void +gnc_price_list_destroy(GList *prices) +{ + g_list_foreach(prices, price_list_destroy_helper, NULL); + g_list_free(prices); +} + +/****************************************************************************/ +/* GNCPriceDB functions + + A pricedb is a hash mapping price commodities (of type + gnc_commodity*) to hashes mapping price currencies (of type + gnc_commodity*) to price lists. The top-level key is the commodity + you want the prices for, and the second level key is the commodity + that the value is expressed in terms of. + + See the header for other info. + + */ + +GNCPriceDB * +gnc_pricedb_create() +{ + GNCPriceDB * result = g_new0(GNCPriceDB, 1); + result->commodity_hash = g_hash_table_new(g_direct_hash, g_direct_equal); + g_return_val_if_fail (result->commodity_hash, NULL); + return result; +} + +static void +destroy_pricedb_currency_hash_data(gpointer key, + gpointer data, + gpointer user_data) +{ + GList *price_list = (GList *) data; + gnc_price_list_destroy(price_list); +} + +static void +destroy_pricedb_commodity_hash_data(gpointer key, + gpointer data, + gpointer user_data) +{ + GHashTable *currency_hash = (GHashTable *) data; + if (!currency_hash) return; + g_hash_table_foreach (currency_hash, + destroy_pricedb_currency_hash_data, + NULL); + g_hash_table_destroy(currency_hash); + currency_hash = NULL; +} + +gboolean +gnc_pricedb_dirty(GNCPriceDB *p) +{ + if(!p) return FALSE; + return p->dirty; +} + +void +gnc_pricedb_mark_clean(GNCPriceDB *p) +{ + if(!p) return; + p->dirty = FALSE; +} + +void +gnc_pricedb_destroy(GNCPriceDB *db) +{ + if(!db) return; + g_hash_table_foreach (db->commodity_hash, + destroy_pricedb_commodity_hash_data, + NULL); + g_hash_table_destroy (db->commodity_hash); + db->commodity_hash = NULL; +} + +gboolean +gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p) +{ + /* this function will use p, adding a ref, so treat p as read-only + if this function succeeds. */ + GList *price_list; + gnc_commodity *commodity; + gnc_commodity *currency; + GHashTable *currency_hash; + + if(!db || !p) return FALSE; + commodity = gnc_price_get_commodity(p); + if(!commodity) return FALSE; + currency = gnc_price_get_currency(p); + if(!currency) return FALSE; + if(!db->commodity_hash) return FALSE; + + currency_hash = g_hash_table_lookup(db->commodity_hash, commodity); + if(!currency_hash) { + currency_hash = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_insert(db->commodity_hash, commodity, currency_hash); + } + + price_list = g_hash_table_lookup(currency_hash, currency); + if(!gnc_price_list_insert(&price_list, p)) return FALSE; + if(!price_list) return FALSE; + g_hash_table_insert(currency_hash, currency, price_list); + db->dirty = TRUE; + return TRUE; +} + +gboolean +gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p) +{ + GList *price_list; + gnc_commodity *commodity; + gnc_commodity *currency; + GHashTable *currency_hash; + + if(!db || !p) return FALSE; + commodity = gnc_price_get_commodity(p); + if(!commodity) return FALSE; + currency = gnc_price_get_currency(p); + if(!currency) return FALSE; + if(!db->commodity_hash) return FALSE; + + currency_hash = g_hash_table_lookup(db->commodity_hash, commodity); + if(!currency_hash) return FALSE; + + price_list = g_hash_table_lookup(currency_hash, currency); + if(!gnc_price_list_remove(&price_list, p)) return FALSE; + if(price_list) { + g_hash_table_insert(currency_hash, currency, price_list); + } else { + g_hash_table_remove(currency_hash, currency); + } + db->dirty = TRUE; + return TRUE; +} + +GNCPrice * +gnc_pricedb_lookup_latest(GNCPriceDB *db, + gnc_commodity *commodity, + gnc_commodity *currency) +{ + GList *price_list; + GNCPrice *result; + GHashTable *currency_hash; + + if(!db || !commodity || !currency) return NULL; + + currency_hash = g_hash_table_lookup(db->commodity_hash, commodity); + if(!currency_hash) return NULL; + + price_list = g_hash_table_lookup(currency_hash, currency); + if(!price_list) return NULL; + + result = price_list->data; + gnc_price_ref(result); + return result; +} + +GList * +gnc_pricedb_lookup_at_time(GNCPriceDB *db, + gnc_commodity *c, + gnc_commodity *currency, + Timespec *t) +{ + GList *price_list; + GList *result = NULL; + GList *item = NULL; + GHashTable *currency_hash; + + if(!db || !c || !currency || !t) return NULL; + + currency_hash = g_hash_table_lookup(db->commodity_hash, c); + if(!currency_hash) return NULL; + + price_list = g_hash_table_lookup(currency_hash, currency); + if(!price_list) return NULL; + + item = price_list; + while(item) { + GNCPrice *p = item->data; + Timespec *price_time = gnc_price_get_time(p); + if(timespec_equal(price_time, t)) { + result = g_list_prepend(result, p); + gnc_price_ref(p); + } + item = item->next; + } + return result; +} + +/***************************************************************************/ +/* gnc_pricedb_foreach_price infrastructure + */ + +typedef struct { + gboolean ok; + gboolean (*func)(GNCPrice *p, gpointer user_data); + gpointer user_data; +} GNCPriceDBForeachData; + +static void +pricedb_foreach_pricelist(gpointer key, gpointer val, gpointer user_data) +{ + GList *price_list = (GList *) val; + GList *node = price_list; + GNCPriceDBForeachData *foreach_data = (GNCPriceDBForeachData *) user_data; + + while(foreach_data->ok && node) { + GNCPrice *p = (GNCPrice *) node->data; + foreach_data->func(p, foreach_data->user_data); + node = node->next; + } +} + +static void +pricedb_foreach_currencies_hash(gpointer key, gpointer val, gpointer user_data) +{ + GHashTable *currencies_hash = (GHashTable *) val; + g_hash_table_foreach(currencies_hash, pricedb_foreach_pricelist, user_data); +} + +static gboolean +unstable_price_traversal(GNCPriceDB *db, + gboolean (*f)(GNCPrice *p, gpointer user_data), + gpointer user_data) +{ + GNCPriceDBForeachData foreach_data; + + if(!db || !f) return FALSE; + foreach_data.ok = TRUE; + foreach_data.func = f; + foreach_data.user_data = user_data; + + g_hash_table_foreach(db->commodity_hash, + pricedb_foreach_currencies_hash, + &foreach_data); + + return foreach_data.ok; +} + +static gint +compare_kvpairs_by_commodity_key(gconstpointer a, gconstpointer b) +{ + GHashTableKVPair *kvpa = (GHashTableKVPair *) a; + GHashTableKVPair *kvpb = (GHashTableKVPair *) b; + gnc_commodity *ca; + gnc_commodity *cb; + int cmp_result; + + if(a == b) return 0; + if(!a && !b) return 0; + if(!a) return -1; + if(!b) return 1; + + ca = (gnc_commodity *) kvpa->key; + cb = (gnc_commodity *) kvpb->key; + + cmp_result = safe_strcmp(gnc_commodity_get_namespace(ca), + gnc_commodity_get_namespace(cb)); + + if(cmp_result != 0) return cmp_result; + + return safe_strcmp(gnc_commodity_get_mnemonic(ca), + gnc_commodity_get_mnemonic(cb)); +} + +static gboolean +stable_price_traversal(GNCPriceDB *db, + gboolean (*f)(GNCPrice *p, gpointer user_data), + gpointer user_data) +{ + GSList *currency_hashes = NULL; + GSList *foo = NULL; + gboolean ok = TRUE; + GSList *i = NULL; + + if(!db || !f) return FALSE; + + currency_hashes = g_hash_table_key_value_pairs(db->commodity_hash); + currency_hashes = g_slist_sort(currency_hashes, + compare_kvpairs_by_commodity_key); + + for(i = currency_hashes; i; i = i->next) { + GHashTableKVPair *kv_pair = (GHashTableKVPair *) i->data; + GHashTable *currency_hash = (GHashTable *) kv_pair->value; + GSList *price_lists = g_hash_table_key_value_pairs(currency_hash); + GSList *j; + + price_lists = g_slist_sort(price_lists, compare_kvpairs_by_commodity_key); + for(j = price_lists; j; j = j->next) { + GHashTableKVPair *pricelist_kvp = (GHashTableKVPair *) j->data; + GList *price_list = (GList *) pricelist_kvp->value; + GList *node; + + for(node = (GList *) price_list; node; node = node->next) { + GNCPrice *price = (GNCPrice *) node->data; + if(!f(price, user_data)) ok = FALSE; + } + } + if(price_lists) { + g_slist_foreach(price_lists, g_hash_table_kv_pair_free_gfunc, NULL); + g_slist_free(price_lists); + price_lists = NULL; + } + } + + if(currency_hashes) { + g_slist_foreach(currency_hashes, g_hash_table_kv_pair_free_gfunc, NULL); + g_slist_free(currency_hashes); + } + return ok; +} + +gboolean +gnc_pricedb_foreach_price(GNCPriceDB *db, + gboolean (*f)(GNCPrice *p, gpointer user_data), + gpointer user_data, + gboolean stable_order) +{ + if(stable_order) return stable_price_traversal(db, f, user_data); + return unstable_price_traversal(db, f, user_data); +} + +/***************************************************************************/ +/* commodity substitution + */ + +typedef struct { + gnc_commodity *old_c; + gnc_commodity *new_c; +} GNCPriceFixupData; + +static gboolean +gnc_price_fixup_legacy_commods(GNCPrice *p, gpointer data) +{ + GNCPriceFixupData *fixup_data = (GNCPriceFixupData *) data; + gnc_commodity *price_c; + + if (!p) return FALSE; + + price_c = gnc_price_get_commodity(p); + if (gnc_commodity_equiv(price_c, fixup_data->old_c)) { + gnc_price_set_commodity(p, fixup_data->new_c); + } + price_c = gnc_price_get_currency(p); + if (gnc_commodity_equiv(price_c, fixup_data->old_c)) { + gnc_price_set_currency(p, fixup_data->new_c); + } + return TRUE; +} + + +static void +remap_currency_hash_keys(gpointer key, gpointer val, gpointer user_data) +{ + GHashTable *currencies_hash = (GHashTable *) val; + GNCPriceFixupData *fixup_data = (GNCPriceFixupData *) user_data; + GList *price_list; + + price_list = g_hash_table_lookup(currencies_hash, fixup_data->old_c); + if(price_list) { + g_hash_table_remove(currencies_hash, fixup_data->old_c); + g_hash_table_insert(currencies_hash, fixup_data->new_c, price_list); + } +} + +void +gnc_pricedb_substitute_commodity(GNCPriceDB *db, + gnc_commodity *old_c, + gnc_commodity *new_c) +{ + GHashTable *currency_hash; + GNCPriceFixupData data; + + if(!db || !old_c || !new_c) return; + + data.old_c = old_c; + data.new_c = new_c; + + /* first remap the relevant commodity -> currency hash, if any */ + currency_hash = g_hash_table_lookup(db->commodity_hash, old_c); + if(currency_hash) { + g_hash_table_remove(db->commodity_hash, old_c); + g_hash_table_insert(db->commodity_hash, new_c, currency_hash); + } + + g_hash_table_foreach(db->commodity_hash, remap_currency_hash_keys, &data); + + if(!gnc_pricedb_foreach_price(db, + gnc_price_fixup_legacy_commods, + &data, + FALSE)) { + PERR("Adjustments to legacy commodity pointers in pricedb failed!"); + } +} + +/***************************************************************************/ + +#if 0 + +/* Semi-lame debugging code */ + +void +gnc_price_print(GNCPrice *p, FILE *f) +{ + gnc_commodity *commodity; + gnc_commodity *currency; + if(!p) return; + if(!f) return; + + commodity = gnc_price_get_commodity(p); + currency = gnc_price_get_currency(p); + + if(!commodity) return; + if(!currency) return; + + fprintf(f, " \n"); + fprintf(f, " \n", commodity); + fprintf(f, " %s\n", + gnc_commodity_get_namespace(commodity)); + fprintf(f, " %s\n", + gnc_commodity_get_mnemonic(commodity)); + fprintf(f, " \n"); + fprintf(f, " \n", currency); + fprintf(f, " %s\n", + gnc_commodity_get_namespace(currency)); + fprintf(f, " %s\n", + gnc_commodity_get_mnemonic(currency)); + fprintf(f, " \n"); + fprintf(f, " %s\n", gnc_price_get_source(p)); + fprintf(f, " %s\n", gnc_price_get_type(p)); + fprintf(f, " %g\n", gnc_numeric_to_double(gnc_price_get_value(p))); + fprintf(f, " \n"); +} + +static gboolean +print_pricedb_adapter(GNCPrice *p, gpointer user_data) +{ + FILE *f = (FILE *) user_data; + gnc_price_print(p, f); + return TRUE; +} + +void +gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f) +{ + if(!db) { PERR("NULL PriceDB\n"); return; } + if(!f) { PERR("NULL FILE*\n"); return; } + + fprintf(f, "\n"); + gnc_pricedb_foreach_price(db, print_pricedb_adapter, f, FALSE); + fprintf(f, "\n"); +} + +#endif diff --git a/src/engine/gnc-pricedb.h b/src/engine/gnc-pricedb.h new file mode 100644 index 0000000000..fdd25d11b6 --- /dev/null +++ b/src/engine/gnc-pricedb.h @@ -0,0 +1,169 @@ +/******************************************************************** + * gnc-pricedb.h -- a simple price database for gnucash. * + * Copyright (C) 2001 Rob Browning * + * * + * 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 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * + *******************************************************************/ + +#ifndef __GNC_PRICEDB_H__ +#define __GNC_PRICEDB_H__ + +#include "date.h" +#include "gnc-numeric.h" +#include "gnc-commodity.h" + +#include + +/**********************************************************************\ + + GNCPrice: + + commodity: the item being priced. + + value: the value of the item being priced. + + currency: the denomination of the value of the item being priced. + + time: the time the price was valid. + + source: a string describing the source of the quote. These + strings will be something like this: "Finance::Quote", + "user:misc", "user:foo", etc. If the quote came from a user, as a + matter of policy, you *must* prefix the string you give with + "user:". For now, the only other reserved values are + "Finance::Quote" and "old-file-import". + + type: the type of quote - types possible right now are bid, ask, + last, and unknown. + + NOTE: for source and type, NULL and the empty string are + considered the same, so if one of these is "", then after a file + save/restore, it might be NULL. Behave accordingly. + + GNCPrices are reference counted. When you gnc_price_create one or + clone it, the new price's count is set to 1. When you're finished + with a price, call gnc_price_unref. If you hand the pointer to + some other code that needs to keep it, make sure it calls + gnc_price_ref to indicate it's interest in that price, and calls + gnc_price_unref when it's finished with the price. + + All of the getters return data that's internal to the GNCPrice, + not copies, so don't free these values. + + All of the setters store copies of the data, with the exception of + the commodity field which just stores the pointer given. It is + assumed that commodities are a global resource and are pointer + unique. + + */ + +typedef struct _GNCPrice GNCPrice; + +/* allocation */ +GNCPrice *gnc_price_create(void); /* create and initialize a price */ +GNCPrice *gnc_price_clone(GNCPrice* p); +void gnc_price_ref(GNCPrice *p); +void gnc_price_unref(GNCPrice *p); + +/* setters */ +void gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c); +void gnc_price_set_currency(GNCPrice *p, gnc_commodity *c); +void gnc_price_set_time(GNCPrice *p, Timespec *t); +void gnc_price_set_source(GNCPrice *p, const char *source); +void gnc_price_set_type(GNCPrice *p, const char* type); +void gnc_price_set_value(GNCPrice *p, gnc_numeric value); + +/* getters */ +gnc_commodity * gnc_price_get_commodity(GNCPrice *p); +gnc_commodity * gnc_price_get_currency(GNCPrice *p); +Timespec * gnc_price_get_time(GNCPrice *p); +const char * gnc_price_get_source(GNCPrice *p); +const char * gnc_price_get_type(GNCPrice *p); +gnc_numeric gnc_price_get_value(GNCPrice *p); + +/* price_list funcs + + A price list is a time sorted GList of prices. The price list + maintains a ref for itself for all the prices in the list + (i.e. adding a price calls gnc_price_ref on it, removing a price + calls gnc_price_unref, etc. Destroying a list also removes this + list reference from the prices in the list. */ +gboolean gnc_price_list_insert(GList **prices, GNCPrice *p); +gboolean gnc_price_list_remove(GList **prices, GNCPrice *p); +void gnc_price_list_destroy(GList *prices); + + +/********************************************************************** + GNCPriceDB + + Whenever a you store a price in the pricedb, the pricedb adds its + own reference to the price, so you can safely unref that price when + you're finished with it. + + Similarly, when the pricedb returns a price to you, either singly, + or in a price list, the price will have had a ref added for you, so + you only need to unref the price(s) when you're finished with them. + +*/ + +typedef struct _GNCPriceDB GNCPriceDB; + +GNCPriceDB * gnc_pricedb_create(void); + +void gnc_pricedb_destroy(GNCPriceDB *db); + +/* Add a price to the pricedb, you may drop your reference to the + price (i.e. call unref) after this succeeds, whenever you're + finished with the price. */ +gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p); + +/* Remove a price from the pricedb. */ +gboolean gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p); + +GNCPrice * gnc_pricedb_lookup_latest(GNCPriceDB *db, + gnc_commodity *comodity, + gnc_commodity *currency); + +/* Return all prices that match the given commodity, currency, and + timespec. Prices will be returned as a price_list (see above) */ +GList * gnc_pricedb_lookup_at_time(GNCPriceDB *db, + gnc_commodity *commodity, + gnc_commodity *currency, + Timespec *t); + +/* Call f once for each price in db, until and unless f returns FALSE. + If stable_order is not FALSE, make sure the ordering of the + traversal is stable (i.e. the same order every time given the same + db contents). */ +gboolean gnc_pricedb_foreach_price(GNCPriceDB *db, + gboolean (*f)(GNCPrice *p, + gpointer user_data), + gpointer user_data, + gboolean stable_order); + +/* Return FALSE if the database has not been modified */ +gboolean gnc_pricedb_dirty(GNCPriceDB *p); + +#if 0 +/* semi-lame debugging code */ +void gnc_price_print(GNCPrice *db, FILE *f); +void gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f); +#endif + +#endif diff --git a/src/engine/io-gncbin-r.c b/src/engine/io-gncbin-r.c index 9299da0be5..1b0aecad95 100644 --- a/src/engine/io-gncbin-r.c +++ b/src/engine/io-gncbin-r.c @@ -108,7 +108,9 @@ #include "gnc-engine.h" #include "gnc-engine-util.h" #include "GNCIdP.h" +#include "gnc-pricedb.h" +#include "gnc-book-p.h" #define PERMS 0666 #define WFLAGS (O_WRONLY | O_CREAT | O_TRUNC) @@ -152,12 +154,96 @@ static AccountGroup *maingrp; /* temporary holder for file in our handling of keys and values here. We use int32s and pointers interchangably ATM, and that may be problematic on some architectures... + + Only used *during* file read process. */ -/* used while reading */ static GHashTable *ids_to_finished_accounts; static GHashTable *ids_to_unfinished_accounts; +/** PriceDB bits ******************************************************/ +/* Commodity prices (stock quotes, etc.) used to be stored as zero + quantity (damount) splits inside the relevant accounts. Now we + used the pricedb. potential_quotes is where we put the info from + all these "quote splits" while reading, until we have a chance to + cram them into the pricedb. We can't do it any sooner because + until we finish reading the file, the Account*'s havent' been + filled out and so their commodities and types may not have been + initilized yet. + + This shouldn't be a static global but see comments above RE ids_to* + hashes. */ + +typedef struct { + Split *split; + gnc_numeric price; +} GNCPotentialQuote; + +static GSList *potential_quotes; + +static void +mark_potential_quote(Split *s, double price, double quantity) +{ + static int count = 0; + + if((price != 0) && DEQ(quantity, 0)){ + GNCPotentialQuote *q = g_new0(GNCPotentialQuote, 1); + q->split = s; + q->price = double_to_gnc_numeric(price, + GNC_DENOM_AUTO, + GNC_DENOM_SIGFIGS(9) | GNC_RND_ROUND); + potential_quotes = g_slist_prepend(potential_quotes, q); + } +} + +static gboolean +cvt_potential_prices_to_pricedb_and_cleanup(GNCPriceDB **prices) +{ + GSList *item = potential_quotes; + + *prices = gnc_pricedb_create(); + if (!*prices) return FALSE; + + while(item) + { + GNCPotentialQuote *q = (GNCPotentialQuote *) item->data; + Account *split_acct = xaccSplitGetAccount(q->split); + GNCAccountType acct_type = xaccAccountGetType(split_acct); + + /* at this point, we already know it's a split with a zero amount */ + if((acct_type == STOCK) || + (acct_type == MUTUAL) || + (acct_type == CURRENCY)) { + /* this is a quote -- file it in the db and kill the split */ + Transaction *txn = xaccSplitGetParent(q->split); + GNCPrice *price = gnc_price_create(); + Timespec time = xaccTransRetDateEnteredTS(txn); + + gnc_price_set_commodity(price, xaccAccountGetSecurity(split_acct)); + gnc_price_set_currency(price, xaccAccountGetCurrency(split_acct)); + gnc_price_set_time(price, &time); + gnc_price_set_source(price, "old-file-import"); + gnc_price_set_type(price, "unknown"); + gnc_price_set_value(price, q->price); + if(!gnc_pricedb_add_price(*prices, price)) { + PERR("problem adding price to pricedb.\n"); + } + gnc_price_unref(price); + + xaccTransBeginEdit(txn); + xaccSplitDestroy(q->split); + xaccTransCommitEdit(txn); + } + g_free(item->data); + item->data = NULL; + item = item->next; + } + g_slist_free(potential_quotes); + potential_quotes = NULL; + + return TRUE; +} + /** PROTOTYPES ******************************************************/ static Account *locateAccount (int acc_id); @@ -197,8 +283,8 @@ static int readTSDate( int fd, Timespec *, int token ); /*******************************************************/ -GNCBackendError -xaccGetGncBinFileIOError (void) +GNCBackendError +gnc_book_get_binfile_io_error(void) { /* reset the error code */ int rc = error_code; @@ -307,15 +393,15 @@ gnc_commodity_import_legacy(const char * currency_name) { \********************************************************************/ /********************************************************************\ - * xaccReadAccountGroup * * reads in the data from file descriptor * * * - * Args: fd -- the file descriptor to read the data from * + * Args: book -- the book in which to store the data * + * fd -- the file descriptor to read the data from * * Return: the struct with the program data in it * \********************************************************************/ -static AccountGroup * -xaccReadAccountGroup(int fd) - { +static gboolean +gnc_load_financials_from_fd(GNCBook *book, int fd) +{ int err=0; int token=0; int num_unclaimed; @@ -328,7 +414,7 @@ xaccReadAccountGroup(int fd) if( 0 > fd ) { error_code = ERR_FILEIO_FILE_NOT_FOUND; - return NULL; + return FALSE; } /* Read in the file format token */ @@ -336,7 +422,7 @@ xaccReadAccountGroup(int fd) if( sizeof(int) != err ) { error_code = ERR_FILEIO_FILE_EMPTY; - return NULL; + return FALSE; } XACC_FLIP_INT (token); @@ -350,36 +436,33 @@ xaccReadAccountGroup(int fd) * with, warn the user */ if( VERSION < token ) { error_code = ERR_FILEIO_FILE_TOO_NEW; - return NULL; + return FALSE; } /* FIXME: is this OK (i.e. direct hashes for ints?) */ ids_to_finished_accounts = g_hash_table_new(g_direct_hash, g_direct_equal); if(!ids_to_finished_accounts) { - error_code = ERR_FILEIO_ALLOC; - return(NULL); + error_code = ERR_BACKEND_ALLOC; + return FALSE; } ids_to_unfinished_accounts = g_hash_table_new(g_direct_hash, g_direct_equal); if(!ids_to_unfinished_accounts) { - error_code = ERR_FILEIO_ALLOC; + error_code = ERR_BACKEND_ALLOC; g_hash_table_destroy(ids_to_finished_accounts); ids_to_finished_accounts = NULL; - return(NULL); + return FALSE; } + potential_quotes = NULL; + /* disable logging during load; otherwise its just a mess */ xaccLogDisable(); holder = xaccMallocAccountGroup(); grp = readGroup (fd, NULL, token); - /* mark the newly read group as saved, since the act of putting - * it together will have caused it to be marked up as not-saved. - */ - xaccGroupMarkSaved (grp); - /* auto-number the accounts, if they are not already numbered */ xaccGroupDepthAutoCode (grp); @@ -409,11 +492,35 @@ xaccReadAccountGroup(int fd) g_hash_table_destroy(ids_to_unfinished_accounts); ids_to_unfinished_accounts = NULL; + { + GNCPriceDB *tmpdb; + if(cvt_potential_prices_to_pricedb_and_cleanup(&tmpdb)) { + GNCPriceDB *db = gnc_book_get_pricedb(book); + if(db) gnc_pricedb_destroy(db); + gnc_book_set_pricedb(book, tmpdb); + } else { + PWARN("pricedb import failed."); + error_code = ERR_BACKEND_MISC; + gnc_pricedb_destroy(tmpdb); + } + } + /* set up various state that is not normally stored in the byte stream */ xaccRecomputeGroupBalance (grp); xaccLogEnable(); - return grp; + + { + AccountGroup *g = gnc_book_get_group(book); + if (g) xaccFreeAccountGroup(g); + gnc_book_set_group(book, grp); + } + + /* mark the newly read book as saved, since the act of putting it + * together will have caused it to be marked up as not-saved. */ + gnc_book_mark_saved(book); + + return (error_code == ERR_BACKEND_NO_ERR); } /********************************************************************\ @@ -423,25 +530,30 @@ xaccReadAccountGroup(int fd) * Args: datafile - the file to load the data from * * Return: the struct with the program data in it * \********************************************************************/ -AccountGroup * -xaccReadGncBinAccountGroupFile( const char *datafile ) - { +void +gnc_book_load_from_binfile(GNCBook *book) +{ int fd; - AccountGroup *grp = 0x0; + + const gchar *datafile = gnc_book_get_file_path(book); + if(!datafile) { + error_code = ERR_BACKEND_MISC; + return; + } maingrp = 0x0; error_code = ERR_BACKEND_NO_ERR; fd = open( datafile, RFLAGS, 0 ); - if( 0 > fd ) - { + if( 0 > fd ) { error_code = ERR_FILEIO_FILE_NOT_FOUND; - return NULL; - } - grp = xaccReadAccountGroup (fd); + return; + } + + if(!gnc_load_financials_from_fd(book, fd)) return; close(fd); - return grp; + return; } /********************************************************************\ @@ -644,17 +756,15 @@ readAccount( int fd, AccountGroup *grp, int token ) DEBUG ("expecting %d transactions \n", numTrans); /* read the transactions */ - for( i=0; i= token) { + if (revision <= 7) { time_t secs; - secs = readDMYDate( fd, token ); + secs = readDMYDate( fd, revision ); if( 0 == secs ) { PERR ("Premature end of Transaction at date"); @@ -855,7 +959,7 @@ readTransaction( int fd, Account *acc, int token ) int rc; /* read posted date first ... */ - rc = readTSDate( fd, &ts, token ); + rc = readTSDate( fd, &ts, revision ); if( -1 == rc ) { PERR ("Premature end of Transaction at date"); @@ -866,7 +970,7 @@ readTransaction( int fd, Account *acc, int token ) xaccTransSetDateTS (trans, &ts); /* then the entered date ... */ - rc = readTSDate( fd, &ts, token ); + rc = readTSDate( fd, &ts, revision ); if( -1 == rc ) { PERR ("Premature end of Transaction at date"); @@ -877,7 +981,7 @@ readTransaction( int fd, Account *acc, int token ) xaccTransSetDateEnteredTS (trans, &ts); } - tmp = readString( fd, token ); + tmp = readString( fd, revision ); if( NULL == tmp ) { PERR ("Premature end of Transaction at description"); @@ -892,8 +996,8 @@ readTransaction( int fd, Account *acc, int token ) deprecated, and we don't think anyone ever used them anyway, but to be safe, if we find one, we store it in the old-docref slot, a la old-price-source. */ - if (8 <= token) { - tmp = readString( fd, token ); + if (revision >= 8) { + tmp = readString( fd, revision ); if( NULL == tmp ) { PERR ("Premature end of Transaction at docref"); xaccTransDestroy(trans); @@ -916,17 +1020,10 @@ readTransaction( int fd, Account *acc, int token ) * moved to splits. Thus, vast majority of stuff below * is skipped */ - if (4 >= token) - { - Split * s; + if (revision <= 4) { + Split* s; - /* The code below really wants to assume that there are a pair - * of splits in every transaction, so make it so. - */ - s = xaccMallocSplit (); - xaccTransAppendSplit (trans, s); - - tmp = readString( fd, token ); + tmp = readString( fd, revision ); if( NULL == tmp ) { PERR ("Premature end of Transaction at memo"); @@ -941,9 +1038,9 @@ readTransaction( int fd, Account *acc, int token ) free (tmp); /* action first introduced in version 3 of the file format */ - if (3 <= token) + if (revision >= 3) { - tmp = readString( fd, token ); + tmp = readString( fd, revision ); if( NULL == tmp ) { PERR ("Premature end of Transaction at action"); @@ -973,12 +1070,21 @@ readTransaction( int fd, Account *acc, int token ) xaccTransCommitEdit (trans); return NULL; } + + /* The code below really wants to assume that there are a pair + * of splits in every transaction, so make it so. + */ + s = xaccMallocSplit (); + xaccTransAppendSplit (trans, s); + s = xaccMallocSplit (); + xaccTransAppendSplit (trans, s); + s = xaccTransGetSplit (trans, 0); xaccSplitSetReconcile (s, recn); s = xaccTransGetSplit (trans, 1); xaccSplitSetReconcile (s, recn); - - if( 1 >= token ) { + + if(revision <= 1) { /* Note: this is for version 0 of file format only. * What used to be reconciled, is now cleared... transactions * aren't reconciled until you get your bank statement, and @@ -996,8 +1102,9 @@ readTransaction( int fd, Account *acc, int token ) * with the amount recorded as pennies. * Version 2 and above store the share amounts and * prices as doubles. */ - if (1 == token) { + if (1 == revision) { int amount; + err = read( fd, &amount, sizeof(int) ); if( sizeof(int) != err ) { @@ -1010,7 +1117,12 @@ readTransaction( int fd, Account *acc, int token ) num_shares = 0.01 * ((double) amount); /* file stores pennies */ s = xaccTransGetSplit (trans, 0); DxaccSplitSetShareAmount (s, num_shares); + + /* Version 1 files did not do double-entry */ + s = xaccTransGetSplit (trans, 0); + xaccAccountInsertSplit( acc, s ); } else { + Account *peer_acc; double damount; /* first, read number of shares ... */ @@ -1037,15 +1149,12 @@ readTransaction( int fd, Account *acc, int token ) XACC_FLIP_DOUBLE (damount); share_price = damount; s = xaccTransGetSplit (trans, 0); + DxaccSplitSetSharePriceAndAmount (s, share_price, num_shares); - } - - DEBUG ("num_shares %f \n", num_shares); - - /* Read the account numbers for double-entry */ - /* These are first used in Version 2 of the file format */ - if (1 < token) { - Account *peer_acc; + + /* Read the account numbers for double-entry */ + /* These are first used in Version 2 of the file format */ + /* first, read the credit account number */ err = read( fd, &acc_id, sizeof(int) ); if( err != sizeof(int) ) @@ -1064,6 +1173,8 @@ readTransaction( int fd, Account *acc, int token ) s = xaccTransGetSplit (trans, 0); if (peer_acc) xaccAccountInsertSplit( peer_acc, s); + mark_potential_quote(s, share_price, num_shares); + /* next read the debit account number */ err = read( fd, &acc_id, sizeof(int) ); if( err != sizeof(int) ) @@ -1077,74 +1188,50 @@ readTransaction( int fd, Account *acc, int token ) DEBUG ("debit %d\n", acc_id); peer_acc = locateAccount (acc_id); if (peer_acc) { - Split *split; - s = xaccTransGetSplit (trans, 0); - split = xaccTransGetSplit (trans, 1); - + Split *s0 = xaccTransGetSplit (trans, 0); + Split *s1 = xaccTransGetSplit (trans, 1); + /* duplicate many of the attributes in the credit split */ - DxaccSplitSetSharePriceAndAmount (split, share_price, -num_shares); - xaccSplitSetReconcile (split, xaccSplitGetReconcile (s)); - xaccSplitSetMemo (split, xaccSplitGetMemo (s)); - xaccSplitSetAction (split, xaccSplitGetAction (s)); - xaccAccountInsertSplit (peer_acc, split); + DxaccSplitSetSharePriceAndAmount (s1, share_price, -num_shares); + xaccSplitSetReconcile (s1, xaccSplitGetReconcile (s0)); + xaccSplitSetMemo (s1, xaccSplitGetMemo (s0)); + xaccSplitSetAction (s1, xaccSplitGetAction (s0)); + xaccAccountInsertSplit (peer_acc, s1); + mark_potential_quote(s1, share_price, -num_shares); } - - } else { - - /* Version 1 files did not do double-entry */ - s = xaccTransGetSplit (trans, 0); - xaccAccountInsertSplit( acc, s ); } } else { /* else, read version 5 and above files */ - Split *split; - int offset = 0; + const char *notes = NULL; - if (5 == token) - { - Split *s = trans->splits->data; - + if (revision == 5) { /* Version 5 files included a split that immediately * followed the transaction, before the destination splits. * Later versions don't have this. */ - offset = 1; - split = readSplit (fd, token); - xaccRemoveEntity(&s->guid); - xaccFreeSplit (s); - trans->splits->data = split; - split->parent = trans; + Split *split = readSplit (fd, revision); + xaccTransAppendSplit(trans, split); } - + /* read number of splits */ err = read( fd, &(numSplits), sizeof(int) ); - if( err != sizeof(int) ) - { + if( err != sizeof(int) ) { PERR ("Premature end of Transaction at num-splits"); xaccTransDestroy(trans); xaccTransCommitEdit (trans); return NULL; } XACC_FLIP_INT (numSplits); - for (i=0; isplits->data; - /* the first split has been malloced. just replace it */ - xaccRemoveEntity (&s->guid); - xaccFreeSplit (s); - trans->splits->data = split; - split->parent = trans; - } else { - xaccTransAppendSplit(trans, split); - } - - if (!notes) { - notes = xaccSplitGetMemo (split); - if (notes) - xaccTransSetNotes (trans, notes); - } - } + for (i = 0; i < numSplits; i++) { + Split *split = readSplit(fd, revision); + xaccTransAppendSplit(trans, split); + + if(!notes) { + notes = xaccSplitGetMemo (split); + if(notes) xaccTransSetNotes (trans, notes); + } + } } + xaccTransCommitEdit (trans); return trans; @@ -1275,6 +1362,7 @@ readSplit ( int fd, int token ) return NULL; } XACC_FLIP_DOUBLE (share_price); + DxaccSplitSetSharePriceAndAmount (split, share_price, num_shares); DEBUG ("num_shares %f \n", num_shares); @@ -1293,6 +1381,7 @@ readSplit ( int fd, int token ) peer_acc = locateAccount (acc_id); xaccAccountInsertSplit (peer_acc, split); + mark_potential_quote(split, share_price, num_shares); return split; } @@ -1486,7 +1575,7 @@ xaccWriteGncBinAccountGroup(FILE *f, AccountGroup *grp ) accounts_to_ids = g_hash_table_new(g_direct_hash, g_direct_equal); if(!accounts_to_ids) { - error_code = ERR_FILEIO_ALLOC; + error_code = ERR_BACKEND_ALLOC; return(-1); } diff --git a/src/engine/io-gncbin.h b/src/engine/io-gncbin.h index d3dc21514c..b57ea1960d 100644 --- a/src/engine/io-gncbin.h +++ b/src/engine/io-gncbin.h @@ -31,47 +31,26 @@ #define __IO_GNCBIN_H__ #include "Backend.h" -#include "Group.h" -#include "FileIO.h" +#include "gnc-book.h" /** PROTOTYPES ******************************************************/ /* - * The xaccReadAccountGroupFD() and xaccWriteAccountGroupFD() - * routines read and write the GnuCash "xacc" byte stream (file) - * format. This is a binary format that exactly represents all of the - * data that can appear in the AccountGroup structure as a sequence of - * bytes. The Read and Write routines are exact inverses of each other: - * that is, there is no loss of data involved in converting an - * AccountGroup into a byte stream and back again. These routines - * can be thought of as implementing a kind of "object persistance" - * for the AccountGroup object. Note that these routines can also - * be used to provide inter-process communication using either pipes or - * sockets. That is, by writing into a socket or pipe with the - * xaccWriteAccountGroupFD() routine, and reading from it at the other - * end with the xaccReadAccountGroupFD() routine, an exact duplicate of - * the AccountGroup can be created in a different process. + * NOTE: These routines should not be used directly for file IO. They + * are not inherently safe against file-locking errors. For direct + * file IO, the gnc-book's higher level functions should be used. * - * NOTE: These routines should not be used directly for file IO. - * They are not inherently safe against file-locking errors. - * For direct file IO, the Session object should be used. + * gnc_book_load_from_binfile() will load the financial data + * represented by the book's file_path into the indicated book. * - * The xaccReadOldBinAccountGroupFile() method will read the "xacc" - * format byte stream from the indicated file, and build and return - * the corresponding AccountGroup structure. - * - * If a read error occurred during reading, the returned value - * may or may not be null. Use the xaccGetOldBinFileIOError() routine - * to check for read errors. - * - * The xaccGetOldBinFileIOError() method will return an error code for - * any error detected that occured during reading or writing. It - * will reset the error code after being called. The current - * implementation can be thought of as a "stack of depth one", and - * this routine as a "pop". Future implementations may have a - * deeper stack. + * gnc_book_get_binfile_io_error() will return an error code for any + * error detected that occured during reading or writing. It will + * reset the error code after being called. The current + * implementation can be thought of as a "stack of depth one", and + * this routine as a "pop". Future implementations may have a + * deeper stack. * */ -AccountGroup *xaccReadGncBinAccountGroupFile (const char *filename); -GNCBackendError xaccGetGncBinFileIOError (void); +void gnc_book_load_from_binfile(GNCBook *book); +GNCBackendError gnc_book_get_binfile_io_error(void); #endif /* __IO_GNCBIN_H__ */ diff --git a/src/engine/io-gncxml-p.h b/src/engine/io-gncxml-p.h new file mode 100644 index 0000000000..c78b60a6e6 --- /dev/null +++ b/src/engine/io-gncxml-p.h @@ -0,0 +1,57 @@ +/********************************************************************\ + * io-gncxml-p.h - private header for xml read code * + * * + * Copyright (C) 1999-2001 Rob Browning * + * * + * 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 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * + ********************************************************************/ + +#ifndef __IO_GNCXML_P_H__ +#define __IO_GNCXML_P_H__ + +#include "sixtp.h" +#include "io-gncxml.h" + +typedef enum { + GNC_PARSE_ERR_NONE, + GNC_PARSE_ERR_BAD_VERSION, +} GNCParseErr; + +typedef struct { + /* have we gotten the file version yet? */ + gboolean seen_version; + gint64 version; + + /* top level parser - we need this so we can set it up + after we see the file version. */ + sixtp *gnc_parser; + + /* The account group */ + AccountGroup *account_group; + + /* The pricedb */ + GNCPriceDB *pricedb; + + /* The query */ + Query *query; + + GNCParseErr error; +} GNCParseStatus; + +#endif diff --git a/src/engine/io-gncxml-r.c b/src/engine/io-gncxml-r.c index d32416b470..9517f627d2 100644 --- a/src/engine/io-gncxml-r.c +++ b/src/engine/io-gncxml-r.c @@ -16,9 +16,12 @@ #include "gnc-xml-helper.h" #include "Account.h" #include "Query.h" +#include "gnc-pricedb.h" #include "gnc-engine-util.h" +#include "gnc-book-p.h" #include "io-gncxml.h" +#include "io-gncxml-p.h" #include "sixtp.h" #include "sixtp-stack.h" @@ -48,33 +51,7 @@ */ -/* static short module = MOD_IO; */ - -/* ========================================================== */ - -typedef enum { - GNC_PARSE_ERR_NONE, - GNC_PARSE_ERR_BAD_VERSION, -} GNCParseErr; - -typedef struct { - /* have we gotten the file version yet? */ - gboolean seen_version; - gint64 version; - - /* top level parser - we need this so we can set it up - after we see the file version. */ - sixtp *gnc_parser; - - /* The account group */ - AccountGroup *account_group; - - /* The query */ - Query *query; - - GNCParseErr error; -} GNCParseStatus; - +static short module = MOD_IO; /* ================================================================= */ /* (lineage ) @@ -283,6 +260,7 @@ gncxml_setup_for_read (GNCParseStatus *global_parse_status) global_parse_status->seen_version = FALSE; global_parse_status->gnc_parser = gnc_pr; global_parse_status->account_group = NULL; + global_parse_status->pricedb = NULL; global_parse_status->query = NULL; global_parse_status->error = GNC_PARSE_ERR_NONE; @@ -292,16 +270,18 @@ gncxml_setup_for_read (GNCParseStatus *global_parse_status) /* ================================================================== */ gboolean -gncxml_read(const gchar *filename, - AccountGroup **result_group) +gnc_book_load_from_xml_file(GNCBook *book) { gboolean parse_ok; gpointer parse_result = NULL; sixtp *top_level_pr; GNCParseStatus global_parse_status; + const gchar *filename; + g_return_val_if_fail(book, FALSE); + + filename = gnc_book_get_file_path(book); g_return_val_if_fail(filename, FALSE); - g_return_val_if_fail(result_group, FALSE); top_level_pr = gncxml_setup_for_read (&global_parse_status); g_return_val_if_fail(top_level_pr, FALSE); @@ -314,8 +294,29 @@ gncxml_read(const gchar *filename, sixtp_destroy(top_level_pr); - if(parse_ok && global_parse_status.account_group) { - *result_group = global_parse_status.account_group; + if(parse_ok) { + if(!global_parse_status.account_group) + return FALSE; + + { + AccountGroup *g = gnc_book_get_group(book); + + if(g) xaccFreeAccountGroup(g); + gnc_book_set_group(book, global_parse_status.account_group); + } + + if(global_parse_status.pricedb) + { + GNCPriceDB *db = gnc_book_get_pricedb(book); + + if(db) gnc_pricedb_destroy(db); + + gnc_book_set_pricedb(book, global_parse_status.pricedb); + } + else + { + gnc_book_set_pricedb(book, gnc_pricedb_create()); + } return(TRUE); } else { return(FALSE); @@ -324,6 +325,40 @@ gncxml_read(const gchar *filename, /* ================================================================== */ +gboolean +gnc_is_xml_data_file(const gchar *filename) +{ + FILE *f = NULL; + char first_chunk[256]; + const char* cursor = NULL; + ssize_t num_read; + gboolean result = FALSE; + + g_return_val_if_fail(filename, result); + + f = fopen(filename, "r"); + g_return_val_if_fail(f, result); + + num_read = fread(first_chunk, sizeof(char), sizeof(first_chunk) - 1, f); + if(num_read == 0) goto cleanup_and_exit; + first_chunk[num_read] = '\0'; + + cursor = first_chunk; + while(*cursor && isspace(*cursor)) cursor++; + + if(cursor && strncmp(cursor, "\n" "\n"; +/* ============================================================== */ + static gboolean gncxml_append_emacs_trailer(const gchar *filename) { @@ -98,6 +100,7 @@ gncxml_append_emacs_trailer(const gchar *filename) return fclose(toappend); } +#if 0 /* =============================================================== */ /* create a new xml document and poke all the query terms into it. */ @@ -134,16 +137,20 @@ gncxml_new_query_doc (Query *q) return doc; } +#endif + /* =============================================================== */ /* create a new xml document and poke all account & txn data into it. */ static xmlDocPtr -gncxml_newdoc (AccountGroup *group, int do_txns) +gncxml_newdoc (GNCBook *book, int do_txns) { xmlDocPtr doc; xmlNodePtr ledger_data; xmlNodePtr tmpnode; - + AccountGroup *group = gnc_book_get_group(book); + GNCPriceDB *pricedb = gnc_book_get_pricedb(book); + doc = xmlNewDoc("1.0"); doc->xmlRootNode = xmlNewDocNode(doc, NULL, "gnc", NULL); @@ -162,7 +169,13 @@ gncxml_newdoc (AccountGroup *group, int do_txns) } if(!xml_add_commodity_restorers(ledger_data)) { - PERR ("couldn't commodity restore"); + PERR ("couldn't create commodity restorers"); + xmlFreeDoc(doc); + return 0x0; + } + + if(!xml_add_gnc_pricedb(ledger_data, "pricedb", pricedb)) { + PERR ("couldn't create pricedb"); xmlFreeDoc(doc); return 0x0; } @@ -184,6 +197,8 @@ gncxml_newdoc (AccountGroup *group, int do_txns) return doc; } +#if 0 + /* =============================================================== */ void @@ -229,18 +244,20 @@ gncxml_write_query_to_buf (Query *q, char **bufp, int *sz) PINFO ("wrote %d bytes", *sz); } +#endif + /* =============================================================== */ /* write the account group to a filename */ gboolean -gncxml_write(AccountGroup *group, const gchar *filename) +gnc_book_write_to_xml_file(GNCBook *book, const gchar *filename) { xmlDocPtr doc; int status; - if (!group || !filename) return FALSE; + if(!filename) return FALSE; - doc = gncxml_newdoc (group, 1); + doc = gncxml_newdoc (book, 1); if (!doc) return FALSE; xmlIndentTreeOutput = TRUE; diff --git a/src/engine/io-gncxml.h b/src/engine/io-gncxml.h index caddc89177..d62744f4ee 100644 --- a/src/engine/io-gncxml.h +++ b/src/engine/io-gncxml.h @@ -12,30 +12,25 @@ #include -#include "Group.h" +#include "gnc-book.h" #include "Query.h" -/* read in an account group from a file */ -gboolean gncxml_read(const gchar *filename, AccountGroup **result_group); +/* FIXME: eventually, we probably need to add an error stack + accessable via gnc_book_get_xml_io_error() a la binfile. */ +/* read in an account group from a file */ +gboolean gnc_book_load_from_xml_file(GNCBook *book); + +/* write all account info to a file */ +gboolean gnc_book_write_to_xml_file(GNCBook *book, const char *filename); + +#if 0 /* read an account group from a buffer in memory * the pointer bufp must point at the ascii xml, and bufsz * must be the size of the buffer. */ -AccountGroup * -gncxml_read_from_buf (char *bufp, int bufsz); - -Query * -gncxml_read_query (char *bufp, int bufsz); - -/* write all account info to a file */ -gboolean gncxml_write(AccountGroup *group, const gchar *name); - -/* The is_gncxml_file() routine checks to see if the first few - * chars of the file look like gnc-xml data. - */ -gboolean is_gncxml_file(const gchar *name); +AccountGroup *gncxml_read_from_buf (char *bufp, int bufsz); /* write all account info into memory. This routine returns a * pointer to freshly malloced memory in bufp. You muse free @@ -43,14 +38,22 @@ gboolean is_gncxml_file(const gchar *name); * returned in sz */ void gncxml_write_to_buf (AccountGroup *group, char **bufp, int *sz); - + /* * write only the account and currency info to the buf, do *not* * write any of the splits or transactions */ void gncxml_write_group_to_buf (AccountGroup *group, char **bufp, int *sz); -/* write the query terms to memory */ -void gncxml_write_query_to_buf (Query *q, char **bufp, int *sz); +#endif +/* write/read query terms to/from memory */ +void gncxml_write_query_to_buf (Query *q, char **bufp, int *sz); +Query *gncxml_read_query (char *bufp, int bufsz); + +/* The is_gncxml_file() routine checks to see if the first few + * chars of the file look like gnc-xml data. + */ +gboolean gnc_is_xml_data_file(const gchar *name); + #endif /* __IO_GNCXML_H__ */ diff --git a/src/engine/sixtp-dom-generators.c b/src/engine/sixtp-dom-generators.c index 5007e0fa2d..7edfff7189 100644 --- a/src/engine/sixtp-dom-generators.c +++ b/src/engine/sixtp-dom-generators.c @@ -10,6 +10,19 @@ #include "GNCId.h" #include "kvp_frame.h" +xmlNodePtr +text_to_dom_tree(const char *tag, const char *str) +{ + xmlNodePtr result; + + g_return_val_if_fail(tag, NULL); + g_return_val_if_fail(str, NULL); + result = xmlNewNode(NULL, tag); + g_return_val_if_fail(result, NULL); + xmlNodeAddContent(result, str); + return result; +} + xmlNodePtr guid_to_dom_tree(const char *tag, const GUID* gid) { diff --git a/src/engine/sixtp-dom-generators.h b/src/engine/sixtp-dom-generators.h index 9bbab80e56..700ff6b96e 100644 --- a/src/engine/sixtp-dom-generators.h +++ b/src/engine/sixtp-dom-generators.h @@ -14,6 +14,7 @@ #include "gnc-numeric.h" #include "kvp_frame.h" +xmlNodePtr text_to_dom_tree(const char *tag, const char *str); xmlNodePtr guid_to_dom_tree(const char *tag, const GUID* gid); xmlNodePtr commodity_ref_to_dom_tree(const char *tag, const gnc_commodity *c); xmlNodePtr timespec_to_dom_tree(const char *tag, const Timespec *spec); diff --git a/src/engine/sixtp-dom-parsers.c b/src/engine/sixtp-dom-parsers.c index b6b4f7928b..71d82e6de8 100644 --- a/src/engine/sixtp-dom-parsers.c +++ b/src/engine/sixtp-dom-parsers.c @@ -368,14 +368,17 @@ dom_tree_to_kvp_frame(xmlNodePtr node) gchar * dom_tree_to_text(xmlNodePtr tree) { - /* Expect *only* text and comment sibling nodes. Ignore comment - nodes and collapse text nodes into one string. Return NULL if - expectations are unsatisfied. + /* Expect *only* text and comment sibling nodes in the given tree -- + which actually may only be a "list". i.e. if you're trying to + extract bar from bar, pass in ->xmlChildrenNode + to this function. This expectation is different from the rest of + the dom_tree_to_* converters... + + Ignores comment nodes and collapse text nodes into one string. + Returns NULL if expectations are unsatisfied. If there are a lot of text sub-nodes, this algorithm may be - inefficient because it'll reallocate a lot. - - */ + inefficient as it will reallocate a lot. */ gboolean ok = TRUE; xmlNodePtr current; @@ -392,7 +395,9 @@ dom_tree_to_text(xmlNodePtr tree) case XML_COMMENT_NODE: break; default: - PERR("dom_tree_to_text: hit illegal node type while extracting text."); + PERR("dom_tree_to_text: hit illegal node while extracting text."); + PERR(" (name %s) (type %d) (content %s)", + current->name, current->type, current->content); current = NULL; ok = FALSE; break; diff --git a/src/engine/sixtp-parsers.h b/src/engine/sixtp-parsers.h index bfcfdacedb..f0105c811f 100644 --- a/src/engine/sixtp-parsers.h +++ b/src/engine/sixtp-parsers.h @@ -26,10 +26,15 @@ sixtp* query_server_parser_new (void); sixtp* kvp_frame_parser_new(void); /* from sixtp-to-dom-parser.c */ -sixtp* sixtp_dom_parser_new(sixtp_end_handler ender); +/* Create a parser that will turn the entire sub-tree into a DOM tree + an pass it in as (don't put anything into parent_data) + you must deal with the xml tree in *result. +*/ +sixtp* sixtp_dom_parser_new(sixtp_end_handler ender, + sixtp_result_handler cleanup_result_by_default_func, + sixtp_result_handler cleanup_result_on_fail_func); + +sixtp* gnc_pricedb_parser_new(void); #endif /* _SIXTP_PARSERS_H_ */ - - - diff --git a/src/engine/sixtp-to-dom-parser.c b/src/engine/sixtp-to-dom-parser.c index ef39be7f70..f19ca35592 100644 --- a/src/engine/sixtp-to-dom-parser.c +++ b/src/engine/sixtp-to-dom-parser.c @@ -21,12 +21,18 @@ static gboolean dom_start_handler( if(parent_data == NULL) { thing = xmlNewNode(global_namespace, tag); + /* only publish the result if we're the parent */ + *result = thing; } else { - thing = xmlNewChild((xmlNodePtr)parent_data, global_namespace, - tag, NULL); + thing = xmlNewChild((xmlNodePtr) parent_data, + global_namespace, + tag, + NULL); + *result = NULL; } + *data_for_children = thing; if(attrs != NULL) { @@ -36,10 +42,32 @@ static gboolean dom_start_handler( atptr += 2; } } + return TRUE; +} - *result = thing; - *data_for_children = thing; - +static void +dom_fail_handler(gpointer data_for_children, + GSList* data_from_children, + GSList* sibling_data, + gpointer parent_data, + gpointer global_data, + gpointer *result, + const gchar *tag) +{ + if(*result) xmlFreeNode(*result); +} + + +static gboolean is_whitespace(const char *text, int len) +{ + int i; + for(i = 0; i < len; i++) + { + if(!isspace(text[i])) + { + return FALSE; + } + } return TRUE; } @@ -55,7 +83,10 @@ static gboolean dom_chars_handler( } -sixtp* sixtp_dom_parser_new(sixtp_end_handler ender) +sixtp * +sixtp_dom_parser_new(sixtp_end_handler ender, + sixtp_result_handler cleanup_result_by_default_func, + sixtp_result_handler cleanup_result_on_fail_func) { sixtp *top_level; @@ -66,11 +97,22 @@ sixtp* sixtp_dom_parser_new(sixtp_end_handler ender) SIXTP_START_HANDLER_ID, dom_start_handler, SIXTP_CHARACTERS_HANDLER_ID, dom_chars_handler, SIXTP_END_HANDLER_ID, ender, + SIXTP_FAIL_HANDLER_ID, dom_fail_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } + if(cleanup_result_by_default_func) + { + sixtp_set_cleanup_result(top_level, cleanup_result_by_default_func); + } + + if(cleanup_result_by_default_func) + { + sixtp_set_result_fail(top_level, cleanup_result_on_fail_func); + } + if(!sixtp_add_sub_parser(top_level, SIXTP_MAGIC_CATCHER, top_level)) { sixtp_destroy(top_level); @@ -79,4 +121,3 @@ sixtp* sixtp_dom_parser_new(sixtp_end_handler ender) return top_level; } - diff --git a/src/engine/sixtp-utils.c b/src/engine/sixtp-utils.c index 0dda32709b..6c8232d22b 100644 --- a/src/engine/sixtp-utils.c +++ b/src/engine/sixtp-utils.c @@ -115,6 +115,10 @@ concatenate_child_result_chars(GSList *data_from_children) { I don't understand the claim; I'm just going to use atof or strtod to accomplish this. + RLB writes: FIXME: OK, but at the very least this may cause a + locale dependency. Whoever fixes that, please delete this whole + comment block. + */ gboolean diff --git a/src/engine/sixtp-writers.h b/src/engine/sixtp-writers.h index 8fdd7bdde4..3c05fc64be 100644 --- a/src/engine/sixtp-writers.h +++ b/src/engine/sixtp-writers.h @@ -1,13 +1,12 @@ - #ifndef _SIXTP_WRITERS_H_ #define _SIXTP_WRITERS_H_ #include - #include #include "gnc-xml-helper.h" #include "Query.h" +#include "gnc-pricedb.h" gboolean xml_add_account_restorers(xmlNodePtr p, AccountGroup *g); @@ -20,6 +19,8 @@ gboolean xml_add_query_restorers(xmlNodePtr p, Query *q); gboolean xml_add_txn_and_split_restorers(xmlNodePtr p, AccountGroup *g); +gboolean xml_add_gnc_price(xmlNodePtr p, const char *tag, GNCPrice *db); +gboolean xml_add_gnc_pricedb(xmlNodePtr p, const char *tag, GNCPriceDB *db); #endif /* _SIXTP_WRITERS_H_ */ diff --git a/src/engine/sixtp.c b/src/engine/sixtp.c index bb0350b05a..47732278ca 100644 --- a/src/engine/sixtp.c +++ b/src/engine/sixtp.c @@ -1,3 +1,5 @@ +#include "config.h" + #include #include @@ -328,7 +330,7 @@ sixtp_sax_start_handler(void *user_data, gchar *next_parser_tag = NULL; gboolean lookup_success = FALSE; sixtp_stack_frame *new_frame = NULL; - + g_return_if_fail(pdata->parsing_ok); current_frame = (sixtp_stack_frame *) pdata->stack->data; diff --git a/src/gnc-ui-util.c b/src/gnc-ui-util.c index f7f12c7a2a..6524a3bdf3 100644 --- a/src/gnc-ui-util.c +++ b/src/gnc-ui-util.c @@ -216,7 +216,7 @@ gnc_ui_get_account_field_value_string (Account *account, break; case ACCOUNT_BALANCE_EURO : { - const gnc_commodity * account_currency = + gnc_commodity * account_currency = xaccAccountGetCurrency(account); gnc_numeric balance = gnc_ui_account_get_balance(account, FALSE); gnc_numeric euro_balance = gnc_convert_to_euro(account_currency, @@ -237,7 +237,7 @@ gnc_ui_get_account_field_value_string (Account *account, break; case ACCOUNT_TOTAL_EURO : { - const gnc_commodity * account_currency = + gnc_commodity * account_currency = xaccAccountGetCurrency(account); gnc_numeric balance = gnc_ui_account_get_balance(account, TRUE); gnc_numeric euro_balance = gnc_convert_to_euro(account_currency, @@ -411,7 +411,7 @@ gnc_localeconv (void) return &lc; } -const gnc_commodity * +gnc_commodity * gnc_locale_default_currency (void) { static gnc_commodity * currency; @@ -526,7 +526,7 @@ is_decimal_fraction (int fraction, guint8 *max_decimal_places_p) } GNCPrintAmountInfo -gnc_commodity_print_info (const gnc_commodity *commodity, +gnc_commodity_print_info (gnc_commodity *commodity, gboolean use_symbol) { GNCPrintAmountInfo info; diff --git a/src/gnc-ui-util.h b/src/gnc-ui-util.h index 9d140b438c..7ef77990d4 100644 --- a/src/gnc-ui-util.h +++ b/src/gnc-ui-util.h @@ -100,7 +100,7 @@ PriceSourceCode gnc_get_source_code (const char * codename); struct lconv * gnc_localeconv (void); /* Returns the default currency of the current locale. */ -const gnc_commodity * gnc_locale_default_currency (void); +gnc_commodity * gnc_locale_default_currency (void); /* Returns the number of decimal place to print in the current locale */ int gnc_locale_decimal_places (void); @@ -123,7 +123,7 @@ int gnc_locale_decimal_places (void); typedef struct _GNCPrintAmountInfo { - const gnc_commodity *commodity; /* may be NULL */ + gnc_commodity *commodity; /* may be NULL */ guint8 max_decimal_places; guint8 min_decimal_places; @@ -137,7 +137,7 @@ typedef struct _GNCPrintAmountInfo GNCPrintAmountInfo gnc_default_print_info (gboolean use_symbol); -GNCPrintAmountInfo gnc_commodity_print_info (const gnc_commodity *commodity, +GNCPrintAmountInfo gnc_commodity_print_info (gnc_commodity *commodity, gboolean use_symbol); GNCPrintAmountInfo gnc_account_quantity_print_info (Account *account, diff --git a/src/gnome/dialog-account.c b/src/gnome/dialog-account.c index 335851923e..354b2bf291 100644 --- a/src/gnome/dialog-account.c +++ b/src/gnome/dialog-account.c @@ -125,7 +125,7 @@ static void gnc_account_to_ui(AccountWindow *aw) { Account *account = aw_get_account (aw); - const gnc_commodity * commodity; + gnc_commodity * commodity; const char *string; gboolean tax_related; gint pos = 0; @@ -179,7 +179,7 @@ static void gnc_ui_to_account(AccountWindow *aw) { Account *account = aw_get_account (aw); - const gnc_commodity *commodity; + gnc_commodity *commodity; Account *parent_account; const char *old_string; const char *string; @@ -280,7 +280,7 @@ gnc_finish_ok (AccountWindow *aw, /* do it all again, if needed */ if (aw->dialog_type == NEW_ACCOUNT && aw->subaccount_names) { - const gnc_commodity *commodity; + gnc_commodity *commodity; Account *parent; Account *account; GList *node; @@ -440,11 +440,11 @@ static void gnc_account_change_currency_security(Account *account, GHashTable *change_currency, GHashTable *change_security, - const gnc_commodity * currency, - const gnc_commodity * security) + gnc_commodity * currency, + gnc_commodity * security) { - const gnc_commodity * old_currency; - const gnc_commodity * old_security; + gnc_commodity * old_currency; + gnc_commodity * old_security; gboolean new_currency; gboolean new_security; GSList *stack; @@ -782,8 +782,8 @@ gnc_edit_account_ok(AccountWindow *aw) GNCAccountType current_type; const char *name; - const gnc_commodity * currency; - const gnc_commodity * security; + gnc_commodity * currency; + gnc_commodity * security; /* check for valid name */ name = gtk_entry_get_text(GTK_ENTRY(aw->name_entry)); @@ -1483,7 +1483,7 @@ static AccountWindow * gnc_ui_new_account_window_internal (Account *base_account, GList *subaccount_names) { - const gnc_commodity *commodity; + gnc_commodity *commodity; AccountWindow *aw; Account *account; diff --git a/src/gnome/dialog-commodity.c b/src/gnome/dialog-commodity.c index 0df6b81c4f..6464ad9838 100644 --- a/src/gnome/dialog-commodity.c +++ b/src/gnome/dialog-commodity.c @@ -78,10 +78,10 @@ select_modal_callback(const gnc_commodity * arg, void * data) { * gnc_ui_select_commodity_modal() ********************************************************************/ -const gnc_commodity * -gnc_ui_select_commodity_modal(const gnc_commodity * orig_sel, +gnc_commodity * +gnc_ui_select_commodity_modal(gnc_commodity * orig_sel, GtkWidget * parent) { - const gnc_commodity * retval = NULL; + gnc_commodity * retval = NULL; SelectCommodityWindow * win = gnc_ui_select_commodity_create(orig_sel, &select_modal_callback, &retval); @@ -406,7 +406,7 @@ new_modal_callback(const gnc_commodity * arg, void * data) { * gnc_ui_new_commodity_modal() ********************************************************************/ -const gnc_commodity * +gnc_commodity * gnc_ui_new_commodity_modal(const char * selected_namespace, GtkWidget * parent) { gnc_commodity * retval = NULL; diff --git a/src/gnome/dialog-commodity.h b/src/gnome/dialog-commodity.h index ad1a956054..e5e0346edc 100644 --- a/src/gnome/dialog-commodity.h +++ b/src/gnome/dialog-commodity.h @@ -38,11 +38,11 @@ void gnc_ui_select_commodity_destroy(SelectCommodityWindow * w); void gnc_ui_new_commodity_destroy(NewCommodityWindow * w); -const gnc_commodity * -gnc_ui_select_commodity_modal(const gnc_commodity * orig_sel, +gnc_commodity * +gnc_ui_select_commodity_modal(gnc_commodity * orig_sel, GtkWidget * parent); -const gnc_commodity * +gnc_commodity * gnc_ui_new_commodity_modal(const char * default_namespace, GtkWidget * parent); diff --git a/src/gnome/dialog-fincalc.c b/src/gnome/dialog-fincalc.c index aca34d8ccd..e890b05744 100644 --- a/src/gnome/dialog-fincalc.c +++ b/src/gnome/dialog-fincalc.c @@ -496,7 +496,7 @@ close_handler (gpointer user_data) FinCalcDialog * gnc_ui_fincalc_dialog_create(void) { - const gnc_commodity *commodity; + gnc_commodity *commodity; GNCPrintAmountInfo print_info; FinCalcDialog *fcd; GtkObject *fcdo; diff --git a/src/gnome/dialog-options.c b/src/gnome/dialog-options.c index 40c1611d80..34ce86b113 100644 --- a/src/gnome/dialog-options.c +++ b/src/gnome/dialog-options.c @@ -195,7 +195,7 @@ gnc_option_set_ui_value(GNCOption *option, gboolean use_default) } else if (safe_strcmp(type, "currency") == 0) { - const gnc_commodity *commodity; + gnc_commodity *commodity; commodity = gnc_scm_to_commodity (value); if (commodity) @@ -206,7 +206,7 @@ gnc_option_set_ui_value(GNCOption *option, gboolean use_default) } else if (safe_strcmp(type, "commodity") == 0) { - const gnc_commodity *commodity; + gnc_commodity *commodity; commodity = gnc_scm_to_commodity (value); if (commodity) @@ -480,7 +480,7 @@ gnc_option_get_ui_value(GNCOption *option) } else if (safe_strcmp(type, "currency") == 0) { - const gnc_commodity *commodity; + gnc_commodity *commodity; commodity = gnc_currency_edit_get_currency(GNC_CURRENCY_EDIT(option->widget)); @@ -489,7 +489,7 @@ gnc_option_get_ui_value(GNCOption *option) } else if (safe_strcmp(type, "commodity") == 0) { - const gnc_commodity *commodity; + gnc_commodity *commodity; commodity = gnc_commodity_edit_get_commodity(GNC_COMMODITY_EDIT(option->widget)); diff --git a/src/gnome/druid-commodity.c b/src/gnome/druid-commodity.c index b025b4f0b6..f3e12d8d2d 100644 --- a/src/gnome/druid-commodity.c +++ b/src/gnome/druid-commodity.c @@ -38,7 +38,12 @@ #include "gnc-commodity.h" #include "gnc-engine.h" #include "gnc-ui.h" +#include "gnc-pricedb-p.h" +#include "gnc-engine-util.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_GUI; struct _commoditydruid { GtkWidget * window; @@ -395,7 +400,6 @@ gnc_ui_commodity_druid_comm_check_cb(GnomeDruidPage * page, gpointer druid, } } - static void finish_helper(gpointer key, gpointer value, gpointer data) { CommodityDruid * cd = data; @@ -404,11 +408,22 @@ finish_helper(gpointer key, gpointer value, gpointer data) { key); GList * accts; GList * node; + GNCBook * book = gncGetCurrentBook(); + + if(!book) { + PERR("finish_helper - no current book."); + return; + } /* key is the old mnemonic, value is a pointer to the gnc_commodity * structure. */ gnc_commodity_table_insert(gnc_engine_commodities(), comm); + /* s/old commodity/new commodity/g in the pricedb */ + gnc_pricedb_substitute_commodity(gnc_book_get_pricedb(book), + old_comm, + comm); + /* now replace all the accounts using old_comm with new_comm */ accts = xaccGroupGetSubAccounts(gncGetCurrentGroup()); diff --git a/src/gnome/druid-qif-import.c b/src/gnome/druid-qif-import.c index c17d3ceb6d..12971146fe 100644 --- a/src/gnome/druid-qif-import.c +++ b/src/gnome/druid-qif-import.c @@ -1184,7 +1184,7 @@ gnc_ui_qif_import_convert(QIFImportWindow * wind) { char * mnemonic = NULL; char * namespace = NULL; char * fullname = NULL; - gchar * row_text[4] = { NULL, NULL, NULL, NULL }; + const gchar * row_text[4] = { NULL, NULL, NULL, NULL }; int rownum; /* get the default currency */ @@ -1281,7 +1281,7 @@ gnc_ui_qif_import_convert(QIFImportWindow * wind) { } rownum = gtk_clist_append(GTK_CLIST(wind->new_transaction_list), - row_text); + (gchar **) row_text); retval = gh_cdr(retval); } @@ -1662,7 +1662,7 @@ refresh_old_transactions(QIFImportWindow * wind, int selection) { SCM selected; Transaction * gnc_xtn; Split * gnc_split; - gchar * row_text[4] = { NULL, NULL, NULL, NULL }; + const gchar * row_text[4] = { NULL, NULL, NULL, NULL }; int rownum; gtk_clist_column_titles_passive (GTK_CLIST(wind->old_transaction_list)); @@ -1703,7 +1703,7 @@ refresh_old_transactions(QIFImportWindow * wind, int selection) { } rownum = gtk_clist_append(GTK_CLIST(wind->old_transaction_list), - row_text); + (gchar **) row_text); gnc_clist_set_check (GTK_CLIST(wind->old_transaction_list), rownum, 3, selected != SCM_BOOL_F); diff --git a/src/gnome/gnc-commodity-edit.c b/src/gnome/gnc-commodity-edit.c index 192b6e3f92..942a259a40 100644 --- a/src/gnome/gnc-commodity-edit.c +++ b/src/gnome/gnc-commodity-edit.c @@ -141,7 +141,7 @@ static void select_currency_cb(GtkButton * button, gpointer user_data) { GNCCommodityEdit *gce = user_data; - const gnc_commodity *new_commodity; + gnc_commodity *new_commodity; GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button)); @@ -202,7 +202,7 @@ gnc_commodity_edit_new (void) */ void gnc_commodity_edit_set_commodity (GNCCommodityEdit *gce, - const gnc_commodity *commodity) + gnc_commodity *commodity) { const char *text; @@ -225,7 +225,7 @@ gnc_commodity_edit_set_commodity (GNCCommodityEdit *gce, * * Returns the commodity currently selected by the widget. */ -const gnc_commodity * +gnc_commodity * gnc_commodity_edit_get_commodity (GNCCommodityEdit *gce) { g_return_val_if_fail(gce != NULL, NULL); diff --git a/src/gnome/gnc-commodity-edit.h b/src/gnome/gnc-commodity-edit.h index c7141326e1..a72eab1bd6 100644 --- a/src/gnome/gnc-commodity-edit.h +++ b/src/gnome/gnc-commodity-edit.h @@ -48,7 +48,7 @@ typedef struct { GtkWidget *entry; /* display of commodity name */ GtkWidget *button; /* button for popping up commodity window */ - const gnc_commodity *selected_commodity; + gnc_commodity *selected_commodity; } GNCCommodityEdit; typedef struct { @@ -60,9 +60,9 @@ guint gnc_commodity_edit_get_type (void); GtkWidget *gnc_commodity_edit_new (void); void gnc_commodity_edit_set_commodity (GNCCommodityEdit *gce, - const gnc_commodity *commodity); + gnc_commodity *commodity); -const gnc_commodity * gnc_commodity_edit_get_commodity (GNCCommodityEdit *gce); +gnc_commodity * gnc_commodity_edit_get_commodity (GNCCommodityEdit *gce); END_GNOME_DECLS diff --git a/src/gnome/gnc-currency-edit.c b/src/gnome/gnc-currency-edit.c index 0abd4f3e03..1467cf2f05 100644 --- a/src/gnome/gnc-currency-edit.c +++ b/src/gnome/gnc-currency-edit.c @@ -202,7 +202,7 @@ gnc_currency_edit_set_currency (GNCCurrencyEdit *gce, * * Returns the selected currency. */ -const gnc_commodity * +gnc_commodity * gnc_currency_edit_get_currency (GNCCurrencyEdit *gce) { const char *mnemonic; diff --git a/src/gnome/gnc-currency-edit.h b/src/gnome/gnc-currency-edit.h index e66d393941..1e6d5dd959 100644 --- a/src/gnome/gnc-currency-edit.h +++ b/src/gnome/gnc-currency-edit.h @@ -57,7 +57,7 @@ GtkWidget *gnc_currency_edit_new (void); void gnc_currency_edit_set_currency (GNCCurrencyEdit *gce, const gnc_commodity *currency); -const gnc_commodity * gnc_currency_edit_get_currency (GNCCurrencyEdit *gce); +gnc_commodity * gnc_currency_edit_get_currency (GNCCurrencyEdit *gce); END_GNOME_DECLS diff --git a/src/gnome/top-level.c b/src/gnome/top-level.c index 979af69155..cb108c649a 100644 --- a/src/gnome/top-level.c +++ b/src/gnome/top-level.c @@ -36,7 +36,6 @@ #include "AccWindow.h" #include "FileBox.h" #include "FileDialog.h" -#include "FileIO.h" #include "MainWindow.h" #include "SplitLedger.h" #include "TransLog.h" diff --git a/src/gnome/window-main.c b/src/gnome/window-main.c index 5ecfa4345d..31f6e7305d 100644 --- a/src/gnome/window-main.c +++ b/src/gnome/window-main.c @@ -105,7 +105,7 @@ static GNCMainInfo * gnc_get_main_info(void); * kept around for the duration of the calculation. There may, in fact * be better ways to do this, but none occurred. */ struct _GNCCurrencyAcc { - const gnc_commodity * currency; + gnc_commodity * currency; gnc_numeric assets; gnc_numeric profits; }; @@ -117,7 +117,7 @@ typedef struct _GNCCurrencyAcc GNCCurrencyAcc; * currency, plus (eventually) one for the default currency * accumulation (like the EURO). */ struct _GNCCurrencyItem { - const gnc_commodity * currency; + gnc_commodity * currency; GtkWidget *listitem; GtkWidget *assets_label; GtkWidget *profits_label; @@ -132,7 +132,7 @@ typedef struct _GNCCurrencyItem GNCCurrencyItem; * only handles a single currency. */ static GNCCurrencyItem * -gnc_ui_build_currency_item(const gnc_commodity * currency) +gnc_ui_build_currency_item(gnc_commodity * currency) { GtkWidget *label; GtkWidget *topbox; @@ -200,7 +200,7 @@ gnc_ui_build_currency_item(const gnc_commodity * currency) * This will search the given list, and if no accumulator is found, * will allocate a fresh one. */ static GNCCurrencyAcc * -gnc_ui_get_currency_accumulator(GList **list, const gnc_commodity * currency) +gnc_ui_get_currency_accumulator(GList **list, gnc_commodity * currency) { GList *current; GNCCurrencyAcc *found; @@ -231,7 +231,7 @@ gnc_ui_get_currency_accumulator(GList **list, const gnc_commodity * currency) * the item into the list. */ static GNCCurrencyItem * -gnc_ui_get_currency_item(GList **list, const gnc_commodity * currency, +gnc_ui_get_currency_item(GList **list, gnc_commodity * currency, GtkWidget *holder) { GList *current; @@ -261,9 +261,9 @@ gnc_ui_accounts_recurse (AccountGroup *group, GList **currency_list, gnc_numeric amount; AccountGroup *children; GNCAccountType account_type; - const gnc_commodity * account_currency; - const gnc_commodity * default_currency; - const gnc_commodity * euro_commodity; + gnc_commodity * account_currency; + gnc_commodity * default_currency; + gnc_commodity * euro_commodity; GNCCurrencyAcc *currency_accum; GNCCurrencyAcc *euro_accum = NULL; GList *list; @@ -368,7 +368,7 @@ gnc_ui_refresh_statusbar (void) AccountGroup *group; char asset_string[256]; char profit_string[256]; - const gnc_commodity * default_currency; + gnc_commodity * default_currency; GNCCurrencyAcc *currency_accum; GNCCurrencyItem *currency_item; GList *currency_list; @@ -1368,7 +1368,7 @@ gnc_main_create_summary_bar (GnomeApp *app, GNCMainInfo *main_info) { GtkWidget *summarybar; GtkWidget *combo_box; - const gnc_commodity * default_currency; + gnc_commodity * default_currency; GNCCurrencyItem *def_item; summarybar = gtk_hbox_new (FALSE, 5); diff --git a/src/gnome/window-register.c b/src/gnome/window-register.c index 747fe9bab9..ad7b1d1fb4 100644 --- a/src/gnome/window-register.c +++ b/src/gnome/window-register.c @@ -2113,7 +2113,7 @@ static void gnc_register_redraw_all_cb (GnucashRegister *g_reg, gpointer data) { RegWindow *regData = data; - const gnc_commodity * currency; + gnc_commodity * currency; GNCPrintAmountInfo print_info; gnc_numeric amount; Account *leader; diff --git a/src/quotes/.cvsignore b/src/quotes/.cvsignore index 7c92ce31bf..025a7e4416 100644 --- a/src/quotes/.cvsignore +++ b/src/quotes/.cvsignore @@ -2,3 +2,4 @@ Makefile Makefile.in gnc-prices gnc-prices-2 +price-quote-helper diff --git a/src/quotes/Makefile.am b/src/quotes/Makefile.am index 9bc1589279..2bb2bb7258 100644 --- a/src/quotes/Makefile.am +++ b/src/quotes/Makefile.am @@ -1,5 +1,6 @@ bin_SCRIPTS = gnc-prices +pkgdata_SCRIPTS=price-quote-helper perllibdir = ${GNC_LIBDIR}/perl @@ -8,8 +9,8 @@ perlsharedir = ${GNC_SHAREDIR}/perl EXTRA_DIST = \ .cvsignore \ Quote_example.pl \ - gnc-prices-2.in \ gnc-prices.in \ + price-qupte-helper.in \ value_portfolio ## We borrow guile's convention and use @-...-@ as the substitution @@ -26,3 +27,13 @@ gnc-prices: gnc-prices.in mv $@.tmp $@ CLEANFILES += gnc-prices + +price-quote-helper: price-quote-helper.in + rm -f $@.tmp + sed < $@.in > $@.tmp \ + -e 's:@-PERL-@:${PERL}:g' \ + -e 's:@-PERLINCL-@:${PERLINCL}:g' + chmod +x $@.tmp + mv $@.tmp $@ + +CLEANFILES += price-quote-helper diff --git a/src/quotes/gnc-prices-2.in b/src/quotes/gnc-prices-2.in deleted file mode 100644 index 3605e598cb..0000000000 --- a/src/quotes/gnc-prices-2.in +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use English; -use Finance::Quote; - -## Simple program to get quotes and feed them back to gnucash. - -## Modified by Paul Fenwick , June 2000, to take -## advantage of new Finance::Quote features. - -## Input (on standard input - one entry per line and one line per entry) -## -## (fetch "NYSE" "IBM") -## (fetch "nyse" "ibm" "axp") -## (fetch "nasdaq" "jdsu") -## (fetch "nasdaq" "CSCO" "jdsu") - -## Output (on standard output, one output form per input line) - -## Schemified version of finance-quote's output, so basically an alist -## of alists, as in the example below. Only 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. - - - -## On error, result may be just #f, or errors may be stored with each -## quote as indicated in Finance::Quote. - -## Exit status -## -## 0 - success -## non-zero - failure - -# TODO: - -# Is this safe? Can we just double all backslashes and backslash -# escape all double quotes and get the right answer? - -# Right now this is more inefficient than it needs to be. We ask for -# each stock individually, but we should batch reqests to the same -# source. Perhaps later... - -my $exit_status = 0; - -# Create a stockquote object. -my $quoter = Finance::Quote->new(); - -sub schemify_str { - my($str) = @_; - - # Right now this is a hack. We just make sure the outgoing string - # has no double quotes in it by mangling them all to single quotes. - # This is wrong, but it's better than letting dangerous strings - # through. We can always improve this later. - - # Have to do this because the perl-mode parser freaks out otherwise. - my $dq = '"'; - my $sq = "'"; - - $str =~ s/$dq/$sq/gmo; - return $str; -} - -my @lookup_items = (); - -while(<>) { - - # This big ugly nasty thing just matches something like this - - # ("FOO" "BAR") - - # where, rougly speaking, whitespace is allowed almost everywhere, - # this text constitutes the entire line, and the double-quotes and - # parens shown are the only occurences of those characters allowed - # in the line. - - # Have to do this because the perl-mode parser freaks out otherwise. - my $dq = '"'; - - my $security_name; - my $quote_source_name; - - if($_ =~ m/^\s* \(\s* $dq ([^$dq]+) $dq \s* $dq([^$dq]+)$dq \s*\) \s*$/ox) { - $security_name = $1; - $quote_source_name = $2; - } else { - my $scm_str = schemify_str($_); - print "(error bad-input-line \"Ignoring bad input line: $scm_str\")\n"; - $exit_status |= 1; - # Yes this is ugly, but it really is what we mean here. - next; - } - - my %quote_data = $quoter->fetch($quote_source_name,$security_name); - - unless($quote_data{$quote_source_name,'success'}) { - # We don't have to schemify the source or name - the regexp filtered it. - print "(error quote-lookup-failed "; - print "\"Lookup of $security_name at $quote_source_name failed.\")\n"; - $exit_status |= 1; - next; - } - - my $security_price = $quote_data{$security_name, 'price'}; - my $quote_date = $quote_data{$security_name, 'date'}; - - if(!$security_price) { - # We don't have to schemify the source or name - the regexp filtered it. - print "(error price-not-found " . - "\"Couldn't find price for $security_name " . - "in response from $quote_source_name.\")\n"; - $exit_status |= 1; - next; - } - - ## We'll just let gnucash deal with the date... - $quote_date = schemify_str($quote_date); - - ## Whew. Finally. - print "(quote"; - print " (name . \"$security_name\")"; - print " (date . \"$quote_date\")"; - print " (price . \"$security_price\")\n"; -} - -exit $exit_status; - -__END__ diff --git a/src/quotes/price-quote-helper.in b/src/quotes/price-quote-helper.in new file mode 100644 index 0000000000..3ecd4e7aee --- /dev/null +++ b/src/quotes/price-quote-helper.in @@ -0,0 +1,282 @@ +#!@-PERL-@ -w +###################################################################### +### price-quote-helper - present a scheme interface to Finance::Quote +### Copyright 2001 Rob Browning +### +### 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 +### 59 Temple Place - Suite 330 Fax: +1-617-542-2652 +### Boston, MA 02111-1307, USA gnu@gnu.org +###################################################################### + +use lib '@-PERLINCL-@'; + +use strict; +use English; +use FileHandle; +use Date::Manip; +use Finance::Quote; + +# 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 :>). +# +# (fetch "NYSE" "IBM") +# (fetch "NYSE" "IBM" "AXP") +# (fetch "NASDAQ" "JDSU") +# (fetch "NASDAQ" "CSCO" "JDSU") +# + +# Output (on standard output, one output form per input line): + +# Schemified version of finance-quote's output, basically an alist of +# alists, as in the example below. Right now, the only a few 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. Right now, you'll get symbol, +# utc, and last, as here: +# +# $ echo '(fetch "NASDAQ" "CSCO")' | ./price-quote-helper +# (("CSCO" (symbol . "CSCO") (utc . 982709400) (last . 26.5625)) +# ("JDSU" (symbol . "JDSU") (utc . 982709400) (last . 33.0625))) + +# On error, result may be just #f, or errors may be stored with each +# quote as indicated in Finance::Quote. Also, whenever the +# conversion 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 + +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+)?)$/o) { + return $1; + } else { + return "failed-conversion"; + } +} + +sub schemify_date { + # return the date in epoch seconds. + my ($datestr) = @_; + + my $date = ParseDate($datestr); + my $result = UnixDate($date, "%s"); + if($result !~ /^(\+|-)?\d+$/) { + $result = "failed-conversion"; + } + return("$result"); +} + +# sub schemify_range { +# #convert range in form ``num1 - num2'' to ``(num1 num2)''. +# } + +sub get_quote_utc { + # return the date in utc epoch seconds. + my ($item, $timezone, $quotehash) = @_; + + if(!$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"; + } + + my $date = Date_ConvTZ(ParseDate($parsestr), $timezone, 'UTC'); + + my $result = UnixDate($date, "%s"); + if($result !~ /^(\+|-)?\d+$/) { + $result = "failed-conversion"; + } + return $result; +} + +sub schemify_quote { + my($itemname, $quotehash, $indentlevel, $timezone) = @_; + my $scmname = schemify_string($itemname); + my $quotedata = ""; + my $field; + my $data; + + $field = 'symbol'; + $data = schemify_string($$quotehash{$itemname, $field}); + $quotedata .= "($field . $data)"; + + $field = 'utc'; + $data = get_quote_utc($itemname, $timezone, $quotehash); + $quotedata .= " ($field . $data)"; + + $field = 'last'; + $data = schemify_num($$quotehash{$itemname, $field}); + $quotedata .= " ($field . $data)"; + + return "($scmname $quotedata)"; +} + +sub schemify_quotes { + my($items, $quotehash, $timezone) = @_; + my $resultstr = ""; + my $i; + 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 $i (@$items) { + $resultstr .= $separator . schemify_quote($i, $quotehash, 2, $timezone); + if(!$separator) { $separator = "\n "; } + } + return "($resultstr)\n"; +} + +sub get_exchange_timezone { + my($exchange) = @_; + my $tz; + $exchange = lc($exchange); + + if($exchange eq "nasdaq") { + $tz = 'EST'; + } elsif ($exchange eq "nyse") { + $tz = 'EST'; + } else { + return undef; + } +} + +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 $exchange; + my @items; + + # Make sure we have an opening ( preceeded only by whitespace. + # and kill it off if we do... + if($input !~ s/^\s*\(\s*fetch\s+//o) { return 0; } + + # 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; } + + # Now grab the exchange. + if($input !~ /^$dq([^$dq]+)$dq\s*/o) { return 0; } + + $exchange = $1; + $input = $POSTMATCH; + + # Now grab all the items. + while($input) { + if($input !~ /^$dq([^$dq]+)$dq\s*/o) { return 0; } + + my $item = $1; + push @items, $item; + $input = $POSTMATCH; + } + + my @result = ($exchange, \@items); + return \@result; +} + +#--------------------------------------------------------------------------- +# Runtime. + +# Create a stockquote object. +my $quoter = Finance::Quote->new(); +my $prgnam = "scmio-finance-quote"; + +# Make sure USD is the default. +$quoter->set_currency("USD"); + +while(<>) { + + my $result = parse_input_line($_); + + if(!$result) { + print STDERR "$prgnam: bad input line ($_)\n"; + exit 1; + } + + my($exchange, $items) = @$result; + + my $quote_data = $quoter->fetch($exchange, @$items); + + if(!$quote_data) { + print "#f\n"; + exit 1; + } + + my $zone = get_exchange_timezone($exchange); + print schemify_quotes(\@$items, $quote_data, $zone); + STDOUT->flush(); +} + +exit 0; + +__END__ + +## Local Variables: +## mode: perl +## End: diff --git a/src/scm/Makefile.am b/src/scm/Makefile.am index 2c22b2ca47..abc88c519f 100644 --- a/src/scm/Makefile.am +++ b/src/scm/Makefile.am @@ -38,6 +38,8 @@ gnc_regular_scm_files = \ options-utilities.scm \ path.scm \ prefs.scm \ + price-quotes.scm \ + process.scm \ report.scm \ report-html.scm \ report-utilities.scm \ diff --git a/src/scm/command-line.scm b/src/scm/command-line.scm index f872c6c079..e938f9df2c 100644 --- a/src/scm/command-line.scm +++ b/src/scm/command-line.scm @@ -19,6 +19,8 @@ (define gnc:*command-line-remaining* #f) +(gnc:depend "price-quotes.scm") + ;;(use-modules (ice-9 getopt-long)) ;;(define (gnc:is-boolean-arg? arg) @@ -169,6 +171,26 @@ (cons (lambda () (load val)) gnc:*batch-mode-things-to-do*))))) +; (cons "add-price-quotes" +; (cons 'string +; (lambda (val) +; (set! gnc:*batch-mode-things-to-do* +; (cons +; (lambda () +; (display (get-1-quote "NASDAQ" val))) +; gnc:*batch-mode-things-to-do*))))) + +; (cons "add-price-quotes" +; (cons 'string +; (lambda (val) +; (set! gnc:*batch-mode-things-to-do* +; (cons +; (lambda () +; (with- +; (gnc:book-add-quotes +; +; gnc:*batch-mode-things-to-do*))))) + (cons "load-user-config" (cons 'boolean gnc:load-user-config-if-needed)) diff --git a/src/scm/commodity-import.scm b/src/scm/commodity-import.scm index 61fedd8dab..dad7cddf11 100644 --- a/src/scm/commodity-import.scm +++ b/src/scm/commodity-import.scm @@ -16,9 +16,8 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (import-old-currencies from-filename) - (if (gnc:commodity-table-has-namespace - (gnc:engine-commodities) - "GNC_LEGACY_CURRENCIES") + (if (gnc:commodity-table-has-namespace (gnc:engine-commodities) + "GNC_LEGACY_CURRENCIES") (gnc:import-legacy-commodities from-filename))) (gnc:hook-add-dangler gnc:*file-opened-hook* import-old-currencies) diff --git a/src/scm/engine-utilities.scm b/src/scm/engine-utilities.scm index 57b9fc1fde..62be766a8c 100644 --- a/src/scm/engine-utilities.scm +++ b/src/scm/engine-utilities.scm @@ -23,8 +23,8 @@ (gnc:support "engine-utilities.scm") -(define (gnc:filename->account-group filename) - "Returns an account group on success and #f on failure" +(define (gnc:filename->book filename) + "Returns a book on success and #f on failure" (let* ((session (gnc:malloc-session))) (if (not session) #f @@ -84,13 +84,15 @@ (loop (cdr splits))))) (reverse retval))) +;;(define (gnc:group-map-accounts thunk group) +;; (let ((retval '())) +;; (let loop ((accounts (or (gnc:group-get-subaccounts group) '()))) +;; (if (not (null? accounts)) +;; (begin +;; (set! retval (cons (thunk (car accounts)) retval)) +;; (loop (cdr accounts))))) +;; (reverse retval))) + (define (gnc:group-map-accounts thunk group) - (let ((retval '())) - (let loop ((accounts (or (gnc:group-get-subaccounts group) '()))) - (if (not (null? accounts)) - (begin - (set! retval (cons (thunk (car accounts)) retval)) - (loop (cdr accounts))))) - (reverse retval))) - - + (let ((accounts (or (gnc:group-get-subaccounts group) '()))) + (map thunk accounts))) diff --git a/src/scm/main.scm b/src/scm/main.scm index b5525201ec..bfa7de7638 100644 --- a/src/scm/main.scm +++ b/src/scm/main.scm @@ -42,6 +42,7 @@ (gnc:depend "report/report-list.scm") (gnc:depend "qif-import/qif-import.scm") (gnc:depend "printing/print-check.scm") + (gnc:depend "src/price-quotes.scm") ;; Load the system configs (if (not (gnc:load-system-config-if-needed)) diff --git a/src/scm/price-quotes.scm b/src/scm/price-quotes.scm new file mode 100644 index 0000000000..69c538f5c6 --- /dev/null +++ b/src/scm/price-quotes.scm @@ -0,0 +1,65 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; price-quotes.scm - manage sub-processes. +;;; Copyright 2001 Rob Browning +;;; +;;; 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 +;;; 59 Temple Place - Suite 330 Fax: +1-617-542-2652 +;;; Boston, MA 02111-1307, USA gnu@gnu.org +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(gnc:support "price-quotes.scm") +(gnc:depend "process.scm") + +(define gnc:*price-quote-helper* + "/home/rlb/opt/gnucash-working/share/gnucash/price-quote-helper") + +(define (get-1-quote exchange . items) + (let ((cmd (apply list 'fetch exchange items)) + (quoter (run-sub-process #f + gnc:*price-quote-helper* + gnc:*price-quote-helper*))) + (and quoter + (write cmd (caddr quoter)) + (newline (caddr quoter)) + (force-output (caddr quoter)) + (let ((result (read (cadr quoter)))) + (close-input-port (cadr quoter)) + (close-output-port (caddr quoter)) + result)))) + + +(define (gnc:book-add-quotes book) + + (define (find-quotables group) + (define (quotable-account? a) + (case (gnc:account-get-type a) + ;; we no longer care what the price source was - Finance::Quote + ;; doesn't let you specify a particular source. + ((stock mutual-fund currency) (gnc:account-get-price-src a)) + (else #f))) + (filter quotable-account? (gnc:group-get-subaccounts group))) + + (display (list book)) (newline) + (display (list (gnc:book-get-group book))) (newline) + + (let* ((group (gnc:book-get-group book)) + (quotables (and group (find-quotables group))) + (commodities (and quotables + (map gnc:account-get-commodity quotables)))) + (for-each (lambda (c) + (display (list "Get quote for" (gnc:commodity-get-mnemonic c))) + (newline)) + commodities))) diff --git a/src/scm/process.scm b/src/scm/process.scm new file mode 100644 index 0000000000..cb19db3bc1 --- /dev/null +++ b/src/scm/process.scm @@ -0,0 +1,179 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; process.scm - manage sub-processes. +;;; Copyright 2001 Rob Browning +;;; +;;; 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 +;;; 59 Temple Place - Suite 330 Fax: +1-617-542-2652 +;;; Boston, MA 02111-1307, USA gnu@gnu.org +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(gnc:support "process.scm") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Run the program specified by path with the given args as a +;;; sub-proces. If envt is not #f, then use it as the sub-process +;;; environment (as per execle in the guile info pages). Note that +;;; you must specify the path explicitly. +;;; +;;; Returns #f on failure, or +;;; (pid child-output-pipe child-input-pipe child-standard-error-pipe) +;;; on success. Right now the standard-error pipe is always #f. +;;; +;;; For example: +;;; +;;; (run-sub-process "/bin/date" "--rfc-822") +;;; + +(define (run-sub-process envt path . args) + (let ((parent-to-child-pipe (false-if-exception (pipe))) + (child-to-parent-pipe (false-if-exception (pipe)))) + (if (not (and parent-to-child-pipe + child-to-parent-pipe)) + #f + (let* ((parent-read-pipe (car child-to-parent-pipe)) + (parent-write-pipe (cdr parent-to-child-pipe)) + (child-read-pipe (car parent-to-child-pipe)) + (child-write-pipe (cdr child-to-parent-pipe)) + (pid (false-if-exception (primitive-fork)))) + + (if (not (zero? pid)) + ;; we're the parent + (begin + (close-input-port child-read-pipe) + (close-output-port child-write-pipe) + (list pid parent-read-pipe parent-write-pipe #f)) + ;; else we're the child + (begin + ;; set standard-input and standard-output at the fd + ;; level -- which is really all that matters since + ;; we're about to exec... + (close-input-port parent-read-pipe) + (close-output-port parent-write-pipe) + (dup->fdes child-read-pipe 0) + (dup->fdes child-write-pipe 1) + ;; now launch the child process. + (or (false-if-exception + (if envt + (apply execle path envt args) + (apply execl path args))) + (exit 1)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Random test code. +;;; + +; (define (get-1-quote exchange . items) +; (let ((cmd (apply list 'fetch exchange items)) +; (quoter (run-sub-process #f +; "./scmio-finance-quote" +; "./scmio-finance-quote"))) +; (and quoter +; (write cmd (caddr quoter)) +; (newline (caddr quoter)) +; (force-output (caddr quoter)) +; (let ((result (read (cadr quoter)))) +; (close-input-port (cadr quoter)) +; (close-output-port (caddr quoter)) +; result)))) + +; (define (parrot) +; (let loop ((input (false-if-exception (read)))) +; (cond +; ((eof-object? input) (quit 0)) +; ((not input) (quit 0)) +; (else (write input) +; (force-output) +; (loop (read)))))) + +; (define (launch-parrot envt path args) +; ;; Returns (pid child-input-port child-output-port child-error-port) +; ;; Right now the error port is broken... + +; (let* ((pid #f) +; (sockets (false-if-exception (socketpair AF_UNIX SOCK_STREAM 0)))) + +; (if sockets +; (set! pid (false-if-exception (primitive-fork)))) + +; (cond +; ((not pid) #f) + +; ((= pid 0) +; ;; We're the child. + +; ;; set standard-input and standard-output, swapping input and +; ;; output sockets from parent... +; (display 'foo) (newline) (flush-all-ports) +; ;;(redirect-port (car sockets) (current-input-port)) +; (set-current-input-port (cdr sockets)) +; (display 'bar) (newline) (flush-all-ports) +; ;;(redirect-port (cdr sockets) (current-output-port)) +; (set-current-output-port (cdr sockets)) + +; (parrot)) + +; ; (or (false-if-exception +; ; (if envt +; ; (apply execle path envt args) +; ; (apply execl path args))) +; ; (exit 1))) + +; (else +; ;; we're the parent +; ;; child-input-port child-output-port child-error-port +; (list pid (car sockets) #f))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; This code was part of an attempt to just return one +;;; read-write-port for the child, but I had some trouble getting it +;;; to work. I think either (1) this was misguided from the start +;;; since you can't hook up the plumbing this way, or (2) I was +;;; forgetting some flushing or something somewhere that kept it from +;;; working. At one point, I knew which of these two options was +;;; true, but I can't recall what I concluded now, so I'll leave the +;;; code here in case we want to resurrect it... + +; (define (run-sub-process envt path . args) +; (let ((pid #f) +; (sockets (false-if-exception (socketpair AF_UNIX SOCK_STREAM 0)))) + +; (if sockets +; (set! pid (false-if-exception (primitive-fork)))) + +; (cond +; ((or (not sockets) (not pid)) #f) + +; ((= pid 0) +; ;; We're the child: set standard-input and standard-output to be +; ;; the socket that's connected to the parent. +; (set-current-input-port (cdr sockets)) +; (set-current-output-port (cdr sockets)) +; (dup->fdes (cdr sockets) 0) +; (dup->fdes (cdr sockets) 1) + +; ;; now launch the child process. +; (or (false-if-exception +; (if envt +; (apply execle path envt args) +; (apply execl path args))) +; (exit 1))) + +; (else +; ;; we're the parent +; (list pid (car sockets) #f)))))