1 /*
2  *
3  * CLEX File Manager
4  *
5  * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6  *
7  * CLEX is free software without warranty of any kind; see the
8  * GNU General Public License as set out in the "COPYING" document
9  * which accompanies the CLEX File Manager package.
10  *
11  * CLEX can be downloaded from http://www.clex.sk
12  *
13  */
14 
15 #include "clexheaders.h"
16 
17 #include <errno.h>			/* errno */
18 #include <stdarg.h>			/* va_list */
19 #include <stdio.h>			/* fprintf() */
20 #include <string.h>			/* strerror() */
21 #include <time.h>			/* strftime() */
22 
23 #include "log.h"
24 
25 #include "control.h"		/* err_exit() */
26 #include "inout.h"			/* win_sethelp() */
27 #include "match.h"			/* match_substr() */
28 #include "mbwstring.h"		/* usw_convert2w() */
29 #include "panel.h"			/* panel_adjust() */
30 #include "ustringutil.h"	/* us_vprintf() */
31 
32 static FILE *logfp = 0;
33 static LOG_ENTRY logbook[LOG_LINES];	/* circular buffer */
34 static int base = 0, cnt = 0;
35 
36 static int
find_nl(const char * str)37 find_nl(const char *str)
38 {
39 	const char *nl;
40 
41 	nl = strchr(str,'\n');
42 	return nl ? nl - str : -1;
43 }
44 
45 static const char *
strip_nl(const char * str)46 strip_nl(const char *str)
47 {
48 	static USTRING nonl = UNULL;
49 	char *p;
50 	int nl;
51 
52 	if ((nl = find_nl(str)) < 0)
53 		return str;
54 
55 	p = us_copy(&nonl,str);
56 	do {
57 		p += nl;
58 		*p++ = ' ';
59 		nl = find_nl(p);
60 	} while (nl >= 0);
61 	return USTR(nonl);
62 }
63 
64 static void
append_record(const char * timestamp,const char * levelstr,const char * msg)65 append_record(const char *timestamp, const char *levelstr, const char *msg)
66 {
67 	fprintf(logfp,"%s %-15s %s\n",timestamp,levelstr,msg);
68 	fflush(logfp);
69 }
70 
71 void
logfile_open(const char * logfile)72 logfile_open(const char *logfile)
73 {
74 	int i;
75 	LOG_ENTRY *plog;
76 
77 	if ( (logfp = fopen(logfile,"a")) == 0)
78 		msgout(MSG_W,"Could not open the logfile \"%s\" (%s)",logfile,strerror(errno));
79 	else {
80 		/* write records collected so far */
81 		for (i = 0; i < cnt; i++) {
82 			plog = logbook + (base + i) % LOG_LINES;
83 			append_record(plog->timestamp,plog->levelstr,convert2mb(USTR(plog->msg)));
84 		}
85 		msgout(MSG_DEBUG,"Logfile: \"%s\"",logfile);
86 	}
87 }
88 
89 /* this is a cleanup function (see err_exit() in control.c) */
90 void
logfile_close(void)91 logfile_close(void)
92 {
93 	if (logfp) {
94 		fclose(logfp);
95 		logfp = 0;
96 	}
97 }
98 
99 static void
store_timestamp(char * dst)100 store_timestamp(char *dst)
101 {
102 	static FLAG format_ok = 1;
103 	time_t now;
104 
105 	now = time(0);
106 	if (format_ok && strftime(dst,TIMESTAMP_STR,"%c",localtime(&now)) == 0) {
107 		format_ok = 0;
108 		msgout(MSG_NOTICE,"LOG: Using YYYY-MM-DD HH:MM:SS date/time format "
109 		  "because the default format (defined by locale) is too long");
110 	}
111 	if (!format_ok)
112 		strftime(dst,TIMESTAMP_STR,"%Y-%m-%d %H:%M:%S",localtime(&now));
113 }
114 
115 static void
log_record(int level,const char * logmsg)116 log_record(int level, const char *logmsg)
117 {
118 	static struct {
119 		const char *str;	/* should not exceed 15 chars */
120 		FLAG panel;			/* insert into panel + append to the logfile */
121 		FLAG screen;		/* display on the screen */
122 		FLAG iswarning;		/* is a warning */
123 	} levdef [_MSG_TOTAL_] = {
124 		{ /* heading */ 0,0,0,0 },
125 		{ "DEBUG",		1, 0, 0 },
126 		{ "NOTICE",		1, 0, 1 },
127 		{ "AUDIT",		1, 0, 0 },
128 		{ "INFO",		1, 1, 0 },
129 		{ "INFO",		0, 1, 0 },
130 		{ "WARNING",	1, 1, 1 },
131 		{ "WARNING",	0, 1, 1 }
132 	};
133 	static USTRING heading_buff = UNULL;
134 	static int notify_hint = 2;	/* display the hint twice */
135 	USTRINGW wmsg_buff;
136 	const wchar_t *wmsg;
137 	const char *msg, *heading;
138 	LOG_ENTRY *plog;
139 	FLAG notify = 0;
140 	int i;
141 
142 	/* modifiers */
143 	if ((level & MSG_NOTIFY) && notify_hint)
144 		notify = 1;
145 	level &= MSG_MASK;
146 
147 	if (level < 0 || level >= _MSG_TOTAL_)
148 		err_exit("BUG: invalid message priority level %d",level);
149 
150 	if (level == MSG_HEADING) {
151 		/* if 'msg' is a null ptr, the stored heading is invalidated */
152 		us_copy(&heading_buff,logmsg);
153 		return;
154 	}
155 	if (levdef[level].iswarning && (heading = USTR(heading_buff)) && logmsg != heading) {
156 		/* emit the heading string first */
157 		log_record(level,heading);
158 		us_reset(&heading_buff);
159 	}
160 
161 	msg = strip_nl(logmsg);
162 	US_INIT(wmsg_buff);
163 	wmsg = usw_convert2w(msg,&wmsg_buff);
164 
165 	/* append it to the log panel and log file */
166 	if (levdef[level].panel) {
167 		if (cnt < LOG_LINES)
168 			/* adding new record */
169 			plog = logbook + (base + cnt++) % LOG_LINES;
170 		else {
171 			/* replacing the oldest one */
172 			plog = logbook + base;
173 			base = (base + 1) % LOG_LINES;
174 			if (plog->cols == panel_log.maxcols)
175 				/* replacing the longest message -> must recalculate the max */
176 				panel_log.maxcols = 0;
177 		}
178 
179 		plog->level = level;
180 		plog->levelstr = levdef[level].str;
181 		usw_copy(&plog->msg,wmsg);
182 		store_timestamp(plog->timestamp);
183 		plog->cols = wc_cols(wmsg,0,-1);
184 
185 		/* max length */
186 		if (cnt < LOG_LINES || panel_log.maxcols > 0) {
187 			if (plog->cols > panel_log.maxcols)
188 				panel_log.maxcols = plog->cols;
189 		}
190 		else
191 			for (i = 0; i < LOG_LINES; i++)
192 				if (logbook[i].cols > panel_log.maxcols)
193 					panel_log.maxcols = logbook[i].cols;
194 
195 		/* live view */
196 		if (get_current_mode() == MODE_LOG) {
197 			log_panel_data();
198 			panel->curs = panel->cnt - 1;
199 			pan_adjust(panel);
200 			win_panel();
201 		}
202 
203 		if (logfp)
204 			append_record(plog->timestamp,plog->levelstr,msg);
205 	}
206 
207 	/* display it on the screen */
208 	if (levdef[level].screen) {
209 		if (disp_data.curses)
210 			win_sethelp(levdef[level].iswarning ? HELPMSG_WARNING : HELPMSG_INFO,wmsg);
211 		else {
212 			puts(logmsg);	/* original message possibly with newlines */
213 			fflush(stdout);
214 			disp_data.wait = 1;
215 		}
216 		if (notify) {
217 			notify_hint--;	/* display this hint only N times */
218 			win_sethelp(HELPMSG_TMP,L"alt-N = notification panel");
219 		}
220 	}
221 }
222 
223 void
vmsgout(int level,const char * format,va_list argptr)224 vmsgout(int level, const char *format, va_list argptr)
225 {
226 	static USTRING buff = UNULL;
227 
228 	us_vprintf(&buff,format,argptr);
229 	log_record(level,USTR(buff));
230 }
231 
232 void
msgout(int level,const char * format,...)233 msgout(int level, const char *format, ...)
234 {
235 	va_list argptr;
236 
237 	if (format && strchr(format,'%')) {
238 		va_start(argptr,format);
239 		vmsgout(level,format,argptr);
240 		va_end(argptr);
241 	}
242 	else
243 		log_record(level,format);
244 }
245 
246 void
log_panel_data(void)247 log_panel_data(void)
248 {
249 	int i, j;
250 	LOG_ENTRY *plog, *curs;
251 
252 	curs = VALID_CURSOR(panel_log.pd) ? panel_log.line[panel_log.pd->curs] : 0;
253 	if (panel_log.pd->filtering)
254 		match_substr_set(panel_log.pd->filter->line);
255 
256 	for (i = j = 0; i < cnt; i++) {
257 		plog = logbook + (base + i) % LOG_LINES;
258 		if (plog == curs)
259 			panel_log.pd->curs = j;
260 		if (panel_log.pd->filtering && !match_substr(USTR(plog->msg)))
261 			continue;
262 		panel_log.line[j++] = plog;
263 	}
264 	panel_log.pd->cnt = j;
265 }
266 
267 int
log_prepare(void)268 log_prepare(void)
269 {
270 	panel_log.pd->filtering = 0;
271 	panel_log.pd->curs = -1;
272 	log_panel_data();
273 	panel_log.pd->top = panel_user.pd->min;
274 	panel_log.pd->curs = panel_log.pd->cnt - 1;
275 
276 	panel = panel_log.pd;
277 	textline = 0;
278 	return 0;
279 }
280 
281 #define SCROLL_UNIT	12
282 
283 void
cx_log_right(void)284 cx_log_right(void)
285 {
286 	if (panel_log.scroll < panel_log.maxcols - disp_data.scrcols / 2) {
287 		panel_log.scroll += SCROLL_UNIT;
288 		win_panel();
289 	}
290 }
291 
292 void
cx_log_left(void)293 cx_log_left(void)
294 {
295 	if (panel_log.scroll >= SCROLL_UNIT) {
296 		panel_log.scroll -= SCROLL_UNIT;
297 		win_panel();
298 	}
299 }
300 
301 void
cx_log_mark(void)302 cx_log_mark(void)
303 {
304 	msgout(MSG_DEBUG,"-- mark --");
305 }
306 
307 void
cx_log_home(void)308 cx_log_home(void)
309 {
310 	panel_log.scroll = 0;
311 	win_panel();
312 }
313