GnuCash 2.4.99
gnc-log-replay.c
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 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines