TransLog.c

00001 /********************************************************************\
00002  * TransLog.c -- the transaction logger                             *
00003  * Copyright (C) 1998 Linas Vepstas                                 *
00004  *                                                                  *
00005  * This program is free software; you can redistribute it and/or    *
00006  * modify it under the terms of the GNU General Public License as   *
00007  * published by the Free Software Foundation; either version 2 of   *
00008  * the License, or (at your option) any later version.              *
00009  *                                                                  *
00010  * This program is distributed in the hope that it will be useful,  *
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00013  * GNU General Public License for more details.                     *
00014  *                                                                  *
00015  * You should have received a copy of the GNU General Public License*
00016  * along with this program; if not, contact:                        *
00017  *                                                                  *
00018  * Free Software Foundation           Voice:  +1-617-542-5942       *
00019  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00020  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00021  *                                                                  *
00022 \********************************************************************/
00023 
00024 #include "config.h"
00025 
00026 #include <errno.h>
00027 #include <glib.h>
00028 #include <glib/gstdio.h>
00029 #include <string.h>
00030 
00031 #include "Account.h"
00032 #include "Transaction.h"
00033 #include "TransactionP.h"
00034 #include "TransLog.h"
00035 #include "qof.h"
00036 
00037 /* 
00038  * Some design philosphy that I think would be good to keep in mind:
00039  * (0) Simplicity and foolproofness are the over-riding design points.
00040  *     This is supposed to be a fail-safe safety net.   We don't want
00041  *     our safety net to fail because of some whiz-bang shenanigans.
00042  *
00043  * (1) Try to keep the code simple.  Want to make it simple and obvious
00044  *     that we are recording everything that we need to record.
00045  *
00046  * (2) Keep the printed format human readable, for the same reasons.
00047  * (2.a) Keep the format, simple, flat, more or less unstructured,
00048  *       record oriented.  This will help parsing by perl scripts.
00049  *       No, using a perl script to analyze a file that's supposed to
00050  *       be human readable is not a contradication in terms -- that's 
00051  *       exactly the point.
00052  * (2.b) Use tabs as a human friendly field separator; its also a 
00053  *       character that does not (should not) appear naturally anywhere 
00054  *       in the data, as it serves no formatting purpose in the current 
00055  *       GUI design.  (hack alert -- this is not currently tested for 
00056  *       or enforced, so this is a very unsafe assumption. Maybe 
00057  *       urlencoding should be used.)
00058  * (2.c) Don't print redundant information in a single record. This 
00059  *       would just confuse any potential user of this file.
00060  * (2.d) Saving space, being compact is not a priority, I don't think.
00061  *       
00062  * (3) There are no compatibility requirements from release to release.
00063  *     Sounds OK to me to change the format of the output when needed.
00064  *
00065  * (-) print transaction start and end delimiters
00066  * (-) print a unique transaction id as a handy label for anyone 
00067  *     who actually examines these logs.  
00068  *     The C address pointer to the transaction struct should be fine, 
00069  *     as it is simple and unique until the transaction is deleted ...
00070  *     and we log deletions, so that's OK.  Just note that the id
00071  *     for a deleted transaction might be recycled.
00072  * (-) print the current timestamp, so that if it is known that a bug
00073  *     occurred at a certain time, it can be located.
00074  * (-) hack alert -- something better than just the account name 
00075  *     is needed for identifying the account.
00076  */
00077 /* ------------------------------------------------------------------ */
00078 
00079 
00080 static int gen_logs = 1;
00081 static FILE * trans_log = NULL; 
00082 static char * trans_log_name = NULL; 
00083 static char * log_base_name = NULL;
00084 
00085 /********************************************************************\
00086 \********************************************************************/
00087 
00088 void xaccLogDisable (void) { gen_logs = 0; }
00089 void xaccLogEnable  (void) { gen_logs = 1; }
00090 
00091 /********************************************************************\
00092 \********************************************************************/
00093 
00094 void 
00095 xaccReopenLog (void)
00096 {
00097    if (trans_log) {
00098       xaccCloseLog();
00099       xaccOpenLog();
00100    }
00101 }
00102 
00103 
00104 void 
00105 xaccLogSetBaseName (const char *basepath)
00106 {
00107    if (!basepath) return;
00108 
00109    g_free (log_base_name);
00110    log_base_name = g_strdup (basepath);
00111 
00112    if (trans_log) {
00113       xaccCloseLog();
00114       xaccOpenLog();
00115    }
00116 }
00117 
00118 
00119 /*
00120  * See if the provided file name is that of the current log file.
00121  * Since the filename is generated with a time-stamp we can ignore the
00122  * directory path and avoid problems with worrying about any ".."
00123  * components in the path.
00124  */
00125 gboolean
00126 xaccFileIsCurrentLog (const gchar *name)
00127 {
00128   gchar *base;
00129   gint result;
00130 
00131   if (!name || !trans_log_name)
00132     return FALSE;
00133 
00134   base = g_path_get_basename(name);
00135   result = (strcmp(base, trans_log_name) == 0);
00136   g_free(base);
00137   return result;
00138 }
00139 
00140 /********************************************************************\
00141 \********************************************************************/
00142 
00143 void
00144 xaccOpenLog (void)
00145 {
00146    char * filename;
00147    char * timestamp;
00148 
00149    if (!gen_logs) return;
00150    if (trans_log) return;
00151 
00152    if (!log_base_name) log_base_name = g_strdup ("translog");
00153 
00154    /* tag each filename with a timestamp */
00155    timestamp = xaccDateUtilGetStampNow ();
00156 
00157    filename = g_strconcat (log_base_name, ".", timestamp, ".log", NULL);
00158 
00159    trans_log = g_fopen (filename, "a");
00160    if (!trans_log) {
00161       int norr = errno;
00162       printf ("Error: xaccOpenLog(): cannot open journal \n"
00163               "\t %d %s\n", norr, strerror (norr));
00164 
00165       g_free (filename);
00166       g_free (timestamp);
00167       return;
00168    }
00169 
00170    /* Save the log file name */
00171    if (trans_log_name)
00172      g_free (trans_log_name);
00173    trans_log_name = g_path_get_basename(filename);
00174 
00175    g_free (filename);
00176    g_free (timestamp);
00177 
00178    /*  Note: this must match src/import-export/log-replay/gnc-log-replay.c */
00179    fprintf (trans_log, "mod\ttrans_guid\tsplit_guid\ttime_now\t"
00180                        "date_entered\tdate_posted\t"
00181                        "acc_guid\tacc_name\tnum\tdescription\t"
00182                        "notes\tmemo\taction\treconciled\t"
00183                        "amount\tvalue\tdate_reconciled\n");
00184    fprintf (trans_log, "-----------------\n");
00185 }
00186 
00187 /********************************************************************\
00188 \********************************************************************/
00189 
00190 void
00191 xaccCloseLog (void)
00192 {
00193    if (!trans_log) return;
00194    fflush (trans_log);
00195    fclose (trans_log);
00196    trans_log = NULL;
00197 }
00198 
00199 /********************************************************************\
00200 \********************************************************************/
00201 
00202 void
00203 xaccTransWriteLog (Transaction *trans, char flag)
00204 {
00205    GList *node;
00206    char trans_guid_str[GUID_ENCODING_LENGTH+1];
00207    char split_guid_str[GUID_ENCODING_LENGTH+1];
00208    const char *trans_notes; 
00209    char dnow[100], dent[100], dpost[100], drecn[100]; 
00210    Timespec ts;
00211 
00212    if (!gen_logs) return;
00213    if (!trans_log) return;
00214 
00215    timespecFromTime_t(&ts,time(NULL));
00216    gnc_timespec_to_iso8601_buff (ts, dnow);
00217 
00218    timespecFromTime_t(&ts,trans->date_entered.tv_sec);
00219    gnc_timespec_to_iso8601_buff (ts, dent);
00220 
00221    timespecFromTime_t(&ts,trans->date_posted.tv_sec);
00222    gnc_timespec_to_iso8601_buff (ts, dpost);
00223 
00224    guid_to_string_buff (xaccTransGetGUID(trans), trans_guid_str);
00225    trans_notes = xaccTransGetNotes(trans);
00226    fprintf (trans_log, "===== START\n");
00227 
00228    for (node = trans->splits; node; node = node->next) 
00229    {
00230       Split *split = node->data;
00231       const char * accname = "";
00232       char acc_guid_str[GUID_ENCODING_LENGTH+1];
00233       gnc_numeric amt,val;
00234 
00235       if (xaccSplitGetAccount(split))
00236       {
00237         accname = xaccAccountGetName (xaccSplitGetAccount(split));
00238         guid_to_string_buff(xaccAccountGetGUID(xaccSplitGetAccount(split)),
00239                             acc_guid_str);
00240       } 
00241       else 
00242       {
00243         acc_guid_str[0] = '\0';
00244       }
00245       
00246       timespecFromTime_t(&ts,split->date_reconciled.tv_sec);
00247       gnc_timespec_to_iso8601_buff (ts, drecn);
00248 
00249       guid_to_string_buff (xaccSplitGetGUID(split), split_guid_str);
00250       amt = xaccSplitGetAmount (split);
00251       val = xaccSplitGetValue (split);
00252 
00253       /* use tab-separated fields */
00254       fprintf (trans_log,
00255                "%c\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
00256                "%s\t%s\t%s\t%s\t%c\t%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT "\t%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT "\t%s\n",
00257                flag,
00258                trans_guid_str, split_guid_str,  /* trans+split make up unique id */
00259                /* Note that the next three strings always exist,
00260                 * so we don't need to test them. */
00261                dnow,
00262                dent,
00263                dpost,
00264                acc_guid_str,
00265                accname ? accname : "",
00266                trans->num ? trans->num : "", 
00267                trans->description ? trans->description : "",
00268                trans_notes ? trans_notes : "",
00269                split->memo ? split->memo : "",
00270                split->action ? split->action : "",
00271                split->reconciled,
00272                gnc_numeric_num(amt), 
00273                gnc_numeric_denom(amt),
00274                gnc_numeric_num(val), 
00275                gnc_numeric_denom(val),
00276                /* The next string always exists. No need to test it. */
00277                drecn);
00278    }
00279 
00280    fprintf (trans_log, "===== END\n");
00281 
00282    /* get data out to the disk */
00283    fflush (trans_log);
00284 }
00285 
00286 /********************************************************************\
00287 \********************************************************************/
00288 
00289 #if 0
00290 /* open_memstream seems to give various distros fits
00291  * this has resulted in warfare on the mailing list.
00292  * I think the truce called required changing this to asprintf
00293  * this code is not currently used ...  so its ifdef out
00294  */
00295 
00296 char *
00297 xaccSplitAsString(Split *split, const char prefix[]) 
00298 {
00299   char *result = NULL;
00300   size_t result_size;
00301   FILE *stream = open_memstream(&result, &result_size); 
00302   const char *split_memo = xaccSplitGetMemo(split);
00303   const double split_value = gnc_numeric_to_double(xaccSplitGetValue(split));
00304   Account *split_dest = xaccSplitGetAccount(split);
00305   const char *dest_name =
00306     split_dest ? xaccAccountGetName(split_dest) : NULL;
00307 
00308   g_return_val_if_fail (stream, NULL);
00309 
00310   fputc('\n', stream);
00311   fputs(prefix, stream);
00312   fprintf(stream, "  %10.2f | %15s | %s",
00313           split_value,
00314           dest_name ? dest_name : "<no-account-name>",
00315           split_memo ? split_memo : "<no-split-memo>");
00316   fclose(stream); 
00317   return(result);
00318 }
00319 
00320 static char *
00321 xaccTransGetDateStr (Transaction *trans)
00322 {
00323    char buf [MAX_DATE_LENGTH];
00324    struct tm *date;
00325    time_t secs;
00326 
00327    secs = xaccTransGetDate (trans);
00328 
00329    date = localtime (&secs);
00330 
00331    qof_print_date_buff(buf, date->tm_mday, date->tm_mon+1, date->tm_year +1900);
00332 
00333    return g_strdup (buf);
00334 }
00335 
00336 char *
00337 xaccTransAsString(Transaction *txn, const char prefix[]) 
00338 {
00339   char *result = NULL;
00340   size_t result_size;
00341   FILE *stream = open_memstream(&result, &result_size); 
00342   time_t date = xaccTransGetDate(txn);
00343   const char *num = xaccTransGetNum(txn);
00344   const char *desc = xaccTransGetDescription(txn);
00345   const char *memo = xaccSplitGetMemo(xaccTransGetSplit(txn, 0));
00346   const double total = gnc_numeric_to_double(xaccSplitGetValue(xaccTransGetSplit(txn, 0)));
00347   
00348   g_return_val_if_fail (stream, NULL);
00349 
00350   fputs(prefix, stream);
00351   if(date) {
00352     char *datestr = xaccTransGetDateStr(txn);
00353     fprintf(stream, "%s", datestr);
00354     free(datestr);
00355   } else {
00356     fprintf(stream, "<no-date>");
00357   }
00358   fputc(' ', stream); 
00359   if(num) {
00360     fputs(num, stream);
00361   } else {
00362     fprintf(stream, "<no-num>");
00363   }
00364 
00365   fputc('\n', stream);
00366   fputs(prefix, stream);
00367   if(desc) {
00368     fputs("  ", stream);
00369     fputs(desc, stream);
00370   } else {
00371     fprintf(stream, "<no-description>");
00372   }
00373   
00374   fputc('\n', stream);
00375   fputs(prefix, stream);
00376   if(memo) {
00377     fputs("  ", stream);
00378     fputs(memo, stream);
00379   } else {
00380     fprintf(stream, "<no-transaction-memo>");
00381   }
00382   
00383   {
00384     int split_count = xaccTransCountSplits(txn);
00385     int i;
00386     for(i = 1; i < split_count; i++) {
00387       Split *split = xaccTransGetSplit(txn, i);
00388       char *split_text = xaccSplitAsString(split, prefix);
00389       fputs(split_text, stream);
00390       free(split_text);
00391     }
00392   }
00393   fputc('\n', stream);
00394 
00395   fputs(prefix, stream);
00396   fprintf(stream, "  %10.2f -- Transaction total\n", total);
00397   fclose(stream); 
00398 
00399   return(result);
00400 }
00401 
00402 #endif
00403 
00404 /************************ END OF ************************************\
00405 \************************* FILE *************************************/

Generated on Thu Jul 3 05:07:09 2008 for GnuCash by  doxygen 1.5.2