|
GnuCash 2.4.99
|
00001 /********************************************************************\ 00002 * This program is free software; you can redistribute it and/or * 00003 * modify it under the terms of the GNU General Public License as * 00004 * published by the Free Software Foundation; either version 2 of * 00005 * the License, or (at your option) any later version. * 00006 * * 00007 * This program is distributed in the hope that it will be useful, * 00008 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00009 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00010 * GNU General Public License for more details. * 00011 * * 00012 * You should have received a copy of the GNU General Public License* 00013 * along with this program; if not, contact: * 00014 * * 00015 * Free Software Foundation Voice: +1-617-542-5942 * 00016 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * 00017 * Boston, MA 02110-1301, USA gnu@gnu.org * 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 /* NW: If you want a new log_module, just define 00050 a unique string either in gnc-engine.h or 00051 locally.*/ 00052 /*static QofLogModule log_module = GNC_MOD_IMPORT;*/ 00053 static QofLogModule log_module = GNC_MOD_TEST; 00054 00055 /* fprintf (trans_log, "mod guid time_now " \ 00056 "date_entered date_posted " \ 00057 "acc_guid acc_name num description " \ 00058 "memo action reconciled " \ 00059 "amount value date_reconciled\n"); 00060 "%c\t%s/%s\t%s\t%s\t%s\t%s\t%s\t%s\t" 00061 "%s\t%s\t%s\t%c\t%lld/%lld\t%lld/%lld\t%s\n", 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 * gnc_file_log_replay_import 00103 * Entry point 00104 \********************************************************************/ 00105 00106 static char *olds; 00107 /* This version of strtok will only match SINGLE occurence of delim, 00108 returning a 0 length valid string between two consecutive ocurence of delim. 00109 It will also return a 0 length string instead of NULL when it reaches the end of s 00110 */ 00111 static char * my_strtok (s, delim) 00112 char *s; 00113 const char *delim; 00114 { 00115 char *token; 00116 /*DEBUG("strtok(): Start...");*/ 00117 if (s == NULL) 00118 s = olds; 00119 00120 /* Scan leading delimiters. */ 00121 /*s += strspn (s, delim);*/ /*Don't do it, or we will loose count.*/ 00122 if (*s == '\0') 00123 { 00124 olds = s; 00125 return s; 00126 } 00127 00128 /* Find the end of the token. */ 00129 token = s; 00130 s = strpbrk (token, delim); 00131 if (s == NULL) 00132 { 00133 /* This token finishes the string. */ 00134 olds = strchr (token, '\0'); 00135 } 00136 else 00137 { 00138 /* Terminate the token and make OLDS point past it. */ 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 /* File pointer must already be at the begining of a record */ 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) /* If we are not at the end of the record */ 00379 { 00380 split_num++; 00381 /*DEBUG("process_trans_record(): Line read: %s%s",read_buf ,"\n");*/ 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"); /*Do nothing, there is no point*/ 00390 break; 00391 case LOG_ROLLBACK: 00392 DEBUG("process_trans_record():Ignoring log action: LOG_ROLLBACK");/*Do nothing, since we didn't do the begin_edit either*/ 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 /*Fill the transaction info*/ 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) /*Fill the split info*/ 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 /* The record ended */ 00523 { 00524 record_ended = TRUE; 00525 DEBUG("process_trans_record(): Record ended\n"); 00526 if (trans != NULL) /*If we played with a transaction, commit it here*/ 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 /* NOTE: This string must match src/engine/TransLog.c (sans newline) */ 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 /* Use g_strdup_printf so we don't get accidental tab -> space conversion */ 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 /* Don't log the log replay. This would only result in redundant logs */ 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 /* Remember the directory as the default. */ 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 /*strncpy(file,selected_filename, 255);*/ 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 /* Translators: %s is the file name. */ 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 /* Translation note: 00600 * First argument is the filename, 00601 * second argument is the error. 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 /*DEBUG("Chunk read: %s",read_retval);*/ 00631 if (strncmp(record_start_str, read_buf, strlen(record_start_str)) == 0) /* If a record started */ 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 /* Start logging again */ 00645 xaccLogEnable(); 00646 00647 LEAVE(""); 00648 } 00649 00650
1.7.4