Mercurial > pidgin
comparison libpurple/log.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 21bc8d84974f |
comparison
equal
deleted
inserted
replaced
15372:f79e0f4df793 | 15373:5fe8042783c1 |
---|---|
1 /** | |
2 * @file log.c Logging API | |
3 * @ingroup core | |
4 * | |
5 * gaim | |
6 * | |
7 * Gaim is the legal property of its developers, whose names are too numerous | |
8 * to list here. Please refer to the COPYRIGHT file distributed with this | |
9 * source distribution. | |
10 * | |
11 * This program is free software; you can redistribute it and/or modify | |
12 * it under the terms of the GNU General Public License as published by | |
13 * the Free Software Foundation; either version 2 of the License, or | |
14 * (at your option) any later version. | |
15 * | |
16 * This program is distributed in the hope that it will be useful, | |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 * GNU General Public License for more details. | |
20 * | |
21 * You should have received a copy of the GNU General Public License | |
22 * along with this program; if not, write to the Free Software | |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
24 */ | |
25 | |
26 #include "internal.h" | |
27 #include "account.h" | |
28 #include "dbus-maybe.h" | |
29 #include "debug.h" | |
30 #include "internal.h" | |
31 #include "log.h" | |
32 #include "prefs.h" | |
33 #include "util.h" | |
34 #include "stringref.h" | |
35 | |
36 static GSList *loggers = NULL; | |
37 | |
38 static GaimLogLogger *html_logger; | |
39 static GaimLogLogger *txt_logger; | |
40 static GaimLogLogger *old_logger; | |
41 | |
42 struct _gaim_logsize_user { | |
43 char *name; | |
44 GaimAccount *account; | |
45 }; | |
46 static GHashTable *logsize_users = NULL; | |
47 | |
48 static void log_get_log_sets_common(GHashTable *sets); | |
49 | |
50 static gsize html_logger_write(GaimLog *log, GaimMessageFlags type, | |
51 const char *from, time_t time, const char *message); | |
52 static void html_logger_finalize(GaimLog *log); | |
53 static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account); | |
54 static GList *html_logger_list_syslog(GaimAccount *account); | |
55 static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags); | |
56 static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); | |
57 | |
58 static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account); | |
59 static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); | |
60 static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags); | |
61 static int old_logger_size (GaimLog *log); | |
62 static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets); | |
63 static void old_logger_finalize(GaimLog *log); | |
64 | |
65 static gsize txt_logger_write(GaimLog *log, | |
66 GaimMessageFlags type, | |
67 const char *from, time_t time, const char *message); | |
68 static void txt_logger_finalize(GaimLog *log); | |
69 static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account); | |
70 static GList *txt_logger_list_syslog(GaimAccount *account); | |
71 static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags); | |
72 static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); | |
73 | |
74 /************************************************************************** | |
75 * PUBLIC LOGGING FUNCTIONS *********************************************** | |
76 **************************************************************************/ | |
77 | |
78 GaimLog *gaim_log_new(GaimLogType type, const char *name, GaimAccount *account, | |
79 GaimConversation *conv, time_t time, const struct tm *tm) | |
80 { | |
81 GaimLog *log; | |
82 | |
83 /* IMPORTANT: Make sure to initialize all the members of GaimLog */ | |
84 log = g_slice_new(GaimLog); | |
85 GAIM_DBUS_REGISTER_POINTER(log, GaimLog); | |
86 | |
87 log->type = type; | |
88 log->name = g_strdup(gaim_normalize(account, name)); | |
89 log->account = account; | |
90 log->conv = conv; | |
91 log->time = time; | |
92 log->logger = gaim_log_logger_get(); | |
93 log->logger_data = NULL; | |
94 | |
95 if (tm == NULL) | |
96 log->tm = NULL; | |
97 else | |
98 { | |
99 /* There's no need to zero this as we immediately do a direct copy. */ | |
100 log->tm = g_slice_new(struct tm); | |
101 | |
102 *(log->tm) = *tm; | |
103 | |
104 #ifdef HAVE_STRUCT_TM_TM_ZONE | |
105 /* XXX: This is so wrong... */ | |
106 if (log->tm->tm_zone != NULL) | |
107 { | |
108 char *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL); | |
109 if (tmp != NULL) | |
110 log->tm->tm_zone = tmp; | |
111 else | |
112 /* Just shove the UTF-8 bytes in and hope... */ | |
113 log->tm->tm_zone = g_strdup(log->tm->tm_zone); | |
114 } | |
115 #endif | |
116 } | |
117 | |
118 if (log->logger && log->logger->create) | |
119 log->logger->create(log); | |
120 return log; | |
121 } | |
122 | |
123 void gaim_log_free(GaimLog *log) | |
124 { | |
125 g_return_if_fail(log); | |
126 if (log->logger && log->logger->finalize) | |
127 log->logger->finalize(log); | |
128 g_free(log->name); | |
129 | |
130 if (log->tm != NULL) | |
131 { | |
132 #ifdef HAVE_STRUCT_TM_TM_ZONE | |
133 /* XXX: This is so wrong... */ | |
134 g_free((char *)log->tm->tm_zone); | |
135 #endif | |
136 g_slice_free(struct tm, log->tm); | |
137 } | |
138 | |
139 GAIM_DBUS_UNREGISTER_POINTER(log); | |
140 g_slice_free(GaimLog, log); | |
141 } | |
142 | |
143 void gaim_log_write(GaimLog *log, GaimMessageFlags type, | |
144 const char *from, time_t time, const char *message) | |
145 { | |
146 struct _gaim_logsize_user *lu; | |
147 gsize written, total = 0; | |
148 gpointer ptrsize; | |
149 | |
150 g_return_if_fail(log); | |
151 g_return_if_fail(log->logger); | |
152 g_return_if_fail(log->logger->write); | |
153 | |
154 written = (log->logger->write)(log, type, from, time, message); | |
155 | |
156 lu = g_new(struct _gaim_logsize_user, 1); | |
157 | |
158 lu->name = g_strdup(gaim_normalize(log->account, log->name)); | |
159 lu->account = log->account; | |
160 | |
161 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { | |
162 total = GPOINTER_TO_INT(ptrsize); | |
163 total += written; | |
164 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total)); | |
165 } else { | |
166 g_free(lu->name); | |
167 g_free(lu); | |
168 } | |
169 | |
170 } | |
171 | |
172 char *gaim_log_read(GaimLog *log, GaimLogReadFlags *flags) | |
173 { | |
174 GaimLogReadFlags mflags; | |
175 g_return_val_if_fail(log && log->logger, NULL); | |
176 if (log->logger->read) { | |
177 char *ret = (log->logger->read)(log, flags ? flags : &mflags); | |
178 gaim_str_strip_char(ret, '\r'); | |
179 return ret; | |
180 } | |
181 return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>")); | |
182 } | |
183 | |
184 int gaim_log_get_size(GaimLog *log) | |
185 { | |
186 g_return_val_if_fail(log && log->logger, 0); | |
187 | |
188 if (log->logger->size) | |
189 return log->logger->size(log); | |
190 return 0; | |
191 } | |
192 | |
193 static guint _gaim_logsize_user_hash(struct _gaim_logsize_user *lu) | |
194 { | |
195 return g_str_hash(lu->name); | |
196 } | |
197 | |
198 static guint _gaim_logsize_user_equal(struct _gaim_logsize_user *lu1, | |
199 struct _gaim_logsize_user *lu2) | |
200 { | |
201 return (lu1->account == lu2->account && (!strcmp(lu1->name, lu2->name))); | |
202 } | |
203 | |
204 static void _gaim_logsize_user_free_key(struct _gaim_logsize_user *lu) | |
205 { | |
206 g_free(lu->name); | |
207 g_free(lu); | |
208 } | |
209 | |
210 int gaim_log_get_total_size(GaimLogType type, const char *name, GaimAccount *account) | |
211 { | |
212 gpointer ptrsize; | |
213 int size = 0; | |
214 GSList *n; | |
215 struct _gaim_logsize_user *lu; | |
216 | |
217 lu = g_new(struct _gaim_logsize_user, 1); | |
218 lu->name = g_strdup(gaim_normalize(account, name)); | |
219 lu->account = account; | |
220 | |
221 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { | |
222 size = GPOINTER_TO_INT(ptrsize); | |
223 g_free(lu->name); | |
224 g_free(lu); | |
225 } else { | |
226 for (n = loggers; n; n = n->next) { | |
227 GaimLogLogger *logger = n->data; | |
228 | |
229 if(logger->total_size){ | |
230 size += (logger->total_size)(type, name, account); | |
231 } else if(logger->list) { | |
232 GList *logs = (logger->list)(type, name, account); | |
233 int this_size = 0; | |
234 | |
235 while (logs) { | |
236 GaimLog *log = (GaimLog*)(logs->data); | |
237 this_size += gaim_log_get_size(log); | |
238 gaim_log_free(log); | |
239 logs = g_list_delete_link(logs, logs); | |
240 } | |
241 | |
242 size += this_size; | |
243 } | |
244 } | |
245 | |
246 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size)); | |
247 } | |
248 return size; | |
249 } | |
250 | |
251 char * | |
252 gaim_log_get_log_dir(GaimLogType type, const char *name, GaimAccount *account) | |
253 { | |
254 GaimPlugin *prpl; | |
255 GaimPluginProtocolInfo *prpl_info; | |
256 const char *prpl_name; | |
257 char *acct_name; | |
258 const char *target; | |
259 char *dir; | |
260 | |
261 prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); | |
262 if (!prpl) | |
263 return NULL; | |
264 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); | |
265 prpl_name = prpl_info->list_icon(account, NULL); | |
266 | |
267 acct_name = g_strdup(gaim_escape_filename(gaim_normalize(account, | |
268 gaim_account_get_username(account)))); | |
269 | |
270 if (type == GAIM_LOG_CHAT) { | |
271 char *temp = g_strdup_printf("%s.chat", gaim_normalize(account, name)); | |
272 target = gaim_escape_filename(temp); | |
273 g_free(temp); | |
274 } else if(type == GAIM_LOG_SYSTEM) { | |
275 target = ".system"; | |
276 } else { | |
277 target = gaim_escape_filename(gaim_normalize(account, name)); | |
278 } | |
279 | |
280 dir = g_build_filename(gaim_user_dir(), "logs", prpl_name, acct_name, target, NULL); | |
281 | |
282 g_free(acct_name); | |
283 | |
284 return dir; | |
285 } | |
286 | |
287 /**************************************************************************** | |
288 * LOGGER FUNCTIONS ********************************************************* | |
289 ****************************************************************************/ | |
290 | |
291 static GaimLogLogger *current_logger = NULL; | |
292 | |
293 static void logger_pref_cb(const char *name, GaimPrefType type, | |
294 gconstpointer value, gpointer data) | |
295 { | |
296 GaimLogLogger *logger; | |
297 GSList *l = loggers; | |
298 while (l) { | |
299 logger = l->data; | |
300 if (!strcmp(logger->id, value)) { | |
301 gaim_log_logger_set(logger); | |
302 return; | |
303 } | |
304 l = l->next; | |
305 } | |
306 gaim_log_logger_set(txt_logger); | |
307 } | |
308 | |
309 | |
310 GaimLogLogger *gaim_log_logger_new(const char *id, const char *name, int functions, ...) | |
311 { | |
312 #if 0 | |
313 void(*create)(GaimLog *), | |
314 gsize(*write)(GaimLog *, GaimMessageFlags, const char *, time_t, const char *), | |
315 void(*finalize)(GaimLog *), | |
316 GList*(*list)(GaimLogType type, const char*, GaimAccount*), | |
317 char*(*read)(GaimLog*, GaimLogReadFlags*), | |
318 int(*size)(GaimLog*), | |
319 int(*total_size)(GaimLogType type, const char *name, GaimAccount *account), | |
320 GList*(*list_syslog)(GaimAccount *account), | |
321 void(*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets)) | |
322 { | |
323 #endif | |
324 GaimLogLogger *logger; | |
325 va_list args; | |
326 | |
327 g_return_val_if_fail(id != NULL, NULL); | |
328 g_return_val_if_fail(name != NULL, NULL); | |
329 g_return_val_if_fail(functions >= 1, NULL); | |
330 | |
331 logger = g_new0(GaimLogLogger, 1); | |
332 logger->id = g_strdup(id); | |
333 logger->name = g_strdup(name); | |
334 | |
335 va_start(args, functions); | |
336 | |
337 if (functions >= 1) | |
338 logger->create = va_arg(args, void *); | |
339 if (functions >= 2) | |
340 logger->write = va_arg(args, void *); | |
341 if (functions >= 3) | |
342 logger->finalize = va_arg(args, void *); | |
343 if (functions >= 4) | |
344 logger->list = va_arg(args, void *); | |
345 if (functions >= 5) | |
346 logger->read = va_arg(args, void *); | |
347 if (functions >= 6) | |
348 logger->size = va_arg(args, void *); | |
349 if (functions >= 7) | |
350 logger->total_size = va_arg(args, void *); | |
351 if (functions >= 8) | |
352 logger->list_syslog = va_arg(args, void *); | |
353 if (functions >= 9) | |
354 logger->get_log_sets = va_arg(args, void *); | |
355 | |
356 if (functions > 9) | |
357 gaim_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id); | |
358 | |
359 va_end(args); | |
360 | |
361 return logger; | |
362 } | |
363 | |
364 void gaim_log_logger_free(GaimLogLogger *logger) | |
365 { | |
366 g_free(logger->name); | |
367 g_free(logger->id); | |
368 g_free(logger); | |
369 } | |
370 | |
371 void gaim_log_logger_add (GaimLogLogger *logger) | |
372 { | |
373 g_return_if_fail(logger); | |
374 if (g_slist_find(loggers, logger)) | |
375 return; | |
376 loggers = g_slist_append(loggers, logger); | |
377 } | |
378 | |
379 void gaim_log_logger_remove (GaimLogLogger *logger) | |
380 { | |
381 g_return_if_fail(logger); | |
382 loggers = g_slist_remove(loggers, logger); | |
383 } | |
384 | |
385 void gaim_log_logger_set (GaimLogLogger *logger) | |
386 { | |
387 g_return_if_fail(logger); | |
388 current_logger = logger; | |
389 } | |
390 | |
391 GaimLogLogger *gaim_log_logger_get() | |
392 { | |
393 return current_logger; | |
394 } | |
395 | |
396 GList *gaim_log_logger_get_options(void) | |
397 { | |
398 GSList *n; | |
399 GList *list = NULL; | |
400 GaimLogLogger *data; | |
401 | |
402 for (n = loggers; n; n = n->next) { | |
403 data = n->data; | |
404 if (!data->write) | |
405 continue; | |
406 list = g_list_append(list, data->name); | |
407 list = g_list_append(list, data->id); | |
408 } | |
409 | |
410 return list; | |
411 } | |
412 | |
413 gint gaim_log_compare(gconstpointer y, gconstpointer z) | |
414 { | |
415 const GaimLog *a = y; | |
416 const GaimLog *b = z; | |
417 | |
418 return b->time - a->time; | |
419 } | |
420 | |
421 GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account) | |
422 { | |
423 GList *logs = NULL; | |
424 GSList *n; | |
425 for (n = loggers; n; n = n->next) { | |
426 GaimLogLogger *logger = n->data; | |
427 if (!logger->list) | |
428 continue; | |
429 logs = g_list_concat(logger->list(type, name, account), logs); | |
430 } | |
431 | |
432 return g_list_sort(logs, gaim_log_compare); | |
433 } | |
434 | |
435 gint gaim_log_set_compare(gconstpointer y, gconstpointer z) | |
436 { | |
437 const GaimLogSet *a = y; | |
438 const GaimLogSet *b = z; | |
439 gint ret = 0; | |
440 | |
441 /* This logic seems weird at first... | |
442 * If either account is NULL, we pretend the accounts are | |
443 * equal. This allows us to detect duplicates that will | |
444 * exist if one logger knows the account and another | |
445 * doesn't. */ | |
446 if (a->account != NULL && b->account != NULL) { | |
447 ret = strcmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account)); | |
448 if (ret != 0) | |
449 return ret; | |
450 } | |
451 | |
452 ret = strcmp(a->normalized_name, b->normalized_name); | |
453 if (ret != 0) | |
454 return ret; | |
455 | |
456 return (gint)b->type - (gint)a->type; | |
457 } | |
458 | |
459 static guint | |
460 log_set_hash(gconstpointer key) | |
461 { | |
462 const GaimLogSet *set = key; | |
463 | |
464 /* The account isn't hashed because we need GaimLogSets with NULL accounts | |
465 * to be found when we search by a GaimLogSet that has a non-NULL account | |
466 * but the same type and name. */ | |
467 return g_int_hash((gint *)&set->type) + g_str_hash(set->name); | |
468 } | |
469 | |
470 static gboolean | |
471 log_set_equal(gconstpointer a, gconstpointer b) | |
472 { | |
473 /* I realize that the choices made for GList and GHashTable | |
474 * make sense for those data types, but I wish the comparison | |
475 * functions were compatible. */ | |
476 return !gaim_log_set_compare(a, b); | |
477 } | |
478 | |
479 static void | |
480 log_add_log_set_to_hash(GHashTable *sets, GaimLogSet *set) | |
481 { | |
482 GaimLogSet *existing_set = g_hash_table_lookup(sets, set); | |
483 | |
484 if (existing_set == NULL) | |
485 g_hash_table_insert(sets, set, set); | |
486 else if (existing_set->account == NULL && set->account != NULL) | |
487 g_hash_table_replace(sets, set, set); | |
488 else | |
489 gaim_log_set_free(set); | |
490 } | |
491 | |
492 GHashTable *gaim_log_get_log_sets(void) | |
493 { | |
494 GSList *n; | |
495 GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal, | |
496 (GDestroyNotify)gaim_log_set_free, NULL); | |
497 | |
498 /* Get the log sets from all the loggers. */ | |
499 for (n = loggers; n; n = n->next) { | |
500 GaimLogLogger *logger = n->data; | |
501 | |
502 if (!logger->get_log_sets) | |
503 continue; | |
504 | |
505 logger->get_log_sets(log_add_log_set_to_hash, sets); | |
506 } | |
507 | |
508 log_get_log_sets_common(sets); | |
509 | |
510 /* Return the GHashTable of unique GaimLogSets. */ | |
511 return sets; | |
512 } | |
513 | |
514 void gaim_log_set_free(GaimLogSet *set) | |
515 { | |
516 g_return_if_fail(set != NULL); | |
517 | |
518 g_free(set->name); | |
519 if (set->normalized_name != set->name) | |
520 g_free(set->normalized_name); | |
521 | |
522 g_slice_free(GaimLogSet, set); | |
523 } | |
524 | |
525 GList *gaim_log_get_system_logs(GaimAccount *account) | |
526 { | |
527 GList *logs = NULL; | |
528 GSList *n; | |
529 for (n = loggers; n; n = n->next) { | |
530 GaimLogLogger *logger = n->data; | |
531 if (!logger->list_syslog) | |
532 continue; | |
533 logs = g_list_concat(logger->list_syslog(account), logs); | |
534 } | |
535 | |
536 return g_list_sort(logs, gaim_log_compare); | |
537 } | |
538 | |
539 /**************************************************************************** | |
540 * LOG SUBSYSTEM ************************************************************ | |
541 ****************************************************************************/ | |
542 | |
543 void * | |
544 gaim_log_get_handle(void) | |
545 { | |
546 static int handle; | |
547 | |
548 return &handle; | |
549 } | |
550 | |
551 void gaim_log_init(void) | |
552 { | |
553 void *handle = gaim_log_get_handle(); | |
554 | |
555 gaim_prefs_add_none("/core/logging"); | |
556 gaim_prefs_add_bool("/core/logging/log_ims", FALSE); | |
557 gaim_prefs_add_bool("/core/logging/log_chats", FALSE); | |
558 gaim_prefs_add_bool("/core/logging/log_system", FALSE); | |
559 | |
560 gaim_prefs_add_string("/core/logging/format", "txt"); | |
561 | |
562 html_logger = gaim_log_logger_new("html", _("HTML"), 8, | |
563 NULL, | |
564 html_logger_write, | |
565 html_logger_finalize, | |
566 html_logger_list, | |
567 html_logger_read, | |
568 gaim_log_common_sizer, | |
569 html_logger_total_size, | |
570 html_logger_list_syslog); | |
571 gaim_log_logger_add(html_logger); | |
572 | |
573 txt_logger = gaim_log_logger_new("txt", _("Plain text"), 8, | |
574 NULL, | |
575 txt_logger_write, | |
576 txt_logger_finalize, | |
577 txt_logger_list, | |
578 txt_logger_read, | |
579 gaim_log_common_sizer, | |
580 txt_logger_total_size, | |
581 txt_logger_list_syslog); | |
582 gaim_log_logger_add(txt_logger); | |
583 | |
584 old_logger = gaim_log_logger_new("old", _("Old Gaim"), 9, | |
585 NULL, | |
586 NULL, | |
587 old_logger_finalize, | |
588 old_logger_list, | |
589 old_logger_read, | |
590 old_logger_size, | |
591 old_logger_total_size, | |
592 NULL, | |
593 old_logger_get_log_sets); | |
594 gaim_log_logger_add(old_logger); | |
595 | |
596 gaim_signal_register(handle, "log-timestamp", | |
597 #if SIZEOF_TIME_T == 4 | |
598 gaim_marshal_POINTER__POINTER_INT, | |
599 #elif SIZEOF_TIME_T == 8 | |
600 gaim_marshal_POINTER__POINTER_INT64, | |
601 #else | |
602 #error Unknown size of time_t | |
603 #endif | |
604 gaim_value_new(GAIM_TYPE_POINTER), 2, | |
605 gaim_value_new(GAIM_TYPE_SUBTYPE, | |
606 GAIM_SUBTYPE_LOG), | |
607 #if SIZEOF_TIME_T == 4 | |
608 gaim_value_new(GAIM_TYPE_INT)); | |
609 #elif SIZEOF_TIME_T == 8 | |
610 gaim_value_new(GAIM_TYPE_INT64)); | |
611 #else | |
612 # error Unknown size of time_t | |
613 #endif | |
614 | |
615 gaim_prefs_connect_callback(NULL, "/core/logging/format", | |
616 logger_pref_cb, NULL); | |
617 gaim_prefs_trigger_callback("/core/logging/format"); | |
618 | |
619 logsize_users = g_hash_table_new_full((GHashFunc)_gaim_logsize_user_hash, | |
620 (GEqualFunc)_gaim_logsize_user_equal, | |
621 (GDestroyNotify)_gaim_logsize_user_free_key, NULL); | |
622 } | |
623 | |
624 void | |
625 gaim_log_uninit(void) | |
626 { | |
627 gaim_signals_unregister_by_instance(gaim_log_get_handle()); | |
628 } | |
629 | |
630 /**************************************************************************** | |
631 * LOGGERS ****************************************************************** | |
632 ****************************************************************************/ | |
633 | |
634 static char *log_get_timestamp(GaimLog *log, time_t when) | |
635 { | |
636 char *date; | |
637 struct tm tm; | |
638 | |
639 date = gaim_signal_emit_return_1(gaim_log_get_handle(), | |
640 "log-timestamp", | |
641 log, when); | |
642 if (date != NULL) | |
643 return date; | |
644 | |
645 tm = *(localtime(&when)); | |
646 if (log->type == GAIM_LOG_SYSTEM || time(NULL) > when + 20*60) | |
647 return g_strdup(gaim_date_format_long(&tm)); | |
648 else | |
649 return g_strdup(gaim_time_format(&tm)); | |
650 } | |
651 | |
652 void gaim_log_common_writer(GaimLog *log, const char *ext) | |
653 { | |
654 GaimLogCommonLoggerData *data = log->logger_data; | |
655 | |
656 if (data == NULL) | |
657 { | |
658 /* This log is new */ | |
659 char *dir; | |
660 struct tm *tm; | |
661 const char *tz; | |
662 const char *date; | |
663 char *filename; | |
664 char *path; | |
665 | |
666 dir = gaim_log_get_log_dir(log->type, log->name, log->account); | |
667 if (dir == NULL) | |
668 return; | |
669 | |
670 gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); | |
671 | |
672 tm = localtime(&log->time); | |
673 tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm)); | |
674 date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); | |
675 | |
676 filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); | |
677 | |
678 path = g_build_filename(dir, filename, NULL); | |
679 g_free(dir); | |
680 g_free(filename); | |
681 | |
682 log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData); | |
683 | |
684 data->file = g_fopen(path, "a"); | |
685 if (data->file == NULL) | |
686 { | |
687 gaim_debug(GAIM_DEBUG_ERROR, "log", | |
688 "Could not create log file %s\n", path); | |
689 | |
690 if (log->conv != NULL) | |
691 gaim_conversation_write(log->conv, NULL, _("Logging of this conversation failed."), | |
692 GAIM_MESSAGE_ERROR, time(NULL)); | |
693 | |
694 g_free(path); | |
695 return; | |
696 } | |
697 g_free(path); | |
698 } | |
699 } | |
700 | |
701 GList *gaim_log_common_lister(GaimLogType type, const char *name, GaimAccount *account, const char *ext, GaimLogLogger *logger) | |
702 { | |
703 GDir *dir; | |
704 GList *list = NULL; | |
705 const char *filename; | |
706 char *path; | |
707 | |
708 if(!account) | |
709 return NULL; | |
710 | |
711 path = gaim_log_get_log_dir(type, name, account); | |
712 if (path == NULL) | |
713 return NULL; | |
714 | |
715 if (!(dir = g_dir_open(path, 0, NULL))) | |
716 { | |
717 g_free(path); | |
718 return NULL; | |
719 } | |
720 | |
721 while ((filename = g_dir_read_name(dir))) | |
722 { | |
723 if (gaim_str_has_suffix(filename, ext) && | |
724 strlen(filename) >= (17 + strlen(ext))) | |
725 { | |
726 GaimLog *log; | |
727 GaimLogCommonLoggerData *data; | |
728 struct tm tm; | |
729 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE) | |
730 long tz_off; | |
731 const char *rest; | |
732 time_t stamp = gaim_str_to_time(gaim_unescape_filename(filename), FALSE, &tm, &tz_off, &rest); | |
733 char *end; | |
734 | |
735 /* As zero is a valid offset, GAIM_NO_TZ_OFF means no offset was | |
736 * provided. See util.h. Yes, it's kinda ugly. */ | |
737 if (tz_off != GAIM_NO_TZ_OFF) | |
738 tm.tm_gmtoff = tz_off - tm.tm_gmtoff; | |
739 | |
740 if (rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL) | |
741 { | |
742 log = gaim_log_new(type, name, account, NULL, stamp, NULL); | |
743 } | |
744 else | |
745 { | |
746 char *tmp = g_strndup(rest, end - rest); | |
747 tm.tm_zone = tmp; | |
748 log = gaim_log_new(type, name, account, NULL, stamp, &tm); | |
749 g_free(tmp); | |
750 } | |
751 #else | |
752 time_t stamp = gaim_str_to_time(filename, FALSE, &tm, NULL, NULL); | |
753 | |
754 log = gaim_log_new(type, name, account, NULL, stamp, &tm); | |
755 #endif | |
756 | |
757 log->logger = logger; | |
758 log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData); | |
759 | |
760 data->path = g_build_filename(path, filename, NULL); | |
761 list = g_list_prepend(list, log); | |
762 } | |
763 } | |
764 g_dir_close(dir); | |
765 g_free(path); | |
766 return list; | |
767 } | |
768 | |
769 int gaim_log_common_total_sizer(GaimLogType type, const char *name, GaimAccount *account, const char *ext) | |
770 { | |
771 GDir *dir; | |
772 int size = 0; | |
773 const char *filename; | |
774 char *path; | |
775 | |
776 if(!account) | |
777 return 0; | |
778 | |
779 path = gaim_log_get_log_dir(type, name, account); | |
780 if (path == NULL) | |
781 return 0; | |
782 | |
783 if (!(dir = g_dir_open(path, 0, NULL))) | |
784 { | |
785 g_free(path); | |
786 return 0; | |
787 } | |
788 | |
789 while ((filename = g_dir_read_name(dir))) | |
790 { | |
791 if (gaim_str_has_suffix(filename, ext) && | |
792 strlen(filename) >= (17 + strlen(ext))) | |
793 { | |
794 char *tmp = g_build_filename(path, filename, NULL); | |
795 struct stat st; | |
796 if (g_stat(tmp, &st)) | |
797 { | |
798 gaim_debug_error("log", "Error stating log file: %s\n", tmp); | |
799 g_free(tmp); | |
800 continue; | |
801 } | |
802 g_free(tmp); | |
803 size += st.st_size; | |
804 } | |
805 } | |
806 g_dir_close(dir); | |
807 g_free(path); | |
808 return size; | |
809 } | |
810 | |
811 int gaim_log_common_sizer(GaimLog *log) | |
812 { | |
813 struct stat st; | |
814 GaimLogCommonLoggerData *data = log->logger_data; | |
815 | |
816 if (!data->path || g_stat(data->path, &st)) | |
817 st.st_size = 0; | |
818 | |
819 return st.st_size; | |
820 } | |
821 | |
822 /* This will build log sets for all loggers that use the common logger | |
823 * functions because they use the same directory structure. */ | |
824 static void log_get_log_sets_common(GHashTable *sets) | |
825 { | |
826 gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); | |
827 GDir *log_dir = g_dir_open(log_path, 0, NULL); | |
828 const gchar *protocol; | |
829 | |
830 if (log_dir == NULL) { | |
831 g_free(log_path); | |
832 return; | |
833 } | |
834 | |
835 while ((protocol = g_dir_read_name(log_dir)) != NULL) { | |
836 gchar *protocol_path = g_build_filename(log_path, protocol, NULL); | |
837 GDir *protocol_dir; | |
838 const gchar *username; | |
839 gchar *protocol_unescaped; | |
840 GList *account_iter; | |
841 GList *accounts = NULL; | |
842 | |
843 if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) { | |
844 g_free(protocol_path); | |
845 continue; | |
846 } | |
847 | |
848 /* Using g_strdup() to cover the one-in-a-million chance that a | |
849 * prpl's list_icon function uses gaim_unescape_filename(). */ | |
850 protocol_unescaped = g_strdup(gaim_unescape_filename(protocol)); | |
851 | |
852 /* Find all the accounts for protocol. */ | |
853 for (account_iter = gaim_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) { | |
854 GaimPlugin *prpl; | |
855 GaimPluginProtocolInfo *prpl_info; | |
856 | |
857 prpl = gaim_find_prpl(gaim_account_get_protocol_id((GaimAccount *)account_iter->data)); | |
858 if (!prpl) | |
859 continue; | |
860 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); | |
861 | |
862 if (!strcmp(protocol_unescaped, prpl_info->list_icon((GaimAccount *)account_iter->data, NULL))) | |
863 accounts = g_list_prepend(accounts, account_iter->data); | |
864 } | |
865 g_free(protocol_unescaped); | |
866 | |
867 while ((username = g_dir_read_name(protocol_dir)) != NULL) { | |
868 gchar *username_path = g_build_filename(protocol_path, username, NULL); | |
869 GDir *username_dir; | |
870 const gchar *username_unescaped; | |
871 GaimAccount *account = NULL; | |
872 gchar *name; | |
873 | |
874 if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) { | |
875 g_free(username_path); | |
876 continue; | |
877 } | |
878 | |
879 /* Find the account for username in the list of accounts for protocol. */ | |
880 username_unescaped = gaim_unescape_filename(username); | |
881 for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) { | |
882 if (!strcmp(((GaimAccount *)account_iter->data)->username, username_unescaped)) { | |
883 account = account_iter->data; | |
884 break; | |
885 } | |
886 } | |
887 | |
888 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */ | |
889 while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) { | |
890 size_t len; | |
891 GaimLogSet *set; | |
892 | |
893 /* IMPORTANT: Always initialize all members of GaimLogSet */ | |
894 set = g_slice_new(GaimLogSet); | |
895 | |
896 /* Unescape the filename. */ | |
897 name = g_strdup(gaim_unescape_filename(name)); | |
898 | |
899 /* Get the (possibly new) length of name. */ | |
900 len = strlen(name); | |
901 | |
902 set->type = GAIM_LOG_IM; | |
903 set->name = name; | |
904 set->account = account; | |
905 /* set->buddy is always set below */ | |
906 set->normalized_name = g_strdup(gaim_normalize(account, name)); | |
907 | |
908 /* Chat for .chat or .system at the end of the name to determine the type. */ | |
909 if (len > 7) { | |
910 gchar *tmp = &name[len - 7]; | |
911 if (!strcmp(tmp, ".system")) { | |
912 set->type = GAIM_LOG_SYSTEM; | |
913 *tmp = '\0'; | |
914 } | |
915 } | |
916 if (len > 5) { | |
917 gchar *tmp = &name[len - 5]; | |
918 if (!strcmp(tmp, ".chat")) { | |
919 set->type = GAIM_LOG_CHAT; | |
920 *tmp = '\0'; | |
921 } | |
922 } | |
923 | |
924 /* Determine if this (account, name) combination exists as a buddy. */ | |
925 if (account != NULL) | |
926 set->buddy = (gaim_find_buddy(account, name) != NULL); | |
927 else | |
928 set->buddy = FALSE; | |
929 | |
930 log_add_log_set_to_hash(sets, set); | |
931 } | |
932 g_free(username_path); | |
933 g_dir_close(username_dir); | |
934 } | |
935 g_free(protocol_path); | |
936 g_dir_close(protocol_dir); | |
937 } | |
938 g_free(log_path); | |
939 g_dir_close(log_dir); | |
940 } | |
941 | |
942 #if 0 /* Maybe some other time. */ | |
943 /**************** | |
944 ** XML LOGGER ** | |
945 ****************/ | |
946 | |
947 static const char *str_from_msg_type (GaimMessageFlags type) | |
948 { | |
949 | |
950 return ""; | |
951 | |
952 } | |
953 | |
954 static void xml_logger_write(GaimLog *log, | |
955 GaimMessageFlags type, | |
956 const char *from, time_t time, const char *message) | |
957 { | |
958 char *xhtml = NULL; | |
959 | |
960 if (!log->logger_data) { | |
961 /* This log is new. We could use the loggers 'new' function, but | |
962 * creating a new file there would result in empty files in the case | |
963 * that you open a convo with someone, but don't say anything. | |
964 */ | |
965 struct tm *tm; | |
966 const char *tz; | |
967 const char *date; | |
968 char *dir = gaim_log_get_log_dir(log->type, log->name, log->account); | |
969 char *name; | |
970 char *filename; | |
971 | |
972 if (dir == NULL) | |
973 return; | |
974 | |
975 tm = localtime(&log->time); | |
976 tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm); | |
977 date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); | |
978 | |
979 name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); | |
980 | |
981 gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); | |
982 | |
983 filename = g_build_filename(dir, name, NULL); | |
984 g_free(dir); | |
985 g_free(name); | |
986 | |
987 log->logger_data = g_fopen(filename, "a"); | |
988 if (!log->logger_data) { | |
989 gaim_debug(GAIM_DEBUG_ERROR, "log", "Could not create log file %s\n", filename); | |
990 g_free(filename); | |
991 return; | |
992 } | |
993 g_free(filename); | |
994 fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n" | |
995 "<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n"); | |
996 | |
997 date = gaim_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time)); | |
998 fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n", | |
999 date, log->name, prpl); | |
1000 } | |
1001 | |
1002 /* if we can't write to the file, give up before we hurt ourselves */ | |
1003 if(!data->file) | |
1004 return; | |
1005 | |
1006 date = log_get_timestamp(log, time); | |
1007 | |
1008 gaim_markup_html_to_xhtml(message, &xhtml, NULL); | |
1009 if (from) | |
1010 fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n", | |
1011 str_from_msg_type(type), | |
1012 type & GAIM_MESSAGE_SEND ? "direction='sent'" : | |
1013 type & GAIM_MESSAGE_RECV ? "direction='received'" : "", | |
1014 from, date, xhtml); | |
1015 else | |
1016 fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n", | |
1017 str_from_msg_type(type), | |
1018 type & GAIM_MESSAGE_SEND ? "direction='sent'" : | |
1019 type & GAIM_MESSAGE_RECV ? "direction='received'" : "", | |
1020 date, xhtml): | |
1021 fflush(log->logger_data); | |
1022 g_free(date); | |
1023 g_free(xhtml); | |
1024 } | |
1025 | |
1026 static void xml_logger_finalize(GaimLog *log) | |
1027 { | |
1028 if (log->logger_data) { | |
1029 fprintf(log->logger_data, "</conversation>\n"); | |
1030 fclose(log->logger_data); | |
1031 log->logger_data = NULL; | |
1032 } | |
1033 } | |
1034 | |
1035 static GList *xml_logger_list(GaimLogType type, const char *sn, GaimAccount *account) | |
1036 { | |
1037 return gaim_log_common_lister(type, sn, account, ".xml", &xml_logger); | |
1038 } | |
1039 | |
1040 static GaimLogLogger xml_logger = { | |
1041 N_("XML"), "xml", | |
1042 NULL, | |
1043 xml_logger_write, | |
1044 xml_logger_finalize, | |
1045 xml_logger_list, | |
1046 NULL, | |
1047 NULL, | |
1048 NULL | |
1049 }; | |
1050 #endif | |
1051 | |
1052 /**************************** | |
1053 ** HTML LOGGER ************* | |
1054 ****************************/ | |
1055 | |
1056 static gsize html_logger_write(GaimLog *log, GaimMessageFlags type, | |
1057 const char *from, time_t time, const char *message) | |
1058 { | |
1059 char *msg_fixed; | |
1060 char *date; | |
1061 char *header; | |
1062 GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account)); | |
1063 GaimLogCommonLoggerData *data = log->logger_data; | |
1064 gsize written = 0; | |
1065 | |
1066 if(!data) { | |
1067 const char *prpl = | |
1068 GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); | |
1069 const char *date; | |
1070 gaim_log_common_writer(log, ".html"); | |
1071 | |
1072 data = log->logger_data; | |
1073 | |
1074 /* if we can't write to the file, give up before we hurt ourselves */ | |
1075 if(!data->file) | |
1076 return 0; | |
1077 | |
1078 date = gaim_date_format_full(localtime(&log->time)); | |
1079 | |
1080 written += fprintf(data->file, "<html><head>"); | |
1081 written += fprintf(data->file, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"); | |
1082 written += fprintf(data->file, "<title>"); | |
1083 if (log->type == GAIM_LOG_SYSTEM) | |
1084 header = g_strdup_printf("System log for account %s (%s) connected at %s", | |
1085 gaim_account_get_username(log->account), prpl, date); | |
1086 else | |
1087 header = g_strdup_printf("Conversation with %s at %s on %s (%s)", | |
1088 log->name, date, gaim_account_get_username(log->account), prpl); | |
1089 | |
1090 written += fprintf(data->file, "%s", header); | |
1091 written += fprintf(data->file, "</title></head><body>"); | |
1092 written += fprintf(data->file, "<h3>%s</h3>\n", header); | |
1093 g_free(header); | |
1094 } | |
1095 | |
1096 /* if we can't write to the file, give up before we hurt ourselves */ | |
1097 if(!data->file) | |
1098 return 0; | |
1099 | |
1100 gaim_markup_html_to_xhtml(message, &msg_fixed, NULL); | |
1101 date = log_get_timestamp(log, time); | |
1102 | |
1103 if(log->type == GAIM_LOG_SYSTEM){ | |
1104 written += fprintf(data->file, "---- %s @ %s ----<br/>\n", msg_fixed, date); | |
1105 } else { | |
1106 if (type & GAIM_MESSAGE_SYSTEM) | |
1107 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed); | |
1108 else if (type & GAIM_MESSAGE_ERROR) | |
1109 written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed); | |
1110 else if (type & GAIM_MESSAGE_WHISPER) | |
1111 written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", | |
1112 date, from, msg_fixed); | |
1113 else if (type & GAIM_MESSAGE_AUTO_RESP) { | |
1114 if (type & GAIM_MESSAGE_SEND) | |
1115 written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); | |
1116 else if (type & GAIM_MESSAGE_RECV) | |
1117 written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); | |
1118 } else if (type & GAIM_MESSAGE_RECV) { | |
1119 if(gaim_message_meify(msg_fixed, -1)) | |
1120 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", | |
1121 date, from, msg_fixed); | |
1122 else | |
1123 written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", | |
1124 date, from, msg_fixed); | |
1125 } else if (type & GAIM_MESSAGE_SEND) { | |
1126 if(gaim_message_meify(msg_fixed, -1)) | |
1127 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", | |
1128 date, from, msg_fixed); | |
1129 else | |
1130 written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", | |
1131 date, from, msg_fixed); | |
1132 } else { | |
1133 gaim_debug_error("log", "Unhandled message type."); | |
1134 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", | |
1135 date, from, msg_fixed); | |
1136 } | |
1137 } | |
1138 g_free(date); | |
1139 g_free(msg_fixed); | |
1140 fflush(data->file); | |
1141 | |
1142 return written; | |
1143 } | |
1144 | |
1145 static void html_logger_finalize(GaimLog *log) | |
1146 { | |
1147 GaimLogCommonLoggerData *data = log->logger_data; | |
1148 if (data) { | |
1149 if(data->file) { | |
1150 fprintf(data->file, "</body></html>\n"); | |
1151 fclose(data->file); | |
1152 } | |
1153 g_free(data->path); | |
1154 | |
1155 g_slice_free(GaimLogCommonLoggerData, data); | |
1156 } | |
1157 } | |
1158 | |
1159 static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account) | |
1160 { | |
1161 return gaim_log_common_lister(type, sn, account, ".html", html_logger); | |
1162 } | |
1163 | |
1164 static GList *html_logger_list_syslog(GaimAccount *account) | |
1165 { | |
1166 return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".html", html_logger); | |
1167 } | |
1168 | |
1169 static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags) | |
1170 { | |
1171 char *read; | |
1172 GaimLogCommonLoggerData *data = log->logger_data; | |
1173 *flags = GAIM_LOG_READ_NO_NEWLINE; | |
1174 if (!data || !data->path) | |
1175 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>")); | |
1176 if (g_file_get_contents(data->path, &read, NULL, NULL)) { | |
1177 char *minus_header = strchr(read, '\n'); | |
1178 | |
1179 if (!minus_header) | |
1180 return read; | |
1181 | |
1182 minus_header = g_strdup(minus_header + 1); | |
1183 g_free(read); | |
1184 | |
1185 return minus_header; | |
1186 } | |
1187 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path); | |
1188 } | |
1189 | |
1190 static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) | |
1191 { | |
1192 return gaim_log_common_total_sizer(type, name, account, ".html"); | |
1193 } | |
1194 | |
1195 | |
1196 /**************************** | |
1197 ** PLAIN TEXT LOGGER ******* | |
1198 ****************************/ | |
1199 | |
1200 static gsize txt_logger_write(GaimLog *log, | |
1201 GaimMessageFlags type, | |
1202 const char *from, time_t time, const char *message) | |
1203 { | |
1204 char *date; | |
1205 GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account)); | |
1206 GaimLogCommonLoggerData *data = log->logger_data; | |
1207 char *stripped = NULL; | |
1208 | |
1209 gsize written = 0; | |
1210 | |
1211 if (data == NULL) { | |
1212 /* This log is new. We could use the loggers 'new' function, but | |
1213 * creating a new file there would result in empty files in the case | |
1214 * that you open a convo with someone, but don't say anything. | |
1215 */ | |
1216 const char *prpl = | |
1217 GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); | |
1218 gaim_log_common_writer(log, ".txt"); | |
1219 | |
1220 data = log->logger_data; | |
1221 | |
1222 /* if we can't write to the file, give up before we hurt ourselves */ | |
1223 if(!data->file) | |
1224 return 0; | |
1225 | |
1226 if (log->type == GAIM_LOG_SYSTEM) | |
1227 written += fprintf(data->file, "System log for account %s (%s) connected at %s\n", | |
1228 gaim_account_get_username(log->account), prpl, | |
1229 gaim_date_format_full(localtime(&log->time))); | |
1230 else | |
1231 written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n", | |
1232 log->name, gaim_date_format_full(localtime(&log->time)), | |
1233 gaim_account_get_username(log->account), prpl); | |
1234 } | |
1235 | |
1236 /* if we can't write to the file, give up before we hurt ourselves */ | |
1237 if(!data->file) | |
1238 return 0; | |
1239 | |
1240 stripped = gaim_markup_strip_html(message); | |
1241 date = log_get_timestamp(log, time); | |
1242 | |
1243 if(log->type == GAIM_LOG_SYSTEM){ | |
1244 written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date); | |
1245 } else { | |
1246 if (type & GAIM_MESSAGE_SEND || | |
1247 type & GAIM_MESSAGE_RECV) { | |
1248 if (type & GAIM_MESSAGE_AUTO_RESP) { | |
1249 written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date, | |
1250 from, stripped); | |
1251 } else { | |
1252 if(gaim_message_meify(stripped, -1)) | |
1253 written += fprintf(data->file, "(%s) ***%s %s\n", date, from, | |
1254 stripped); | |
1255 else | |
1256 written += fprintf(data->file, "(%s) %s: %s\n", date, from, | |
1257 stripped); | |
1258 } | |
1259 } else if (type & GAIM_MESSAGE_SYSTEM) | |
1260 written += fprintf(data->file, "(%s) %s\n", date, stripped); | |
1261 else if (type & GAIM_MESSAGE_NO_LOG) { | |
1262 /* This shouldn't happen */ | |
1263 g_free(stripped); | |
1264 return written; | |
1265 } else if (type & GAIM_MESSAGE_WHISPER) | |
1266 written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped); | |
1267 else | |
1268 written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "", | |
1269 from ? ":" : "", stripped); | |
1270 } | |
1271 g_free(date); | |
1272 g_free(stripped); | |
1273 fflush(data->file); | |
1274 | |
1275 return written; | |
1276 } | |
1277 | |
1278 static void txt_logger_finalize(GaimLog *log) | |
1279 { | |
1280 GaimLogCommonLoggerData *data = log->logger_data; | |
1281 if (data) { | |
1282 if(data->file) | |
1283 fclose(data->file); | |
1284 g_free(data->path); | |
1285 | |
1286 g_slice_free(GaimLogCommonLoggerData, data); | |
1287 } | |
1288 } | |
1289 | |
1290 static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account) | |
1291 { | |
1292 return gaim_log_common_lister(type, sn, account, ".txt", txt_logger); | |
1293 } | |
1294 | |
1295 static GList *txt_logger_list_syslog(GaimAccount *account) | |
1296 { | |
1297 return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".txt", txt_logger); | |
1298 } | |
1299 | |
1300 static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags) | |
1301 { | |
1302 char *read, *minus_header, *minus_header2; | |
1303 GaimLogCommonLoggerData *data = log->logger_data; | |
1304 *flags = 0; | |
1305 if (!data || !data->path) | |
1306 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>")); | |
1307 if (g_file_get_contents(data->path, &read, NULL, NULL)) { | |
1308 minus_header = strchr(read, '\n'); | |
1309 if (!minus_header) | |
1310 minus_header = g_strdup(read); | |
1311 else | |
1312 minus_header = g_strdup(minus_header + 1); | |
1313 g_free(read); | |
1314 minus_header2 = g_markup_escape_text(minus_header, -1); | |
1315 g_free(minus_header); | |
1316 read = gaim_markup_linkify(minus_header2); | |
1317 g_free(minus_header2); | |
1318 return read; | |
1319 } | |
1320 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path); | |
1321 } | |
1322 | |
1323 static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) | |
1324 { | |
1325 return gaim_log_common_total_sizer(type, name, account, ".txt"); | |
1326 } | |
1327 | |
1328 | |
1329 /**************** | |
1330 * OLD LOGGER *** | |
1331 ****************/ | |
1332 | |
1333 /* The old logger doesn't write logs, only reads them. This is to include | |
1334 * old logs in the log viewer transparently. | |
1335 */ | |
1336 | |
1337 struct old_logger_data { | |
1338 GaimStringref *pathref; | |
1339 int offset; | |
1340 int length; | |
1341 }; | |
1342 | |
1343 static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account) | |
1344 { | |
1345 char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, sn)); | |
1346 char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL); | |
1347 GaimStringref *pathref = gaim_stringref_new(pathstr); | |
1348 struct stat st; | |
1349 time_t log_last_modified; | |
1350 FILE *index; | |
1351 FILE *file; | |
1352 int index_fd; | |
1353 char *index_tmp; | |
1354 char buf[BUF_LONG]; | |
1355 struct tm tm; | |
1356 char month[4]; | |
1357 struct old_logger_data *data = NULL; | |
1358 char *newlog; | |
1359 int logfound = 0; | |
1360 int lastoff = 0; | |
1361 int newlen; | |
1362 time_t lasttime = 0; | |
1363 | |
1364 GaimLog *log = NULL; | |
1365 GList *list = NULL; | |
1366 | |
1367 g_free(logfile); | |
1368 | |
1369 if (g_stat(gaim_stringref_value(pathref), &st)) | |
1370 { | |
1371 gaim_stringref_unref(pathref); | |
1372 g_free(pathstr); | |
1373 return NULL; | |
1374 } | |
1375 else | |
1376 log_last_modified = st.st_mtime; | |
1377 | |
1378 /* Change the .log extension to .idx */ | |
1379 strcpy(pathstr + strlen(pathstr) - 3, "idx"); | |
1380 | |
1381 if (g_stat(pathstr, &st) == 0) | |
1382 { | |
1383 if (st.st_mtime < log_last_modified) | |
1384 { | |
1385 gaim_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr); | |
1386 } | |
1387 else | |
1388 { | |
1389 /* The index file exists and is at least as new as the log, so open it. */ | |
1390 if (!(index = g_fopen(pathstr, "rb"))) | |
1391 { | |
1392 gaim_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n", | |
1393 pathstr, strerror(errno)); | |
1394 | |
1395 /* Fall through so that we'll parse the log file. */ | |
1396 } | |
1397 else | |
1398 { | |
1399 gaim_debug_info("log", "Using index: %s\n", pathstr); | |
1400 g_free(pathstr); | |
1401 while (fgets(buf, BUF_LONG, index)) | |
1402 { | |
1403 unsigned long idx_time; | |
1404 if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3) | |
1405 { | |
1406 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); | |
1407 log->logger = old_logger; | |
1408 log->time = (time_t)idx_time; | |
1409 | |
1410 /* IMPORTANT: Always set all members of struct old_logger_data */ | |
1411 data = g_slice_new(struct old_logger_data); | |
1412 | |
1413 data->pathref = gaim_stringref_ref(pathref); | |
1414 data->offset = lastoff; | |
1415 data->length = newlen; | |
1416 | |
1417 log->logger_data = data; | |
1418 list = g_list_prepend(list, log); | |
1419 } | |
1420 } | |
1421 fclose(index); | |
1422 | |
1423 return list; | |
1424 } | |
1425 } | |
1426 } | |
1427 | |
1428 if (!(file = g_fopen(gaim_stringref_value(pathref), "rb"))) { | |
1429 gaim_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n", | |
1430 gaim_stringref_value(pathref), strerror(errno)); | |
1431 gaim_stringref_unref(pathref); | |
1432 g_free(pathstr); | |
1433 return NULL; | |
1434 } | |
1435 | |
1436 index_tmp = g_strdup_printf("%s.XXXXXX", pathstr); | |
1437 if ((index_fd = g_mkstemp(index_tmp)) == -1) { | |
1438 gaim_debug_error("log", "Failed to open index temp file: %s\n", | |
1439 strerror(errno)); | |
1440 g_free(pathstr); | |
1441 g_free(index_tmp); | |
1442 index = NULL; | |
1443 } else { | |
1444 if ((index = fdopen(index_fd, "wb")) == NULL) | |
1445 { | |
1446 gaim_debug_error("log", "Failed to fdopen() index temp file: %s\n", | |
1447 strerror(errno)); | |
1448 close(index_fd); | |
1449 if (index_tmp != NULL) | |
1450 { | |
1451 g_unlink(index_tmp); | |
1452 g_free(index_tmp); | |
1453 } | |
1454 g_free(pathstr); | |
1455 } | |
1456 } | |
1457 | |
1458 while (fgets(buf, BUF_LONG, file)) { | |
1459 if ((newlog = strstr(buf, "---- New C"))) { | |
1460 int length; | |
1461 int offset; | |
1462 char convostart[32]; | |
1463 char *temp = strchr(buf, '@'); | |
1464 | |
1465 if (temp == NULL || strlen(temp) < 2) | |
1466 continue; | |
1467 | |
1468 temp++; | |
1469 length = strcspn(temp, "-"); | |
1470 if (length > 31) length = 31; | |
1471 | |
1472 offset = ftell(file); | |
1473 | |
1474 if (logfound) { | |
1475 newlen = offset - lastoff - length; | |
1476 if(strstr(buf, "----</H3><BR>")) { | |
1477 newlen -= | |
1478 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") + | |
1479 sizeof("----</H3><BR>") - 2; | |
1480 } else { | |
1481 newlen -= | |
1482 sizeof("---- New Conversation @ ") + sizeof("----") - 2; | |
1483 } | |
1484 | |
1485 if(strchr(buf, '\r')) | |
1486 newlen--; | |
1487 | |
1488 if (newlen != 0) { | |
1489 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); | |
1490 log->logger = old_logger; | |
1491 log->time = lasttime; | |
1492 | |
1493 /* IMPORTANT: Always set all members of struct old_logger_data */ | |
1494 data = g_slice_new(struct old_logger_data); | |
1495 | |
1496 data->pathref = gaim_stringref_ref(pathref); | |
1497 data->offset = lastoff; | |
1498 data->length = newlen; | |
1499 | |
1500 log->logger_data = data; | |
1501 list = g_list_prepend(list, log); | |
1502 | |
1503 /* XXX: There is apparently Is there a proper way to print a time_t? */ | |
1504 if (index != NULL) | |
1505 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time); | |
1506 } | |
1507 } | |
1508 | |
1509 logfound = 1; | |
1510 lastoff = offset; | |
1511 | |
1512 g_snprintf(convostart, length, "%s", temp); | |
1513 sscanf(convostart, "%*s %s %d %d:%d:%d %d", | |
1514 month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year); | |
1515 /* Ugly hack, in case current locale is not English */ | |
1516 if (strcmp(month, "Jan") == 0) { | |
1517 tm.tm_mon= 0; | |
1518 } else if (strcmp(month, "Feb") == 0) { | |
1519 tm.tm_mon = 1; | |
1520 } else if (strcmp(month, "Mar") == 0) { | |
1521 tm.tm_mon = 2; | |
1522 } else if (strcmp(month, "Apr") == 0) { | |
1523 tm.tm_mon = 3; | |
1524 } else if (strcmp(month, "May") == 0) { | |
1525 tm.tm_mon = 4; | |
1526 } else if (strcmp(month, "Jun") == 0) { | |
1527 tm.tm_mon = 5; | |
1528 } else if (strcmp(month, "Jul") == 0) { | |
1529 tm.tm_mon = 6; | |
1530 } else if (strcmp(month, "Aug") == 0) { | |
1531 tm.tm_mon = 7; | |
1532 } else if (strcmp(month, "Sep") == 0) { | |
1533 tm.tm_mon = 8; | |
1534 } else if (strcmp(month, "Oct") == 0) { | |
1535 tm.tm_mon = 9; | |
1536 } else if (strcmp(month, "Nov") == 0) { | |
1537 tm.tm_mon = 10; | |
1538 } else if (strcmp(month, "Dec") == 0) { | |
1539 tm.tm_mon = 11; | |
1540 } | |
1541 tm.tm_year -= 1900; | |
1542 lasttime = mktime(&tm); | |
1543 } | |
1544 } | |
1545 | |
1546 if (logfound) { | |
1547 if ((newlen = ftell(file) - lastoff) != 0) { | |
1548 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); | |
1549 log->logger = old_logger; | |
1550 log->time = lasttime; | |
1551 | |
1552 /* IMPORTANT: Always set all members of struct old_logger_data */ | |
1553 data = g_slice_new(struct old_logger_data); | |
1554 | |
1555 data->pathref = gaim_stringref_ref(pathref); | |
1556 data->offset = lastoff; | |
1557 data->length = newlen; | |
1558 | |
1559 log->logger_data = data; | |
1560 list = g_list_prepend(list, log); | |
1561 | |
1562 /* XXX: Is there a proper way to print a time_t? */ | |
1563 if (index != NULL) | |
1564 fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time); | |
1565 } | |
1566 } | |
1567 | |
1568 gaim_stringref_unref(pathref); | |
1569 fclose(file); | |
1570 if (index != NULL) | |
1571 { | |
1572 fclose(index); | |
1573 | |
1574 if (index_tmp == NULL) | |
1575 { | |
1576 g_free(pathstr); | |
1577 g_return_val_if_reached(list); | |
1578 } | |
1579 | |
1580 if (g_rename(index_tmp, pathstr)) | |
1581 { | |
1582 gaim_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n", | |
1583 index_tmp, pathstr, strerror(errno)); | |
1584 g_unlink(index_tmp); | |
1585 g_free(index_tmp); | |
1586 } | |
1587 else | |
1588 gaim_debug_info("log", "Built index: %s\n", pathstr); | |
1589 | |
1590 g_free(pathstr); | |
1591 } | |
1592 return list; | |
1593 } | |
1594 | |
1595 static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) | |
1596 { | |
1597 char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, name)); | |
1598 char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL); | |
1599 int size; | |
1600 struct stat st; | |
1601 | |
1602 if (g_stat(pathstr, &st)) | |
1603 size = 0; | |
1604 else | |
1605 size = st.st_size; | |
1606 | |
1607 g_free(logfile); | |
1608 g_free(pathstr); | |
1609 | |
1610 return size; | |
1611 } | |
1612 | |
1613 static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags) | |
1614 { | |
1615 struct old_logger_data *data = log->logger_data; | |
1616 FILE *file = g_fopen(gaim_stringref_value(data->pathref), "rb"); | |
1617 char *tmp, *read = g_malloc(data->length + 1); | |
1618 fseek(file, data->offset, SEEK_SET); | |
1619 fread(read, data->length, 1, file); | |
1620 fclose(file); | |
1621 read[data->length] = '\0'; | |
1622 *flags = 0; | |
1623 if(strstr(read, "<BR>")) | |
1624 *flags |= GAIM_LOG_READ_NO_NEWLINE; | |
1625 else { | |
1626 tmp = g_markup_escape_text(read, -1); | |
1627 g_free(read); | |
1628 read = gaim_markup_linkify(tmp); | |
1629 g_free(tmp); | |
1630 } | |
1631 return read; | |
1632 } | |
1633 | |
1634 static int old_logger_size (GaimLog *log) | |
1635 { | |
1636 struct old_logger_data *data = log->logger_data; | |
1637 return data ? data->length : 0; | |
1638 } | |
1639 | |
1640 static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets) | |
1641 { | |
1642 char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); | |
1643 GDir *log_dir = g_dir_open(log_path, 0, NULL); | |
1644 gchar *name; | |
1645 GaimBlistNode *gnode, *cnode, *bnode; | |
1646 | |
1647 g_free(log_path); | |
1648 if (log_dir == NULL) | |
1649 return; | |
1650 | |
1651 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */ | |
1652 while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) { | |
1653 size_t len; | |
1654 gchar *ext; | |
1655 GaimLogSet *set; | |
1656 gboolean found = FALSE; | |
1657 | |
1658 /* Unescape the filename. */ | |
1659 name = g_strdup(gaim_unescape_filename(name)); | |
1660 | |
1661 /* Get the (possibly new) length of name. */ | |
1662 len = strlen(name); | |
1663 | |
1664 if (len < 5) { | |
1665 g_free(name); | |
1666 continue; | |
1667 } | |
1668 | |
1669 /* Make sure we're dealing with a log file. */ | |
1670 ext = &name[len - 4]; | |
1671 if (strcmp(ext, ".log")) { | |
1672 g_free(name); | |
1673 continue; | |
1674 } | |
1675 | |
1676 /* IMPORTANT: Always set all members of GaimLogSet */ | |
1677 set = g_slice_new(GaimLogSet); | |
1678 | |
1679 /* Chat for .chat at the end of the name to determine the type. */ | |
1680 *ext = '\0'; | |
1681 set->type = GAIM_LOG_IM; | |
1682 if (len > 9) { | |
1683 char *tmp = &name[len - 9]; | |
1684 if (!strcmp(tmp, ".chat")) { | |
1685 set->type = GAIM_LOG_CHAT; | |
1686 *tmp = '\0'; | |
1687 } | |
1688 } | |
1689 | |
1690 set->name = set->normalized_name = name; | |
1691 | |
1692 /* Search the buddy list to find the account and to determine if this is a buddy. */ | |
1693 for (gnode = gaim_get_blist()->root; !found && gnode != NULL; gnode = gnode->next) | |
1694 { | |
1695 if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) | |
1696 continue; | |
1697 | |
1698 for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next) | |
1699 { | |
1700 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) | |
1701 continue; | |
1702 | |
1703 for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next) | |
1704 { | |
1705 GaimBuddy *buddy = (GaimBuddy *)bnode; | |
1706 | |
1707 if (!strcmp(buddy->name, name)) { | |
1708 set->account = buddy->account; | |
1709 set->buddy = TRUE; | |
1710 found = TRUE; | |
1711 } | |
1712 } | |
1713 } | |
1714 } | |
1715 | |
1716 if (!found) | |
1717 { | |
1718 set->account = NULL; | |
1719 set->buddy = FALSE; | |
1720 } | |
1721 | |
1722 cb(sets, set); | |
1723 } | |
1724 g_dir_close(log_dir); | |
1725 } | |
1726 | |
1727 static void old_logger_finalize(GaimLog *log) | |
1728 { | |
1729 struct old_logger_data *data = log->logger_data; | |
1730 gaim_stringref_unref(data->pathref); | |
1731 g_slice_free(struct old_logger_data, data); | |
1732 } |