2 * @file log.c Logging API
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
29 #include "dbus-maybe.h"
35 #include "stringref.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
{
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
)
87 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
88 log
= g_slice_new(PurpleLog
);
89 PURPLE_DBUS_REGISTER_POINTER(log
, PurpleLog
);
92 log
->name
= g_strdup(purple_normalize(account
, name
));
93 log
->account
= account
;
96 log
->logger
= purple_log_logger_get();
97 log
->logger_data
= NULL
;
103 /* There's no need to zero this as we immediately do a direct copy. */
104 log
->tm
= g_slice_new(struct 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
);
114 log
->tm
->tm_zone
= tmp
;
116 /* Just shove the UTF-8 bytes in and hope... */
117 log
->tm
->tm_zone
= g_strdup(log
->tm
->tm_zone
);
122 if (log
->logger
&& log
->logger
->create
)
123 log
->logger
->create(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
);
136 #ifdef HAVE_STRUCT_TM_TM_ZONE
137 /* XXX: This is so wrong... */
138 g_free((char *)log
->tm
->tm_zone
);
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;
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
);
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
);
182 g_hash_table_replace(logsize_users_decayed
, lu
, GINT_TO_POINTER(total
));
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');
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
);
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
)
227 int purple_log_get_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
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
);
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
);
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
);
263 g_hash_table_replace(logsize_users
, lu
, GINT_TO_POINTER(size
));
268 gint
purple_log_get_activity_score(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
273 struct _purple_logsize_user
*lu
;
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
);
286 double score_double
= 0.0;
287 for (n
= loggers
; n
; n
= n
->next
) {
288 PurpleLogLogger
*logger
= n
->data
;
291 GList
*logs
= (logger
->list
)(type
, name
, account
);
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
));
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
)
319 if (log
->logger
->is_deletable
!= NULL
)
320 return log
->logger
->is_deletable(log
);
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
);
337 purple_log_get_log_dir(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
340 PurplePluginProtocolInfo
*prpl_info
;
341 const char *prpl_name
;
346 prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
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
);
359 } else if(type
== PURPLE_LOG_SYSTEM
) {
362 target
= purple_escape_filename(purple_normalize(account
, name
));
365 dir
= g_build_filename(purple_user_dir(), "logs", prpl_name
, acct_name
, target
, NULL
);
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
;
385 if (purple_strequal(logger
->id
, value
)) {
386 purple_log_logger_set(logger
);
391 purple_log_logger_set(txt_logger
);
395 PurpleLogLogger
*purple_log_logger_new(const char *id
, const char *name
, int functions
, ...)
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
))
410 PurpleLogLogger
*logger
;
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
);
424 logger
->create
= va_arg(args
, void *);
426 logger
->write
= va_arg(args
, void *);
428 logger
->finalize
= va_arg(args
, void *);
430 logger
->list
= va_arg(args
, void *);
432 logger
->read
= va_arg(args
, void *);
434 logger
->size
= va_arg(args
, void *);
436 logger
->total_size
= va_arg(args
, void *);
438 logger
->list_syslog
= va_arg(args
, void *);
440 logger
->get_log_sets
= va_arg(args
, void *);
442 logger
->remove
= va_arg(args
, void *);
444 logger
->is_deletable
= va_arg(args
, void *);
447 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name
, id
);
454 void purple_log_logger_free(PurpleLogLogger
*logger
)
456 g_free(logger
->name
);
461 void purple_log_logger_add (PurpleLogLogger
*logger
)
463 g_return_if_fail(logger
);
464 if (g_slist_find(loggers
, logger
))
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)
493 PurpleLogLogger
*data
;
495 for (n
= loggers
; n
; n
= n
->next
) {
499 list
= g_list_append(list
, data
->name
);
500 list
= g_list_append(list
, data
->id
);
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
)
518 for (n
= loggers
; n
; n
= n
->next
) {
519 PurpleLogLogger
*logger
= n
->data
;
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
;
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
539 if (a
->account
!= NULL
&& b
->account
!= NULL
) {
540 ret
= strcmp(purple_account_get_username(a
->account
), purple_account_get_username(b
->account
));
545 ret
= strcmp(a
->normalized_name
, b
->normalized_name
);
549 return (gint
)b
->type
- (gint
)a
->type
;
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
);
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
);
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
);
582 purple_log_set_free(set
);
585 GHashTable
*purple_log_get_log_sets(void)
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
)
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. */
607 void purple_log_set_free(PurpleLogSet
*set
)
609 g_return_if_fail(set
!= NULL
);
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
)
622 for (n
= loggers
; n
; n
= n
->next
) {
623 PurpleLogLogger
*logger
= n
->data
;
624 if (!logger
->list_syslog
)
626 logs
= g_list_concat(logger
->list_syslog(account
), logs
);
629 return g_list_sort(logs
, purple_log_compare
);
632 /****************************************************************************
633 * LOG SUBSYSTEM ************************************************************
634 ****************************************************************************/
637 purple_log_get_handle(void)
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,
658 html_logger_finalize
,
661 purple_log_common_sizer
,
662 html_logger_total_size
,
663 html_logger_list_syslog
,
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,
675 purple_log_common_sizer
,
676 txt_logger_total_size
,
677 txt_logger_list_syslog
,
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,
690 old_logger_total_size
,
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
,
701 #error Unknown size of time_t
703 purple_value_new(PURPLE_TYPE_STRING
), 3,
704 purple_value_new(PURPLE_TYPE_SUBTYPE
,
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
),
711 # error Unknown size of time_t
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
);
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
);
736 purple_log_logger_remove(txt_logger
);
737 purple_log_logger_free(txt_logger
);
740 purple_log_logger_remove(old_logger
);
741 purple_log_logger_free(old_logger
);
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
)
758 show_date
= (log
->type
== PURPLE_LOG_SYSTEM
) || (time(NULL
) > when
+ 20*60);
760 date
= purple_signal_emit_return_1(purple_log_get_handle(),
762 log
, when
, show_date
);
766 tm
= *(localtime(&when
));
768 return g_strdup(purple_date_format_long(&tm
));
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(). */
776 convert_image_tags(const PurpleLog
*log
, const char *msg
)
782 GString
*newmsg
= NULL
;
786 while (purple_markup_find_tag("img", tmp
, &start
, &end
, &attributes
)) {
791 newmsg
= g_string_new("");
793 /* copy any text before the img tag */
795 g_string_append_len(newmsg
, tmp
, start
- tmp
);
797 if ((idstr
= g_datalist_get_data(&attributes
, "id")) != NULL
)
804 PurpleStoredImage
*image
;
805 gconstpointer image_data
;
806 char *new_filename
= NULL
;
808 size_t image_byte_count
;
810 image
= purple_imgstore_find_by_id(imgid
);
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
));
837 /* Attempt to not leave half-written files around. */
842 purple_debug_info("log", "Wrote image file: %s\n", path
);
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
);
859 /* Continue from the end of the tag */
865 /* No images were found to change. */
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
;
881 /* This log is new */
889 dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
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
);
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
));
924 GList
*purple_log_common_lister(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
, PurpleLogLogger
*logger
)
928 const char *filename
;
934 path
= purple_log_get_log_dir(type
, name
, account
);
938 if (!(dir
= g_dir_open(path
, 0, NULL
)))
944 while ((filename
= g_dir_read_name(dir
)))
946 if (purple_str_has_suffix(filename
, ext
) &&
947 strlen(filename
) >= (17 + strlen(ext
)))
950 PurpleLogCommonLoggerData
*data
;
952 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
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
);
968 char *tmp
= g_strndup(rest
, end
- rest
);
970 log
= purple_log_new(type
, name
, account
, NULL
, stamp
, &tm
);
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
);
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
);
991 int purple_log_common_total_sizer(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
)
995 const char *filename
;
1001 path
= purple_log_get_log_dir(type
, name
, account
);
1005 if (!(dir
= g_dir_open(path
, 0, NULL
)))
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
);
1018 if (g_stat(tmp
, &st
))
1020 purple_debug_error("log", "Error stating log file: %s\n", tmp
);
1033 int purple_log_common_sizer(PurpleLog
*log
)
1036 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1038 g_return_val_if_fail(data
!= NULL
, 0);
1040 if (!data
->path
|| g_stat(data
->path
, &st
))
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
) {
1059 while ((protocol
= g_dir_read_name(log_dir
)) != NULL
) {
1060 gchar
*protocol_path
= g_build_filename(log_path
, protocol
, NULL
);
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
);
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
) {
1079 PurplePluginProtocolInfo
*prpl_info
;
1081 prpl
= purple_find_prpl(purple_account_get_protocol_id((PurpleAccount
*)account_iter
->data
));
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
);
1094 const gchar
*username_unescaped
;
1095 PurpleAccount
*account
= NULL
;
1098 if ((username_dir
= g_dir_open(username_path
, 0, NULL
)) == NULL
) {
1099 g_free(username_path
);
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
;
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
) {
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. */
1126 set
->type
= PURPLE_LOG_IM
;
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. */
1134 gchar
*tmp
= &name
[len
- 7];
1135 if (purple_strequal(tmp
, ".system")) {
1136 set
->type
= PURPLE_LOG_SYSTEM
;
1141 gchar
*tmp
= &name
[len
- 5];
1142 if (purple_strequal(tmp
, ".chat")) {
1143 set
->type
= PURPLE_LOG_CHAT
;
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
);
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
);
1163 g_dir_close(log_dir
);
1166 gboolean
purple_log_common_deleter(PurpleLog
*log
)
1168 PurpleLogCommonLoggerData
*data
;
1171 g_return_val_if_fail(log
!= NULL
, FALSE
);
1173 data
= log
->logger_data
;
1177 if (data
->path
== NULL
)
1180 ret
= g_unlink(data
->path
);
1185 purple_debug_error("log", "Failed to delete: %s - %s\n", data
->path
, g_strerror(errno
));
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
);
1197 gboolean
purple_log_common_is_deletable(PurpleLog
*log
)
1199 PurpleLogCommonLoggerData
*data
;
1204 g_return_val_if_fail(log
!= NULL
, FALSE
);
1206 data
= log
->logger_data
;
1210 if (data
->path
== NULL
)
1214 dirname
= g_path_get_dirname(data
->path
);
1215 if (g_access(dirname
, W_OK
) == 0)
1220 purple_debug_info("log", "access(%s) failed: %s\n", dirname
, g_strerror(errno
));
1223 /* Unless and until someone writes equivalent win32 code,
1224 * we'll assume the file is deletable. */
1231 static char *process_txt_log(char *txt
, char *to_free
)
1235 /* The to_free argument allows us to save a
1236 * g_strdup() in some cases. */
1238 if (to_free
== NULL
)
1241 /* g_markup_escape_text requires valid UTF-8 */
1242 if (!g_utf8_validate(txt
, -1, NULL
))
1244 tmp
= purple_utf8_salvage(txt
);
1246 to_free
= txt
= tmp
;
1249 tmp
= g_markup_escape_text(txt
, -1);
1251 txt
= purple_markup_linkify(tmp
);
1257 #if 0 /* Maybe some other time. */
1262 static const char *str_from_msg_type (PurpleMessageFlags type
)
1269 static void xml_logger_write(PurpleLog
*log
,
1270 PurpleMessageFlags type
,
1271 const char *from
, time_t time
, const char *message
)
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.
1283 char *dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
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
);
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
);
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 */
1321 date
= log_get_timestamp(log
, time
);
1323 purple_markup_html_to_xhtml(message
, &xhtml
, NULL
);
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'" : "",
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'" : "",
1336 fflush(log
->logger_data
);
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
= {
1359 xml_logger_finalize
,
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
)
1375 char *image_corrected_msg
;
1379 PurplePlugin
*plugin
= purple_find_prpl(purple_account_get_protocol_id(log
->account
));
1380 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1385 PURPLE_PLUGIN_PROTOCOL_INFO(plugin
)->list_icon(log
->account
, NULL
);
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 */
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
);
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
);
1413 /* if we can't write to the file, give up before we hurt ourselves */
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
);
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 <AUTO-REPLY>:</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 <AUTO-REPLY>:</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
);
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
);
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
);
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
);
1468 g_free(escaped_from
);
1474 static void html_logger_finalize(PurpleLog
*log
)
1476 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1479 fprintf(data
->file
, "</body></html>\n");
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
)
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');
1511 minus_header
= g_strdup(minus_header
+ 1);
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
)
1534 PurplePlugin
*plugin
= purple_find_prpl(purple_account_get_protocol_id(log
->account
));
1535 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1536 char *stripped
= 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.
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 */
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
)));
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 */
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
);
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
,
1581 if(purple_message_meify(stripped
, -1))
1582 written
+= fprintf(data
->file
, "(%s) ***%s %s\n", date
, from
,
1585 written
+= fprintf(data
->file
, "(%s) %s: %s\n", date
, from
,
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 */
1596 } else if (type
& PURPLE_MESSAGE_WHISPER
)
1597 written
+= fprintf(data
->file
, "(%s) *%s* %s", date
, from
, stripped
);
1599 written
+= fprintf(data
->file
, "(%s) %s%s %s\n", date
, from
? from
: "",
1600 from
? ":" : "", stripped
);
1609 static void txt_logger_finalize(PurpleLog
*log
)
1611 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
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
;
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');
1642 return process_txt_log(minus_header
+ 1, read
);
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");
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
;
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
);
1675 time_t log_last_modified
;
1683 struct old_logger_data
*data
= NULL
;
1688 time_t lasttime
= 0;
1690 PurpleLog
*log
= NULL
;
1695 if (g_stat(purple_stringref_value(pathref
), &st
))
1697 purple_stringref_unref(pathref
);
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
);
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. */
1725 purple_debug_info("log", "Using index: %s\n", 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
);
1748 purple_stringref_unref(pathref
);
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
);
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",
1771 if ((index
= fdopen(index_fd
, "wb")) == NULL
)
1773 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1776 if (index_tmp
!= NULL
)
1778 g_unlink(index_tmp
);
1785 while (fgets(buf
, BUF_LONG
, file
)) {
1786 if ((newlog
= strstr(buf
, "---- New C"))) {
1789 char convostart
[32];
1790 char *temp
= strchr(buf
, '@');
1792 if (temp
== NULL
|| strlen(temp
) < 2)
1796 length
= strcspn(temp
, "-");
1797 if (length
> 31) length
= 31;
1799 offset
= ftell(file
);
1802 newlen
= offset
- lastoff
- length
;
1803 if(strstr(buf
, "----</H3><BR>")) {
1805 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1806 sizeof("----</H3><BR>") - 2;
1809 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1812 if(strchr(buf
, '\r'))
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? */
1832 fprintf(index
, "%d\t%d\t%lu\n", data
->offset
, data
->length
, (unsigned long)log
->time
);
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")) {
1846 } else if (purple_strequal(month
, "Feb")) {
1848 } else if (purple_strequal(month
, "Mar")) {
1850 } else if (purple_strequal(month
, "Apr")) {
1852 } else if (purple_strequal(month
, "May")) {
1854 } else if (purple_strequal(month
, "Jun")) {
1856 } else if (purple_strequal(month
, "Jul")) {
1858 } else if (purple_strequal(month
, "Aug")) {
1860 } else if (purple_strequal(month
, "Sep")) {
1862 } else if (purple_strequal(month
, "Oct")) {
1864 } else if (purple_strequal(month
, "Nov")) {
1866 } else if (purple_strequal(month
, "Dec")) {
1870 lasttime
= mktime(&tm
);
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? */
1892 fprintf(index
, "%d\t%d\t%d\n", data
->offset
, data
->length
, (int)log
->time
);
1896 purple_stringref_unref(pathref
);
1902 if (index_tmp
== NULL
)
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
);
1915 purple_debug_info("log", "Built index: %s\n", pathstr
);
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
);
1930 if (g_stat(pathstr
, &st
))
1941 static char * old_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
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
);
1951 purple_debug_error("log", "Unable to read from log file: %s\n", path
);
1953 read
[data
->length
] = '\0';
1955 if (strstr(read
, "<BR>"))
1957 *flags
|= PURPLE_LOG_READ_NO_NEWLINE
;
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
);
1975 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
1978 if (log_dir
== NULL
)
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
) {
1986 gboolean found
= FALSE
;
1988 /* Unescape the filename. */
1989 name
= g_strdup(purple_unescape_filename(name
));
1991 /* Get the (possibly new) length of name. */
1999 /* Make sure we're dealing with a log file. */
2000 ext
= &name
[len
- 4];
2001 if (!purple_strequal(ext
, ".log")) {
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. */
2011 set
->type
= PURPLE_LOG_IM
;
2013 char *tmp
= &name
[len
- 9];
2014 if (purple_strequal(tmp
, ".chat")) {
2015 set
->type
= PURPLE_LOG_CHAT
;
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
))
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
))
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
);
2054 set
->account
= NULL
;
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
);