1 /********************************************************************\
2  * TransLog.c -- the transaction logger                             *
3  * Copyright (C) 1998 Linas Vepstas                                 *
4  *                                                                  *
5  * This program is free software; you can redistribute it and/or    *
6  * modify it under the terms of the GNU General Public License as   *
7  * published by the Free Software Foundation; either version 2 of   *
8  * the License, or (at your option) any later version.              *
9  *                                                                  *
10  * This program is distributed in the hope that it will be useful,  *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
13  * GNU General Public License for more details.                     *
14  *                                                                  *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact:                        *
17  *                                                                  *
18  * Free Software Foundation           Voice:  +1-617-542-5942       *
19  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
20  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
21  *                                                                  *
22 \********************************************************************/
23 
24 #include <config.h>
25 #ifdef __MINGW32__
26 #define __USE_MINGW_ANSI_STDIO 1
27 #endif
28 #include <errno.h>
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <string.h>
32 
33 #include "Account.h"
34 #include "Transaction.h"
35 #include "TransactionP.h"
36 #include "TransLog.h"
37 #include "qof.h"
38 #ifdef _MSC_VER
39 # define g_fopen fopen
40 #endif
41 
42 static QofLogModule log_module = "gnc.translog";
43 
44 /*
45  * Some design philosophy that I think would be good to keep in mind:
46  * (0) Simplicity and foolproofness are the over-riding design points.
47  *     This is supposed to be a fail-safe safety net.   We don't want
48  *     our safety net to fail because of some whiz-bang shenanigans.
49  *
50  * (1) Try to keep the code simple.  Want to make it simple and obvious
51  *     that we are recording everything that we need to record.
52  *
53  * (2) Keep the printed format human readable, for the same reasons.
54  * (2.a) Keep the format, simple, flat, more or less unstructured,
55  *       record oriented.  This will help parsing by perl scripts.
56  *       No, using a perl script to analyze a file that's supposed to
57  *       be human readable is not a contradication in terms -- that's
58  *       exactly the point.
59  * (2.b) Use tabs as a human friendly field separator; its also a
60  *       character that does not (should not) appear naturally anywhere
61  *       in the data, as it serves no formatting purpose in the current
62  *       GUI design.  (hack alert -- this is not currently tested for
63  *       or enforced, so this is a very unsafe assumption. Maybe
64  *       urlencoding should be used.)
65  * (2.c) Don't print redundant information in a single record. This
66  *       would just confuse any potential user of this file.
67  * (2.d) Saving space, being compact is not a priority, I don't think.
68  *
69  * (3) There are no compatibility requirements from release to release.
70  *     Sounds OK to me to change the format of the output when needed.
71  *
72  * (-) print transaction start and end delimiters
73  * (-) print a unique transaction id as a handy label for anyone
74  *     who actually examines these logs.
75  *     The C address pointer to the transaction struct should be fine,
76  *     as it is simple and unique until the transaction is deleted ...
77  *     and we log deletions, so that's OK.  Just note that the id
78  *     for a deleted transaction might be recycled.
79  * (-) print the current timestamp, so that if it is known that a bug
80  *     occurred at a certain time, it can be located.
81  * (-) hack alert -- something better than just the account name
82  *     is needed for identifying the account.
83  */
84 /* ------------------------------------------------------------------ */
85 
86 
87 static int gen_logs = 1;
88 static FILE * trans_log = NULL; /**< current log file handle */
89 static char * trans_log_name = NULL; /**< current log file name */
90 static char * log_base_name = NULL;
91 
92 /********************************************************************\
93 \********************************************************************/
94 
xaccLogDisable(void)95 void xaccLogDisable (void)
96 {
97     gen_logs = 0;
98 }
xaccLogEnable(void)99 void xaccLogEnable  (void)
100 {
101     gen_logs = 1;
102 }
103 
104 /********************************************************************\
105 \********************************************************************/
106 
107 void
xaccReopenLog(void)108 xaccReopenLog (void)
109 {
110     if (trans_log)
111     {
112         xaccCloseLog();
113         xaccOpenLog();
114     }
115 }
116 
117 
118 void
xaccLogSetBaseName(const char * basepath)119 xaccLogSetBaseName (const char *basepath)
120 {
121     if (!basepath) return;
122 
123     g_free (log_base_name);
124     log_base_name = g_strdup (basepath);
125 
126     if (trans_log)
127     {
128         xaccCloseLog();
129         xaccOpenLog();
130     }
131 }
132 
133 
134 /*
135  * See if the provided file name is that of the current log file.
136  * Since the filename is generated with a time-stamp we can ignore the
137  * directory path and avoid problems with worrying about any ".."
138  * components in the path.
139  */
140 gboolean
xaccFileIsCurrentLog(const gchar * name)141 xaccFileIsCurrentLog (const gchar *name)
142 {
143     gchar *base;
144     gint result;
145 
146     if (!name || !trans_log_name)
147         return FALSE;
148 
149     base = g_path_get_basename(name);
150     result = (strcmp(base, trans_log_name) == 0);
151     g_free(base);
152     return result;
153 }
154 
155 /********************************************************************\
156 \********************************************************************/
157 
158 void
xaccOpenLog(void)159 xaccOpenLog (void)
160 {
161     char * filename;
162     char * timestamp;
163 
164     if (!gen_logs)
165     {
166 	 PINFO ("Attempt to open disabled transaction log");
167 	 return;
168     }
169     if (trans_log) return;
170 
171     if (!log_base_name) log_base_name = g_strdup ("translog");
172 
173     /* tag each filename with a timestamp */
174     timestamp = gnc_date_timestamp ();
175 
176     filename = g_strconcat (log_base_name, ".", timestamp, ".log", NULL);
177 
178     trans_log = g_fopen (filename, "a");
179     if (!trans_log)
180     {
181         int norr = errno;
182         printf ("Error: xaccOpenLog(): cannot open journal\n"
183                 "\t %d %s\n", norr, g_strerror (norr) ? g_strerror (norr) : "");
184 
185         g_free (filename);
186         g_free (timestamp);
187         return;
188     }
189 
190     /* Save the log file name */
191     if (trans_log_name)
192         g_free (trans_log_name);
193     trans_log_name = g_path_get_basename(filename);
194 
195     g_free (filename);
196     g_free (timestamp);
197 
198     /*  Note: this must match src/import-export/log-replay/gnc-log-replay.c */
199     fprintf (trans_log, "mod\ttrans_guid\tsplit_guid\ttime_now\t"
200              "date_entered\tdate_posted\t"
201              "acc_guid\tacc_name\tnum\tdescription\t"
202              "notes\tmemo\taction\treconciled\t"
203              "amount\tvalue\tdate_reconciled\n");
204     fprintf (trans_log, "-----------------\n");
205 }
206 
207 /********************************************************************\
208 \********************************************************************/
209 
210 void
xaccCloseLog(void)211 xaccCloseLog (void)
212 {
213     if (!trans_log) return;
214     fflush (trans_log);
215     fclose (trans_log);
216     trans_log = NULL;
217 }
218 
219 /********************************************************************\
220 \********************************************************************/
221 
222 void
xaccTransWriteLog(Transaction * trans,char flag)223 xaccTransWriteLog (Transaction *trans, char flag)
224 {
225     GList *node;
226     char trans_guid_str[GUID_ENCODING_LENGTH + 1];
227     char split_guid_str[GUID_ENCODING_LENGTH + 1];
228     const char *trans_notes;
229     char dnow[100], dent[100], dpost[100], drecn[100];
230 
231     if (!gen_logs)
232     {
233          PINFO ("Attempt to write disabled transaction log");
234 	 return;
235     }
236     if (!trans_log) return;
237 
238     gnc_time64_to_iso8601_buff (gnc_time(NULL), dnow);
239     gnc_time64_to_iso8601_buff (trans->date_entered, dent);
240     gnc_time64_to_iso8601_buff (trans->date_posted, dpost);
241     guid_to_string_buff (xaccTransGetGUID(trans), trans_guid_str);
242     trans_notes = xaccTransGetNotes(trans);
243     fprintf (trans_log, "===== START\n");
244 
245     for (node = trans->splits; node; node = node->next)
246     {
247         time64 time;
248         Split *split = node->data;
249         const char * accname = "";
250         char acc_guid_str[GUID_ENCODING_LENGTH + 1];
251         gnc_numeric amt, val;
252 
253         if (xaccSplitGetAccount(split))
254         {
255             accname = xaccAccountGetName (xaccSplitGetAccount(split));
256             guid_to_string_buff(xaccAccountGetGUID(xaccSplitGetAccount(split)),
257                                 acc_guid_str);
258         }
259         else
260         {
261             acc_guid_str[0] = '\0';
262         }
263 
264         gnc_time64_to_iso8601_buff (split->date_reconciled, drecn);
265 
266         guid_to_string_buff (xaccSplitGetGUID(split), split_guid_str);
267         amt = xaccSplitGetAmount (split);
268         val = xaccSplitGetValue (split);
269 
270         /* use tab-separated fields */
271         fprintf (trans_log,
272                  "%c\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
273                  "%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",
274                  flag,
275                  trans_guid_str, split_guid_str,  /* trans+split make up unique id */
276                  /* Note that the next three strings always exist,
277                  		* so we don't need to test them. */
278                  dnow,
279                  dent,
280                  dpost,
281                  acc_guid_str,
282                  accname ? accname : "",
283                  trans->num ? trans->num : "",
284                  trans->description ? trans->description : "",
285                  trans_notes ? trans_notes : "",
286                  split->memo ? split->memo : "",
287                  split->action ? split->action : "",
288                  split->reconciled,
289                  gnc_numeric_num(amt),
290                  gnc_numeric_denom(amt),
291                  gnc_numeric_num(val),
292                  gnc_numeric_denom(val),
293                  /* The next string always exists. No need to test it. */
294                  drecn);
295     }
296 
297     fprintf (trans_log, "===== END\n");
298 
299     /* get data out to the disk */
300     fflush (trans_log);
301 }
302 
303 /************************ END OF ************************************\
304 \************************* FILE *************************************/
305