libpurple/log.c

changeset 16238
33bf2fd32108
parent 13069
37b879f161f4
parent 16116
bccaa68c86ad
equal deleted inserted replaced
13071:b98e72d4089a 16238:33bf2fd32108
1 /**
2 * @file log.c Logging API
3 * @ingroup core
4 *
5 * purple
6 *
7 * Purple 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 PurpleLogLogger *html_logger;
39 static PurpleLogLogger *txt_logger;
40 static PurpleLogLogger *old_logger;
41
42 struct _purple_logsize_user {
43 char *name;
44 PurpleAccount *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(PurpleLog *log, PurpleMessageFlags type,
51 const char *from, time_t time, const char *message);
52 static void html_logger_finalize(PurpleLog *log);
53 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
54 static GList *html_logger_list_syslog(PurpleAccount *account);
55 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
56 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
57
58 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
59 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
60 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags);
61 static int old_logger_size (PurpleLog *log);
62 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets);
63 static void old_logger_finalize(PurpleLog *log);
64
65 static gsize txt_logger_write(PurpleLog *log,
66 PurpleMessageFlags type,
67 const char *from, time_t time, const char *message);
68 static void txt_logger_finalize(PurpleLog *log);
69 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
70 static GList *txt_logger_list_syslog(PurpleAccount *account);
71 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
72 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
73
74 /**************************************************************************
75 * PUBLIC LOGGING FUNCTIONS ***********************************************
76 **************************************************************************/
77
78 PurpleLog *purple_log_new(PurpleLogType type, const char *name, PurpleAccount *account,
79 PurpleConversation *conv, time_t time, const struct tm *tm)
80 {
81 PurpleLog *log;
82
83 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
84 log = g_slice_new(PurpleLog);
85 PURPLE_DBUS_REGISTER_POINTER(log, PurpleLog);
86
87 log->type = type;
88 log->name = g_strdup(purple_normalize(account, name));
89 log->account = account;
90 log->conv = conv;
91 log->time = time;
92 log->logger = purple_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 purple_log_free(PurpleLog *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 PURPLE_DBUS_UNREGISTER_POINTER(log);
140 g_slice_free(PurpleLog, log);
141 }
142
143 void purple_log_write(PurpleLog *log, PurpleMessageFlags type,
144 const char *from, time_t time, const char *message)
145 {
146 struct _purple_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 _purple_logsize_user, 1);
157
158 lu->name = g_strdup(purple_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 *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags)
173 {
174 PurpleLogReadFlags 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 purple_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 purple_log_get_size(PurpleLog *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 _purple_logsize_user_hash(struct _purple_logsize_user *lu)
194 {
195 return g_str_hash(lu->name);
196 }
197
198 static guint _purple_logsize_user_equal(struct _purple_logsize_user *lu1,
199 struct _purple_logsize_user *lu2)
200 {
201 return (lu1->account == lu2->account && (!strcmp(lu1->name, lu2->name)));
202 }
203
204 static void _purple_logsize_user_free_key(struct _purple_logsize_user *lu)
205 {
206 g_free(lu->name);
207 g_free(lu);
208 }
209
210 int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
211 {
212 gpointer ptrsize;
213 int size = 0;
214 GSList *n;
215 struct _purple_logsize_user *lu;
216
217 lu = g_new(struct _purple_logsize_user, 1);
218 lu->name = g_strdup(purple_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 PurpleLogLogger *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 PurpleLog *log = (PurpleLog*)(logs->data);
237 this_size += purple_log_get_size(log);
238 purple_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 gboolean purple_log_is_deletable(PurpleLog *log)
252 {
253 g_return_val_if_fail(log != NULL, FALSE);
254 g_return_val_if_fail(log->logger != NULL, FALSE);
255
256 if (log->logger->remove == NULL)
257 return FALSE;
258
259 if (log->logger->is_deletable != NULL)
260 return log->logger->is_deletable(log);
261
262 return TRUE;
263 }
264
265 gboolean purple_log_delete(PurpleLog *log)
266 {
267 g_return_val_if_fail(log != NULL, FALSE);
268 g_return_val_if_fail(log->logger != NULL, FALSE);
269
270 if (log->logger->remove != NULL)
271 return log->logger->remove(log);
272
273 return FALSE;
274 }
275
276 char *
277 purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account)
278 {
279 PurplePlugin *prpl;
280 PurplePluginProtocolInfo *prpl_info;
281 const char *prpl_name;
282 char *acct_name;
283 const char *target;
284 char *dir;
285
286 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
287 if (!prpl)
288 return NULL;
289 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
290 prpl_name = prpl_info->list_icon(account, NULL);
291
292 acct_name = g_strdup(purple_escape_filename(purple_normalize(account,
293 purple_account_get_username(account))));
294
295 if (type == PURPLE_LOG_CHAT) {
296 char *temp = g_strdup_printf("%s.chat", purple_normalize(account, name));
297 target = purple_escape_filename(temp);
298 g_free(temp);
299 } else if(type == PURPLE_LOG_SYSTEM) {
300 target = ".system";
301 } else {
302 target = purple_escape_filename(purple_normalize(account, name));
303 }
304
305 dir = g_build_filename(purple_user_dir(), "logs", prpl_name, acct_name, target, NULL);
306
307 g_free(acct_name);
308
309 return dir;
310 }
311
312 /****************************************************************************
313 * LOGGER FUNCTIONS *********************************************************
314 ****************************************************************************/
315
316 static PurpleLogLogger *current_logger = NULL;
317
318 static void logger_pref_cb(const char *name, PurplePrefType type,
319 gconstpointer value, gpointer data)
320 {
321 PurpleLogLogger *logger;
322 GSList *l = loggers;
323 while (l) {
324 logger = l->data;
325 if (!strcmp(logger->id, value)) {
326 purple_log_logger_set(logger);
327 return;
328 }
329 l = l->next;
330 }
331 purple_log_logger_set(txt_logger);
332 }
333
334
335 PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...)
336 {
337 #if 0
338 void(*create)(PurpleLog *),
339 gsize(*write)(PurpleLog *, PurpleMessageFlags, const char *, time_t, const char *),
340 void(*finalize)(PurpleLog *),
341 GList*(*list)(PurpleLogType type, const char*, PurpleAccount*),
342 char*(*read)(PurpleLog*, PurpleLogReadFlags*),
343 int(*size)(PurpleLog*),
344 int(*total_size)(PurpleLogType type, const char *name, PurpleAccount *account),
345 GList*(*list_syslog)(PurpleAccount *account),
346 void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets),
347 gboolean(*remove)(PurpleLog *log),
348 gboolean(*is_deletable)(PurpleLog *log))
349 {
350 #endif
351 PurpleLogLogger *logger;
352 va_list args;
353
354 g_return_val_if_fail(id != NULL, NULL);
355 g_return_val_if_fail(name != NULL, NULL);
356 g_return_val_if_fail(functions >= 1, NULL);
357
358 logger = g_new0(PurpleLogLogger, 1);
359 logger->id = g_strdup(id);
360 logger->name = g_strdup(name);
361
362 va_start(args, functions);
363
364 if (functions >= 1)
365 logger->create = va_arg(args, void *);
366 if (functions >= 2)
367 logger->write = va_arg(args, void *);
368 if (functions >= 3)
369 logger->finalize = va_arg(args, void *);
370 if (functions >= 4)
371 logger->list = va_arg(args, void *);
372 if (functions >= 5)
373 logger->read = va_arg(args, void *);
374 if (functions >= 6)
375 logger->size = va_arg(args, void *);
376 if (functions >= 7)
377 logger->total_size = va_arg(args, void *);
378 if (functions >= 8)
379 logger->list_syslog = va_arg(args, void *);
380 if (functions >= 9)
381 logger->get_log_sets = va_arg(args, void *);
382 if (functions >= 10)
383 logger->remove = va_arg(args, void *);
384 if (functions >= 11)
385 logger->is_deletable = va_arg(args, void *);
386
387 if (functions >= 12)
388 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id);
389
390 va_end(args);
391
392 return logger;
393 }
394
395 void purple_log_logger_free(PurpleLogLogger *logger)
396 {
397 g_free(logger->name);
398 g_free(logger->id);
399 g_free(logger);
400 }
401
402 void purple_log_logger_add (PurpleLogLogger *logger)
403 {
404 g_return_if_fail(logger);
405 if (g_slist_find(loggers, logger))
406 return;
407 loggers = g_slist_append(loggers, logger);
408 }
409
410 void purple_log_logger_remove (PurpleLogLogger *logger)
411 {
412 g_return_if_fail(logger);
413 loggers = g_slist_remove(loggers, logger);
414 }
415
416 void purple_log_logger_set (PurpleLogLogger *logger)
417 {
418 g_return_if_fail(logger);
419 current_logger = logger;
420 }
421
422 PurpleLogLogger *purple_log_logger_get()
423 {
424 return current_logger;
425 }
426
427 GList *purple_log_logger_get_options(void)
428 {
429 GSList *n;
430 GList *list = NULL;
431 PurpleLogLogger *data;
432
433 for (n = loggers; n; n = n->next) {
434 data = n->data;
435 if (!data->write)
436 continue;
437 list = g_list_append(list, data->name);
438 list = g_list_append(list, data->id);
439 }
440
441 return list;
442 }
443
444 gint purple_log_compare(gconstpointer y, gconstpointer z)
445 {
446 const PurpleLog *a = y;
447 const PurpleLog *b = z;
448
449 return b->time - a->time;
450 }
451
452 GList *purple_log_get_logs(PurpleLogType type, const char *name, PurpleAccount *account)
453 {
454 GList *logs = NULL;
455 GSList *n;
456 for (n = loggers; n; n = n->next) {
457 PurpleLogLogger *logger = n->data;
458 if (!logger->list)
459 continue;
460 logs = g_list_concat(logger->list(type, name, account), logs);
461 }
462
463 return g_list_sort(logs, purple_log_compare);
464 }
465
466 gint purple_log_set_compare(gconstpointer y, gconstpointer z)
467 {
468 const PurpleLogSet *a = y;
469 const PurpleLogSet *b = z;
470 gint ret = 0;
471
472 /* This logic seems weird at first...
473 * If either account is NULL, we pretend the accounts are
474 * equal. This allows us to detect duplicates that will
475 * exist if one logger knows the account and another
476 * doesn't. */
477 if (a->account != NULL && b->account != NULL) {
478 ret = strcmp(purple_account_get_username(a->account), purple_account_get_username(b->account));
479 if (ret != 0)
480 return ret;
481 }
482
483 ret = strcmp(a->normalized_name, b->normalized_name);
484 if (ret != 0)
485 return ret;
486
487 return (gint)b->type - (gint)a->type;
488 }
489
490 static guint
491 log_set_hash(gconstpointer key)
492 {
493 const PurpleLogSet *set = key;
494
495 /* The account isn't hashed because we need PurpleLogSets with NULL accounts
496 * to be found when we search by a PurpleLogSet that has a non-NULL account
497 * but the same type and name. */
498 return g_int_hash(&set->type) + g_str_hash(set->name);
499 }
500
501 static gboolean
502 log_set_equal(gconstpointer a, gconstpointer b)
503 {
504 /* I realize that the choices made for GList and GHashTable
505 * make sense for those data types, but I wish the comparison
506 * functions were compatible. */
507 return !purple_log_set_compare(a, b);
508 }
509
510 static void
511 log_add_log_set_to_hash(GHashTable *sets, PurpleLogSet *set)
512 {
513 PurpleLogSet *existing_set = g_hash_table_lookup(sets, set);
514
515 if (existing_set == NULL)
516 g_hash_table_insert(sets, set, set);
517 else if (existing_set->account == NULL && set->account != NULL)
518 g_hash_table_replace(sets, set, set);
519 else
520 purple_log_set_free(set);
521 }
522
523 GHashTable *purple_log_get_log_sets(void)
524 {
525 GSList *n;
526 GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal,
527 (GDestroyNotify)purple_log_set_free, NULL);
528
529 /* Get the log sets from all the loggers. */
530 for (n = loggers; n; n = n->next) {
531 PurpleLogLogger *logger = n->data;
532
533 if (!logger->get_log_sets)
534 continue;
535
536 logger->get_log_sets(log_add_log_set_to_hash, sets);
537 }
538
539 log_get_log_sets_common(sets);
540
541 /* Return the GHashTable of unique PurpleLogSets. */
542 return sets;
543 }
544
545 void purple_log_set_free(PurpleLogSet *set)
546 {
547 g_return_if_fail(set != NULL);
548
549 g_free(set->name);
550 if (set->normalized_name != set->name)
551 g_free(set->normalized_name);
552
553 g_slice_free(PurpleLogSet, set);
554 }
555
556 GList *purple_log_get_system_logs(PurpleAccount *account)
557 {
558 GList *logs = NULL;
559 GSList *n;
560 for (n = loggers; n; n = n->next) {
561 PurpleLogLogger *logger = n->data;
562 if (!logger->list_syslog)
563 continue;
564 logs = g_list_concat(logger->list_syslog(account), logs);
565 }
566
567 return g_list_sort(logs, purple_log_compare);
568 }
569
570 /****************************************************************************
571 * LOG SUBSYSTEM ************************************************************
572 ****************************************************************************/
573
574 void *
575 purple_log_get_handle(void)
576 {
577 static int handle;
578
579 return &handle;
580 }
581
582 void purple_log_init(void)
583 {
584 void *handle = purple_log_get_handle();
585
586 purple_prefs_add_none("/core/logging");
587 purple_prefs_add_bool("/core/logging/log_ims", FALSE);
588 purple_prefs_add_bool("/core/logging/log_chats", FALSE);
589 purple_prefs_add_bool("/core/logging/log_system", FALSE);
590
591 purple_prefs_add_string("/core/logging/format", "txt");
592
593 html_logger = purple_log_logger_new("html", _("HTML"), 11,
594 NULL,
595 html_logger_write,
596 html_logger_finalize,
597 html_logger_list,
598 html_logger_read,
599 purple_log_common_sizer,
600 html_logger_total_size,
601 html_logger_list_syslog,
602 NULL,
603 purple_log_common_deleter,
604 purple_log_common_is_deletable);
605 purple_log_logger_add(html_logger);
606
607 txt_logger = purple_log_logger_new("txt", _("Plain text"), 11,
608 NULL,
609 txt_logger_write,
610 txt_logger_finalize,
611 txt_logger_list,
612 txt_logger_read,
613 purple_log_common_sizer,
614 txt_logger_total_size,
615 txt_logger_list_syslog,
616 NULL,
617 purple_log_common_deleter,
618 purple_log_common_is_deletable);
619 purple_log_logger_add(txt_logger);
620
621 old_logger = purple_log_logger_new("old", _("Old flat format"), 9,
622 NULL,
623 NULL,
624 old_logger_finalize,
625 old_logger_list,
626 old_logger_read,
627 old_logger_size,
628 old_logger_total_size,
629 NULL,
630 old_logger_get_log_sets);
631 purple_log_logger_add(old_logger);
632
633 purple_signal_register(handle, "log-timestamp",
634 #if SIZEOF_TIME_T == 4
635 purple_marshal_POINTER__POINTER_INT_BOOLEAN,
636 #elif SIZEOF_TIME_T == 8
637 purple_marshal_POINTER__POINTER_INT64_BOOLEAN,
638 #else
639 #error Unknown size of time_t
640 #endif
641 purple_value_new(PURPLE_TYPE_STRING), 3,
642 purple_value_new(PURPLE_TYPE_SUBTYPE,
643 PURPLE_SUBTYPE_LOG),
644 #if SIZEOF_TIME_T == 4
645 purple_value_new(PURPLE_TYPE_INT),
646 #elif SIZEOF_TIME_T == 8
647 purple_value_new(PURPLE_TYPE_INT64),
648 #else
649 # error Unknown size of time_t
650 #endif
651 purple_value_new(PURPLE_TYPE_BOOLEAN));
652
653 purple_prefs_connect_callback(NULL, "/core/logging/format",
654 logger_pref_cb, NULL);
655 purple_prefs_trigger_callback("/core/logging/format");
656
657 logsize_users = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash,
658 (GEqualFunc)_purple_logsize_user_equal,
659 (GDestroyNotify)_purple_logsize_user_free_key, NULL);
660 }
661
662 void
663 purple_log_uninit(void)
664 {
665 purple_signals_unregister_by_instance(purple_log_get_handle());
666 }
667
668 /****************************************************************************
669 * LOGGERS ******************************************************************
670 ****************************************************************************/
671
672 static char *log_get_timestamp(PurpleLog *log, time_t when)
673 {
674 gboolean show_date;
675 char *date;
676 struct tm tm;
677
678 show_date = (log->type == PURPLE_LOG_SYSTEM) || (time(NULL) > when + 20*60);
679
680 date = purple_signal_emit_return_1(purple_log_get_handle(),
681 "log-timestamp",
682 log, when, show_date);
683 if (date != NULL)
684 return date;
685
686 tm = *(localtime(&when));
687 if (show_date)
688 return g_strdup(purple_date_format_long(&tm));
689 else
690 return g_strdup(purple_time_format(&tm));
691 }
692
693 void purple_log_common_writer(PurpleLog *log, const char *ext)
694 {
695 PurpleLogCommonLoggerData *data = log->logger_data;
696
697 if (data == NULL)
698 {
699 /* This log is new */
700 char *dir;
701 struct tm *tm;
702 const char *tz;
703 const char *date;
704 char *filename;
705 char *path;
706
707 dir = purple_log_get_log_dir(log->type, log->name, log->account);
708 if (dir == NULL)
709 return;
710
711 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
712
713 tm = localtime(&log->time);
714 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm));
715 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
716
717 filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
718
719 path = g_build_filename(dir, filename, NULL);
720 g_free(dir);
721 g_free(filename);
722
723 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
724
725 data->file = g_fopen(path, "a");
726 if (data->file == NULL)
727 {
728 purple_debug(PURPLE_DEBUG_ERROR, "log",
729 "Could not create log file %s\n", path);
730
731 if (log->conv != NULL)
732 purple_conversation_write(log->conv, NULL, _("Logging of this conversation failed."),
733 PURPLE_MESSAGE_ERROR, time(NULL));
734
735 g_free(path);
736 return;
737 }
738 g_free(path);
739 }
740 }
741
742 GList *purple_log_common_lister(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext, PurpleLogLogger *logger)
743 {
744 GDir *dir;
745 GList *list = NULL;
746 const char *filename;
747 char *path;
748
749 if(!account)
750 return NULL;
751
752 path = purple_log_get_log_dir(type, name, account);
753 if (path == NULL)
754 return NULL;
755
756 if (!(dir = g_dir_open(path, 0, NULL)))
757 {
758 g_free(path);
759 return NULL;
760 }
761
762 while ((filename = g_dir_read_name(dir)))
763 {
764 if (purple_str_has_suffix(filename, ext) &&
765 strlen(filename) >= (17 + strlen(ext)))
766 {
767 PurpleLog *log;
768 PurpleLogCommonLoggerData *data;
769 struct tm tm;
770 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
771 long tz_off;
772 const char *rest;
773 time_t stamp = purple_str_to_time(purple_unescape_filename(filename), FALSE, &tm, &tz_off, &rest);
774 char *end;
775
776 /* As zero is a valid offset, PURPLE_NO_TZ_OFF means no offset was
777 * provided. See util.h. Yes, it's kinda ugly. */
778 if (tz_off != PURPLE_NO_TZ_OFF)
779 tm.tm_gmtoff = tz_off - tm.tm_gmtoff;
780
781 if (rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL)
782 {
783 log = purple_log_new(type, name, account, NULL, stamp, NULL);
784 }
785 else
786 {
787 char *tmp = g_strndup(rest, end - rest);
788 tm.tm_zone = tmp;
789 log = purple_log_new(type, name, account, NULL, stamp, &tm);
790 g_free(tmp);
791 }
792 #else
793 time_t stamp = purple_str_to_time(filename, FALSE, &tm, NULL, NULL);
794
795 log = purple_log_new(type, name, account, NULL, stamp, &tm);
796 #endif
797
798 log->logger = logger;
799 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
800
801 data->path = g_build_filename(path, filename, NULL);
802 list = g_list_prepend(list, log);
803 }
804 }
805 g_dir_close(dir);
806 g_free(path);
807 return list;
808 }
809
810 int purple_log_common_total_sizer(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext)
811 {
812 GDir *dir;
813 int size = 0;
814 const char *filename;
815 char *path;
816
817 if(!account)
818 return 0;
819
820 path = purple_log_get_log_dir(type, name, account);
821 if (path == NULL)
822 return 0;
823
824 if (!(dir = g_dir_open(path, 0, NULL)))
825 {
826 g_free(path);
827 return 0;
828 }
829
830 while ((filename = g_dir_read_name(dir)))
831 {
832 if (purple_str_has_suffix(filename, ext) &&
833 strlen(filename) >= (17 + strlen(ext)))
834 {
835 char *tmp = g_build_filename(path, filename, NULL);
836 struct stat st;
837 if (g_stat(tmp, &st))
838 {
839 purple_debug_error("log", "Error stating log file: %s\n", tmp);
840 g_free(tmp);
841 continue;
842 }
843 g_free(tmp);
844 size += st.st_size;
845 }
846 }
847 g_dir_close(dir);
848 g_free(path);
849 return size;
850 }
851
852 int purple_log_common_sizer(PurpleLog *log)
853 {
854 struct stat st;
855 PurpleLogCommonLoggerData *data = log->logger_data;
856
857 g_return_val_if_fail(data != NULL, 0);
858
859 if (!data->path || g_stat(data->path, &st))
860 st.st_size = 0;
861
862 return st.st_size;
863 }
864
865 /* This will build log sets for all loggers that use the common logger
866 * functions because they use the same directory structure. */
867 static void log_get_log_sets_common(GHashTable *sets)
868 {
869 gchar *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
870 GDir *log_dir = g_dir_open(log_path, 0, NULL);
871 const gchar *protocol;
872
873 if (log_dir == NULL) {
874 g_free(log_path);
875 return;
876 }
877
878 while ((protocol = g_dir_read_name(log_dir)) != NULL) {
879 gchar *protocol_path = g_build_filename(log_path, protocol, NULL);
880 GDir *protocol_dir;
881 const gchar *username;
882 gchar *protocol_unescaped;
883 GList *account_iter;
884 GList *accounts = NULL;
885
886 if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
887 g_free(protocol_path);
888 continue;
889 }
890
891 /* Using g_strdup() to cover the one-in-a-million chance that a
892 * prpl's list_icon function uses purple_unescape_filename(). */
893 protocol_unescaped = g_strdup(purple_unescape_filename(protocol));
894
895 /* Find all the accounts for protocol. */
896 for (account_iter = purple_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) {
897 PurplePlugin *prpl;
898 PurplePluginProtocolInfo *prpl_info;
899
900 prpl = purple_find_prpl(purple_account_get_protocol_id((PurpleAccount *)account_iter->data));
901 if (!prpl)
902 continue;
903 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
904
905 if (!strcmp(protocol_unescaped, prpl_info->list_icon((PurpleAccount *)account_iter->data, NULL)))
906 accounts = g_list_prepend(accounts, account_iter->data);
907 }
908 g_free(protocol_unescaped);
909
910 while ((username = g_dir_read_name(protocol_dir)) != NULL) {
911 gchar *username_path = g_build_filename(protocol_path, username, NULL);
912 GDir *username_dir;
913 const gchar *username_unescaped;
914 PurpleAccount *account = NULL;
915 gchar *name;
916
917 if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
918 g_free(username_path);
919 continue;
920 }
921
922 /* Find the account for username in the list of accounts for protocol. */
923 username_unescaped = purple_unescape_filename(username);
924 for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) {
925 if (!strcmp(((PurpleAccount *)account_iter->data)->username, username_unescaped)) {
926 account = account_iter->data;
927 break;
928 }
929 }
930
931 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
932 while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) {
933 size_t len;
934 PurpleLogSet *set;
935
936 /* IMPORTANT: Always initialize all members of PurpleLogSet */
937 set = g_slice_new(PurpleLogSet);
938
939 /* Unescape the filename. */
940 name = g_strdup(purple_unescape_filename(name));
941
942 /* Get the (possibly new) length of name. */
943 len = strlen(name);
944
945 set->type = PURPLE_LOG_IM;
946 set->name = name;
947 set->account = account;
948 /* set->buddy is always set below */
949 set->normalized_name = g_strdup(purple_normalize(account, name));
950
951 /* Chat for .chat or .system at the end of the name to determine the type. */
952 if (len > 7) {
953 gchar *tmp = &name[len - 7];
954 if (!strcmp(tmp, ".system")) {
955 set->type = PURPLE_LOG_SYSTEM;
956 *tmp = '\0';
957 }
958 }
959 if (len > 5) {
960 gchar *tmp = &name[len - 5];
961 if (!strcmp(tmp, ".chat")) {
962 set->type = PURPLE_LOG_CHAT;
963 *tmp = '\0';
964 }
965 }
966
967 /* Determine if this (account, name) combination exists as a buddy. */
968 if (account != NULL)
969 set->buddy = (purple_find_buddy(account, name) != NULL);
970 else
971 set->buddy = FALSE;
972
973 log_add_log_set_to_hash(sets, set);
974 }
975 g_free(username_path);
976 g_dir_close(username_dir);
977 }
978 g_free(protocol_path);
979 g_dir_close(protocol_dir);
980 }
981 g_free(log_path);
982 g_dir_close(log_dir);
983 }
984
985 gboolean purple_log_common_deleter(PurpleLog *log)
986 {
987 PurpleLogCommonLoggerData *data;
988 int ret;
989
990 g_return_val_if_fail(log != NULL, FALSE);
991
992 data = log->logger_data;
993 if (data == NULL)
994 return FALSE;
995
996 if (data->path == NULL)
997 return FALSE;
998
999 ret = g_unlink(data->path);
1000 if (ret == 0)
1001 return TRUE;
1002 else if (ret == -1)
1003 {
1004 purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, strerror(errno));
1005 }
1006 else
1007 {
1008 /* I'm not sure that g_unlink() will ever return
1009 * something other than 0 or -1. -- rlaager */
1010 purple_debug_error("log", "Failed to delete: %s\n", data->path);
1011 }
1012
1013 return FALSE;
1014 }
1015
1016 gboolean purple_log_common_is_deletable(PurpleLog *log)
1017 {
1018 PurpleLogCommonLoggerData *data;
1019 #ifndef _WIN32
1020 gchar *dirname;
1021 #endif
1022
1023 g_return_val_if_fail(log != NULL, FALSE);
1024
1025 data = log->logger_data;
1026 if (data == NULL)
1027 return FALSE;
1028
1029 if (data->path == NULL)
1030 return FALSE;
1031
1032 #ifndef _WIN32
1033 dirname = g_path_get_dirname(data->path);
1034 if (g_access(dirname, W_OK) == 0)
1035 {
1036 g_free(dirname);
1037 return TRUE;
1038 }
1039 purple_debug_info("log", "access(%s) failed: %s\n", dirname, strerror(errno));
1040 g_free(dirname);
1041 #else
1042 /* Unless and until someone writes equivalent win32 code,
1043 * we'll assume the file is deletable. */
1044 return TRUE;
1045 #endif
1046
1047 return FALSE;
1048 }
1049
1050 static char *process_txt_log(char *txt, char *to_free)
1051 {
1052 char *tmp;
1053
1054 /* The to_free argument allows us to save a
1055 * g_strdup() in some cases. */
1056
1057 if (to_free == NULL)
1058 to_free = txt;
1059
1060 /* g_markup_escape_text requires valid UTF-8 */
1061 if (!g_utf8_validate(txt, -1, NULL))
1062 {
1063 tmp = purple_utf8_salvage(txt);
1064 g_free(to_free);
1065 to_free = txt = tmp;
1066 }
1067
1068 tmp = g_markup_escape_text(txt, -1);
1069 g_free(to_free);
1070 txt = purple_markup_linkify(tmp);
1071 g_free(tmp);
1072
1073 return txt;
1074 }
1075
1076 #if 0 /* Maybe some other time. */
1077 /****************
1078 ** XML LOGGER **
1079 ****************/
1080
1081 static const char *str_from_msg_type (PurpleMessageFlags type)
1082 {
1083
1084 return "";
1085
1086 }
1087
1088 static void xml_logger_write(PurpleLog *log,
1089 PurpleMessageFlags type,
1090 const char *from, time_t time, const char *message)
1091 {
1092 char *xhtml = NULL;
1093
1094 if (!log->logger_data) {
1095 /* This log is new. We could use the loggers 'new' function, but
1096 * creating a new file there would result in empty files in the case
1097 * that you open a convo with someone, but don't say anything.
1098 */
1099 struct tm *tm;
1100 const char *tz;
1101 const char *date;
1102 char *dir = purple_log_get_log_dir(log->type, log->name, log->account);
1103 char *name;
1104 char *filename;
1105
1106 if (dir == NULL)
1107 return;
1108
1109 tm = localtime(&log->time);
1110 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm);
1111 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
1112
1113 name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
1114
1115 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1116
1117 filename = g_build_filename(dir, name, NULL);
1118 g_free(dir);
1119 g_free(name);
1120
1121 log->logger_data = g_fopen(filename, "a");
1122 if (!log->logger_data) {
1123 purple_debug(PURPLE_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
1124 g_free(filename);
1125 return;
1126 }
1127 g_free(filename);
1128 fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n"
1129 "<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n");
1130
1131 date = purple_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time));
1132 fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n",
1133 date, log->name, prpl);
1134 }
1135
1136 /* if we can't write to the file, give up before we hurt ourselves */
1137 if(!data->file)
1138 return;
1139
1140 date = log_get_timestamp(log, time);
1141
1142 purple_markup_html_to_xhtml(message, &xhtml, NULL);
1143 if (from)
1144 fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n",
1145 str_from_msg_type(type),
1146 type & PURPLE_MESSAGE_SEND ? "direction='sent'" :
1147 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "",
1148 from, date, xhtml);
1149 else
1150 fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n",
1151 str_from_msg_type(type),
1152 type & PURPLE_MESSAGE_SEND ? "direction='sent'" :
1153 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "",
1154 date, xhtml):
1155 fflush(log->logger_data);
1156 g_free(date);
1157 g_free(xhtml);
1158 }
1159
1160 static void xml_logger_finalize(PurpleLog *log)
1161 {
1162 if (log->logger_data) {
1163 fprintf(log->logger_data, "</conversation>\n");
1164 fclose(log->logger_data);
1165 log->logger_data = NULL;
1166 }
1167 }
1168
1169 static GList *xml_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1170 {
1171 return purple_log_common_lister(type, sn, account, ".xml", &xml_logger);
1172 }
1173
1174 static PurpleLogLogger xml_logger = {
1175 N_("XML"), "xml",
1176 NULL,
1177 xml_logger_write,
1178 xml_logger_finalize,
1179 xml_logger_list,
1180 NULL,
1181 NULL,
1182 NULL
1183 };
1184 #endif
1185
1186 /****************************
1187 ** HTML LOGGER *************
1188 ****************************/
1189
1190 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
1191 const char *from, time_t time, const char *message)
1192 {
1193 char *msg_fixed;
1194 char *date;
1195 char *header;
1196 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
1197 PurpleLogCommonLoggerData *data = log->logger_data;
1198 gsize written = 0;
1199
1200 if(!data) {
1201 const char *prpl =
1202 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
1203 const char *date;
1204 purple_log_common_writer(log, ".html");
1205
1206 data = log->logger_data;
1207
1208 /* if we can't write to the file, give up before we hurt ourselves */
1209 if(!data->file)
1210 return 0;
1211
1212 date = purple_date_format_full(localtime(&log->time));
1213
1214 written += fprintf(data->file, "<html><head>");
1215 written += fprintf(data->file, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
1216 written += fprintf(data->file, "<title>");
1217 if (log->type == PURPLE_LOG_SYSTEM)
1218 header = g_strdup_printf("System log for account %s (%s) connected at %s",
1219 purple_account_get_username(log->account), prpl, date);
1220 else
1221 header = g_strdup_printf("Conversation with %s at %s on %s (%s)",
1222 log->name, date, purple_account_get_username(log->account), prpl);
1223
1224 written += fprintf(data->file, "%s", header);
1225 written += fprintf(data->file, "</title></head><body>");
1226 written += fprintf(data->file, "<h3>%s</h3>\n", header);
1227 g_free(header);
1228 }
1229
1230 /* if we can't write to the file, give up before we hurt ourselves */
1231 if(!data->file)
1232 return 0;
1233
1234 purple_markup_html_to_xhtml(message, &msg_fixed, NULL);
1235 date = log_get_timestamp(log, time);
1236
1237 if(log->type == PURPLE_LOG_SYSTEM){
1238 written += fprintf(data->file, "---- %s @ %s ----<br/>\n", msg_fixed, date);
1239 } else {
1240 if (type & PURPLE_MESSAGE_SYSTEM)
1241 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed);
1242 else if (type & PURPLE_MESSAGE_RAW)
1243 written += fprintf(data->file, "<font size=\"2\">(%s)</font> %s<br/>\n", date, msg_fixed);
1244 else if (type & PURPLE_MESSAGE_ERROR)
1245 written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed);
1246 else if (type & PURPLE_MESSAGE_WHISPER)
1247 written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1248 date, from, msg_fixed);
1249 else if (type & PURPLE_MESSAGE_AUTO_RESP) {
1250 if (type & PURPLE_MESSAGE_SEND)
1251 written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, from, msg_fixed);
1252 else if (type & PURPLE_MESSAGE_RECV)
1253 written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, from, msg_fixed);
1254 } else if (type & PURPLE_MESSAGE_RECV) {
1255 if(purple_message_meify(msg_fixed, -1))
1256 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1257 date, from, msg_fixed);
1258 else
1259 written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1260 date, from, msg_fixed);
1261 } else if (type & PURPLE_MESSAGE_SEND) {
1262 if(purple_message_meify(msg_fixed, -1))
1263 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1264 date, from, msg_fixed);
1265 else
1266 written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1267 date, from, msg_fixed);
1268 } else {
1269 purple_debug_error("log", "Unhandled message type.");
1270 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1271 date, from, msg_fixed);
1272 }
1273 }
1274 g_free(date);
1275 g_free(msg_fixed);
1276 fflush(data->file);
1277
1278 return written;
1279 }
1280
1281 static void html_logger_finalize(PurpleLog *log)
1282 {
1283 PurpleLogCommonLoggerData *data = log->logger_data;
1284 if (data) {
1285 if(data->file) {
1286 fprintf(data->file, "</body></html>\n");
1287 fclose(data->file);
1288 }
1289 g_free(data->path);
1290
1291 g_slice_free(PurpleLogCommonLoggerData, data);
1292 }
1293 }
1294
1295 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1296 {
1297 return purple_log_common_lister(type, sn, account, ".html", html_logger);
1298 }
1299
1300 static GList *html_logger_list_syslog(PurpleAccount *account)
1301 {
1302 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger);
1303 }
1304
1305 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
1306 {
1307 char *read;
1308 PurpleLogCommonLoggerData *data = log->logger_data;
1309 *flags = PURPLE_LOG_READ_NO_NEWLINE;
1310 if (!data || !data->path)
1311 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1312 if (g_file_get_contents(data->path, &read, NULL, NULL)) {
1313 char *minus_header = strchr(read, '\n');
1314
1315 if (!minus_header)
1316 return read;
1317
1318 minus_header = g_strdup(minus_header + 1);
1319 g_free(read);
1320
1321 return minus_header;
1322 }
1323 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
1324 }
1325
1326 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1327 {
1328 return purple_log_common_total_sizer(type, name, account, ".html");
1329 }
1330
1331
1332 /****************************
1333 ** PLAIN TEXT LOGGER *******
1334 ****************************/
1335
1336 static gsize txt_logger_write(PurpleLog *log,
1337 PurpleMessageFlags type,
1338 const char *from, time_t time, const char *message)
1339 {
1340 char *date;
1341 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
1342 PurpleLogCommonLoggerData *data = log->logger_data;
1343 char *stripped = NULL;
1344
1345 gsize written = 0;
1346
1347 if (data == NULL) {
1348 /* This log is new. We could use the loggers 'new' function, but
1349 * creating a new file there would result in empty files in the case
1350 * that you open a convo with someone, but don't say anything.
1351 */
1352 const char *prpl =
1353 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
1354 purple_log_common_writer(log, ".txt");
1355
1356 data = log->logger_data;
1357
1358 /* if we can't write to the file, give up before we hurt ourselves */
1359 if(!data->file)
1360 return 0;
1361
1362 if (log->type == PURPLE_LOG_SYSTEM)
1363 written += fprintf(data->file, "System log for account %s (%s) connected at %s\n",
1364 purple_account_get_username(log->account), prpl,
1365 purple_date_format_full(localtime(&log->time)));
1366 else
1367 written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n",
1368 log->name, purple_date_format_full(localtime(&log->time)),
1369 purple_account_get_username(log->account), prpl);
1370 }
1371
1372 /* if we can't write to the file, give up before we hurt ourselves */
1373 if(!data->file)
1374 return 0;
1375
1376 stripped = purple_markup_strip_html(message);
1377 date = log_get_timestamp(log, time);
1378
1379 if(log->type == PURPLE_LOG_SYSTEM){
1380 written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date);
1381 } else {
1382 if (type & PURPLE_MESSAGE_SEND ||
1383 type & PURPLE_MESSAGE_RECV) {
1384 if (type & PURPLE_MESSAGE_AUTO_RESP) {
1385 written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date,
1386 from, stripped);
1387 } else {
1388 if(purple_message_meify(stripped, -1))
1389 written += fprintf(data->file, "(%s) ***%s %s\n", date, from,
1390 stripped);
1391 else
1392 written += fprintf(data->file, "(%s) %s: %s\n", date, from,
1393 stripped);
1394 }
1395 } else if (type & PURPLE_MESSAGE_SYSTEM ||
1396 type & PURPLE_MESSAGE_ERROR ||
1397 type & PURPLE_MESSAGE_RAW)
1398 written += fprintf(data->file, "(%s) %s\n", date, stripped);
1399 else if (type & PURPLE_MESSAGE_NO_LOG) {
1400 /* This shouldn't happen */
1401 g_free(stripped);
1402 return written;
1403 } else if (type & PURPLE_MESSAGE_WHISPER)
1404 written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped);
1405 else
1406 written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "",
1407 from ? ":" : "", stripped);
1408 }
1409 g_free(date);
1410 g_free(stripped);
1411 fflush(data->file);
1412
1413 return written;
1414 }
1415
1416 static void txt_logger_finalize(PurpleLog *log)
1417 {
1418 PurpleLogCommonLoggerData *data = log->logger_data;
1419 if (data) {
1420 if(data->file)
1421 fclose(data->file);
1422 g_free(data->path);
1423
1424 g_slice_free(PurpleLogCommonLoggerData, data);
1425 }
1426 }
1427
1428 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1429 {
1430 return purple_log_common_lister(type, sn, account, ".txt", txt_logger);
1431 }
1432
1433 static GList *txt_logger_list_syslog(PurpleAccount *account)
1434 {
1435 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger);
1436 }
1437
1438 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
1439 {
1440 char *read, *minus_header;
1441 PurpleLogCommonLoggerData *data = log->logger_data;
1442 *flags = 0;
1443 if (!data || !data->path)
1444 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1445 if (g_file_get_contents(data->path, &read, NULL, NULL)) {
1446 minus_header = strchr(read, '\n');
1447
1448 if (minus_header)
1449 return process_txt_log(minus_header + 1, read);
1450 else
1451 return process_txt_log(read, NULL);
1452 }
1453 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
1454 }
1455
1456 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1457 {
1458 return purple_log_common_total_sizer(type, name, account, ".txt");
1459 }
1460
1461
1462 /****************
1463 * OLD LOGGER ***
1464 ****************/
1465
1466 /* The old logger doesn't write logs, only reads them. This is to include
1467 * old logs in the log viewer transparently.
1468 */
1469
1470 struct old_logger_data {
1471 PurpleStringref *pathref;
1472 int offset;
1473 int length;
1474 };
1475
1476 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1477 {
1478 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, sn));
1479 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
1480 PurpleStringref *pathref = purple_stringref_new(pathstr);
1481 struct stat st;
1482 time_t log_last_modified;
1483 FILE *index;
1484 FILE *file;
1485 int index_fd;
1486 char *index_tmp;
1487 char buf[BUF_LONG];
1488 struct tm tm;
1489 char month[4];
1490 struct old_logger_data *data = NULL;
1491 char *newlog;
1492 int logfound = 0;
1493 int lastoff = 0;
1494 int newlen;
1495 time_t lasttime = 0;
1496
1497 PurpleLog *log = NULL;
1498 GList *list = NULL;
1499
1500 g_free(logfile);
1501
1502 if (g_stat(purple_stringref_value(pathref), &st))
1503 {
1504 purple_stringref_unref(pathref);
1505 g_free(pathstr);
1506 return NULL;
1507 }
1508 else
1509 log_last_modified = st.st_mtime;
1510
1511 /* Change the .log extension to .idx */
1512 strcpy(pathstr + strlen(pathstr) - 3, "idx");
1513
1514 if (g_stat(pathstr, &st) == 0)
1515 {
1516 if (st.st_mtime < log_last_modified)
1517 {
1518 purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr);
1519 }
1520 else
1521 {
1522 /* The index file exists and is at least as new as the log, so open it. */
1523 if (!(index = g_fopen(pathstr, "rb")))
1524 {
1525 purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
1526 pathstr, strerror(errno));
1527
1528 /* Fall through so that we'll parse the log file. */
1529 }
1530 else
1531 {
1532 purple_debug_info("log", "Using index: %s\n", pathstr);
1533 g_free(pathstr);
1534 while (fgets(buf, BUF_LONG, index))
1535 {
1536 unsigned long idx_time;
1537 if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3)
1538 {
1539 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1540 log->logger = old_logger;
1541 log->time = (time_t)idx_time;
1542
1543 /* IMPORTANT: Always set all members of struct old_logger_data */
1544 data = g_slice_new(struct old_logger_data);
1545
1546 data->pathref = purple_stringref_ref(pathref);
1547 data->offset = lastoff;
1548 data->length = newlen;
1549
1550 log->logger_data = data;
1551 list = g_list_prepend(list, log);
1552 }
1553 }
1554 fclose(index);
1555
1556 return list;
1557 }
1558 }
1559 }
1560
1561 if (!(file = g_fopen(purple_stringref_value(pathref), "rb"))) {
1562 purple_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n",
1563 purple_stringref_value(pathref), strerror(errno));
1564 purple_stringref_unref(pathref);
1565 g_free(pathstr);
1566 return NULL;
1567 }
1568
1569 index_tmp = g_strdup_printf("%s.XXXXXX", pathstr);
1570 if ((index_fd = g_mkstemp(index_tmp)) == -1) {
1571 purple_debug_error("log", "Failed to open index temp file: %s\n",
1572 strerror(errno));
1573 g_free(pathstr);
1574 g_free(index_tmp);
1575 index = NULL;
1576 } else {
1577 if ((index = fdopen(index_fd, "wb")) == NULL)
1578 {
1579 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1580 strerror(errno));
1581 close(index_fd);
1582 if (index_tmp != NULL)
1583 {
1584 g_unlink(index_tmp);
1585 g_free(index_tmp);
1586 }
1587 g_free(pathstr);
1588 }
1589 }
1590
1591 while (fgets(buf, BUF_LONG, file)) {
1592 if ((newlog = strstr(buf, "---- New C"))) {
1593 int length;
1594 int offset;
1595 char convostart[32];
1596 char *temp = strchr(buf, '@');
1597
1598 if (temp == NULL || strlen(temp) < 2)
1599 continue;
1600
1601 temp++;
1602 length = strcspn(temp, "-");
1603 if (length > 31) length = 31;
1604
1605 offset = ftell(file);
1606
1607 if (logfound) {
1608 newlen = offset - lastoff - length;
1609 if(strstr(buf, "----</H3><BR>")) {
1610 newlen -=
1611 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1612 sizeof("----</H3><BR>") - 2;
1613 } else {
1614 newlen -=
1615 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1616 }
1617
1618 if(strchr(buf, '\r'))
1619 newlen--;
1620
1621 if (newlen != 0) {
1622 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1623 log->logger = old_logger;
1624 log->time = lasttime;
1625
1626 /* IMPORTANT: Always set all members of struct old_logger_data */
1627 data = g_slice_new(struct old_logger_data);
1628
1629 data->pathref = purple_stringref_ref(pathref);
1630 data->offset = lastoff;
1631 data->length = newlen;
1632
1633 log->logger_data = data;
1634 list = g_list_prepend(list, log);
1635
1636 /* XXX: There is apparently Is there a proper way to print a time_t? */
1637 if (index != NULL)
1638 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time);
1639 }
1640 }
1641
1642 logfound = 1;
1643 lastoff = offset;
1644
1645 g_snprintf(convostart, length, "%s", temp);
1646 sscanf(convostart, "%*s %s %d %d:%d:%d %d",
1647 month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year);
1648 /* Ugly hack, in case current locale is not English */
1649 if (strcmp(month, "Jan") == 0) {
1650 tm.tm_mon= 0;
1651 } else if (strcmp(month, "Feb") == 0) {
1652 tm.tm_mon = 1;
1653 } else if (strcmp(month, "Mar") == 0) {
1654 tm.tm_mon = 2;
1655 } else if (strcmp(month, "Apr") == 0) {
1656 tm.tm_mon = 3;
1657 } else if (strcmp(month, "May") == 0) {
1658 tm.tm_mon = 4;
1659 } else if (strcmp(month, "Jun") == 0) {
1660 tm.tm_mon = 5;
1661 } else if (strcmp(month, "Jul") == 0) {
1662 tm.tm_mon = 6;
1663 } else if (strcmp(month, "Aug") == 0) {
1664 tm.tm_mon = 7;
1665 } else if (strcmp(month, "Sep") == 0) {
1666 tm.tm_mon = 8;
1667 } else if (strcmp(month, "Oct") == 0) {
1668 tm.tm_mon = 9;
1669 } else if (strcmp(month, "Nov") == 0) {
1670 tm.tm_mon = 10;
1671 } else if (strcmp(month, "Dec") == 0) {
1672 tm.tm_mon = 11;
1673 }
1674 tm.tm_year -= 1900;
1675 lasttime = mktime(&tm);
1676 }
1677 }
1678
1679 if (logfound) {
1680 if ((newlen = ftell(file) - lastoff) != 0) {
1681 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1682 log->logger = old_logger;
1683 log->time = lasttime;
1684
1685 /* IMPORTANT: Always set all members of struct old_logger_data */
1686 data = g_slice_new(struct old_logger_data);
1687
1688 data->pathref = purple_stringref_ref(pathref);
1689 data->offset = lastoff;
1690 data->length = newlen;
1691
1692 log->logger_data = data;
1693 list = g_list_prepend(list, log);
1694
1695 /* XXX: Is there a proper way to print a time_t? */
1696 if (index != NULL)
1697 fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time);
1698 }
1699 }
1700
1701 purple_stringref_unref(pathref);
1702 fclose(file);
1703 if (index != NULL)
1704 {
1705 fclose(index);
1706
1707 if (index_tmp == NULL)
1708 {
1709 g_free(pathstr);
1710 g_return_val_if_reached(list);
1711 }
1712
1713 if (g_rename(index_tmp, pathstr))
1714 {
1715 purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
1716 index_tmp, pathstr, strerror(errno));
1717 g_unlink(index_tmp);
1718 g_free(index_tmp);
1719 }
1720 else
1721 purple_debug_info("log", "Built index: %s\n", pathstr);
1722
1723 g_free(pathstr);
1724 }
1725 return list;
1726 }
1727
1728 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1729 {
1730 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, name));
1731 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
1732 int size;
1733 struct stat st;
1734
1735 if (g_stat(pathstr, &st))
1736 size = 0;
1737 else
1738 size = st.st_size;
1739
1740 g_free(logfile);
1741 g_free(pathstr);
1742
1743 return size;
1744 }
1745
1746 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
1747 {
1748 struct old_logger_data *data = log->logger_data;
1749 FILE *file = g_fopen(purple_stringref_value(data->pathref), "rb");
1750 char *read = g_malloc(data->length + 1);
1751 fseek(file, data->offset, SEEK_SET);
1752 fread(read, data->length, 1, file);
1753 fclose(file);
1754 read[data->length] = '\0';
1755 *flags = 0;
1756 if (strstr(read, "<BR>"))
1757 {
1758 *flags |= PURPLE_LOG_READ_NO_NEWLINE;
1759 return read;
1760 }
1761
1762 return process_txt_log(read, NULL);
1763 }
1764
1765 static int old_logger_size (PurpleLog *log)
1766 {
1767 struct old_logger_data *data = log->logger_data;
1768 return data ? data->length : 0;
1769 }
1770
1771 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets)
1772 {
1773 char *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
1774 GDir *log_dir = g_dir_open(log_path, 0, NULL);
1775 gchar *name;
1776 PurpleBlistNode *gnode, *cnode, *bnode;
1777
1778 g_free(log_path);
1779 if (log_dir == NULL)
1780 return;
1781
1782 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
1783 while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) {
1784 size_t len;
1785 gchar *ext;
1786 PurpleLogSet *set;
1787 gboolean found = FALSE;
1788
1789 /* Unescape the filename. */
1790 name = g_strdup(purple_unescape_filename(name));
1791
1792 /* Get the (possibly new) length of name. */
1793 len = strlen(name);
1794
1795 if (len < 5) {
1796 g_free(name);
1797 continue;
1798 }
1799
1800 /* Make sure we're dealing with a log file. */
1801 ext = &name[len - 4];
1802 if (strcmp(ext, ".log")) {
1803 g_free(name);
1804 continue;
1805 }
1806
1807 /* IMPORTANT: Always set all members of PurpleLogSet */
1808 set = g_slice_new(PurpleLogSet);
1809
1810 /* Chat for .chat at the end of the name to determine the type. */
1811 *ext = '\0';
1812 set->type = PURPLE_LOG_IM;
1813 if (len > 9) {
1814 char *tmp = &name[len - 9];
1815 if (!strcmp(tmp, ".chat")) {
1816 set->type = PURPLE_LOG_CHAT;
1817 *tmp = '\0';
1818 }
1819 }
1820
1821 set->name = set->normalized_name = name;
1822
1823 /* Search the buddy list to find the account and to determine if this is a buddy. */
1824 for (gnode = purple_get_blist()->root; !found && gnode != NULL; gnode = gnode->next)
1825 {
1826 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
1827 continue;
1828
1829 for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next)
1830 {
1831 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
1832 continue;
1833
1834 for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next)
1835 {
1836 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
1837
1838 if (!strcmp(buddy->name, name)) {
1839 set->account = buddy->account;
1840 set->buddy = TRUE;
1841 found = TRUE;
1842 }
1843 }
1844 }
1845 }
1846
1847 if (!found)
1848 {
1849 set->account = NULL;
1850 set->buddy = FALSE;
1851 }
1852
1853 cb(sets, set);
1854 }
1855 g_dir_close(log_dir);
1856 }
1857
1858 static void old_logger_finalize(PurpleLog *log)
1859 {
1860 struct old_logger_data *data = log->logger_data;
1861 purple_stringref_unref(data->pathref);
1862 g_slice_free(struct old_logger_data, data);
1863 }

mercurial