2003-05-26 Benoit Gr�goire <bock@step.polymtl.ca>

* src/import-export/import-settings.c: Revert previous gettext macro addition.
	* src/engine/TransLog.c,h: Change the log format to use GUID instead of C pointers and to use ISO8601 instead of proprietary format.

	* src/engine/gnc-numeric.h
	* src/import-export/import-match-map.c: Doxygen update

	* configure.in
	* src/scm/main.scm
	* src/import-export/Makefile.am
	* src/import-export/log-replay/*: New log replay module.  This ALMOST works, except I forgot you can't set the GUID of gnucash's objects, and thus completely screwed up on the playback logic.  I'll think of a solution when I am rested.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@8440 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Benoit Grégoire 2003-05-29 04:47:54 +00:00
parent e67e3c4254
commit dbed96de7b
15 changed files with 889 additions and 87 deletions

View File

@ -1,3 +1,16 @@
2003-05-26 Benoit Grégoire <bock@step.polymtl.ca>
* src/import-export/import-settings.c: Revert previous gettext macro addition.
* src/engine/TransLog.c,h: Change the log format to use GUID instead of C pointers and to use ISO8601 instead of proprietary format.
* src/engine/gnc-numeric.h
* src/import-export/import-match-map.c: Doxygen update
* configure.in
* src/scm/main.scm
* src/import-export/Makefile.am
* src/import-export/log-replay/*: New log replay module. This ALMOST works, except I forgot you can't set the GUID of gnucash's objects, and thus completely screwed up on the playback logic. I'll think of a solution when I am rested.
2003-05-28 Derek Atkins <derek@ihtfp.com>
* src/business/business-core/gncTaxTable.c: when asking for

View File

@ -1169,6 +1169,7 @@ AC_OUTPUT( m4/Makefile intl/Makefile po/Makefile.in
src/import-export/qif-io-core/test/Makefile
src/import-export/ofx/Makefile
src/import-export/ofx/test/Makefile
src/import-export/log-replay/Makefile
src/import-export/hbci/Makefile
src/import-export/hbci/glade/Makefile
src/import-export/hbci/test/Makefile

View File

@ -37,12 +37,6 @@
#include "TransLog.h"
#include "gnc-engine-util.h"
/*
* The logfiles are useful for tracing, journalling, error recovery.
* Note that the current support for journalling is at best
* embryonic, at worst, is dangerous by setting the wrong expectations.
*/
/*
* Some design philosphy that I think would be good to keep in mind:
* (0) Simplicity and foolproofness are the over-riding design points.
@ -84,19 +78,6 @@
* is needed for identifying the account.
*/
/* ------------------------------------------------------------------ */
/*
* The engine currently uses the log mechanism with flag char set as
* follows:
*
* 'B' for 'begin edit' (followed by the transaction as it looks
* before any changes, i.e. the 'old value')
* 'D' for delete (i.e. delete the previous B; echoes the data in the
* 'old B')
* 'C' for commit (i.e. accept a previous B; data that follows is the
* 'new value')
* 'R' for rollback (i.e. revert to previous B; data that follows should
* be identical to old B)
*/
static int gen_logs = 1;
@ -160,11 +141,11 @@ xaccOpenLog (void)
g_free (timestamp);
/* use tab-separated fields */
fprintf (trans_log, "mod id time_now " \
fprintf (trans_log, "mod trans_guid split_guid time_now " \
"date_entered date_posted " \
"account num description " \
"acc_guid acc_name num description " \
"memo action reconciled " \
"amount price date_reconciled\n");
"amount value date_reconciled\n");
fprintf (trans_log, "-----------------\n");
}
@ -187,35 +168,50 @@ void
xaccTransWriteLog (Transaction *trans, char flag)
{
GList *node;
char *dnow, *dent, *dpost, *drecn;
char *trans_guid_str, *split_guid_str;
char dnow[100], dent[100], dpost[100], drecn[100];
Timespec ts;
if (!gen_logs) return;
if (!trans_log) return;
dnow = xaccDateUtilGetStampNow ();
dent = xaccDateUtilGetStamp (trans->date_entered.tv_sec);
dpost = xaccDateUtilGetStamp (trans->date_posted.tv_sec);
timespecFromTime_t(&ts,time(NULL));
gnc_timespec_to_iso8601_buff (ts, dnow);
timespecFromTime_t(&ts,trans->date_entered.tv_sec);
gnc_timespec_to_iso8601_buff (ts, dent);
timespecFromTime_t(&ts,trans->date_posted.tv_sec);
gnc_timespec_to_iso8601_buff (ts, dpost);
trans_guid_str = guid_to_string (xaccTransGetGUID(trans));
fprintf (trans_log, "===== START\n");
for (node = trans->splits; node; node = node->next) {
Split *split = node->data;
const char * accname = "";
char * acc_guid_str = NULL;
if (xaccSplitGetAccount(split))
if (xaccSplitGetAccount(split)){
accname = xaccAccountGetName (xaccSplitGetAccount(split));
acc_guid_str = guid_to_string (xaccAccountGetGUID(xaccSplitGetAccount(split)));
}
drecn = xaccDateUtilGetStamp (split->date_reconciled.tv_sec);
timespecFromTime_t(&ts,split->date_reconciled.tv_sec);
gnc_timespec_to_iso8601_buff (ts, drecn);
split_guid_str = guid_to_string (xaccSplitGetGUID(split));
/* use tab-separated fields */
fprintf (trans_log,
"%c\t%p/%p\t%s\t%s\t%s\t%s\t%s\t"
"%c\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
"%s\t%s\t%s\t%c\t%lld/%lld\t%lld/%lld\t%s\n",
flag,
trans, split, /* trans+split make up unique id */
trans_guid_str, split_guid_str, /* trans+split make up unique id */
dnow ? dnow : "",
dent ? dent : "",
dpost ? dpost : "",
acc_guid_str ? acc_guid_str : "",
accname ? accname : "",
trans->num ? trans->num : "",
trans->description ? trans->description : "",
@ -228,14 +224,12 @@ xaccTransWriteLog (Transaction *trans, char flag)
(long long int) gnc_numeric_denom(split->value),
drecn ? drecn : "");
g_free (drecn);
g_free (split_guid_str);
}
fprintf (trans_log, "===== END\n");
g_free (dnow);
g_free (dent);
g_free (dpost);
g_free (trans_guid_str);
/* get data out to the disk */
fflush (trans_log);

View File

@ -1,7 +1,4 @@
/********************************************************************\
* TransLog.h -- the transaction logger *
* Copyright (C) 1998 Linas Vepstas *
* *
* 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 *
@ -21,7 +18,17 @@
* *
\********************************************************************/
/** @file TransLog.h The transaction logger */
/** @addtogroup Engine
@{ */
/** @file TransLog.h
@brief API for the transaction logger
*
* The logfiles are useful for tracing, journalling, error recovery.
* Note that the current support for journalling is at best
* embryonic, at worst, is dangerous by setting the wrong expectations.
*
@author Copyright (C) 1998 Linas Vepstas
*/
#ifndef XACC_TRANS_LOG_H
#define XACC_TRANS_LOG_H
@ -33,11 +40,27 @@
void xaccOpenLog (void);
void xaccCloseLog (void);
/**
\param char The engine currently uses the log mechanism with flag char set as
* follows:
* 'B' for 'begin edit' (followed by the transaction as it looks
* before any changes, i.e. the 'old value')
* 'D' for delete (i.e. delete the previous B; echoes the data in the
* 'old B')
* 'C' for commit (i.e. accept a previous B; data that follows is the
* 'new value')
* 'R' for rollback (i.e. revert to previous B; data that follows should
* be identical to old B)
*/
void xaccTransWriteLog (Transaction *, char);
/** document me */
void xaccLogEnable (void);
/** document me */
void xaccLogDisable (void);
/* The xaccLogSetBaseName() method sets the base filepath and the
/** The xaccLogSetBaseName() method sets the base filepath and the
* root part of the journal file name. If the journal file is
* already open, it will close it and reopen it with the new
* base name.
@ -45,4 +68,5 @@ void xaccLogDisable (void);
void xaccLogSetBaseName (const char *);
#endif /* XACC_TRANS_LOG_H */
/** @} */

View File

@ -1,7 +1,4 @@
/********************************************************************
* gnc-numeric.h -- an exact-number library for gnucash. *
* Copyright (C) 2000 Bill Gribble *
* *
* 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 *
@ -21,7 +18,12 @@
* *
*******************************************************************/
/** @file gnc-numeric.h @brief An exact-number library for gnucash.*/
/** @addtogroup Engine
@{ */
/** @file gnc-numeric.h
@brief An exact-number library for gnucash.
@author Copyright (C) 2000 Bill Gribble
*/
#ifndef GNC_NUMERIC_H
#define GNC_NUMERIC_H
@ -211,3 +213,5 @@ gnc_numeric gnc_numeric_reduce(gnc_numeric in);
#endif
/*@}*/

View File

@ -1,4 +1,4 @@
SUBDIRS = . binary-import qif-import ${OFX_DIR} ${HBCI_DIR} test
SUBDIRS = . binary-import qif-import ${OFX_DIR} ${HBCI_DIR} test log-replay
pkglib_LTLIBRARIES=libgncmod-generic-import.la
@ -80,4 +80,5 @@ EXTRA_DIST = \
DISTCLEANFILES = gnucash g-wrapped .scm-links import-export
# FIXME remove this when qif-io-core is finished
DIST_SUBDIRS = binary-import qif-import qif-io-core ofx test hbci
DIST_SUBDIRS = binary-import qif-import qif-io-core ofx test hbci log-replay

View File

@ -73,7 +73,7 @@ gnc_imap_create_from_frame (kvp_frame *frame, Account *acc, GNCBook *book)
return imap;
}
/* Obtain an ImportMatchMap object from an Account or a Book */
/** Obtain an ImportMatchMap object from an Account or a Book */
GncImportMatchMap * gnc_imap_create_from_account (Account *acc)
{
kvp_frame * frame;
@ -96,14 +96,14 @@ GncImportMatchMap * gnc_imap_create_from_book (GNCBook *book)
return gnc_imap_create_from_frame (frame, NULL, book);
}
/* Destroy an import map */
/** Destroy an import map */
void gnc_imap_destroy (GncImportMatchMap *imap)
{
if (!imap) return;
g_free (imap);
}
/* Clear an import map -- this removes ALL entries in the map */
/** Clear an import map -- this removes ALL entries in the map */
void gnc_imap_clear (GncImportMatchMap *imap)
{
if (!imap) return;
@ -117,7 +117,7 @@ void gnc_imap_clear (GncImportMatchMap *imap)
/* XXX: mark the account (or book) as dirty! */
}
/* Look up an Account in the map */
/** Look up an Account in the map */
Account * gnc_imap_find_account (GncImportMatchMap *imap, const char *category,
const char *key)
{
@ -137,7 +137,7 @@ Account * gnc_imap_find_account (GncImportMatchMap *imap, const char *category,
return xaccAccountLookup (guid, imap->book);
}
/* Store an Account in the map */
/** Store an Account in the map */
void gnc_imap_add_account (GncImportMatchMap *imap, const char *category,
const char *key, Account *acc)
{
@ -161,24 +161,28 @@ void gnc_imap_add_account (GncImportMatchMap *imap, const char *category,
/* Below here is the bayes transaction to account matching system */
/*--------------------------------------------------------------------------
Below here is the bayes transaction to account matching system
--------------------------------------------------------------------------*/
struct account_token_count
{
char* account_name;
gint64 token_count; /* occurances of a given token for this account_name */
gint64 token_count; /**< occurances of a given token for this account_name */
};
/* total_count and the token_count for a given account let us calculate the
/** total_count and the token_count for a given account let us calculate the
* probability of a given account with any single token
*/
struct token_accounts_info
{
GList *accounts; /* array of struct account_token_count */
GList *accounts; /**< array of struct account_token_count */
gint64 total_count;
};
/* gpointer is a pointer to a struct token_accounts_info
* NOTE: can always assume that keys are unique, reduces code in this function
/** gpointer is a pointer to a struct token_accounts_info
* \note Can always assume that keys are unique, reduces code in this function
*/
static void buildTokenInfo(const char *key, kvp_value *value, gpointer data)
{
@ -203,9 +207,9 @@ static void buildTokenInfo(const char *key, kvp_value *value, gpointer data)
tokenInfo->accounts = g_list_prepend(tokenInfo->accounts, this_account);
}
/* intermediate values used to calculate the bayes probability of a given account
* where p(AB) = (a*b)/[a*b + (1-a)(1-b)], product is (a*b),
* product_difference is (1-a) * (1-b)
/** intermediate values used to calculate the bayes probability of a given account
where p(AB) = (a*b)/[a*b + (1-a)(1-b)], product is (a*b),
product_difference is (1-a) * (1-b)
*/
struct account_probability
{
@ -213,9 +217,9 @@ struct account_probability
double product_difference; /* product of (1-probabilities) */
};
/* convert a hash table of account names and (struct account_probability*)
* into a hash table of 100000x the percentage match value, ie. 10% would be
* 0.10 * 100000 = 10000
/** convert a hash table of account names and (struct account_probability*)
into a hash table of 100000x the percentage match value, ie. 10% would be
0.10 * 100000 = 10000
*/
#define PROBABILITY_FACTOR 100000
static void buildProbabilities(gpointer key, gpointer value, gpointer data)
@ -237,7 +241,7 @@ static void buildProbabilities(gpointer key, gpointer value, gpointer data)
g_hash_table_insert(final_probabilities, key, (gpointer)probability);
}
/* Frees an array of the same time that buildProperties built */
/** Frees an array of the same time that buildProperties built */
static void freeProbabilities(gpointer key, gpointer value, gpointer data)
{
/* free up the struct account_probability that was allocated
@ -246,8 +250,8 @@ static void freeProbabilities(gpointer key, gpointer value, gpointer data)
g_free(value);
}
/* holds an account name and its corresponding integer probability
* the integer probability is some factor of 10
/** holds an account name and its corresponding integer probability
the integer probability is some factor of 10
*/
struct account_info
{
@ -255,11 +259,11 @@ struct account_info
gint32 probability;
};
/* Find the highest probability and the corresponding account name
* store in data, a (struct account_info*)
* NOTE: this is a g_hash_table_foreach() function for a hash table of entries
* key is a pointer to the account name, value is a gint32, 100000x
* the probability for this account
/** Find the highest probability and the corresponding account name
store in data, a (struct account_info*)
NOTE: this is a g_hash_table_foreach() function for a hash table of entries
key is a pointer to the account name, value is a gint32, 100000x
the probability for this account
*/
static void highestProbability(gpointer key, gpointer value, gpointer data)
{
@ -277,19 +281,19 @@ static void highestProbability(gpointer key, gpointer value, gpointer data)
#define threshold (.90 * PROBABILITY_FACTOR) /* 90% */
/* Look up an Account in the map */
/** Look up an Account in the map */
Account* gnc_imap_find_account_bayes(GncImportMatchMap *imap, GList *tokens)
{
struct token_accounts_info tokenInfo; /* holds the accounts and total
struct token_accounts_info tokenInfo; /**< holds the accounts and total
* token count for a single token */
GList *current_token; /* pointer to the current token from the
GList *current_token; /**< pointer to the current token from the
* input GList *tokens */
GList *current_account_token; /* pointer to the struct
GList *current_account_token; /**< pointer to the struct
* account_token_count */
struct account_token_count *account_c; /* an account name and the number
struct account_token_count *account_c; /**< an account name and the number
* of times a token has appeared
* for the account */
struct account_probability *account_p; /* intermediate storage of values
struct account_probability *account_p; /**< intermediate storage of values
* to compute the bayes probability
* of an account */
GHashTable *running_probabilities = g_hash_table_new(g_str_hash, g_str_equal);
@ -300,6 +304,8 @@ Account* gnc_imap_find_account_bayes(GncImportMatchMap *imap, GList *tokens)
ENTER(" ");
gnc_set_log_level(MOD_IMPORT, GNC_LOG_INFO);
/* check to see if the imap is NULL */
if(!imap)
{
@ -450,7 +456,7 @@ Account* gnc_imap_find_account_bayes(GncImportMatchMap *imap, GList *tokens)
}
/* Updates the imap for a given account using a list of tokens */
/** Updates the imap for a given account using a list of tokens */
void gnc_imap_add_account_bayes(GncImportMatchMap *imap, GList *tokens, Account *acc)
{
GList *current_token;

View File

@ -29,7 +29,6 @@
#include "config.h"
#include <glib.h>
#include <stdlib.h>
#include "dialog-utils.h"
#include "global-options.h"
#include "import-settings.h"
@ -102,26 +101,26 @@ gnc_import_Settings_new (void)
settings->action_skip_enabled =
gnc_lookup_boolean_option(MATCHER_PREF_PAGE,
_("Enable SKIP transaction action"),
"Enable SKIP transaction action",
DEFAULT_ACTION_SKIP_ENABLED);
settings->action_edit_enabled =
gnc_lookup_boolean_option(MATCHER_PREF_PAGE,
_("Enable EDIT match action"),
"Enable EDIT match action",
DEFAULT_ACTION_EDIT_ENABLED);
settings->action_add_enabled=DEFAULT_ACTION_ADD_ENABLED;
settings->action_clear_enabled=DEFAULT_ACTION_CLEAR_ENABLED;
settings->clear_threshold=gnc_lookup_number_option(MATCHER_PREF_PAGE,
_("Auto-CLEAR threshold"),
"Auto-CLEAR threshold",
DEFAULT_CLEAR_THRESHOLD);
settings->add_threshold=gnc_lookup_number_option(MATCHER_PREF_PAGE,
_("Auto-ADD threshold"),
"Auto-ADD threshold",
DEFAULT_ADD_THRESHOLD);
settings->display_threshold =
gnc_lookup_number_option(MATCHER_PREF_PAGE,_("Match display threshold"),
gnc_lookup_number_option(MATCHER_PREF_PAGE,"Match display threshold",
DEFAULT_DISPLAY_THRESHOLD);
settings->fuzzy_amount =
gnc_lookup_number_option(MATCHER_PREF_PAGE,_("Commercial ATM fees threshold"),
gnc_lookup_number_option(MATCHER_PREF_PAGE,"Commercial ATM fees threshold",
DEFAULT_ATM_FEE_THRESHOLD);
return settings;

View File

@ -0,0 +1,6 @@
*.lo
*.la
.deps
.libs
Makefile
Makefile.in

View File

@ -0,0 +1,44 @@
SUBDIRS = .
pkglib_LTLIBRARIES=libgncmod-log-replay.la
libgncmod_log_replay_la_SOURCES = \
gnc-log-replay.c \
gncmod-log-replay.c
noinst_HEADERS = \
gnc-log-replay.h
libgncmod_log_replay_la_LDFLAGS = -module
libgncmod_log_replay_la_LIBADD = \
${top_builddir}/src/gnc-module/libgncmodule.la \
${top_builddir}/src/engine/libgncmod-engine.la \
${top_builddir}/src/import-export/libgncmod-generic-import.la \
${GLIB_LIBS}
gncscmdir = ${GNC_SCM_INSTALL_DIR}/log-replay
gncscm_DATA = \
log-replay.scm
AM_CFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/engine \
-I${top_srcdir}/src/gnc-module \
-I${top_srcdir}/src/app-utils \
-I${top_srcdir}/src/app-file \
-I${top_srcdir}/src/gnome \
-I${top_srcdir}/src/gnome-utils \
-I${top_srcdir}/src/import-export \
${GNOME_INCLUDEDIR} \
${GTKHTML_CFLAGS} \
${GLADE_CFLAGS} \
${GUILE_INCS} \
${GLIB_CFLAGS}
EXTRA_DIST = \
.cvsignore \
${gncscm_DATA}
CLEANFILES = g-wrapped .scm-links

View File

@ -0,0 +1,573 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @addtogroup Import_Export
@{ */
/** @internal
@file gnc-log-replay.c
@brief .log file replay code
@author Copyright (c) 2003 Benoit Grégoire <bock@step.polymtl.ca>
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <glib.h>
#include <libguile.h>
#include <gmodule.h>
#include "libofx/libofx.h"
#include "import-account-matcher.h"
#include "import-commodity-matcher.h"
#include "import-utilities.h"
#include "import-main-matcher.h"
#include "Account.h"
#include "Transaction.h"
#include "global-options.h"
#include "gnc-associate-account.h"
#include "gnc-log-replay.h"
#include "gnc-file-dialog.h"
#include "gnc-engine-util.h"
#include "gnc-book.h"
#include "gnc-ui-util.h"
#include "dialog-utils.h"
/*static short module = MOD_IMPORT;*/
static short module = MOD_TEST;
/* fprintf (trans_log, "mod guid time_now " \
"date_entered date_posted " \
"acc_guid acc_name num description " \
"memo action reconciled " \
"amount value date_reconciled\n");
"%c\t%s/%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
"%s\t%s\t%s\t%c\t%lld/%lld\t%lld/%lld\t%s\n",
*/
#define STRING_FIELD_SIZE 256
typedef struct _split_record
{
enum _enum_action {LOG_BEGIN_EDIT, LOG_ROLLBACK, LOG_COMMIT, LOG_DELETE} log_action;
int log_action_present;
GUID trans_guid;
int trans_guid_present;
GUID split_guid;
int split_guid_present;
Timespec log_date;
int log_date_present;
Timespec date_entered;
int date_entered_present;
Timespec date_posted;
int date_posted_present;
GUID acc_guid;
int acc_guid_present;
char acc_name[STRING_FIELD_SIZE];
int acc_name_present;
char trans_num[STRING_FIELD_SIZE];
int trans_num_present;
char trans_descr[STRING_FIELD_SIZE];
int trans_descr_present;
char split_memo[STRING_FIELD_SIZE];
int split_memo_present;
char split_action[STRING_FIELD_SIZE];
int split_action_present;
char split_reconcile;
int split_reconcile_present;
gnc_numeric amount;
int amount_present;
gnc_numeric value;
int value_present;
Timespec date_reconciled;
int date_reconciled_present;
} split_record;
/********************************************************************\
* gnc_file_ofx_import
* Entry point
\********************************************************************/
SCM scm_gnc_file_log_replay ()
{
gnc_file_log_replay();
return SCM_EOL;
}
static char *olds;
/* This version of strtok will only match SINGLE occurence of delim,
returning a 0 length valid string between two consecutive ocurence of delim.
It will also return a 0 length string instead of NULL when it reaches the end of s
*/
static char * my_strtok (s, delim)
char *s;
const char *delim;
{
char *token;
/*DEBUG("strtok(): Start...");*/
if (s == NULL)
s = olds;
/* Scan leading delimiters. */
/*s += strspn (s, delim);*/ /*Don't do it, or we will loose count.*/
if (*s == '\0')
{
olds = s;
return s;
}
/* Find the end of the token. */
token = s;
s = strpbrk (token, delim);
if (s == NULL)
{
/* This token finishes the string. */
olds = strchr (token, '\0');
}
else
{
/* Terminate the token and make OLDS point past it. */
*s = '\0';
olds = s + 1;
}
return token;
}
static split_record interpret_split_record( char *record_line)
{
char * tok_ptr;
split_record record;
memset(&record,0,sizeof(record));
DEBUG("interpret_split_record(): Start...");
if(strlen(tok_ptr = my_strtok(record_line,"\t"))!=0)
{
switch(tok_ptr[0])
{
case 'B': record.log_action=LOG_BEGIN_EDIT;
break;
case 'D': record.log_action=LOG_DELETE;
break;
case 'C': record.log_action=LOG_COMMIT;
break;
case 'R': record.log_action=LOG_ROLLBACK;
break;
}
record.log_action_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.trans_guid));
record.trans_guid_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.split_guid));
record.split_guid_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.log_date = gnc_iso8601_to_timespec_local(tok_ptr);
record.log_date_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_entered = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_entered_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_posted = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_posted_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.acc_guid));
record.acc_guid_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.acc_name,tok_ptr,STRING_FIELD_SIZE-1);
record.acc_name_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.trans_num,tok_ptr,STRING_FIELD_SIZE-1);
record.trans_num_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.trans_descr,tok_ptr,STRING_FIELD_SIZE-1);
record.trans_descr_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.split_memo,tok_ptr,STRING_FIELD_SIZE-1);
record.split_memo_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.split_action,tok_ptr,STRING_FIELD_SIZE-1);
record.split_action_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.split_reconcile = tok_ptr[0];
record.split_reconcile_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_gnc_numeric(tok_ptr, &(record.amount));
record.amount_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_gnc_numeric(tok_ptr, &(record.value));
record.value_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_reconciled = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_reconciled_present=true;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
PERR("interpret_split_record(): Expected number of fields exceeded!");
}
DEBUG("interpret_split_record(): End");
return record;
}
static void dump_split_record(split_record record)
{
char * string_ptr = NULL;
char string_buf[256];
DEBUG("dump_split_record(): Start...");
if(record.log_action_present)
{
switch(record.log_action)
{
case LOG_BEGIN_EDIT: DEBUG("Log action: LOG_BEGIN_EDIT");
break;
case LOG_DELETE: DEBUG("Log action: LOG_DELETE");
break;
case LOG_COMMIT: DEBUG("Log action: LOG_COMMIT");
break;
case LOG_ROLLBACK: DEBUG("Log action: LOG_ROLLBACK");
break;
}
}
if(record.trans_guid_present)
{
string_ptr = guid_to_string (&(record.trans_guid));
DEBUG("Transaction GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.split_guid_present)
{
string_ptr = guid_to_string (&(record.split_guid));
DEBUG("Split GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.log_date_present)
{
gnc_timespec_to_iso8601_buff (record.log_date, string_buf);
DEBUG("Log entry date: %s", string_buf);
}
if(record.date_entered_present)
{
gnc_timespec_to_iso8601_buff (record.date_entered, string_buf);
DEBUG("Date entered: %s", string_buf);
}
if(record.date_posted_present)
{
gnc_timespec_to_iso8601_buff (record.date_posted, string_buf);
DEBUG("Date posted: %s", string_buf);
}
if(record.acc_guid_present)
{
string_ptr = guid_to_string (&(record.acc_guid));
DEBUG("Account GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.acc_name_present)
{
DEBUG("Account name: %s", record.acc_name);
}
if(record.trans_num_present)
{
DEBUG("Transaction number: %s", record.trans_num);
}
if(record.trans_descr_present)
{
DEBUG("Transaction description: %s", record.trans_descr);
}
if(record.split_memo_present)
{
DEBUG("Split memo: %s", record.split_memo);
}
if(record.split_action_present)
{
DEBUG("Split action: %s", record.split_action);
}
if(record.split_reconcile_present)
{
DEBUG("Split reconcile: %c", record.split_reconcile);
}
if(record.amount_present)
{
string_ptr = gnc_numeric_to_string(record.amount);
DEBUG("Record amount: %s", string_ptr);
g_free(string_ptr);
}
if(record.value_present)
{
string_ptr = gnc_numeric_to_string(record.value);
DEBUG("Record value: %s", string_ptr);
g_free(string_ptr);
}
if(record.date_reconciled_present)
{
gnc_timespec_to_iso8601_buff (record.date_reconciled, string_buf);
DEBUG("Reconciled date: %s", string_buf);
}
}
/* File pointer must already be at the begining of a record */
static void process_trans_record( FILE *log_file)
{
char read_buf[256];
char *read_retval;
const char * record_end_str = "===== END";
int first_record=true;
int record_ended = false;
int split_num = 0;
split_record record;
Transaction * trans = NULL;
Split * split = NULL;
Account * acct = NULL;
GNCBook * book = gnc_get_current_book();
DEBUG("process_trans_record(): Begin...\n");
while( record_ended == false)
{
read_retval = fgets(read_buf,sizeof(read_buf),log_file);
if(read_retval!=NULL && strncmp(record_end_str,read_buf,strlen(record_end_str))!=0)/* If we are not at the end of the record */
{
split_num++;
/*DEBUG("process_trans_record(): Line read: %s%s",read_buf ,"\n");*/
record = interpret_split_record( read_buf);
dump_split_record( record);
if(record.log_action_present)
{
switch(record.log_action)
{
case LOG_BEGIN_EDIT: DEBUG("process_trans_record():Ignoring log action: LOG_BEGIN_EDIT"); /*Do nothing, there is no point*/
break;
case LOG_ROLLBACK: DEBUG("process_trans_record():Ignoring log action: LOG_ROLLBACK");/*Do nothing, since we didn't do the begin_edit either*/
break;
case LOG_DELETE: DEBUG("process_trans_record(): Playing back LOG_DELETE");
if((trans=xaccTransLookup (&(record.trans_guid), book))!=NULL
&& first_record==true)
{
xaccTransBeginEdit(trans);
xaccTransDestroy(trans);
}
else if(first_record==true)
{
PERR("The transaction to delete was not found!");
}
break;
case LOG_COMMIT: DEBUG("process_trans_record(): Playing back LOG_COMMIT");
if(record.trans_guid_present == true
&& (trans=xaccTransLookupDirect (record.trans_guid, book)) != NULL
&& first_record == true)
{
DEBUG("process_trans_record(): Transaction to be edited was found");/*Destroy the current transaction, we will create a new one to replace it*/
xaccTransBeginEdit(trans);
xaccTransDestroy(trans);
xaccTransCommitEdit(trans);
}
if(record.trans_guid_present == true
&& first_record==true)
{
DEBUG("process_trans_record(): Creating the new transaction");
trans = xaccMallocTransaction (book);
xaccTransBeginEdit(trans);
/*Fill the transaction info*/
if(record.date_entered_present)
{
xaccTransSetDateEnteredTS(trans,&(record.date_entered));
}
if(record.date_posted_present)
{
xaccTransSetDatePostedTS(trans,&(record.date_posted));
}
if(record.trans_num_present)
{
xaccTransSetNum(trans,record.trans_num);
}
if(record.trans_descr_present)
{
xaccTransSetDescription(trans,record.trans_descr);
}
}
if(record.split_guid_present == true) /*Fill the split info*/
{
split=xaccMallocSplit(book);
if(record.acc_guid_present)
{
acct = xaccAccountLookupDirect(record.acc_guid,book);
xaccAccountInsertSplit(acct,split);
}
xaccTransAppendSplit(trans,split);
if(record.split_memo_present)
{
xaccSplitSetMemo(split,record.split_memo);
}
if(record.split_action_present)
{
xaccSplitSetAction(split,record.split_action);
}
if(record.date_reconciled_present)
{
xaccSplitSetDateReconciledTS (split, &(record.date_reconciled));
}
if(record.split_reconcile_present)
{
xaccSplitSetReconcile(split, record.split_reconcile);
}
if(record.amount_present)
{
xaccSplitSetAmount(split, record.amount);
}
if(record.value_present)
{
xaccSplitSetValue(split, record.value);
}
}
first_record=false;
break;
}
}
else
{
PERR("Corrupted record");
}
}
else /* The record ended */
{
record_ended = true;
DEBUG("process_trans_record(): Record ended\n");
if(trans!=NULL)/*If we played with a transaction, commit it here*/
{
xaccTransCommitEdit(trans);
}
}
}
}
void gnc_file_log_replay (void)
{
const char *selected_filename;
char *default_dir;
char read_buf[256];
char *read_retval;
FILE *log_file;
char * expected_header = "mod trans_guid split_guid time_now date_entered date_posted acc_guid acc_name num description memo action reconciled amount value date_reconciled";
char * record_start_str = "===== START";
gnc_should_log(MOD_IMPORT, GNC_LOG_DEBUG);
DEBUG("gnc_file_log_replay(): Begin...\n");
default_dir = gnc_lookup_string_option("__paths", "Log Files", NULL);
if (default_dir == NULL)
gnc_init_default_directory(&default_dir);
selected_filename = gnc_file_dialog(_("Select a .log file to replay"),
NULL,
default_dir);
if(selected_filename!=NULL)
{
/* Remember the directory as the default. */
gnc_extract_directory(&default_dir, selected_filename);
gnc_set_string_option("__paths", "Log Files", default_dir);
g_free(default_dir);
/*strncpy(file,selected_filename, 255);*/
DEBUG("Filename found: %s",selected_filename);
DEBUG("Opening selected file");
log_file = fopen(selected_filename, "r");
if(ferror(log_file)!=0)
{
perror("File open failed");
}
else
{
if((read_retval = fgets(read_buf,sizeof(read_buf),log_file)) == NULL)
{
DEBUG("Read error or EOF");
}
else
{
if(strncmp(expected_header,read_buf,strlen(expected_header))!=0)
{
PERR("File header not recognised:\n%s",read_buf);
PERR("Expected:\n%s",expected_header);
}
else
{
do
{
read_retval = fgets(read_buf,sizeof(read_buf),log_file);
/*DEBUG("Chunk read: %s",read_retval);*/
if(strncmp(record_start_str,read_buf,strlen(record_start_str))==0)/* If a record started */
{
process_trans_record(log_file);
}
}while(feof(log_file)==0);
}
}
fclose(log_file);
}
}
}
/** @} */

View File

@ -0,0 +1,34 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @file
@brief .log replay module interface
*
gnc-log-replay.h
@author Copyright (c) 2003 Benoit Grégoire <bock@step.polymtl.ca>
*/
#ifndef OFX_IMPORT_H
#define OFX_IMPORT_H
/** The gnc_file_log_replay() routine will pop up a standard file
* selection dialogue asking the user to pick a log file to replay. If one
* is selected the the .log file is opened and read. It's contents
* are then silently merged in the current log file. */
void gnc_file_log_replay (void);
SCM scm_gnc_file_log_replay (void);
#endif

View File

@ -0,0 +1,92 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @addtogroup Import_Export
@{ */
/**@internal
@file gncmod-log-replay.c
@brief module definition/initialization for the log replay module
@author Copyright (c) 2003 Benoit Grégoire bock@step.polymtl.ca
*/
#include "config.h"
#include <glib.h>
#include <libguile.h>
#include "guile-mappings.h"
#include "gnc-log-replay.h"
#include "gnc-module.h"
#include "gnc-module-api.h"
/* version of the gnc module system interface we require */
int libgncmod_log_replay_LTX_gnc_module_system_interface = 0;
/* module versioning uses libtool semantics. */
int libgncmod_log_replay_LTX_gnc_module_current = 0;
int libgncmod_log_replay_LTX_gnc_module_revision = 0;
int libgncmod_log_replay_LTX_gnc_module_age = 0;
//static GNCModule bus_core;
//static GNCModule file;
/* forward references */
char *libgncmod_log_replay_LTX_gnc_module_path(void);
char *libgncmod_log_replay_LTX_gnc_module_description(void);
int libgncmod_log_replay_LTX_gnc_module_init(int refcount);
int libgncmod_log_replay_LTX_gnc_module_end(int refcount);
char *
libgncmod_log_replay_LTX_gnc_module_path(void)
{
return g_strdup("gnucash/import-export/log-replay");
}
char *
libgncmod_log_replay_LTX_gnc_module_description(void)
{
return g_strdup("C code for log file replay");
}
int
libgncmod_log_replay_LTX_gnc_module_init(int refcount)
{
if(!gnc_module_load("gnucash/engine", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/app-utils", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/gnome-utils", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/import-export", 0))
{
return FALSE;
}
scm_c_eval_string("(load-from-path \"log-replay/log-replay.scm\")");
scm_c_define_gsubr("gnc:log-replay", 0, 0, 0, scm_gnc_file_log_replay);
return TRUE;
}
int
libgncmod_log_replay_LTX_gnc_module_end(int refcount)
{
return TRUE;
}
/** @}*/

View File

@ -0,0 +1,10 @@
(define (add-log-replay-menu-item)
(gnc:add-extension
(gnc:make-menu-item(N_ "Replay GnuCash .log file")
(N_ "Replay a gnucash log file after a crash. This cannot be undone.")
(list gnc:window-name-main "File" "_Import" "")
(lambda ()
(gnc:log-replay)))))
(gnc:hook-add-dangler gnc:*ui-startup-hook* add-log-replay-menu-item)

View File

@ -427,6 +427,7 @@ string and 'directories' must be a list of strings."
(load-module "gnucash/import-export/binary-import" 0 #f)
(load-module "gnucash/import-export/qif-import" 0 #f)
(load-module "gnucash/import-export/ofx" 0 #t)
(load-module "gnucash/import-export/log-replay" 0 #t)
(load-module "gnucash/import-export/hbci" 0 #t)
(load-module "gnucash/report/report-system" 0 #f)
(load-module "gnucash/report/stylesheets" 0 #f)