29 #include <glib/gi18n.h> 30 #include <glib/gstdio.h> 37 #include "TransactionP.hpp" 43 #include "qofinstance-p.h" 45 #include "gnc-gui-query.h" 47 #define GNC_PREFS_GROUP "dialogs.log-replay" 52 static QofLogModule log_module = GNC_MOD_IMPORT;
62 #define STRING_FIELD_SIZE 256 63 typedef struct _split_record
65 enum _enum_action {LOG_BEGIN_EDIT, LOG_ROLLBACK, LOG_COMMIT, LOG_DELETE} log_action;
66 int log_action_present;
68 int trans_guid_present;
70 int split_guid_present;
74 int date_entered_present;
76 int date_posted_present;
79 char acc_name[STRING_FIELD_SIZE];
81 char trans_num[STRING_FIELD_SIZE];
82 int trans_num_present;
83 char trans_descr[STRING_FIELD_SIZE];
84 int trans_descr_present;
85 char trans_notes[STRING_FIELD_SIZE];
86 int trans_notes_present;
87 char split_memo[STRING_FIELD_SIZE];
88 int split_memo_present;
89 char split_action[STRING_FIELD_SIZE];
90 int split_action_present;
92 int split_reconcile_present;
98 int date_reconciled_present;
110 static char * my_strtok (
char *s,
const char *delim)
127 s = strpbrk (token, delim);
131 olds = strchr (token,
'\0');
142 static split_record interpret_split_record(
char *record_line)
146 memset(&record, 0,
sizeof(record));
147 DEBUG(
"interpret_split_record(): Start...");
148 if (strlen(tok_ptr = my_strtok(record_line,
"\t")) != 0)
153 record.log_action = split_record::_enum_action::LOG_BEGIN_EDIT;
156 record.log_action = split_record::_enum_action::LOG_DELETE;
159 record.log_action = split_record::_enum_action::LOG_COMMIT;
162 record.log_action = split_record::_enum_action::LOG_ROLLBACK;
165 record.log_action_present = TRUE;
167 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
170 record.trans_guid_present = TRUE;
172 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
175 record.split_guid_present = TRUE;
177 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
180 record.log_date_present = TRUE;
182 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
185 record.date_entered_present = TRUE;
187 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
190 record.date_posted_present = TRUE;
192 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
195 record.acc_guid_present = TRUE;
197 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
199 strncpy(record.acc_name, tok_ptr, STRING_FIELD_SIZE - 1);
200 record.acc_name_present = TRUE;
202 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
204 strncpy(record.trans_num, tok_ptr, STRING_FIELD_SIZE - 1);
205 record.trans_num_present = TRUE;
207 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
209 strncpy(record.trans_descr, tok_ptr, STRING_FIELD_SIZE - 1);
210 record.trans_descr_present = TRUE;
212 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
214 strncpy(record.trans_notes, tok_ptr, STRING_FIELD_SIZE - 1);
215 record.trans_notes_present = TRUE;
217 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
219 strncpy(record.split_memo, tok_ptr, STRING_FIELD_SIZE - 1);
220 record.split_memo_present = TRUE;
222 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
224 strncpy(record.split_action, tok_ptr, STRING_FIELD_SIZE - 1);
225 record.split_action_present = TRUE;
227 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
229 record.split_reconcile = tok_ptr[0];
230 record.split_reconcile_present = TRUE;
232 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
235 record.amount_present = TRUE;
237 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
240 record.value_present = TRUE;
242 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
245 record.date_reconciled_present = TRUE;
248 if (strlen(tok_ptr = my_strtok(NULL,
"\t")) != 0)
250 PERR(
"interpret_split_record(): Expected number of fields exceeded!");
252 DEBUG(
"interpret_split_record(): End");
258 char * string_ptr = NULL;
259 char string_buf[256];
261 DEBUG(
"dump_split_record(): Start...");
262 if (record.log_action_present)
264 switch (record.log_action)
266 case split_record::_enum_action::LOG_BEGIN_EDIT:
267 DEBUG(
"Log action: LOG_BEGIN_EDIT");
269 case split_record::_enum_action::LOG_DELETE:
270 DEBUG(
"Log action: LOG_DELETE");
272 case split_record::_enum_action::LOG_COMMIT:
273 DEBUG(
"Log action: LOG_COMMIT");
275 case split_record::_enum_action::LOG_ROLLBACK:
276 DEBUG(
"Log action: LOG_ROLLBACK");
280 if (record.trans_guid_present)
283 DEBUG(
"Transaction GncGUID: %s", string_buf);
285 if (record.split_guid_present)
288 DEBUG(
"Split GncGUID: %s", string_buf);
290 if (record.log_date_present)
293 DEBUG(
"Log entry date: %s", string_buf);
295 if (record.date_entered_present)
298 DEBUG(
"Date entered: %s", string_buf);
300 if (record.date_posted_present)
303 DEBUG(
"Date posted: %s", string_buf);
305 if (record.acc_guid_present)
308 DEBUG(
"Account GncGUID: %s", string_buf);
310 if (record.acc_name_present)
312 DEBUG(
"Account name: %s", record.acc_name);
314 if (record.trans_num_present)
316 DEBUG(
"Transaction number: %s", record.trans_num);
318 if (record.trans_descr_present)
320 DEBUG(
"Transaction description: %s", record.trans_descr);
322 if (record.trans_notes_present)
324 DEBUG(
"Transaction notes: %s", record.trans_notes);
326 if (record.split_memo_present)
328 DEBUG(
"Split memo: %s", record.split_memo);
330 if (record.split_action_present)
332 DEBUG(
"Split action: %s", record.split_action);
334 if (record.split_reconcile_present)
336 DEBUG(
"Split reconcile: %c", record.split_reconcile);
338 if (record.amount_present)
341 DEBUG(
"Record amount: %s", string_ptr);
344 if (record.value_present)
347 DEBUG(
"Record value: %s", string_ptr);
350 if (record.date_reconciled_present)
353 DEBUG(
"Reconciled date: %s", string_buf);
358 static void process_trans_record( FILE *log_file)
362 char * trans_ro = NULL;
363 const char * record_end_str =
"===== END";
364 int first_record = TRUE;
365 int record_ended = FALSE;
367 Transaction * trans = NULL;
368 Split * split = NULL;
370 QofBook * book = gnc_get_current_book();
372 DEBUG(
"process_trans_record(): Begin...\n");
374 while ( record_ended == FALSE)
376 read_retval = fgets(read_buf,
sizeof(read_buf), log_file);
377 if (read_retval != NULL &&
378 strncmp(record_end_str, read_buf, strlen(record_end_str)) != 0)
382 record = interpret_split_record(g_strchomp(read_buf));
383 dump_split_record( record);
384 if (record.log_action_present)
386 switch (record.log_action)
388 case split_record::_enum_action::LOG_BEGIN_EDIT:
389 DEBUG(
"process_trans_record():Ignoring log action: LOG_BEGIN_EDIT");
391 case split_record::_enum_action::LOG_ROLLBACK:
392 DEBUG(
"process_trans_record():Ignoring log action: LOG_ROLLBACK");
394 case split_record::_enum_action::LOG_DELETE:
395 DEBUG(
"process_trans_record(): Playing back LOG_DELETE");
397 && first_record == TRUE)
399 first_record = FALSE;
402 PWARN(
"Destroying a read only transaction.");
403 xaccTransClearReadOnly(trans);
408 else if (first_record == TRUE)
410 PERR(
"The transaction to delete was not found!");
415 case split_record::_enum_action::LOG_COMMIT:
416 DEBUG(
"process_trans_record(): Playing back LOG_COMMIT");
417 if (record.trans_guid_present == TRUE
418 && first_record == TRUE)
420 trans = xaccTransLookupDirect (record.trans_guid, book);
423 DEBUG(
"process_trans_record(): Transaction to be edited was found");
428 PWARN(
"Replaying a read only transaction.");
429 xaccTransClearReadOnly(trans);
434 DEBUG(
"process_trans_record(): Creating a new transaction");
439 qof_instance_set_guid (QOF_INSTANCE (trans),
440 &(record.trans_guid));
442 if (record.date_entered_present)
446 if (record.date_posted_present)
450 if (record.trans_num_present)
454 if (record.trans_descr_present)
458 if (record.trans_notes_present)
463 if (record.split_guid_present == TRUE)
465 gboolean is_new_split;
467 split = xaccSplitLookupDirect (record.split_guid, book);
470 DEBUG(
"process_trans_record(): Split to be edited was found");
471 is_new_split = FALSE;
475 DEBUG(
"process_trans_record(): Creating a new split");
479 xaccSplitSetGUID (split, &(record.split_guid));
480 if (record.acc_guid_present)
482 acct = xaccAccountLookupDirect(record.acc_guid, book);
492 if (record.split_memo_present)
496 if (record.split_action_present)
500 if (record.date_reconciled_present)
504 if (record.split_reconcile_present)
509 if (record.amount_present)
513 if (record.value_present)
518 first_record = FALSE;
524 PERR(
"Corrupted record");
530 DEBUG(
"process_trans_record(): Record ended\n");
544 char *selected_filename;
548 GtkFileFilter *filter;
550 const char * record_start_str =
"===== START";
552 const char * expected_header_orig =
"mod\ttrans_guid\tsplit_guid\ttime_now\t" 553 "date_entered\tdate_posted\tacc_guid\tacc_name\tnum\tdescription\t" 554 "notes\tmemo\taction\treconciled\tamount\tvalue\tdate_reconciled";
555 static char *expected_header = NULL;
558 if (!expected_header)
559 expected_header = g_strdup(expected_header_orig);
567 default_dir = gnc_get_default_directory(GNC_PREFS_GROUP);
569 filter = gtk_file_filter_new();
570 gtk_file_filter_set_name(filter,
"*.log");
571 gtk_file_filter_add_pattern(filter,
"*.[Ll][Oo][Gg]");
572 selected_filename = gnc_file_dialog(parent,
573 _(
"Select a .log file to replay"),
574 g_list_prepend(NULL, filter),
576 GNC_FILE_DIALOG_OPEN);
579 if (selected_filename != NULL)
582 default_dir = g_path_get_dirname(selected_filename);
583 gnc_set_default_directory(GNC_PREFS_GROUP, default_dir);
587 DEBUG(
"Filename found: %s", selected_filename);
590 g_warning(
"Cannot open the current log file: %s", selected_filename);
591 gnc_error_dialog(NULL,
593 _(
"Cannot open the current log file: %s"),
598 DEBUG(
"Opening selected file");
599 log_file = g_fopen(selected_filename,
"r");
600 if (!log_file || ferror(log_file) != 0)
603 perror(
"File open failed");
607 gnc_error_dialog(NULL,
608 _(
"Failed to open log file: %s: %s"),
614 if ((read_retval = fgets(read_buf,
sizeof(read_buf), log_file)) == NULL)
616 DEBUG(
"Read error or EOF");
617 gnc_info_dialog(NULL,
"%s",
618 _(
"The log file you selected was empty."));
622 if (strncmp(expected_header, read_buf, strlen(expected_header)) != 0)
624 PERR(
"File header not recognised:\n%s", read_buf);
625 PERR(
"Expected:\n%s", expected_header);
626 gnc_error_dialog(NULL,
"%s",
627 _(
"The log file you selected cannot be read. " 628 "The file header was not recognized."));
634 read_retval = fgets(read_buf,
sizeof(read_buf), log_file);
636 if (read_retval && strncmp(record_start_str, read_buf, strlen(record_start_str)) == 0)
638 process_trans_record(log_file);
641 while (feof(log_file) == 0);
647 g_free(selected_filename);
void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction's commodity.
time64 gnc_iso8601_to_time64_gmt(const gchar *)
The gnc_iso8601_to_time64_gmt() routine converts an ISO-8601 style date/time string to time64...
#define xaccTransAppendSplit(t, s)
Add a split to the transaction.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
void xaccTransScrubCurrency(Transaction *trans)
The xaccTransScrubCurrency method fixes transactions without a common_currency by looking for the mos...
void xaccSplitSetAction(Split *split, const char *actn)
The Action is an arbitrary user-assigned string.
utility functions for the GnuCash UI
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes.
void xaccLogDisable(void)
document me
const char * xaccTransGetReadOnly(Transaction *trans)
Returns a non-NULL value if this Transaction was marked as read-only with some specific "reason" text...
#define DEBUG(format, args...)
Print a debugging message.
.log replay module interface
gboolean string_to_guid(const gchar *string, GncGUID *guid)
Given a string, replace the given guid with the parsed one unless the given value is null...
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
void xaccTransSetNum(Transaction *trans, const char *xnum)
Sets the transaction Number (or ID) field; rather than use this function directly, see 'gnc_set_num_action' in engine/engine-helpers.c & .h which takes a user-set book option for selecting the source for the num-cell (the transaction-number or the split-action field) in registers/reports into account automatically.
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag.
gchar * guid_to_string_buff(const GncGUID *guid, gchar *str)
The guid_to_string_buff() routine puts a null-terminated string encoding of the id into the memory po...
gchar * gnc_numeric_to_string(gnc_numeric n)
Convert to string.
#define PERR(format, args...)
Log a serious error.
#define ENTER(format, args...)
Print a function entry debugging message.
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
#define PWARN(format, args...)
Log a warning.
Transaction * xaccTransLookup(const GncGUID *guid, QofBook *book)
The xaccTransLookup() subroutine will return the transaction associated with the given id...
convert single-entry accounts to clean double-entry
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account's commodity that the split should have...
Account handling public routines.
void xaccTransSetReadOnly(Transaction *trans, const char *reason)
Set the transaction to be ReadOnly by setting a non-NULL value as "reason".
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
void gnc_file_log_replay(GtkWindow *parent)
The gnc_file_log_replay() routine will pop up a standard file selection dialogue asking the user to p...
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
Split * xaccMallocSplit(QofBook *book)
Constructor.
gnc_commodity * gnc_account_or_default_currency(const Account *account, gboolean *currency_from_account_found)
Returns a gnc_commodity that is a currency, suitable for being a Transaction's currency.
API for the transaction logger.
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Set the date on which this split was reconciled by specifying the time as time64. ...
void xaccTransSetDatePostedSecs(Transaction *trans, time64 secs)
The xaccTransSetDatePostedSecs() method will modify the posted date of the transaction, specified by a time64 (see ctime(3)).
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
#define xaccAccountInsertSplit(acc, s)
The xaccAccountInsertSplit() method will insert the indicated split into the indicated account...
#define LEAVE(format, args...)
Print a function exit debugging message.
gnc_numeric gnc_numeric_from_string(const gchar *str)
Read a gnc_numeric from str, skipping any leading whitespace.
gboolean xaccFileIsCurrentLog(const gchar *name)
Test a filename to see if it is the name of the current logfile.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
void xaccTransSetDateEnteredSecs(Transaction *trans, time64 secs)
Modify the date of when the transaction was entered.
API for Transactions and Splits (journal entries)
The type used to store guids in C.
char * gnc_time64_to_iso8601_buff(time64 time, char *buff)
The gnc_time64_to_iso8601_buff() routine takes the input UTC time64 value and prints it as an ISO-860...
void xaccLogEnable(void)
document me