Move the "Change status to" menu to be beside the checkbox controlling it.
[pidgin-git.git] / libpurple / log.c
blob79106bfd8accbf434a5d9b7cf76a773526b5100c
1 /**
2 * @file log.c Logging API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
28 #include "account.h"
29 #include "dbus-maybe.h"
30 #include "debug.h"
31 #include "internal.h"
32 #include "log.h"
33 #include "prefs.h"
34 #include "util.h"
35 #include "stringref.h"
36 #include "imgstore.h"
37 #include "time.h"
39 static GSList *loggers = NULL;
41 static PurpleLogLogger *html_logger;
42 static PurpleLogLogger *txt_logger;
43 static PurpleLogLogger *old_logger;
45 struct _purple_logsize_user {
46 char *name;
47 PurpleAccount *account;
49 static GHashTable *logsize_users = NULL;
50 static GHashTable *logsize_users_decayed = NULL;
52 static void log_get_log_sets_common(GHashTable *sets);
54 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
55 const char *from, time_t time, const char *message);
56 static void html_logger_finalize(PurpleLog *log);
57 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
58 static GList *html_logger_list_syslog(PurpleAccount *account);
59 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
60 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
62 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
63 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
64 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags);
65 static int old_logger_size (PurpleLog *log);
66 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets);
67 static void old_logger_finalize(PurpleLog *log);
69 static gsize txt_logger_write(PurpleLog *log,
70 PurpleMessageFlags type,
71 const char *from, time_t time, const char *message);
72 static void txt_logger_finalize(PurpleLog *log);
73 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
74 static GList *txt_logger_list_syslog(PurpleAccount *account);
75 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
76 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
78 /**************************************************************************
79 * PUBLIC LOGGING FUNCTIONS ***********************************************
80 **************************************************************************/
82 PurpleLog *purple_log_new(PurpleLogType type, const char *name, PurpleAccount *account,
83 PurpleConversation *conv, time_t time, const struct tm *tm)
85 PurpleLog *log;
87 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
88 log = g_slice_new(PurpleLog);
89 PURPLE_DBUS_REGISTER_POINTER(log, PurpleLog);
91 log->type = type;
92 log->name = g_strdup(purple_normalize(account, name));
93 log->account = account;
94 log->conv = conv;
95 log->time = time;
96 log->logger = purple_log_logger_get();
97 log->logger_data = NULL;
99 if (tm == NULL)
100 log->tm = NULL;
101 else
103 /* There's no need to zero this as we immediately do a direct copy. */
104 log->tm = g_slice_new(struct tm);
106 *(log->tm) = *tm;
108 #ifdef HAVE_STRUCT_TM_TM_ZONE
109 /* XXX: This is so wrong... */
110 if (log->tm->tm_zone != NULL)
112 char *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL);
113 if (tmp != NULL)
114 log->tm->tm_zone = tmp;
115 else
116 /* Just shove the UTF-8 bytes in and hope... */
117 log->tm->tm_zone = g_strdup(log->tm->tm_zone);
119 #endif
122 if (log->logger && log->logger->create)
123 log->logger->create(log);
124 return log;
127 void purple_log_free(PurpleLog *log)
129 g_return_if_fail(log);
130 if (log->logger && log->logger->finalize)
131 log->logger->finalize(log);
132 g_free(log->name);
134 if (log->tm != NULL)
136 #ifdef HAVE_STRUCT_TM_TM_ZONE
137 /* XXX: This is so wrong... */
138 g_free((char *)log->tm->tm_zone);
139 #endif
140 g_slice_free(struct tm, log->tm);
143 PURPLE_DBUS_UNREGISTER_POINTER(log);
144 g_slice_free(PurpleLog, log);
147 void purple_log_write(PurpleLog *log, PurpleMessageFlags type,
148 const char *from, time_t time, const char *message)
150 struct _purple_logsize_user *lu;
151 gsize written, total = 0;
152 gpointer ptrsize;
154 g_return_if_fail(log);
155 g_return_if_fail(log->logger);
156 g_return_if_fail(log->logger->write);
158 written = (log->logger->write)(log, type, from, time, message);
160 lu = g_new(struct _purple_logsize_user, 1);
162 lu->name = g_strdup(purple_normalize(log->account, log->name));
163 lu->account = log->account;
165 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
166 char *tmp = lu->name;
168 total = GPOINTER_TO_INT(ptrsize);
169 total += written;
170 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total));
172 /* The hash table takes ownership of lu, so create a new one
173 * for the logsize_users_decayed check below. */
174 lu = g_new(struct _purple_logsize_user, 1);
175 lu->name = g_strdup(tmp);
176 lu->account = log->account;
179 if(g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrsize)) {
180 total = GPOINTER_TO_INT(ptrsize);
181 total += written;
182 g_hash_table_replace(logsize_users_decayed, lu, GINT_TO_POINTER(total));
183 } else {
184 g_free(lu->name);
185 g_free(lu);
189 char *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags)
191 PurpleLogReadFlags mflags;
192 g_return_val_if_fail(log && log->logger, NULL);
193 if (log->logger->read) {
194 char *ret = (log->logger->read)(log, flags ? flags : &mflags);
195 purple_str_strip_char(ret, '\r');
196 return ret;
198 return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>"));
201 int purple_log_get_size(PurpleLog *log)
203 g_return_val_if_fail(log && log->logger, 0);
205 if (log->logger->size)
206 return log->logger->size(log);
207 return 0;
210 static guint _purple_logsize_user_hash(struct _purple_logsize_user *lu)
212 return g_str_hash(lu->name);
215 static guint _purple_logsize_user_equal(struct _purple_logsize_user *lu1,
216 struct _purple_logsize_user *lu2)
218 return (lu1->account == lu2->account && purple_strequal(lu1->name, lu2->name));
221 static void _purple_logsize_user_free_key(struct _purple_logsize_user *lu)
223 g_free(lu->name);
224 g_free(lu);
227 int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
229 gpointer ptrsize;
230 int size = 0;
231 GSList *n;
232 struct _purple_logsize_user *lu;
234 lu = g_new(struct _purple_logsize_user, 1);
235 lu->name = g_strdup(purple_normalize(account, name));
236 lu->account = account;
238 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
239 size = GPOINTER_TO_INT(ptrsize);
240 g_free(lu->name);
241 g_free(lu);
242 } else {
243 for (n = loggers; n; n = n->next) {
244 PurpleLogLogger *logger = n->data;
246 if(logger->total_size){
247 size += (logger->total_size)(type, name, account);
248 } else if(logger->list) {
249 GList *logs = (logger->list)(type, name, account);
250 int this_size = 0;
252 while (logs) {
253 PurpleLog *log = (PurpleLog*)(logs->data);
254 this_size += purple_log_get_size(log);
255 purple_log_free(log);
256 logs = g_list_delete_link(logs, logs);
259 size += this_size;
263 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
265 return size;
268 gint purple_log_get_activity_score(PurpleLogType type, const char *name, PurpleAccount *account)
270 gpointer ptrscore;
271 int score;
272 GSList *n;
273 struct _purple_logsize_user *lu;
274 time_t now;
275 time(&now);
277 lu = g_new(struct _purple_logsize_user, 1);
278 lu->name = g_strdup(purple_normalize(account, name));
279 lu->account = account;
281 if(g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrscore)) {
282 score = GPOINTER_TO_INT(ptrscore);
283 g_free(lu->name);
284 g_free(lu);
285 } else {
286 double score_double = 0.0;
287 for (n = loggers; n; n = n->next) {
288 PurpleLogLogger *logger = n->data;
290 if(logger->list) {
291 GList *logs = (logger->list)(type, name, account);
293 while (logs) {
294 PurpleLog *log = (PurpleLog*)(logs->data);
295 /* Activity score counts bytes in the log, exponentially
296 decayed with a half-life of 14 days. */
297 score_double += purple_log_get_size(log) *
298 pow(0.5, difftime(now, log->time)/1209600.0);
299 purple_log_free(log);
300 logs = g_list_delete_link(logs, logs);
305 score = (gint)score_double;
306 g_hash_table_replace(logsize_users_decayed, lu, GINT_TO_POINTER(score));
308 return score;
311 gboolean purple_log_is_deletable(PurpleLog *log)
313 g_return_val_if_fail(log != NULL, FALSE);
314 g_return_val_if_fail(log->logger != NULL, FALSE);
316 if (log->logger->remove == NULL)
317 return FALSE;
319 if (log->logger->is_deletable != NULL)
320 return log->logger->is_deletable(log);
322 return TRUE;
325 gboolean purple_log_delete(PurpleLog *log)
327 g_return_val_if_fail(log != NULL, FALSE);
328 g_return_val_if_fail(log->logger != NULL, FALSE);
330 if (log->logger->remove != NULL)
331 return log->logger->remove(log);
333 return FALSE;
336 char *
337 purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account)
339 PurplePlugin *prpl;
340 PurplePluginProtocolInfo *prpl_info;
341 const char *prpl_name;
342 char *acct_name;
343 const char *target;
344 char *dir;
346 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
347 if (!prpl)
348 return NULL;
349 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
350 prpl_name = prpl_info->list_icon(account, NULL);
352 acct_name = g_strdup(purple_escape_filename(purple_normalize(account,
353 purple_account_get_username(account))));
355 if (type == PURPLE_LOG_CHAT) {
356 char *temp = g_strdup_printf("%s.chat", purple_normalize(account, name));
357 target = purple_escape_filename(temp);
358 g_free(temp);
359 } else if(type == PURPLE_LOG_SYSTEM) {
360 target = ".system";
361 } else {
362 target = purple_escape_filename(purple_normalize(account, name));
365 dir = g_build_filename(purple_user_dir(), "logs", prpl_name, acct_name, target, NULL);
367 g_free(acct_name);
369 return dir;
372 /****************************************************************************
373 * LOGGER FUNCTIONS *********************************************************
374 ****************************************************************************/
376 static PurpleLogLogger *current_logger = NULL;
378 static void logger_pref_cb(const char *name, PurplePrefType type,
379 gconstpointer value, gpointer data)
381 PurpleLogLogger *logger;
382 GSList *l = loggers;
383 while (l) {
384 logger = l->data;
385 if (purple_strequal(logger->id, value)) {
386 purple_log_logger_set(logger);
387 return;
389 l = l->next;
391 purple_log_logger_set(txt_logger);
395 PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...)
397 #if 0
398 void(*create)(PurpleLog *),
399 gsize(*write)(PurpleLog *, PurpleMessageFlags, const char *, time_t, const char *),
400 void(*finalize)(PurpleLog *),
401 GList*(*list)(PurpleLogType type, const char*, PurpleAccount*),
402 char*(*read)(PurpleLog*, PurpleLogReadFlags*),
403 int(*size)(PurpleLog*),
404 int(*total_size)(PurpleLogType type, const char *name, PurpleAccount *account),
405 GList*(*list_syslog)(PurpleAccount *account),
406 void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets),
407 gboolean(*remove)(PurpleLog *log),
408 gboolean(*is_deletable)(PurpleLog *log))
409 #endif
410 PurpleLogLogger *logger;
411 va_list args;
413 g_return_val_if_fail(id != NULL, NULL);
414 g_return_val_if_fail(name != NULL, NULL);
415 g_return_val_if_fail(functions >= 1, NULL);
417 logger = g_new0(PurpleLogLogger, 1);
418 logger->id = g_strdup(id);
419 logger->name = g_strdup(name);
421 va_start(args, functions);
423 if (functions >= 1)
424 logger->create = va_arg(args, void *);
425 if (functions >= 2)
426 logger->write = va_arg(args, void *);
427 if (functions >= 3)
428 logger->finalize = va_arg(args, void *);
429 if (functions >= 4)
430 logger->list = va_arg(args, void *);
431 if (functions >= 5)
432 logger->read = va_arg(args, void *);
433 if (functions >= 6)
434 logger->size = va_arg(args, void *);
435 if (functions >= 7)
436 logger->total_size = va_arg(args, void *);
437 if (functions >= 8)
438 logger->list_syslog = va_arg(args, void *);
439 if (functions >= 9)
440 logger->get_log_sets = va_arg(args, void *);
441 if (functions >= 10)
442 logger->remove = va_arg(args, void *);
443 if (functions >= 11)
444 logger->is_deletable = va_arg(args, void *);
446 if (functions >= 12)
447 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id);
449 va_end(args);
451 return logger;
454 void purple_log_logger_free(PurpleLogLogger *logger)
456 g_free(logger->name);
457 g_free(logger->id);
458 g_free(logger);
461 void purple_log_logger_add (PurpleLogLogger *logger)
463 g_return_if_fail(logger);
464 if (g_slist_find(loggers, logger))
465 return;
466 loggers = g_slist_append(loggers, logger);
467 if (purple_strequal(purple_prefs_get_string("/purple/logging/format"), logger->id)) {
468 purple_prefs_trigger_callback("/purple/logging/format");
472 void purple_log_logger_remove (PurpleLogLogger *logger)
474 g_return_if_fail(logger);
475 loggers = g_slist_remove(loggers, logger);
478 void purple_log_logger_set (PurpleLogLogger *logger)
480 g_return_if_fail(logger);
481 current_logger = logger;
484 PurpleLogLogger *purple_log_logger_get()
486 return current_logger;
489 GList *purple_log_logger_get_options(void)
491 GSList *n;
492 GList *list = NULL;
493 PurpleLogLogger *data;
495 for (n = loggers; n; n = n->next) {
496 data = n->data;
497 if (!data->write)
498 continue;
499 list = g_list_append(list, data->name);
500 list = g_list_append(list, data->id);
503 return list;
506 gint purple_log_compare(gconstpointer y, gconstpointer z)
508 const PurpleLog *a = y;
509 const PurpleLog *b = z;
511 return b->time - a->time;
514 GList *purple_log_get_logs(PurpleLogType type, const char *name, PurpleAccount *account)
516 GList *logs = NULL;
517 GSList *n;
518 for (n = loggers; n; n = n->next) {
519 PurpleLogLogger *logger = n->data;
520 if (!logger->list)
521 continue;
522 logs = g_list_concat(logger->list(type, name, account), logs);
525 return g_list_sort(logs, purple_log_compare);
528 gint purple_log_set_compare(gconstpointer y, gconstpointer z)
530 const PurpleLogSet *a = y;
531 const PurpleLogSet *b = z;
532 gint ret = 0;
534 /* This logic seems weird at first...
535 * If either account is NULL, we pretend the accounts are
536 * equal. This allows us to detect duplicates that will
537 * exist if one logger knows the account and another
538 * doesn't. */
539 if (a->account != NULL && b->account != NULL) {
540 ret = strcmp(purple_account_get_username(a->account), purple_account_get_username(b->account));
541 if (ret != 0)
542 return ret;
545 ret = strcmp(a->normalized_name, b->normalized_name);
546 if (ret != 0)
547 return ret;
549 return (gint)b->type - (gint)a->type;
552 static guint
553 log_set_hash(gconstpointer key)
555 const PurpleLogSet *set = key;
557 /* The account isn't hashed because we need PurpleLogSets with NULL accounts
558 * to be found when we search by a PurpleLogSet that has a non-NULL account
559 * but the same type and name. */
560 return g_int_hash(&set->type) + g_str_hash(set->name);
563 static gboolean
564 log_set_equal(gconstpointer a, gconstpointer b)
566 /* I realize that the choices made for GList and GHashTable
567 * make sense for those data types, but I wish the comparison
568 * functions were compatible. */
569 return !purple_log_set_compare(a, b);
572 static void
573 log_add_log_set_to_hash(GHashTable *sets, PurpleLogSet *set)
575 PurpleLogSet *existing_set = g_hash_table_lookup(sets, set);
577 if (existing_set == NULL)
578 g_hash_table_insert(sets, set, set);
579 else if (existing_set->account == NULL && set->account != NULL)
580 g_hash_table_replace(sets, set, set);
581 else
582 purple_log_set_free(set);
585 GHashTable *purple_log_get_log_sets(void)
587 GSList *n;
588 GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal,
589 (GDestroyNotify)purple_log_set_free, NULL);
591 /* Get the log sets from all the loggers. */
592 for (n = loggers; n; n = n->next) {
593 PurpleLogLogger *logger = n->data;
595 if (!logger->get_log_sets)
596 continue;
598 logger->get_log_sets(log_add_log_set_to_hash, sets);
601 log_get_log_sets_common(sets);
603 /* Return the GHashTable of unique PurpleLogSets. */
604 return sets;
607 void purple_log_set_free(PurpleLogSet *set)
609 g_return_if_fail(set != NULL);
611 g_free(set->name);
612 if (set->normalized_name != set->name)
613 g_free(set->normalized_name);
615 g_slice_free(PurpleLogSet, set);
618 GList *purple_log_get_system_logs(PurpleAccount *account)
620 GList *logs = NULL;
621 GSList *n;
622 for (n = loggers; n; n = n->next) {
623 PurpleLogLogger *logger = n->data;
624 if (!logger->list_syslog)
625 continue;
626 logs = g_list_concat(logger->list_syslog(account), logs);
629 return g_list_sort(logs, purple_log_compare);
632 /****************************************************************************
633 * LOG SUBSYSTEM ************************************************************
634 ****************************************************************************/
636 void *
637 purple_log_get_handle(void)
639 static int handle;
641 return &handle;
644 void purple_log_init(void)
646 void *handle = purple_log_get_handle();
648 purple_prefs_add_none("/purple/logging");
649 purple_prefs_add_bool("/purple/logging/log_ims", TRUE);
650 purple_prefs_add_bool("/purple/logging/log_chats", TRUE);
651 purple_prefs_add_bool("/purple/logging/log_system", FALSE);
653 purple_prefs_add_string("/purple/logging/format", "html");
655 html_logger = purple_log_logger_new("html", _("HTML"), 11,
656 NULL,
657 html_logger_write,
658 html_logger_finalize,
659 html_logger_list,
660 html_logger_read,
661 purple_log_common_sizer,
662 html_logger_total_size,
663 html_logger_list_syslog,
664 NULL,
665 purple_log_common_deleter,
666 purple_log_common_is_deletable);
667 purple_log_logger_add(html_logger);
669 txt_logger = purple_log_logger_new("txt", _("Plain text"), 11,
670 NULL,
671 txt_logger_write,
672 txt_logger_finalize,
673 txt_logger_list,
674 txt_logger_read,
675 purple_log_common_sizer,
676 txt_logger_total_size,
677 txt_logger_list_syslog,
678 NULL,
679 purple_log_common_deleter,
680 purple_log_common_is_deletable);
681 purple_log_logger_add(txt_logger);
683 old_logger = purple_log_logger_new("old", _("Old flat format"), 9,
684 NULL,
685 NULL,
686 old_logger_finalize,
687 old_logger_list,
688 old_logger_read,
689 old_logger_size,
690 old_logger_total_size,
691 NULL,
692 old_logger_get_log_sets);
693 purple_log_logger_add(old_logger);
695 purple_signal_register(handle, "log-timestamp",
696 #if SIZEOF_TIME_T == 4
697 purple_marshal_POINTER__POINTER_INT_BOOLEAN,
698 #elif SIZEOF_TIME_T == 8
699 purple_marshal_POINTER__POINTER_INT64_BOOLEAN,
700 #else
701 #error Unknown size of time_t
702 #endif
703 purple_value_new(PURPLE_TYPE_STRING), 3,
704 purple_value_new(PURPLE_TYPE_SUBTYPE,
705 PURPLE_SUBTYPE_LOG),
706 #if SIZEOF_TIME_T == 4
707 purple_value_new(PURPLE_TYPE_INT),
708 #elif SIZEOF_TIME_T == 8
709 purple_value_new(PURPLE_TYPE_INT64),
710 #else
711 # error Unknown size of time_t
712 #endif
713 purple_value_new(PURPLE_TYPE_BOOLEAN));
715 purple_prefs_connect_callback(NULL, "/purple/logging/format",
716 logger_pref_cb, NULL);
717 purple_prefs_trigger_callback("/purple/logging/format");
719 logsize_users = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash,
720 (GEqualFunc)_purple_logsize_user_equal,
721 (GDestroyNotify)_purple_logsize_user_free_key, NULL);
722 logsize_users_decayed = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash,
723 (GEqualFunc)_purple_logsize_user_equal,
724 (GDestroyNotify)_purple_logsize_user_free_key, NULL);
727 void
728 purple_log_uninit(void)
730 purple_signals_unregister_by_instance(purple_log_get_handle());
732 purple_log_logger_remove(html_logger);
733 purple_log_logger_free(html_logger);
734 html_logger = NULL;
736 purple_log_logger_remove(txt_logger);
737 purple_log_logger_free(txt_logger);
738 txt_logger = NULL;
740 purple_log_logger_remove(old_logger);
741 purple_log_logger_free(old_logger);
742 old_logger = NULL;
744 g_hash_table_destroy(logsize_users);
745 g_hash_table_destroy(logsize_users_decayed);
748 /****************************************************************************
749 * LOGGERS ******************************************************************
750 ****************************************************************************/
752 static char *log_get_timestamp(PurpleLog *log, time_t when)
754 gboolean show_date;
755 char *date;
756 struct tm tm;
758 show_date = (log->type == PURPLE_LOG_SYSTEM) || (time(NULL) > when + 20*60);
760 date = purple_signal_emit_return_1(purple_log_get_handle(),
761 "log-timestamp",
762 log, when, show_date);
763 if (date != NULL)
764 return date;
766 tm = *(localtime(&when));
767 if (show_date)
768 return g_strdup(purple_date_format_long(&tm));
769 else
770 return g_strdup(purple_time_format(&tm));
773 /* NOTE: This can return msg (which you may or may not want to g_free())
774 * NOTE: or a newly allocated string which you MUST g_free(). */
775 static char *
776 convert_image_tags(const PurpleLog *log, const char *msg)
778 const char *tmp;
779 const char *start;
780 const char *end;
781 GData *attributes;
782 GString *newmsg = NULL;
784 tmp = msg;
786 while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) {
787 int imgid = 0;
788 char *idstr = NULL;
790 if (newmsg == NULL)
791 newmsg = g_string_new("");
793 /* copy any text before the img tag */
794 if (tmp < start)
795 g_string_append_len(newmsg, tmp, start - tmp);
797 if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL)
798 imgid = atoi(idstr);
800 if (imgid != 0)
802 FILE *image_file;
803 char *dir;
804 PurpleStoredImage *image;
805 gconstpointer image_data;
806 char *new_filename = NULL;
807 char *path = NULL;
808 size_t image_byte_count;
810 image = purple_imgstore_find_by_id(imgid);
811 if (image == NULL)
813 /* This should never happen. */
814 /* This *does* happen for failed Direct-IMs -DAA */
815 g_string_free(newmsg, TRUE);
816 g_return_val_if_reached((char *)msg);
819 image_data = purple_imgstore_get_data(image);
820 image_byte_count = purple_imgstore_get_size(image);
821 dir = purple_log_get_log_dir(log->type, log->name, log->account);
822 new_filename = purple_util_get_image_filename(image_data, image_byte_count);
824 path = g_build_filename(dir, new_filename, NULL);
826 /* Only save unique files. */
827 if (!g_file_test(path, G_FILE_TEST_EXISTS))
829 if ((image_file = g_fopen(path, "wb")) != NULL)
831 if (!fwrite(image_data, image_byte_count, 1, image_file))
833 purple_debug_error("log", "Error writing %s: %s\n",
834 path, g_strerror(errno));
835 fclose(image_file);
837 /* Attempt to not leave half-written files around. */
838 unlink(path);
840 else
842 purple_debug_info("log", "Wrote image file: %s\n", path);
843 fclose(image_file);
846 else
848 purple_debug_error("log", "Unable to create file %s: %s\n",
849 path, g_strerror(errno));
853 /* Write the new image tag */
854 g_string_append_printf(newmsg, "<IMG SRC=\"%s\">", new_filename);
855 g_free(new_filename);
856 g_free(path);
859 /* Continue from the end of the tag */
860 tmp = end + 1;
863 if (newmsg == NULL)
865 /* No images were found to change. */
866 return (char *)msg;
869 /* Append any remaining message data */
870 g_string_append(newmsg, tmp);
872 return g_string_free(newmsg, FALSE);
875 void purple_log_common_writer(PurpleLog *log, const char *ext)
877 PurpleLogCommonLoggerData *data = log->logger_data;
879 if (data == NULL)
881 /* This log is new */
882 char *dir;
883 struct tm *tm;
884 const char *tz;
885 const char *date;
886 char *filename;
887 char *path;
889 dir = purple_log_get_log_dir(log->type, log->name, log->account);
890 if (dir == NULL)
891 return;
893 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
895 tm = localtime(&log->time);
896 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm));
897 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
899 filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
901 path = g_build_filename(dir, filename, NULL);
902 g_free(dir);
903 g_free(filename);
905 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
907 data->file = g_fopen(path, "a");
908 if (data->file == NULL)
910 purple_debug(PURPLE_DEBUG_ERROR, "log",
911 "Could not create log file %s\n", path);
913 if (log->conv != NULL)
914 purple_conversation_write(log->conv, NULL, _("Logging of this conversation failed."),
915 PURPLE_MESSAGE_ERROR, time(NULL));
917 g_free(path);
918 return;
920 g_free(path);
924 GList *purple_log_common_lister(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext, PurpleLogLogger *logger)
926 GDir *dir;
927 GList *list = NULL;
928 const char *filename;
929 char *path;
931 if(!account)
932 return NULL;
934 path = purple_log_get_log_dir(type, name, account);
935 if (path == NULL)
936 return NULL;
938 if (!(dir = g_dir_open(path, 0, NULL)))
940 g_free(path);
941 return NULL;
944 while ((filename = g_dir_read_name(dir)))
946 if (purple_str_has_suffix(filename, ext) &&
947 strlen(filename) >= (17 + strlen(ext)))
949 PurpleLog *log;
950 PurpleLogCommonLoggerData *data;
951 struct tm tm;
952 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
953 long tz_off;
954 const char *rest, *end;
955 time_t stamp = purple_str_to_time(purple_unescape_filename(filename), FALSE, &tm, &tz_off, &rest);
957 /* As zero is a valid offset, PURPLE_NO_TZ_OFF means no offset was
958 * provided. See util.h. Yes, it's kinda ugly. */
959 if (tz_off != PURPLE_NO_TZ_OFF)
960 tm.tm_gmtoff = tz_off - tm.tm_gmtoff;
962 if (stamp == 0 || rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL)
964 log = purple_log_new(type, name, account, NULL, stamp, NULL);
966 else
968 char *tmp = g_strndup(rest, end - rest);
969 tm.tm_zone = tmp;
970 log = purple_log_new(type, name, account, NULL, stamp, &tm);
971 g_free(tmp);
973 #else
974 time_t stamp = purple_str_to_time(filename, FALSE, &tm, NULL, NULL);
976 log = purple_log_new(type, name, account, NULL, stamp, (stamp != 0) ? &tm : NULL);
977 #endif
979 log->logger = logger;
980 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
982 data->path = g_build_filename(path, filename, NULL);
983 list = g_list_prepend(list, log);
986 g_dir_close(dir);
987 g_free(path);
988 return list;
991 int purple_log_common_total_sizer(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext)
993 GDir *dir;
994 int size = 0;
995 const char *filename;
996 char *path;
998 if(!account)
999 return 0;
1001 path = purple_log_get_log_dir(type, name, account);
1002 if (path == NULL)
1003 return 0;
1005 if (!(dir = g_dir_open(path, 0, NULL)))
1007 g_free(path);
1008 return 0;
1011 while ((filename = g_dir_read_name(dir)))
1013 if (purple_str_has_suffix(filename, ext) &&
1014 strlen(filename) >= (17 + strlen(ext)))
1016 char *tmp = g_build_filename(path, filename, NULL);
1017 struct stat st;
1018 if (g_stat(tmp, &st))
1020 purple_debug_error("log", "Error stating log file: %s\n", tmp);
1021 g_free(tmp);
1022 continue;
1024 g_free(tmp);
1025 size += st.st_size;
1028 g_dir_close(dir);
1029 g_free(path);
1030 return size;
1033 int purple_log_common_sizer(PurpleLog *log)
1035 struct stat st;
1036 PurpleLogCommonLoggerData *data = log->logger_data;
1038 g_return_val_if_fail(data != NULL, 0);
1040 if (!data->path || g_stat(data->path, &st))
1041 st.st_size = 0;
1043 return st.st_size;
1046 /* This will build log sets for all loggers that use the common logger
1047 * functions because they use the same directory structure. */
1048 static void log_get_log_sets_common(GHashTable *sets)
1050 gchar *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
1051 GDir *log_dir = g_dir_open(log_path, 0, NULL);
1052 const gchar *protocol;
1054 if (log_dir == NULL) {
1055 g_free(log_path);
1056 return;
1059 while ((protocol = g_dir_read_name(log_dir)) != NULL) {
1060 gchar *protocol_path = g_build_filename(log_path, protocol, NULL);
1061 GDir *protocol_dir;
1062 const gchar *username;
1063 gchar *protocol_unescaped;
1064 GList *account_iter;
1065 GList *accounts = NULL;
1067 if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
1068 g_free(protocol_path);
1069 continue;
1072 /* Using g_strdup() to cover the one-in-a-million chance that a
1073 * prpl's list_icon function uses purple_unescape_filename(). */
1074 protocol_unescaped = g_strdup(purple_unescape_filename(protocol));
1076 /* Find all the accounts for protocol. */
1077 for (account_iter = purple_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) {
1078 PurplePlugin *prpl;
1079 PurplePluginProtocolInfo *prpl_info;
1081 prpl = purple_find_prpl(purple_account_get_protocol_id((PurpleAccount *)account_iter->data));
1082 if (!prpl)
1083 continue;
1084 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1086 if (purple_strequal(protocol_unescaped, prpl_info->list_icon((PurpleAccount *)account_iter->data, NULL)))
1087 accounts = g_list_prepend(accounts, account_iter->data);
1089 g_free(protocol_unescaped);
1091 while ((username = g_dir_read_name(protocol_dir)) != NULL) {
1092 gchar *username_path = g_build_filename(protocol_path, username, NULL);
1093 GDir *username_dir;
1094 const gchar *username_unescaped;
1095 PurpleAccount *account = NULL;
1096 gchar *name;
1098 if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
1099 g_free(username_path);
1100 continue;
1103 /* Find the account for username in the list of accounts for protocol. */
1104 username_unescaped = purple_unescape_filename(username);
1105 for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) {
1106 if (purple_strequal(((PurpleAccount *)account_iter->data)->username, username_unescaped)) {
1107 account = account_iter->data;
1108 break;
1112 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
1113 while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) {
1114 size_t len;
1115 PurpleLogSet *set;
1117 /* IMPORTANT: Always initialize all members of PurpleLogSet */
1118 set = g_slice_new(PurpleLogSet);
1120 /* Unescape the filename. */
1121 name = g_strdup(purple_unescape_filename(name));
1123 /* Get the (possibly new) length of name. */
1124 len = strlen(name);
1126 set->type = PURPLE_LOG_IM;
1127 set->name = name;
1128 set->account = account;
1129 /* set->buddy is always set below */
1130 set->normalized_name = g_strdup(purple_normalize(account, name));
1132 /* Check for .chat or .system at the end of the name to determine the type. */
1133 if (len >= 7) {
1134 gchar *tmp = &name[len - 7];
1135 if (purple_strequal(tmp, ".system")) {
1136 set->type = PURPLE_LOG_SYSTEM;
1137 *tmp = '\0';
1140 if (len > 5) {
1141 gchar *tmp = &name[len - 5];
1142 if (purple_strequal(tmp, ".chat")) {
1143 set->type = PURPLE_LOG_CHAT;
1144 *tmp = '\0';
1148 /* Determine if this (account, name) combination exists as a buddy. */
1149 if (account != NULL && name != NULL && *name != '\0')
1150 set->buddy = (purple_find_buddy(account, name) != NULL);
1151 else
1152 set->buddy = FALSE;
1154 log_add_log_set_to_hash(sets, set);
1156 g_free(username_path);
1157 g_dir_close(username_dir);
1159 g_free(protocol_path);
1160 g_dir_close(protocol_dir);
1162 g_free(log_path);
1163 g_dir_close(log_dir);
1166 gboolean purple_log_common_deleter(PurpleLog *log)
1168 PurpleLogCommonLoggerData *data;
1169 int ret;
1171 g_return_val_if_fail(log != NULL, FALSE);
1173 data = log->logger_data;
1174 if (data == NULL)
1175 return FALSE;
1177 if (data->path == NULL)
1178 return FALSE;
1180 ret = g_unlink(data->path);
1181 if (ret == 0)
1182 return TRUE;
1183 else if (ret == -1)
1185 purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, g_strerror(errno));
1187 else
1189 /* I'm not sure that g_unlink() will ever return
1190 * something other than 0 or -1. -- rlaager */
1191 purple_debug_error("log", "Failed to delete: %s\n", data->path);
1194 return FALSE;
1197 gboolean purple_log_common_is_deletable(PurpleLog *log)
1199 PurpleLogCommonLoggerData *data;
1200 #ifndef _WIN32
1201 gchar *dirname;
1202 #endif
1204 g_return_val_if_fail(log != NULL, FALSE);
1206 data = log->logger_data;
1207 if (data == NULL)
1208 return FALSE;
1210 if (data->path == NULL)
1211 return FALSE;
1213 #ifndef _WIN32
1214 dirname = g_path_get_dirname(data->path);
1215 if (g_access(dirname, W_OK) == 0)
1217 g_free(dirname);
1218 return TRUE;
1220 purple_debug_info("log", "access(%s) failed: %s\n", dirname, g_strerror(errno));
1221 g_free(dirname);
1222 #else
1223 /* Unless and until someone writes equivalent win32 code,
1224 * we'll assume the file is deletable. */
1225 return TRUE;
1226 #endif
1228 return FALSE;
1231 static char *process_txt_log(char *txt, char *to_free)
1233 char *tmp;
1235 /* The to_free argument allows us to save a
1236 * g_strdup() in some cases. */
1238 if (to_free == NULL)
1239 to_free = txt;
1241 /* g_markup_escape_text requires valid UTF-8 */
1242 if (!g_utf8_validate(txt, -1, NULL))
1244 tmp = purple_utf8_salvage(txt);
1245 g_free(to_free);
1246 to_free = txt = tmp;
1249 tmp = g_markup_escape_text(txt, -1);
1250 g_free(to_free);
1251 txt = purple_markup_linkify(tmp);
1252 g_free(tmp);
1254 return txt;
1257 #if 0 /* Maybe some other time. */
1258 /****************
1259 ** XML LOGGER **
1260 ****************/
1262 static const char *str_from_msg_type (PurpleMessageFlags type)
1265 return "";
1269 static void xml_logger_write(PurpleLog *log,
1270 PurpleMessageFlags type,
1271 const char *from, time_t time, const char *message)
1273 char *xhtml = NULL;
1275 if (!log->logger_data) {
1276 /* This log is new. We could use the loggers 'new' function, but
1277 * creating a new file there would result in empty files in the case
1278 * that you open a convo with someone, but don't say anything.
1280 struct tm *tm;
1281 const char *tz;
1282 const char *date;
1283 char *dir = purple_log_get_log_dir(log->type, log->name, log->account);
1284 char *name;
1285 char *filename;
1287 if (dir == NULL)
1288 return;
1290 tm = localtime(&log->time);
1291 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm);
1292 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
1294 name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
1296 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1298 filename = g_build_filename(dir, name, NULL);
1299 g_free(dir);
1300 g_free(name);
1302 log->logger_data = g_fopen(filename, "a");
1303 if (!log->logger_data) {
1304 purple_debug(PURPLE_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
1305 g_free(filename);
1306 return;
1308 g_free(filename);
1309 fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n"
1310 "<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n");
1312 date = purple_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time));
1313 fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n",
1314 date, log->name, prpl);
1317 /* if we can't write to the file, give up before we hurt ourselves */
1318 if(!data->file)
1319 return;
1321 date = log_get_timestamp(log, time);
1323 purple_markup_html_to_xhtml(message, &xhtml, NULL);
1324 if (from)
1325 fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n",
1326 str_from_msg_type(type),
1327 type & PURPLE_MESSAGE_SEND ? "direction='sent'" :
1328 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "",
1329 from, date, xhtml);
1330 else
1331 fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n",
1332 str_from_msg_type(type),
1333 type & PURPLE_MESSAGE_SEND ? "direction='sent'" :
1334 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "",
1335 date, xhtml):
1336 fflush(log->logger_data);
1337 g_free(date);
1338 g_free(xhtml);
1341 static void xml_logger_finalize(PurpleLog *log)
1343 if (log->logger_data) {
1344 fprintf(log->logger_data, "</conversation>\n");
1345 fclose(log->logger_data);
1346 log->logger_data = NULL;
1350 static GList *xml_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1352 return purple_log_common_lister(type, sn, account, ".xml", &xml_logger);
1355 static PurpleLogLogger xml_logger = {
1356 N_("XML"), "xml",
1357 NULL,
1358 xml_logger_write,
1359 xml_logger_finalize,
1360 xml_logger_list,
1361 NULL,
1362 NULL,
1363 NULL
1365 #endif
1367 /****************************
1368 ** HTML LOGGER *************
1369 ****************************/
1371 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
1372 const char *from, time_t time, const char *message)
1374 char *msg_fixed;
1375 char *image_corrected_msg;
1376 char *date;
1377 char *header;
1378 char *escaped_from;
1379 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
1380 PurpleLogCommonLoggerData *data = log->logger_data;
1381 gsize written = 0;
1383 if(!data) {
1384 const char *prpl =
1385 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
1386 const char *date;
1387 purple_log_common_writer(log, ".html");
1389 data = log->logger_data;
1391 /* if we can't write to the file, give up before we hurt ourselves */
1392 if(!data->file)
1393 return 0;
1395 date = purple_date_format_full(localtime(&log->time));
1397 written += fprintf(data->file, "<html><head>");
1398 written += fprintf(data->file, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
1399 written += fprintf(data->file, "<title>");
1400 if (log->type == PURPLE_LOG_SYSTEM)
1401 header = g_strdup_printf("System log for account %s (%s) connected at %s",
1402 purple_account_get_username(log->account), prpl, date);
1403 else
1404 header = g_strdup_printf("Conversation with %s at %s on %s (%s)",
1405 log->name, date, purple_account_get_username(log->account), prpl);
1407 written += fprintf(data->file, "%s", header);
1408 written += fprintf(data->file, "</title></head><body>");
1409 written += fprintf(data->file, "<h3>%s</h3>\n", header);
1410 g_free(header);
1413 /* if we can't write to the file, give up before we hurt ourselves */
1414 if(!data->file)
1415 return 0;
1417 escaped_from = g_markup_escape_text(from, -1);
1419 image_corrected_msg = convert_image_tags(log, message);
1420 purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL);
1422 /* Yes, this breaks encapsulation. But it's a static function and
1423 * this saves a needless strdup(). */
1424 if (image_corrected_msg != message)
1425 g_free(image_corrected_msg);
1427 date = log_get_timestamp(log, time);
1429 if(log->type == PURPLE_LOG_SYSTEM){
1430 written += fprintf(data->file, "---- %s @ %s ----<br/>\n", msg_fixed, date);
1431 } else {
1432 if (type & PURPLE_MESSAGE_SYSTEM)
1433 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed);
1434 else if (type & PURPLE_MESSAGE_RAW)
1435 written += fprintf(data->file, "<font size=\"2\">(%s)</font> %s<br/>\n", date, msg_fixed);
1436 else if (type & PURPLE_MESSAGE_ERROR)
1437 written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed);
1438 else if (type & PURPLE_MESSAGE_WHISPER)
1439 written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1440 date, escaped_from, msg_fixed);
1441 else if (type & PURPLE_MESSAGE_AUTO_RESP) {
1442 if (type & PURPLE_MESSAGE_SEND)
1443 written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed);
1444 else if (type & PURPLE_MESSAGE_RECV)
1445 written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed);
1446 } else if (type & PURPLE_MESSAGE_RECV) {
1447 if(purple_message_meify(msg_fixed, -1))
1448 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1449 date, escaped_from, msg_fixed);
1450 else
1451 written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1452 date, escaped_from, msg_fixed);
1453 } else if (type & PURPLE_MESSAGE_SEND) {
1454 if(purple_message_meify(msg_fixed, -1))
1455 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1456 date, escaped_from, msg_fixed);
1457 else
1458 written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1459 date, escaped_from, msg_fixed);
1460 } else {
1461 purple_debug_error("log", "Unhandled message type.\n");
1462 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1463 date, escaped_from, msg_fixed);
1466 g_free(date);
1467 g_free(msg_fixed);
1468 g_free(escaped_from);
1469 fflush(data->file);
1471 return written;
1474 static void html_logger_finalize(PurpleLog *log)
1476 PurpleLogCommonLoggerData *data = log->logger_data;
1477 if (data) {
1478 if(data->file) {
1479 fprintf(data->file, "</body></html>\n");
1480 fclose(data->file);
1482 g_free(data->path);
1484 g_slice_free(PurpleLogCommonLoggerData, data);
1488 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1490 return purple_log_common_lister(type, sn, account, ".html", html_logger);
1493 static GList *html_logger_list_syslog(PurpleAccount *account)
1495 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger);
1498 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
1500 char *read;
1501 PurpleLogCommonLoggerData *data = log->logger_data;
1502 *flags = PURPLE_LOG_READ_NO_NEWLINE;
1503 if (!data || !data->path)
1504 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1505 if (g_file_get_contents(data->path, &read, NULL, NULL)) {
1506 char *minus_header = strchr(read, '\n');
1508 if (!minus_header)
1509 return read;
1511 minus_header = g_strdup(minus_header + 1);
1512 g_free(read);
1514 return minus_header;
1516 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
1519 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1521 return purple_log_common_total_sizer(type, name, account, ".html");
1525 /****************************
1526 ** PLAIN TEXT LOGGER *******
1527 ****************************/
1529 static gsize txt_logger_write(PurpleLog *log,
1530 PurpleMessageFlags type,
1531 const char *from, time_t time, const char *message)
1533 char *date;
1534 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
1535 PurpleLogCommonLoggerData *data = log->logger_data;
1536 char *stripped = NULL;
1538 gsize written = 0;
1540 if (data == NULL) {
1541 /* This log is new. We could use the loggers 'new' function, but
1542 * creating a new file there would result in empty files in the case
1543 * that you open a convo with someone, but don't say anything.
1545 const char *prpl =
1546 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
1547 purple_log_common_writer(log, ".txt");
1549 data = log->logger_data;
1551 /* if we can't write to the file, give up before we hurt ourselves */
1552 if(!data->file)
1553 return 0;
1555 if (log->type == PURPLE_LOG_SYSTEM)
1556 written += fprintf(data->file, "System log for account %s (%s) connected at %s\n",
1557 purple_account_get_username(log->account), prpl,
1558 purple_date_format_full(localtime(&log->time)));
1559 else
1560 written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n",
1561 log->name, purple_date_format_full(localtime(&log->time)),
1562 purple_account_get_username(log->account), prpl);
1565 /* if we can't write to the file, give up before we hurt ourselves */
1566 if(!data->file)
1567 return 0;
1569 stripped = purple_markup_strip_html(message);
1570 date = log_get_timestamp(log, time);
1572 if(log->type == PURPLE_LOG_SYSTEM){
1573 written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date);
1574 } else {
1575 if (type & PURPLE_MESSAGE_SEND ||
1576 type & PURPLE_MESSAGE_RECV) {
1577 if (type & PURPLE_MESSAGE_AUTO_RESP) {
1578 written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date,
1579 from, stripped);
1580 } else {
1581 if(purple_message_meify(stripped, -1))
1582 written += fprintf(data->file, "(%s) ***%s %s\n", date, from,
1583 stripped);
1584 else
1585 written += fprintf(data->file, "(%s) %s: %s\n", date, from,
1586 stripped);
1588 } else if (type & PURPLE_MESSAGE_SYSTEM ||
1589 type & PURPLE_MESSAGE_ERROR ||
1590 type & PURPLE_MESSAGE_RAW)
1591 written += fprintf(data->file, "(%s) %s\n", date, stripped);
1592 else if (type & PURPLE_MESSAGE_NO_LOG) {
1593 /* This shouldn't happen */
1594 g_free(stripped);
1595 return written;
1596 } else if (type & PURPLE_MESSAGE_WHISPER)
1597 written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped);
1598 else
1599 written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "",
1600 from ? ":" : "", stripped);
1602 g_free(date);
1603 g_free(stripped);
1604 fflush(data->file);
1606 return written;
1609 static void txt_logger_finalize(PurpleLog *log)
1611 PurpleLogCommonLoggerData *data = log->logger_data;
1612 if (data) {
1613 if(data->file)
1614 fclose(data->file);
1615 g_free(data->path);
1617 g_slice_free(PurpleLogCommonLoggerData, data);
1621 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1623 return purple_log_common_lister(type, sn, account, ".txt", txt_logger);
1626 static GList *txt_logger_list_syslog(PurpleAccount *account)
1628 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger);
1631 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
1633 char *read, *minus_header;
1634 PurpleLogCommonLoggerData *data = log->logger_data;
1635 *flags = 0;
1636 if (!data || !data->path)
1637 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1638 if (g_file_get_contents(data->path, &read, NULL, NULL)) {
1639 minus_header = strchr(read, '\n');
1641 if (minus_header)
1642 return process_txt_log(minus_header + 1, read);
1643 else
1644 return process_txt_log(read, NULL);
1646 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
1649 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1651 return purple_log_common_total_sizer(type, name, account, ".txt");
1655 /****************
1656 * OLD LOGGER ***
1657 ****************/
1659 /* The old logger doesn't write logs, only reads them. This is to include
1660 * old logs in the log viewer transparently.
1663 struct old_logger_data {
1664 PurpleStringref *pathref;
1665 int offset;
1666 int length;
1669 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1671 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, sn));
1672 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
1673 PurpleStringref *pathref = purple_stringref_new(pathstr);
1674 struct stat st;
1675 time_t log_last_modified;
1676 FILE *index;
1677 FILE *file;
1678 int index_fd;
1679 char *index_tmp;
1680 char buf[BUF_LONG];
1681 struct tm tm;
1682 char month[4];
1683 struct old_logger_data *data = NULL;
1684 char *newlog;
1685 int logfound = 0;
1686 int lastoff = 0;
1687 int newlen;
1688 time_t lasttime = 0;
1690 PurpleLog *log = NULL;
1691 GList *list = NULL;
1693 g_free(logfile);
1695 if (g_stat(purple_stringref_value(pathref), &st))
1697 purple_stringref_unref(pathref);
1698 g_free(pathstr);
1699 return NULL;
1701 else
1702 log_last_modified = st.st_mtime;
1704 /* Change the .log extension to .idx */
1705 strcpy(pathstr + strlen(pathstr) - 3, "idx");
1707 if (g_stat(pathstr, &st) == 0)
1709 if (st.st_mtime < log_last_modified)
1711 purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr);
1713 else
1715 /* The index file exists and is at least as new as the log, so open it. */
1716 if (!(index = g_fopen(pathstr, "rb")))
1718 purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
1719 pathstr, g_strerror(errno));
1721 /* Fall through so that we'll parse the log file. */
1723 else
1725 purple_debug_info("log", "Using index: %s\n", pathstr);
1726 g_free(pathstr);
1727 while (fgets(buf, BUF_LONG, index))
1729 unsigned long idx_time;
1730 if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3)
1732 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1733 log->logger = old_logger;
1734 log->time = (time_t)idx_time;
1736 /* IMPORTANT: Always set all members of struct old_logger_data */
1737 data = g_slice_new(struct old_logger_data);
1739 data->pathref = purple_stringref_ref(pathref);
1740 data->offset = lastoff;
1741 data->length = newlen;
1743 log->logger_data = data;
1744 list = g_list_prepend(list, log);
1747 fclose(index);
1748 purple_stringref_unref(pathref);
1750 return list;
1755 if (!(file = g_fopen(purple_stringref_value(pathref), "rb"))) {
1756 purple_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n",
1757 purple_stringref_value(pathref), g_strerror(errno));
1758 purple_stringref_unref(pathref);
1759 g_free(pathstr);
1760 return NULL;
1763 index_tmp = g_strdup_printf("%s.XXXXXX", pathstr);
1764 if ((index_fd = g_mkstemp(index_tmp)) == -1) {
1765 purple_debug_error("log", "Failed to open index temp file: %s\n",
1766 g_strerror(errno));
1767 g_free(pathstr);
1768 g_free(index_tmp);
1769 index = NULL;
1770 } else {
1771 if ((index = fdopen(index_fd, "wb")) == NULL)
1773 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1774 g_strerror(errno));
1775 close(index_fd);
1776 if (index_tmp != NULL)
1778 g_unlink(index_tmp);
1779 g_free(index_tmp);
1781 g_free(pathstr);
1785 while (fgets(buf, BUF_LONG, file)) {
1786 if ((newlog = strstr(buf, "---- New C"))) {
1787 int length;
1788 int offset;
1789 char convostart[32];
1790 char *temp = strchr(buf, '@');
1792 if (temp == NULL || strlen(temp) < 2)
1793 continue;
1795 temp++;
1796 length = strcspn(temp, "-");
1797 if (length > 31) length = 31;
1799 offset = ftell(file);
1801 if (logfound) {
1802 newlen = offset - lastoff - length;
1803 if(strstr(buf, "----</H3><BR>")) {
1804 newlen -=
1805 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1806 sizeof("----</H3><BR>") - 2;
1807 } else {
1808 newlen -=
1809 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1812 if(strchr(buf, '\r'))
1813 newlen--;
1815 if (newlen != 0) {
1816 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1817 log->logger = old_logger;
1818 log->time = lasttime;
1820 /* IMPORTANT: Always set all members of struct old_logger_data */
1821 data = g_slice_new(struct old_logger_data);
1823 data->pathref = purple_stringref_ref(pathref);
1824 data->offset = lastoff;
1825 data->length = newlen;
1827 log->logger_data = data;
1828 list = g_list_prepend(list, log);
1830 /* XXX: There is apparently Is there a proper way to print a time_t? */
1831 if (index != NULL)
1832 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time);
1836 logfound = 1;
1837 lastoff = offset;
1839 g_snprintf(convostart, length, "%s", temp);
1840 memset(&tm, 0, sizeof(tm));
1841 sscanf(convostart, "%*s %s %d %d:%d:%d %d",
1842 month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year);
1843 /* Ugly hack, in case current locale is not English */
1844 if (purple_strequal(month, "Jan")) {
1845 tm.tm_mon= 0;
1846 } else if (purple_strequal(month, "Feb")) {
1847 tm.tm_mon = 1;
1848 } else if (purple_strequal(month, "Mar")) {
1849 tm.tm_mon = 2;
1850 } else if (purple_strequal(month, "Apr")) {
1851 tm.tm_mon = 3;
1852 } else if (purple_strequal(month, "May")) {
1853 tm.tm_mon = 4;
1854 } else if (purple_strequal(month, "Jun")) {
1855 tm.tm_mon = 5;
1856 } else if (purple_strequal(month, "Jul")) {
1857 tm.tm_mon = 6;
1858 } else if (purple_strequal(month, "Aug")) {
1859 tm.tm_mon = 7;
1860 } else if (purple_strequal(month, "Sep")) {
1861 tm.tm_mon = 8;
1862 } else if (purple_strequal(month, "Oct")) {
1863 tm.tm_mon = 9;
1864 } else if (purple_strequal(month, "Nov")) {
1865 tm.tm_mon = 10;
1866 } else if (purple_strequal(month, "Dec")) {
1867 tm.tm_mon = 11;
1869 tm.tm_year -= 1900;
1870 lasttime = mktime(&tm);
1874 if (logfound) {
1875 if ((newlen = ftell(file) - lastoff) != 0) {
1876 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
1877 log->logger = old_logger;
1878 log->time = lasttime;
1880 /* IMPORTANT: Always set all members of struct old_logger_data */
1881 data = g_slice_new(struct old_logger_data);
1883 data->pathref = purple_stringref_ref(pathref);
1884 data->offset = lastoff;
1885 data->length = newlen;
1887 log->logger_data = data;
1888 list = g_list_prepend(list, log);
1890 /* XXX: Is there a proper way to print a time_t? */
1891 if (index != NULL)
1892 fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time);
1896 purple_stringref_unref(pathref);
1897 fclose(file);
1898 if (index != NULL)
1900 fclose(index);
1902 if (index_tmp == NULL)
1904 g_free(pathstr);
1905 g_return_val_if_reached(list);
1908 if (g_rename(index_tmp, pathstr))
1910 purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
1911 index_tmp, pathstr, g_strerror(errno));
1912 g_unlink(index_tmp);
1914 else
1915 purple_debug_info("log", "Built index: %s\n", pathstr);
1917 g_free(index_tmp);
1918 g_free(pathstr);
1920 return list;
1923 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
1925 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, name));
1926 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
1927 int size;
1928 struct stat st;
1930 if (g_stat(pathstr, &st))
1931 size = 0;
1932 else
1933 size = st.st_size;
1935 g_free(logfile);
1936 g_free(pathstr);
1938 return size;
1941 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
1943 size_t result;
1944 struct old_logger_data *data = log->logger_data;
1945 const char *path = purple_stringref_value(data->pathref);
1946 FILE *file = g_fopen(path, "rb");
1947 char *read = g_malloc(data->length + 1);
1948 fseek(file, data->offset, SEEK_SET);
1949 result = fread(read, data->length, 1, file);
1950 if (result != 1)
1951 purple_debug_error("log", "Unable to read from log file: %s\n", path);
1952 fclose(file);
1953 read[data->length] = '\0';
1954 *flags = 0;
1955 if (strstr(read, "<BR>"))
1957 *flags |= PURPLE_LOG_READ_NO_NEWLINE;
1958 return read;
1961 return process_txt_log(read, NULL);
1964 static int old_logger_size (PurpleLog *log)
1966 struct old_logger_data *data = log->logger_data;
1967 return data ? data->length : 0;
1970 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets)
1972 char *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
1973 GDir *log_dir = g_dir_open(log_path, 0, NULL);
1974 gchar *name;
1975 PurpleBlistNode *gnode, *cnode, *bnode;
1977 g_free(log_path);
1978 if (log_dir == NULL)
1979 return;
1981 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
1982 while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) {
1983 size_t len;
1984 gchar *ext;
1985 PurpleLogSet *set;
1986 gboolean found = FALSE;
1988 /* Unescape the filename. */
1989 name = g_strdup(purple_unescape_filename(name));
1991 /* Get the (possibly new) length of name. */
1992 len = strlen(name);
1994 if (len < 5) {
1995 g_free(name);
1996 continue;
1999 /* Make sure we're dealing with a log file. */
2000 ext = &name[len - 4];
2001 if (!purple_strequal(ext, ".log")) {
2002 g_free(name);
2003 continue;
2006 /* IMPORTANT: Always set all members of PurpleLogSet */
2007 set = g_slice_new(PurpleLogSet);
2009 /* Chat for .chat at the end of the name to determine the type. */
2010 *ext = '\0';
2011 set->type = PURPLE_LOG_IM;
2012 if (len > 9) {
2013 char *tmp = &name[len - 9];
2014 if (purple_strequal(tmp, ".chat")) {
2015 set->type = PURPLE_LOG_CHAT;
2016 *tmp = '\0';
2020 set->name = set->normalized_name = name;
2022 /* Search the buddy list to find the account and to determine if this is a buddy. */
2023 for (gnode = purple_blist_get_root();
2024 !found && gnode != NULL;
2025 gnode = purple_blist_node_get_sibling_next(gnode))
2027 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2028 continue;
2030 for (cnode = purple_blist_node_get_first_child(gnode);
2031 !found && cnode != NULL;
2032 cnode = purple_blist_node_get_sibling_next(cnode))
2034 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2035 continue;
2037 for (bnode = purple_blist_node_get_first_child(cnode);
2038 !found && bnode != NULL;
2039 bnode = purple_blist_node_get_sibling_next(bnode))
2041 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
2043 if (purple_strequal(purple_buddy_get_name(buddy), name)) {
2044 set->account = purple_buddy_get_account(buddy);
2045 set->buddy = TRUE;
2046 found = TRUE;
2052 if (!found)
2054 set->account = NULL;
2055 set->buddy = FALSE;
2058 cb(sets, set);
2060 g_dir_close(log_dir);
2063 static void old_logger_finalize(PurpleLog *log)
2065 struct old_logger_data *data = log->logger_data;
2066 purple_stringref_unref(data->pathref);
2067 g_slice_free(struct old_logger_data, data);