00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00026 #include "config.h"
00027
00028 #include <gtk/gtk.h>
00029 #include <glib/gi18n.h>
00030 #include <glib/gstdio.h>
00031 #include <string.h>
00032 #include <sys/time.h>
00033 #include <libguile.h>
00034 #include <errno.h>
00035
00036 #include "Account.h"
00037 #include "Transaction.h"
00038 #include "TransactionP.h"
00039 #include "TransLog.h"
00040 #include "Scrub.h"
00041 #include "gnc-log-replay.h"
00042 #include "gnc-file.h"
00043 #include "qof.h"
00044 #include "gnc-ui-util.h"
00045 #include "gnc-gui-query.h"
00046
00047 #define GCONF_SECTION "dialogs/log_replay"
00048
00049
00050
00051
00052
00053 static QofLogModule log_module = GNC_MOD_TEST;
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063 #define STRING_FIELD_SIZE 256
00064 typedef struct _split_record
00065 {
00066 enum _enum_action {LOG_BEGIN_EDIT, LOG_ROLLBACK, LOG_COMMIT, LOG_DELETE} log_action;
00067 int log_action_present;
00068 GncGUID trans_guid;
00069 int trans_guid_present;
00070 GncGUID split_guid;
00071 int split_guid_present;
00072 Timespec log_date;
00073 int log_date_present;
00074 Timespec date_entered;
00075 int date_entered_present;
00076 Timespec date_posted;
00077 int date_posted_present;
00078 GncGUID acc_guid;
00079 int acc_guid_present;
00080 char acc_name[STRING_FIELD_SIZE];
00081 int acc_name_present;
00082 char trans_num[STRING_FIELD_SIZE];
00083 int trans_num_present;
00084 char trans_descr[STRING_FIELD_SIZE];
00085 int trans_descr_present;
00086 char trans_notes[STRING_FIELD_SIZE];
00087 int trans_notes_present;
00088 char split_memo[STRING_FIELD_SIZE];
00089 int split_memo_present;
00090 char split_action[STRING_FIELD_SIZE];
00091 int split_action_present;
00092 char split_reconcile;
00093 int split_reconcile_present;
00094 gnc_numeric amount;
00095 int amount_present;
00096 gnc_numeric value;
00097 int value_present;
00098 Timespec date_reconciled;
00099 int date_reconciled_present;
00100 } split_record;
00101
00102
00103
00104
00105
00106 static char *olds;
00107
00108
00109
00110
00111 static char * my_strtok (s, delim)
00112 char *s;
00113 const char *delim;
00114 {
00115 char *token;
00116
00117 if (s == NULL)
00118 s = olds;
00119
00120
00121
00122 if (*s == '\0')
00123 {
00124 olds = s;
00125 return s;
00126 }
00127
00128
00129 token = s;
00130 s = strpbrk (token, delim);
00131 if (s == NULL)
00132 {
00133
00134 olds = strchr (token, '\0');
00135 }
00136 else
00137 {
00138
00139 *s = '\0';
00140 olds = s + 1;
00141 }
00142 return token;
00143 }
00144
00145 static split_record interpret_split_record( char *record_line)
00146 {
00147 char * tok_ptr;
00148 split_record record;
00149 memset(&record, 0, sizeof(record));
00150 DEBUG("interpret_split_record(): Start...");
00151 if (strlen(tok_ptr = my_strtok(record_line, "\t")) != 0)
00152 {
00153 switch (tok_ptr[0])
00154 {
00155 case 'B':
00156 record.log_action = LOG_BEGIN_EDIT;
00157 break;
00158 case 'D':
00159 record.log_action = LOG_DELETE;
00160 break;
00161 case 'C':
00162 record.log_action = LOG_COMMIT;
00163 break;
00164 case 'R':
00165 record.log_action = LOG_ROLLBACK;
00166 break;
00167 }
00168 record.log_action_present = TRUE;
00169 }
00170 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00171 {
00172 string_to_guid(tok_ptr, &(record.trans_guid));
00173 record.trans_guid_present = TRUE;
00174 }
00175 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00176 {
00177 string_to_guid(tok_ptr, &(record.split_guid));
00178 record.split_guid_present = TRUE;
00179 }
00180 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00181 {
00182 record.log_date = gnc_iso8601_to_timespec_gmt(tok_ptr);
00183 record.log_date_present = TRUE;
00184 }
00185 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00186 {
00187 record.date_entered = gnc_iso8601_to_timespec_gmt(tok_ptr);
00188 record.date_entered_present = TRUE;
00189 }
00190 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00191 {
00192 record.date_posted = gnc_iso8601_to_timespec_gmt(tok_ptr);
00193 record.date_posted_present = TRUE;
00194 }
00195 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00196 {
00197 string_to_guid(tok_ptr, &(record.acc_guid));
00198 record.acc_guid_present = TRUE;
00199 }
00200 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00201 {
00202 strncpy(record.acc_name, tok_ptr, STRING_FIELD_SIZE - 1);
00203 record.acc_name_present = TRUE;
00204 }
00205 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00206 {
00207 strncpy(record.trans_num, tok_ptr, STRING_FIELD_SIZE - 1);
00208 record.trans_num_present = TRUE;
00209 }
00210 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00211 {
00212 strncpy(record.trans_descr, tok_ptr, STRING_FIELD_SIZE - 1);
00213 record.trans_descr_present = TRUE;
00214 }
00215 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00216 {
00217 strncpy(record.trans_notes, tok_ptr, STRING_FIELD_SIZE - 1);
00218 record.trans_notes_present = TRUE;
00219 }
00220 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00221 {
00222 strncpy(record.split_memo, tok_ptr, STRING_FIELD_SIZE - 1);
00223 record.split_memo_present = TRUE;
00224 }
00225 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00226 {
00227 strncpy(record.split_action, tok_ptr, STRING_FIELD_SIZE - 1);
00228 record.split_action_present = TRUE;
00229 }
00230 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00231 {
00232 record.split_reconcile = tok_ptr[0];
00233 record.split_reconcile_present = TRUE;
00234 }
00235 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00236 {
00237 string_to_gnc_numeric(tok_ptr, &(record.amount));
00238 record.amount_present = TRUE;
00239 }
00240 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00241 {
00242 string_to_gnc_numeric(tok_ptr, &(record.value));
00243 record.value_present = TRUE;
00244 }
00245 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00246 {
00247 record.date_reconciled = gnc_iso8601_to_timespec_gmt(tok_ptr);
00248 record.date_reconciled_present = TRUE;
00249 }
00250
00251 if (strlen(tok_ptr = my_strtok(NULL, "\t")) != 0)
00252 {
00253 PERR("interpret_split_record(): Expected number of fields exceeded!");
00254 }
00255 DEBUG("interpret_split_record(): End");
00256 return record;
00257 }
00258
00259 static void dump_split_record(split_record record)
00260 {
00261 char * string_ptr = NULL;
00262 char string_buf[256];
00263
00264 DEBUG("dump_split_record(): Start...");
00265 if (record.log_action_present)
00266 {
00267 switch (record.log_action)
00268 {
00269 case LOG_BEGIN_EDIT:
00270 DEBUG("Log action: LOG_BEGIN_EDIT");
00271 break;
00272 case LOG_DELETE:
00273 DEBUG("Log action: LOG_DELETE");
00274 break;
00275 case LOG_COMMIT:
00276 DEBUG("Log action: LOG_COMMIT");
00277 break;
00278 case LOG_ROLLBACK:
00279 DEBUG("Log action: LOG_ROLLBACK");
00280 break;
00281 }
00282 }
00283 if (record.trans_guid_present)
00284 {
00285 DEBUG("Transaction GncGUID: %s", guid_to_string (&(record.trans_guid)));
00286 }
00287 if (record.split_guid_present)
00288 {
00289 DEBUG("Split GncGUID: %s", guid_to_string (&(record.split_guid)));
00290 }
00291 if (record.log_date_present)
00292 {
00293 gnc_timespec_to_iso8601_buff (record.log_date, string_buf);
00294 DEBUG("Log entry date: %s", string_buf);
00295 }
00296 if (record.date_entered_present)
00297 {
00298 gnc_timespec_to_iso8601_buff (record.date_entered, string_buf);
00299 DEBUG("Date entered: %s", string_buf);
00300 }
00301 if (record.date_posted_present)
00302 {
00303 gnc_timespec_to_iso8601_buff (record.date_posted, string_buf);
00304 DEBUG("Date posted: %s", string_buf);
00305 }
00306 if (record.acc_guid_present)
00307 {
00308 DEBUG("Account GncGUID: %s", guid_to_string (&(record.acc_guid)));
00309 }
00310 if (record.acc_name_present)
00311 {
00312 DEBUG("Account name: %s", record.acc_name);
00313 }
00314 if (record.trans_num_present)
00315 {
00316 DEBUG("Transaction number: %s", record.trans_num);
00317 }
00318 if (record.trans_descr_present)
00319 {
00320 DEBUG("Transaction description: %s", record.trans_descr);
00321 }
00322 if (record.trans_notes_present)
00323 {
00324 DEBUG("Transaction notes: %s", record.trans_notes);
00325 }
00326 if (record.split_memo_present)
00327 {
00328 DEBUG("Split memo: %s", record.split_memo);
00329 }
00330 if (record.split_action_present)
00331 {
00332 DEBUG("Split action: %s", record.split_action);
00333 }
00334 if (record.split_reconcile_present)
00335 {
00336 DEBUG("Split reconcile: %c", record.split_reconcile);
00337 }
00338 if (record.amount_present)
00339 {
00340 string_ptr = gnc_numeric_to_string(record.amount);
00341 DEBUG("Record amount: %s", string_ptr);
00342 g_free(string_ptr);
00343 }
00344 if (record.value_present)
00345 {
00346 string_ptr = gnc_numeric_to_string(record.value);
00347 DEBUG("Record value: %s", string_ptr);
00348 g_free(string_ptr);
00349 }
00350 if (record.date_reconciled_present)
00351 {
00352 gnc_timespec_to_iso8601_buff (record.date_reconciled, string_buf);
00353 DEBUG("Reconciled date: %s", string_buf);
00354 }
00355 }
00356
00357
00358 static void process_trans_record( FILE *log_file)
00359 {
00360 char read_buf[2048];
00361 char *read_retval;
00362 char * trans_ro = NULL;
00363 const char * record_end_str = "===== END";
00364 int first_record = TRUE;
00365 int record_ended = FALSE;
00366 int split_num = 0;
00367 split_record record;
00368 Transaction * trans = NULL;
00369 Split * split = NULL;
00370 Account * acct = NULL;
00371 QofBook * book = gnc_get_current_book();
00372
00373 DEBUG("process_trans_record(): Begin...\n");
00374
00375 while ( record_ended == FALSE)
00376 {
00377 read_retval = fgets(read_buf, sizeof(read_buf), log_file);
00378 if (read_retval != NULL && strncmp(record_end_str, read_buf, strlen(record_end_str)) != 0)
00379 {
00380 split_num++;
00381
00382 record = interpret_split_record( read_buf);
00383 dump_split_record( record);
00384 if (record.log_action_present)
00385 {
00386 switch (record.log_action)
00387 {
00388 case LOG_BEGIN_EDIT:
00389 DEBUG("process_trans_record():Ignoring log action: LOG_BEGIN_EDIT");
00390 break;
00391 case LOG_ROLLBACK:
00392 DEBUG("process_trans_record():Ignoring log action: LOG_ROLLBACK");
00393 break;
00394 case LOG_DELETE:
00395 DEBUG("process_trans_record(): Playing back LOG_DELETE");
00396 if ((trans = xaccTransLookup (&(record.trans_guid), book)) != NULL
00397 && first_record == TRUE)
00398 {
00399 first_record = FALSE;
00400 if (xaccTransGetReadOnly(trans))
00401 {
00402 PWARN("Destroying a read only transaction.");
00403 xaccTransClearReadOnly(trans);
00404 }
00405 xaccTransBeginEdit(trans);
00406 xaccTransDestroy(trans);
00407 }
00408 else if (first_record == TRUE)
00409 {
00410 PERR("The transaction to delete was not found!");
00411 }
00412 else
00413 xaccTransDestroy(trans);
00414 break;
00415 case LOG_COMMIT:
00416 DEBUG("process_trans_record(): Playing back LOG_COMMIT");
00417 if (record.trans_guid_present == TRUE
00418 && first_record == TRUE)
00419 {
00420 trans = xaccTransLookupDirect (record.trans_guid, book);
00421 if (trans != NULL)
00422 {
00423 DEBUG("process_trans_record(): Transaction to be edited was found");
00424 xaccTransBeginEdit(trans);
00425 trans_ro = g_strdup(xaccTransGetReadOnly(trans));
00426 if (trans_ro)
00427 {
00428 PWARN("Replaying a read only transaction.");
00429 xaccTransClearReadOnly(trans);
00430 }
00431 }
00432 else
00433 {
00434 DEBUG("process_trans_record(): Creating a new transaction");
00435 trans = xaccMallocTransaction (book);
00436 xaccTransBeginEdit(trans);
00437 }
00438
00439 xaccTransSetGUID (trans, &(record.trans_guid));
00440
00441 if (record.date_entered_present)
00442 {
00443 xaccTransSetDateEnteredTS(trans, &(record.date_entered));
00444 }
00445 if (record.date_posted_present)
00446 {
00447 xaccTransSetDatePostedTS(trans, &(record.date_posted));
00448 }
00449 if (record.trans_num_present)
00450 {
00451 xaccTransSetNum(trans, record.trans_num);
00452 }
00453 if (record.trans_descr_present)
00454 {
00455 xaccTransSetDescription(trans, record.trans_descr);
00456 }
00457 if (record.trans_notes_present)
00458 {
00459 xaccTransSetNotes(trans, record.trans_notes);
00460 }
00461 }
00462 if (record.split_guid_present == TRUE)
00463 {
00464 gboolean is_new_split;
00465
00466 split = xaccSplitLookupDirect (record.split_guid, book);
00467 if (split != NULL)
00468 {
00469 DEBUG("process_trans_record(): Split to be edited was found");
00470 is_new_split = FALSE;
00471 }
00472 else
00473 {
00474 DEBUG("process_trans_record(): Creating a new split");
00475 split = xaccMallocSplit(book);
00476 is_new_split = TRUE;
00477 }
00478 xaccSplitSetGUID (split, &(record.split_guid));
00479 if (record.acc_guid_present)
00480 {
00481 acct = xaccAccountLookupDirect(record.acc_guid, book);
00482 xaccAccountInsertSplit(acct, split);
00483 }
00484 if (is_new_split)
00485 xaccTransAppendSplit(trans, split);
00486
00487 if (record.split_memo_present)
00488 {
00489 xaccSplitSetMemo(split, record.split_memo);
00490 }
00491 if (record.split_action_present)
00492 {
00493 xaccSplitSetAction(split, record.split_action);
00494 }
00495 if (record.date_reconciled_present)
00496 {
00497 xaccSplitSetDateReconciledTS (split, &(record.date_reconciled));
00498 }
00499 if (record.split_reconcile_present)
00500 {
00501 xaccSplitSetReconcile(split, record.split_reconcile);
00502 }
00503
00504 if (record.amount_present)
00505 {
00506 xaccSplitSetAmount(split, record.amount);
00507 }
00508 if (record.value_present)
00509 {
00510 xaccSplitSetValue(split, record.value);
00511 }
00512 }
00513 first_record = FALSE;
00514 break;
00515 }
00516 }
00517 else
00518 {
00519 PERR("Corrupted record");
00520 }
00521 }
00522 else
00523 {
00524 record_ended = TRUE;
00525 DEBUG("process_trans_record(): Record ended\n");
00526 if (trans != NULL)
00527 {
00528 xaccTransScrubCurrencyFromSplits(trans);
00529 xaccTransSetReadOnly(trans, trans_ro);
00530 xaccTransCommitEdit(trans);
00531 g_free(trans_ro);
00532 }
00533 }
00534 }
00535 }
00536
00537 void gnc_file_log_replay (void)
00538 {
00539 char *selected_filename;
00540 char *default_dir;
00541 char read_buf[256];
00542 char *read_retval;
00543 GtkFileFilter *filter;
00544 FILE *log_file;
00545 char * record_start_str = "===== START";
00546
00547 char * expected_header_orig = "mod\ttrans_guid\tsplit_guid\ttime_now\t"
00548 "date_entered\tdate_posted\tacc_guid\tacc_name\tnum\tdescription\t"
00549 "notes\tmemo\taction\treconciled\tamount\tvalue\tdate_reconciled";
00550 static char *expected_header = NULL;
00551
00552
00553 if (!expected_header)
00554 expected_header = g_strdup(expected_header_orig);
00555
00556 qof_log_set_level(GNC_MOD_IMPORT, QOF_LOG_DEBUG);
00557 ENTER(" ");
00558
00559
00560 xaccLogDisable();
00561
00562 default_dir = gnc_get_default_directory(GCONF_SECTION);
00563
00564 filter = gtk_file_filter_new();
00565 gtk_file_filter_set_name(filter, "*.log");
00566 gtk_file_filter_add_pattern(filter, "*.[Ll][Oo][Gg]");
00567 selected_filename = gnc_file_dialog(_("Select a .log file to replay"),
00568 g_list_prepend(NULL, filter),
00569 default_dir,
00570 GNC_FILE_DIALOG_OPEN);
00571 g_free(default_dir);
00572
00573 if (selected_filename != NULL)
00574 {
00575
00576 default_dir = g_path_get_dirname(selected_filename);
00577 gnc_set_default_directory(GCONF_SECTION, default_dir);
00578 g_free(default_dir);
00579
00580
00581 DEBUG("Filename found: %s", selected_filename);
00582 if (xaccFileIsCurrentLog(selected_filename))
00583 {
00584 g_warning("Cannot open the current log file: %s", selected_filename);
00585 gnc_error_dialog(NULL,
00586
00587 _("Cannot open the current log file: %s"),
00588 selected_filename);
00589 }
00590 else
00591 {
00592 DEBUG("Opening selected file");
00593 log_file = g_fopen(selected_filename, "r");
00594 if (!log_file || ferror(log_file) != 0)
00595 {
00596 int err = errno;
00597 perror("File open failed");
00598 gnc_error_dialog(NULL,
00599
00600
00601
00602
00603 _("Failed to open log file: %s: %s"),
00604 selected_filename,
00605 strerror(err));
00606 }
00607 else
00608 {
00609 if ((read_retval = fgets(read_buf, sizeof(read_buf), log_file)) == NULL)
00610 {
00611 DEBUG("Read error or EOF");
00612 gnc_info_dialog(NULL, "%s",
00613 _("The log file you selected was empty."));
00614 }
00615 else
00616 {
00617 if (strncmp(expected_header, read_buf, strlen(expected_header)) != 0)
00618 {
00619 PERR("File header not recognised:\n%s", read_buf);
00620 PERR("Expected:\n%s", expected_header);
00621 gnc_error_dialog(NULL, "%s",
00622 _("The log file you selected cannot be read. "
00623 "The file header was not recognized."));
00624 }
00625 else
00626 {
00627 do
00628 {
00629 read_retval = fgets(read_buf, sizeof(read_buf), log_file);
00630
00631 if (strncmp(record_start_str, read_buf, strlen(record_start_str)) == 0)
00632 {
00633 process_trans_record(log_file);
00634 }
00635 }
00636 while (feof(log_file) == 0);
00637 }
00638 }
00639 fclose(log_file);
00640 }
00641 }
00642 g_free(selected_filename);
00643 }
00644
00645 xaccLogEnable();
00646
00647 LEAVE("");
00648 }
00649
00650