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