Mercurial > ~mikael > mcabber > hg
comparison mcabber/mcabber/histolog.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/histolog.c@f4a2c6f767d1 |
children | d1e8fb14ce2d |
comparison
equal
deleted
inserted
replaced
1667:8af0e0ad20ad | 1668:41c26b7d2890 |
---|---|
1 /* | |
2 * histolog.c -- File history handling | |
3 * | |
4 * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net> | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or (at | |
9 * your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program; if not, write to the Free Software | |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |
19 * USA | |
20 */ | |
21 | |
22 #include <ctype.h> | |
23 #include <errno.h> | |
24 #include <fcntl.h> | |
25 #include <stdlib.h> | |
26 #include <string.h> | |
27 #include <sys/types.h> | |
28 #include <sys/stat.h> | |
29 #include <time.h> | |
30 #include <unistd.h> | |
31 | |
32 #include "histolog.h" | |
33 #include "hbuf.h" | |
34 #include "utils.h" | |
35 #include "screen.h" | |
36 #include "settings.h" | |
37 #include "utils.h" | |
38 #include "roster.h" | |
39 #include "xmpp.h" | |
40 | |
41 static guint UseFileLogging; | |
42 static guint FileLoadLogs; | |
43 static char *RootDir; | |
44 | |
45 | |
46 // user_histo_file(jid) | |
47 // Returns history filename for the given jid | |
48 // Note: the caller *must* free the filename after use (if not null). | |
49 static char *user_histo_file(const char *bjid) | |
50 { | |
51 char *filename; | |
52 char *lowerid; | |
53 | |
54 if (!(UseFileLogging || FileLoadLogs)) | |
55 return NULL; | |
56 | |
57 lowerid = g_strdup(bjid); | |
58 if (!lowerid) | |
59 return NULL; | |
60 mc_strtolower(lowerid); | |
61 | |
62 filename = g_strdup_printf("%s%s", RootDir, lowerid); | |
63 g_free(lowerid); | |
64 return filename; | |
65 } | |
66 | |
67 char *hlog_get_log_jid(const char *bjid) | |
68 { | |
69 struct stat bufstat; | |
70 char *path; | |
71 char *log_jid = NULL; | |
72 | |
73 path = user_histo_file(bjid); | |
74 while (path) { | |
75 if (lstat(path, &bufstat) != 0) | |
76 break; | |
77 if (S_ISLNK(bufstat.st_mode)) { | |
78 g_free(log_jid); | |
79 log_jid = g_new0(char, bufstat.st_size+1); | |
80 if (readlink(path, log_jid, bufstat.st_size) < 0) return NULL; | |
81 g_free(path); | |
82 path = user_histo_file(log_jid); | |
83 } else | |
84 break; | |
85 } | |
86 | |
87 g_free(path); | |
88 return log_jid; | |
89 } | |
90 | |
91 // write_histo_line() | |
92 // Adds a history (multi-)line to the jid's history logfile | |
93 static void write_histo_line(const char *bjid, | |
94 time_t timestamp, guchar type, guchar info, const char *data) | |
95 { | |
96 guint len = 0; | |
97 FILE *fp; | |
98 time_t ts; | |
99 const char *p; | |
100 char *filename; | |
101 char str_ts[20]; | |
102 int err; | |
103 | |
104 if (!UseFileLogging) | |
105 return; | |
106 | |
107 // Do not log status messages when 'logging_ignore_status' is set | |
108 if (type == 'S' && settings_opt_get_int("logging_ignore_status")) | |
109 return; | |
110 | |
111 filename = user_histo_file(bjid); | |
112 | |
113 // If timestamp is null, get current date | |
114 if (timestamp) | |
115 ts = timestamp; | |
116 else | |
117 time(&ts); | |
118 | |
119 if (!data) | |
120 data = ""; | |
121 | |
122 // Count number of extra lines | |
123 for (p=data ; *p ; p++) | |
124 if (*p == '\n') len++; | |
125 | |
126 /* Line format: "TI yyyymmddThh:mm:ssZ LLL [data]" | |
127 * T=Type, I=Info, yyyymmddThh:mm:ssZ=date, LLL=0-padded-len | |
128 * | |
129 * Types: | |
130 * - M message Info: S (send) R (receive) I (info) | |
131 * - S status Info: [_ofdnai] | |
132 * We don't check them, we trust the caller. | |
133 * (Info messages are not sent nor received, they're generated | |
134 * locally by mcabber.) | |
135 */ | |
136 | |
137 fp = fopen(filename, "a"); | |
138 g_free(filename); | |
139 if (!fp) { | |
140 scr_LogPrint(LPRINT_LOGNORM, "Unable to write history " | |
141 "(cannot open logfile)"); | |
142 return; | |
143 } | |
144 | |
145 to_iso8601(str_ts, ts); | |
146 err = fprintf(fp, "%c%c %-18.18s %03d %s\n", type, info, str_ts, len, data); | |
147 fclose(fp); | |
148 if (err < 0) { | |
149 scr_LogPrint(LPRINT_LOGNORM, "Error while writing to log file: %s", | |
150 strerror(errno)); | |
151 } | |
152 } | |
153 | |
154 // hlog_read_history() | |
155 // Reads the jid's history logfile | |
156 void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width) | |
157 { | |
158 char *filename; | |
159 guchar type, info; | |
160 char *data, *tail; | |
161 guint data_size; | |
162 char *xtext; | |
163 time_t timestamp; | |
164 guint prefix_flags; | |
165 guint len; | |
166 FILE *fp; | |
167 struct stat bufstat; | |
168 guint err = 0; | |
169 guint ln = 0; // line number | |
170 time_t starttime; | |
171 int max_num_of_blocks; | |
172 | |
173 if (!FileLoadLogs) | |
174 return; | |
175 | |
176 if ((roster_gettype(bjid) & ROSTER_TYPE_ROOM) && | |
177 (settings_opt_get_int("load_muc_logs") != 1)) | |
178 return; | |
179 | |
180 data_size = HBB_BLOCKSIZE+32; | |
181 data = g_new(char, data_size); | |
182 if (!data) { | |
183 scr_LogPrint(LPRINT_LOGNORM, "Not enough memory to read history file"); | |
184 return; | |
185 } | |
186 | |
187 filename = user_histo_file(bjid); | |
188 | |
189 fp = fopen(filename, "r"); | |
190 g_free(filename); | |
191 if (!fp) { | |
192 g_free(data); | |
193 return; | |
194 } | |
195 | |
196 // If file is large (> 3MB here), display a message to inform the user | |
197 // (it can take a while...) | |
198 if (!fstat(fileno(fp), &bufstat)) { | |
199 if (bufstat.st_size > 3145728) { | |
200 scr_LogPrint(LPRINT_NORMAL, "Reading <%s> history file...", bjid); | |
201 scr_DoUpdate(); | |
202 } | |
203 } | |
204 | |
205 max_num_of_blocks = get_max_history_blocks(); | |
206 | |
207 starttime = 0L; | |
208 if (settings_opt_get_int("max_history_age") > 0) { | |
209 int maxdays = settings_opt_get_int("max_history_age"); | |
210 time(&starttime); | |
211 if (maxdays >= starttime/86400L) | |
212 starttime = 0L; | |
213 else | |
214 starttime -= maxdays * 86400L; | |
215 } | |
216 | |
217 /* See write_histo_line() for line format... */ | |
218 while (!feof(fp)) { | |
219 guint dataoffset = 25; | |
220 guint noeol; | |
221 | |
222 if (fgets(data, data_size-1, fp) == NULL) | |
223 break; | |
224 ln++; | |
225 | |
226 while (1) { | |
227 for (tail = data; *tail; tail++) ; | |
228 noeol = (*(tail-1) != '\n'); | |
229 if (!noeol) | |
230 break; | |
231 /* TODO: duplicated code... could do better... */ | |
232 if (tail == data + data_size-2) { | |
233 // The buffer is too small to contain the whole line. | |
234 // Let's allocate some more space. | |
235 if (!max_num_of_blocks || | |
236 data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { | |
237 guint toffset = tail - data; | |
238 // Allocate one more block. | |
239 data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); | |
240 data = g_renew(char, data, data_size); | |
241 // Update the tail pointer, as the data may have been moved. | |
242 tail = data + toffset; | |
243 if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) | |
244 break; | |
245 } else { | |
246 scr_LogPrint(LPRINT_LOGNORM, "Line too long in history file!"); | |
247 ln--; | |
248 break; | |
249 } | |
250 } | |
251 } | |
252 | |
253 type = data[0]; | |
254 info = data[1]; | |
255 | |
256 if ((type != 'M' && type != 'S') || | |
257 ((data[11] != 'T') || (data[20] != 'Z') || | |
258 (data[21] != ' ') || | |
259 (data[25] != ' ' && data[26] != ' '))) { | |
260 if (!err) { | |
261 scr_LogPrint(LPRINT_LOGNORM, | |
262 "Error in history file format (%s), l.%u", bjid, ln); | |
263 err = 1; | |
264 } | |
265 continue; | |
266 } | |
267 // The number of lines can be written with 3 or 4 bytes. | |
268 if (data[25] != ' ') dataoffset = 26; | |
269 data[21] = data[dataoffset] = 0; | |
270 timestamp = from_iso8601(&data[3], 1); | |
271 len = (guint) atoi(&data[22]); | |
272 | |
273 // Some checks | |
274 if (((type == 'M') && (info != 'S' && info != 'R' && info != 'I')) || | |
275 ((type == 'S') && (!strchr("_OFDNAI", info)))) { | |
276 if (!err) { | |
277 scr_LogPrint(LPRINT_LOGNORM, "Error in history file format (%s), l.%u", | |
278 bjid, ln); | |
279 err = 1; | |
280 } | |
281 continue; | |
282 } | |
283 | |
284 while (len--) { | |
285 ln++; | |
286 if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) | |
287 break; | |
288 | |
289 while (*tail) tail++; | |
290 noeol = (*(tail-1) != '\n'); | |
291 if (tail == data + data_size-2 && (len || noeol)) { | |
292 // The buffer is too small to contain the whole message. | |
293 // Let's allocate some more space. | |
294 if (!max_num_of_blocks || | |
295 data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { | |
296 guint toffset = tail - data; | |
297 // If the line hasn't been read completely and we reallocate the | |
298 // buffer, we want to read one more time. | |
299 if (noeol) | |
300 len++; | |
301 // Allocate one more block. | |
302 data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); | |
303 data = g_renew(char, data, data_size); | |
304 // Update the tail pointer, as the data may have been moved. | |
305 tail = data + toffset; | |
306 } else { | |
307 // There will probably be a parse error on next read, because | |
308 // this message hasn't been read entirely. | |
309 scr_LogPrint(LPRINT_LOGNORM, "Message too big in history file!"); | |
310 } | |
311 } | |
312 } | |
313 // Remove last CR (we keep it if the line is empty, too) | |
314 if ((tail > data+dataoffset+1) && (*(tail-1) == '\n')) | |
315 *(tail-1) = 0; | |
316 | |
317 // Check if the data is older than max_history_age | |
318 if (starttime) { | |
319 if (timestamp > starttime) | |
320 starttime = 0L; // From now on, load everything | |
321 else | |
322 continue; | |
323 } | |
324 | |
325 if (type == 'M') { | |
326 char *converted; | |
327 if (info == 'S') { | |
328 prefix_flags = HBB_PREFIX_OUT | HBB_PREFIX_HLIGHT_OUT; | |
329 } else { | |
330 prefix_flags = HBB_PREFIX_IN; | |
331 if (info == 'I') | |
332 prefix_flags = HBB_PREFIX_INFO; | |
333 } | |
334 converted = from_utf8(&data[dataoffset+1]); | |
335 if (converted) { | |
336 xtext = ut_expand_tabs(converted); // Expand tabs | |
337 hbuf_add_line(p_buddyhbuf, xtext, timestamp, prefix_flags, width, | |
338 max_num_of_blocks, 0, NULL); | |
339 if (xtext != converted) | |
340 g_free(xtext); | |
341 g_free(converted); | |
342 } | |
343 err = 0; | |
344 } | |
345 } | |
346 fclose(fp); | |
347 g_free(data); | |
348 } | |
349 | |
350 // hlog_enable() | |
351 // Enable logging to files. If root_dir is NULL, then $HOME/.mcabber is used. | |
352 // If loadfiles is TRUE, we will try to load buddies history logs from file. | |
353 void hlog_enable(guint enable, const char *root_dir, guint loadfiles) | |
354 { | |
355 UseFileLogging = enable; | |
356 FileLoadLogs = loadfiles; | |
357 | |
358 if (enable || loadfiles) { | |
359 if (root_dir) { | |
360 char *xp_root_dir; | |
361 int l = strlen(root_dir); | |
362 if (l < 1) { | |
363 scr_LogPrint(LPRINT_LOGNORM, "Error: logging dir name too short"); | |
364 UseFileLogging = FileLoadLogs = FALSE; | |
365 return; | |
366 } | |
367 xp_root_dir = expand_filename(root_dir); | |
368 // RootDir must be slash-terminated | |
369 if (root_dir[l-1] == '/') { | |
370 RootDir = xp_root_dir; | |
371 } else { | |
372 RootDir = g_strdup_printf("%s/", xp_root_dir); | |
373 g_free(xp_root_dir); | |
374 } | |
375 } else { | |
376 char *home = getenv("HOME"); | |
377 const char *dir = "/.mcabber/histo/"; | |
378 RootDir = g_strdup_printf("%s%s", home, dir); | |
379 } | |
380 // Check directory permissions (should not be readable by group/others) | |
381 if (checkset_perm(RootDir, TRUE) == -1) { | |
382 // The directory does not actually exists | |
383 g_free(RootDir); | |
384 RootDir = NULL; | |
385 scr_LogPrint(LPRINT_LOGNORM, "ERROR: Cannot access " | |
386 "history log directory, logging DISABLED"); | |
387 UseFileLogging = FileLoadLogs = FALSE; | |
388 } | |
389 } else { // Disable history logging | |
390 g_free(RootDir); | |
391 RootDir = NULL; | |
392 } | |
393 } | |
394 | |
395 guint hlog_is_enabled(void) | |
396 { | |
397 return UseFileLogging; | |
398 } | |
399 | |
400 inline void hlog_write_message(const char *bjid, time_t timestamp, int sent, | |
401 const char *msg) | |
402 { | |
403 guchar info; | |
404 /* sent=1 message sent by mcabber | |
405 * sent=0 message received by mcabber | |
406 * sent=-1 local info message | |
407 */ | |
408 if (sent == 1) | |
409 info = 'S'; | |
410 else if (sent == 0) | |
411 info = 'R'; | |
412 else | |
413 info = 'I'; | |
414 write_histo_line(bjid, timestamp, 'M', info, msg); | |
415 } | |
416 | |
417 inline void hlog_write_status(const char *bjid, time_t timestamp, | |
418 enum imstatus status, const char *status_msg) | |
419 { | |
420 // XXX Check status value? | |
421 write_histo_line(bjid, timestamp, 'S', toupper(imstatus2char[status]), | |
422 status_msg); | |
423 } | |
424 | |
425 | |
426 // hlog_save_state() | |
427 // If enabled, save the current state of the roster | |
428 // (i.e. pending messages) to a temporary file. | |
429 void hlog_save_state(void) | |
430 { | |
431 gpointer unread_ptr, first_unread; | |
432 const char *bjid; | |
433 char *statefile_xp; | |
434 FILE *fp; | |
435 const char *statefile = settings_opt_get("statefile"); | |
436 | |
437 if (!statefile || !UseFileLogging) | |
438 return; | |
439 | |
440 statefile_xp = expand_filename(statefile); | |
441 fp = fopen(statefile_xp, "w"); | |
442 if (!fp) { | |
443 scr_LogPrint(LPRINT_NORMAL, "Cannot open state file [%s]", | |
444 strerror(errno)); | |
445 goto hlog_save_state_return; | |
446 } | |
447 | |
448 if (!lm_connection_is_authenticated(lconnection)) { | |
449 // We're not connected. Let's use the unread_jids hash. | |
450 GList *unread_jid = unread_jid_get_list(); | |
451 unread_ptr = unread_jid; | |
452 for ( ; unread_jid ; unread_jid = g_list_next(unread_jid)) | |
453 fprintf(fp, "%s\n", (char*)unread_jid->data); | |
454 g_list_free(unread_ptr); | |
455 goto hlog_save_state_return; | |
456 } | |
457 | |
458 if (!current_buddy) // Safety check -- shouldn't happen. | |
459 goto hlog_save_state_return; | |
460 | |
461 // We're connected. Let's use unread_msg(). | |
462 unread_ptr = first_unread = unread_msg(NULL); | |
463 if (!first_unread) | |
464 goto hlog_save_state_return; | |
465 | |
466 do { | |
467 guint type = buddy_gettype(unread_ptr); | |
468 if (type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) { | |
469 bjid = buddy_getjid(unread_ptr); | |
470 if (bjid) | |
471 fprintf(fp, "%s\n", bjid); | |
472 } | |
473 unread_ptr = unread_msg(unread_ptr); | |
474 } while (unread_ptr && unread_ptr != first_unread); | |
475 | |
476 hlog_save_state_return: | |
477 if (fp) { | |
478 long filelen = ftell(fp); | |
479 fclose(fp); | |
480 if (!filelen) | |
481 unlink(statefile_xp); | |
482 } | |
483 g_free(statefile_xp); | |
484 } | |
485 | |
486 // hlog_load_state() | |
487 // If enabled, load the current state of the roster | |
488 // (i.e. pending messages) from a temporary file. | |
489 // This function adds the JIDs to the unread_jids hash table, | |
490 // so it should only be called at startup. | |
491 void hlog_load_state(void) | |
492 { | |
493 char bjid[1024]; | |
494 char *statefile_xp; | |
495 FILE *fp; | |
496 const char *statefile = settings_opt_get("statefile"); | |
497 | |
498 if (!statefile || !UseFileLogging) | |
499 return; | |
500 | |
501 statefile_xp = expand_filename(statefile); | |
502 fp = fopen(statefile_xp, "r"); | |
503 if (fp) { | |
504 char *eol; | |
505 while (!feof(fp)) { | |
506 if (fgets(bjid, sizeof bjid, fp) == NULL) | |
507 break; | |
508 // Let's remove the trailing newline. | |
509 // Also remove whitespace, if the file as been (badly) manually modified. | |
510 for (eol = bjid; *eol; eol++) ; | |
511 for (eol--; eol >= bjid && (*eol == '\n' || *eol == ' '); *eol-- = 0) ; | |
512 // Safety checks... | |
513 if (!bjid[0]) | |
514 continue; | |
515 if (check_jid_syntax(bjid)) { | |
516 scr_LogPrint(LPRINT_LOGNORM, | |
517 "ERROR: Invalid JID in state file. Corrupted file?"); | |
518 break; | |
519 } | |
520 // Display a warning if there are pending messages but the user | |
521 // won't see them because load_log isn't set. | |
522 if (!FileLoadLogs) { | |
523 scr_LogPrint(LPRINT_LOGNORM, "WARNING: unread message from <%s>.", | |
524 bjid); | |
525 scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); | |
526 } | |
527 // Add the JID to unread_jids. It will be used when the contact is | |
528 // added to the roster. | |
529 unread_jid_add(bjid); | |
530 } | |
531 fclose(fp); | |
532 } | |
533 g_free(statefile_xp); | |
534 } | |
535 | |
536 /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ |