3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "dbus-maybe.h"
26 #include "glibcompat.h"
27 #include "image-store.h"
31 #include "stringref.h"
34 static GSList
*loggers
= NULL
;
36 static PurpleLogLogger
*html_logger
;
37 static PurpleLogLogger
*txt_logger
;
38 static PurpleLogLogger
*old_logger
;
40 struct _purple_logsize_user
{
42 PurpleAccount
*account
;
44 static GHashTable
*logsize_users
= NULL
;
45 static GHashTable
*logsize_users_decayed
= NULL
;
47 static void log_get_log_sets_common(GHashTable
*sets
);
49 static gsize
html_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
50 const char *from
, GDateTime
*time
, const char *message
);
51 static void html_logger_finalize(PurpleLog
*log
);
52 static GList
*html_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
53 static GList
*html_logger_list_syslog(PurpleAccount
*account
);
54 static char *html_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
);
55 static int html_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
57 static GList
*old_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
58 static int old_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
59 static char * old_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
);
60 static int old_logger_size (PurpleLog
*log
);
61 static void old_logger_get_log_sets(PurpleLogSetCallback cb
, GHashTable
*sets
);
62 static void old_logger_finalize(PurpleLog
*log
);
64 static gsize
txt_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
65 const char *from
, GDateTime
*time
, const char *message
);
66 static void txt_logger_finalize(PurpleLog
*log
);
67 static GList
*txt_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
68 static GList
*txt_logger_list_syslog(PurpleAccount
*account
);
69 static char *txt_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
);
70 static int txt_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
72 /**************************************************************************
73 * PUBLIC LOGGING FUNCTIONS ***********************************************
74 **************************************************************************/
76 PurpleLog
*purple_log_new(PurpleLogType type
, const char *name
, PurpleAccount
*account
,
77 PurpleConversation
*conv
, GDateTime
*time
)
81 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
82 log
= g_slice_new(PurpleLog
);
83 PURPLE_DBUS_REGISTER_POINTER(log
, PurpleLog
);
86 log
->name
= g_strdup(purple_normalize(account
, name
));
87 log
->account
= account
;
90 log
->time
= g_date_time_ref(time
);
93 log
->logger
= purple_log_logger_get();
94 log
->logger_data
= NULL
;
96 if (log
->logger
&& log
->logger
->create
)
97 log
->logger
->create(log
);
101 void purple_log_free(PurpleLog
*log
)
103 g_return_if_fail(log
);
104 if (log
->logger
&& log
->logger
->finalize
)
105 log
->logger
->finalize(log
);
108 g_date_time_unref(log
->time
);
110 PURPLE_DBUS_UNREGISTER_POINTER(log
);
111 g_slice_free(PurpleLog
, log
);
114 void purple_log_write(PurpleLog
*log
, PurpleMessageFlags type
,
115 const char *from
, GDateTime
*time
, const char *message
)
117 struct _purple_logsize_user
*lu
;
118 gsize written
, total
= 0;
121 g_return_if_fail(log
);
122 g_return_if_fail(log
->logger
);
123 g_return_if_fail(log
->logger
->write
);
125 written
= (log
->logger
->write
)(log
, type
, from
, time
, message
);
127 lu
= g_new(struct _purple_logsize_user
, 1);
129 lu
->name
= g_strdup(purple_normalize(log
->account
, log
->name
));
130 lu
->account
= log
->account
;
132 if(g_hash_table_lookup_extended(logsize_users
, lu
, NULL
, &ptrsize
)) {
133 char *tmp
= lu
->name
;
135 total
= GPOINTER_TO_INT(ptrsize
);
137 g_hash_table_replace(logsize_users
, lu
, GINT_TO_POINTER(total
));
139 /* The hash table takes ownership of lu, so create a new one
140 * for the logsize_users_decayed check below. */
141 lu
= g_new(struct _purple_logsize_user
, 1);
142 lu
->name
= g_strdup(tmp
);
143 lu
->account
= log
->account
;
146 if(g_hash_table_lookup_extended(logsize_users_decayed
, lu
, NULL
, &ptrsize
)) {
147 total
= GPOINTER_TO_INT(ptrsize
);
149 g_hash_table_replace(logsize_users_decayed
, lu
, GINT_TO_POINTER(total
));
156 char *purple_log_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
158 PurpleLogReadFlags mflags
;
159 g_return_val_if_fail(log
&& log
->logger
, NULL
);
160 if (log
->logger
->read
) {
161 char *ret
= (log
->logger
->read
)(log
, flags
? flags
: &mflags
);
162 purple_str_strip_char(ret
, '\r');
165 return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>"));
168 int purple_log_get_size(PurpleLog
*log
)
170 g_return_val_if_fail(log
&& log
->logger
, 0);
172 if (log
->logger
->size
)
173 return log
->logger
->size(log
);
177 static guint
_purple_logsize_user_hash(struct _purple_logsize_user
*lu
)
179 return g_str_hash(lu
->name
);
182 static guint
_purple_logsize_user_equal(struct _purple_logsize_user
*lu1
,
183 struct _purple_logsize_user
*lu2
)
185 return (lu1
->account
== lu2
->account
&& purple_strequal(lu1
->name
, lu2
->name
));
188 static void _purple_logsize_user_free_key(struct _purple_logsize_user
*lu
)
194 int purple_log_get_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
199 struct _purple_logsize_user
*lu
;
201 lu
= g_new(struct _purple_logsize_user
, 1);
202 lu
->name
= g_strdup(purple_normalize(account
, name
));
203 lu
->account
= account
;
205 if(g_hash_table_lookup_extended(logsize_users
, lu
, NULL
, &ptrsize
)) {
206 size
= GPOINTER_TO_INT(ptrsize
);
210 for (n
= loggers
; n
; n
= n
->next
) {
211 PurpleLogLogger
*logger
= n
->data
;
213 if(logger
->total_size
){
214 size
+= (logger
->total_size
)(type
, name
, account
);
215 } else if(logger
->list
) {
216 GList
*logs
= (logger
->list
)(type
, name
, account
);
220 PurpleLog
*log
= (PurpleLog
*)(logs
->data
);
221 this_size
+= purple_log_get_size(log
);
222 purple_log_free(log
);
223 logs
= g_list_delete_link(logs
, logs
);
230 g_hash_table_replace(logsize_users
, lu
, GINT_TO_POINTER(size
));
235 gint
purple_log_get_activity_score(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
240 struct _purple_logsize_user
*lu
;
242 lu
= g_new(struct _purple_logsize_user
, 1);
243 lu
->name
= g_strdup(purple_normalize(account
, name
));
244 lu
->account
= account
;
246 if(g_hash_table_lookup_extended(logsize_users_decayed
, lu
, NULL
, &ptrscore
)) {
247 score
= GPOINTER_TO_INT(ptrscore
);
251 GDateTime
*now
= g_date_time_new_now_utc();
252 double score_double
= 0.0;
253 for (n
= loggers
; n
; n
= n
->next
) {
254 PurpleLogLogger
*logger
= n
->data
;
257 GList
*logs
= (logger
->list
)(type
, name
, account
);
260 PurpleLog
*log
= (PurpleLog
*)(logs
->data
);
265 /* Activity score counts bytes in the log, exponentially
266 decayed with a half-life of 14 days. */
267 score_double
+= purple_log_get_size(log
) *
268 pow(0.5, g_date_time_difference(now
, log
->time
)/(14LL*G_TIME_SPAN_DAY
));
269 purple_log_free(log
);
270 logs
= g_list_delete_link(logs
, logs
);
274 g_date_time_unref(now
);
276 score
= (gint
) ceil(score_double
);
277 g_hash_table_replace(logsize_users_decayed
, lu
, GINT_TO_POINTER(score
));
282 gboolean
purple_log_is_deletable(PurpleLog
*log
)
284 g_return_val_if_fail(log
!= NULL
, FALSE
);
285 g_return_val_if_fail(log
->logger
!= NULL
, FALSE
);
287 if (log
->logger
->remove
== NULL
)
290 if (log
->logger
->is_deletable
!= NULL
)
291 return log
->logger
->is_deletable(log
);
296 gboolean
purple_log_delete(PurpleLog
*log
)
298 g_return_val_if_fail(log
!= NULL
, FALSE
);
299 g_return_val_if_fail(log
->logger
!= NULL
, FALSE
);
301 if (log
->logger
->remove
!= NULL
)
302 return log
->logger
->remove(log
);
308 purple_log_get_log_dir(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
310 PurpleProtocol
*protocol
;
311 const char *protocol_name
;
316 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
320 protocol_name
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
322 acct_name
= g_strdup(purple_escape_filename(purple_normalize(account
,
323 purple_account_get_username(account
))));
325 if (type
== PURPLE_LOG_CHAT
) {
326 char *temp
= g_strdup_printf("%s.chat", purple_normalize(account
, name
));
327 target
= purple_escape_filename(temp
);
329 } else if(type
== PURPLE_LOG_SYSTEM
) {
332 target
= purple_escape_filename(purple_normalize(account
, name
));
335 dir
= g_build_filename(purple_data_dir(), "logs", protocol_name
, acct_name
, target
, NULL
);
342 /****************************************************************************
343 * LOGGER FUNCTIONS *********************************************************
344 ****************************************************************************/
346 static PurpleLogLogger
*current_logger
= NULL
;
348 static void logger_pref_cb(const char *name
, PurplePrefType type
,
349 gconstpointer value
, gpointer data
)
351 PurpleLogLogger
*logger
;
355 if (purple_strequal(logger
->id
, value
)) {
356 purple_log_logger_set(logger
);
361 purple_log_logger_set(txt_logger
);
365 PurpleLogLogger
*purple_log_logger_new(const char *id
, const char *name
, int functions
, ...)
367 PurpleLogLogger
*logger
;
370 g_return_val_if_fail(id
!= NULL
, NULL
);
371 g_return_val_if_fail(name
!= NULL
, NULL
);
372 g_return_val_if_fail(functions
>= 1, NULL
);
374 logger
= g_new0(PurpleLogLogger
, 1);
375 logger
->id
= g_strdup(id
);
376 logger
->name
= g_strdup(name
);
378 va_start(args
, functions
);
381 logger
->create
= va_arg(args
, void *);
383 logger
->write
= va_arg(args
, void *);
385 logger
->finalize
= va_arg(args
, void *);
387 logger
->list
= va_arg(args
, void *);
389 logger
->read
= va_arg(args
, void *);
391 logger
->size
= va_arg(args
, void *);
393 logger
->total_size
= va_arg(args
, void *);
395 logger
->list_syslog
= va_arg(args
, void *);
397 logger
->get_log_sets
= va_arg(args
, void *);
399 logger
->remove
= va_arg(args
, void *);
401 logger
->is_deletable
= va_arg(args
, void *);
404 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name
, id
);
411 void purple_log_logger_free(PurpleLogLogger
*logger
)
415 g_free(logger
->name
);
420 void purple_log_logger_add (PurpleLogLogger
*logger
)
422 g_return_if_fail(logger
);
423 if (g_slist_find(loggers
, logger
))
425 loggers
= g_slist_append(loggers
, logger
);
426 if (purple_strequal(purple_prefs_get_string("/purple/logging/format"), logger
->id
)) {
427 purple_prefs_trigger_callback("/purple/logging/format");
431 void purple_log_logger_remove (PurpleLogLogger
*logger
)
433 g_return_if_fail(logger
);
434 loggers
= g_slist_remove(loggers
, logger
);
437 void purple_log_logger_set (PurpleLogLogger
*logger
)
439 g_return_if_fail(logger
);
440 current_logger
= logger
;
443 PurpleLogLogger
*purple_log_logger_get()
445 return current_logger
;
448 GList
*purple_log_logger_get_options(void)
452 PurpleLogLogger
*data
;
454 for (n
= loggers
; n
; n
= n
->next
) {
458 list
= g_list_append(list
, data
->name
);
459 list
= g_list_append(list
, data
->id
);
465 gint
purple_log_compare(gconstpointer y
, gconstpointer z
)
467 const PurpleLog
*a
= y
;
468 const PurpleLog
*b
= z
;
470 /* Sort in reverse order. */
471 return g_date_time_compare(b
->time
, a
->time
);
474 GList
*purple_log_get_logs(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
478 for (n
= loggers
; n
; n
= n
->next
) {
479 PurpleLogLogger
*logger
= n
->data
;
482 logs
= g_list_concat(logger
->list(type
, name
, account
), logs
);
485 return g_list_sort(logs
, purple_log_compare
);
488 gint
purple_log_set_compare(gconstpointer y
, gconstpointer z
)
490 const PurpleLogSet
*a
= y
;
491 const PurpleLogSet
*b
= z
;
494 /* This logic seems weird at first...
495 * If either account is NULL, we pretend the accounts are
496 * equal. This allows us to detect duplicates that will
497 * exist if one logger knows the account and another
499 if (a
->account
!= NULL
&& b
->account
!= NULL
) {
500 ret
= strcmp(purple_account_get_username(a
->account
), purple_account_get_username(b
->account
));
505 ret
= strcmp(a
->normalized_name
, b
->normalized_name
);
509 return (gint
)b
->type
- (gint
)a
->type
;
513 log_set_hash(gconstpointer key
)
515 const PurpleLogSet
*set
= key
;
517 /* The account isn't hashed because we need PurpleLogSets with NULL accounts
518 * to be found when we search by a PurpleLogSet that has a non-NULL account
519 * but the same type and name. */
520 return g_int_hash(&set
->type
) + g_str_hash(set
->name
);
524 log_set_equal(gconstpointer a
, gconstpointer b
)
526 /* I realize that the choices made for GList and GHashTable
527 * make sense for those data types, but I wish the comparison
528 * functions were compatible. */
529 return !purple_log_set_compare(a
, b
);
533 log_add_log_set_to_hash(GHashTable
*sets
, PurpleLogSet
*set
)
535 PurpleLogSet
*existing_set
= g_hash_table_lookup(sets
, set
);
537 if (existing_set
== NULL
)
538 g_hash_table_insert(sets
, set
, set
);
539 else if (existing_set
->account
== NULL
&& set
->account
!= NULL
)
540 g_hash_table_replace(sets
, set
, set
);
542 purple_log_set_free(set
);
545 GHashTable
*purple_log_get_log_sets(void)
548 GHashTable
*sets
= g_hash_table_new_full(log_set_hash
, log_set_equal
,
549 (GDestroyNotify
)purple_log_set_free
, NULL
);
551 /* Get the log sets from all the loggers. */
552 for (n
= loggers
; n
; n
= n
->next
) {
553 PurpleLogLogger
*logger
= n
->data
;
555 if (!logger
->get_log_sets
)
558 logger
->get_log_sets(log_add_log_set_to_hash
, sets
);
561 log_get_log_sets_common(sets
);
563 /* Return the GHashTable of unique PurpleLogSets. */
567 void purple_log_set_free(PurpleLogSet
*set
)
569 g_return_if_fail(set
!= NULL
);
572 if (set
->normalized_name
!= set
->name
)
573 g_free(set
->normalized_name
);
575 g_slice_free(PurpleLogSet
, set
);
578 GList
*purple_log_get_system_logs(PurpleAccount
*account
)
582 for (n
= loggers
; n
; n
= n
->next
) {
583 PurpleLogLogger
*logger
= n
->data
;
584 if (!logger
->list_syslog
)
586 logs
= g_list_concat(logger
->list_syslog(account
), logs
);
589 return g_list_sort(logs
, purple_log_compare
);
592 /****************************************************************************
593 * LOG SUBSYSTEM ************************************************************
594 ****************************************************************************/
597 purple_log_get_handle(void)
604 void purple_log_init(void)
606 void *handle
= purple_log_get_handle();
608 purple_prefs_add_none("/purple/logging");
609 purple_prefs_add_bool("/purple/logging/log_ims", TRUE
);
610 purple_prefs_add_bool("/purple/logging/log_chats", TRUE
);
611 purple_prefs_add_bool("/purple/logging/log_system", FALSE
);
613 purple_prefs_add_string("/purple/logging/format", "html");
615 html_logger
= purple_log_logger_new("html", _("HTML"), 11,
618 html_logger_finalize
,
621 purple_log_common_sizer
,
622 html_logger_total_size
,
623 html_logger_list_syslog
,
625 purple_log_common_deleter
,
626 purple_log_common_is_deletable
);
627 purple_log_logger_add(html_logger
);
629 txt_logger
= purple_log_logger_new("txt", _("Plain text"), 11,
635 purple_log_common_sizer
,
636 txt_logger_total_size
,
637 txt_logger_list_syslog
,
639 purple_log_common_deleter
,
640 purple_log_common_is_deletable
);
641 purple_log_logger_add(txt_logger
);
643 old_logger
= purple_log_logger_new("old", _("Old flat format"), 9,
650 old_logger_total_size
,
652 old_logger_get_log_sets
);
653 purple_log_logger_add(old_logger
);
655 purple_signal_register(handle
, "log-timestamp",
656 purple_marshal_POINTER__POINTER_POINTER_BOOLEAN
,
662 purple_prefs_connect_callback(NULL
, "/purple/logging/format",
663 logger_pref_cb
, NULL
);
664 purple_prefs_trigger_callback("/purple/logging/format");
666 logsize_users
= g_hash_table_new_full((GHashFunc
)_purple_logsize_user_hash
,
667 (GEqualFunc
)_purple_logsize_user_equal
,
668 (GDestroyNotify
)_purple_logsize_user_free_key
, NULL
);
669 logsize_users_decayed
= g_hash_table_new_full((GHashFunc
)_purple_logsize_user_hash
,
670 (GEqualFunc
)_purple_logsize_user_equal
,
671 (GDestroyNotify
)_purple_logsize_user_free_key
, NULL
);
675 purple_log_uninit(void)
677 purple_signals_unregister_by_instance(purple_log_get_handle());
679 purple_log_logger_remove(html_logger
);
680 purple_log_logger_free(html_logger
);
683 purple_log_logger_remove(txt_logger
);
684 purple_log_logger_free(txt_logger
);
687 purple_log_logger_remove(old_logger
);
688 purple_log_logger_free(old_logger
);
691 g_hash_table_destroy(logsize_users
);
692 g_hash_table_destroy(logsize_users_decayed
);
696 purple_log_copy(PurpleLog
*log
)
700 g_return_val_if_fail(log
!= NULL
, NULL
);
702 log_copy
= g_new(PurpleLog
, 1);
709 purple_log_get_type(void)
711 static GType type
= 0;
714 type
= g_boxed_type_register_static("PurpleLog",
715 (GBoxedCopyFunc
)purple_log_copy
,
716 (GBoxedFreeFunc
)g_free
);
722 /****************************************************************************
723 * LOGGERS ******************************************************************
724 ****************************************************************************/
726 static char *log_get_timestamp(PurpleLog
*log
, GDateTime
*when
)
732 dt
= g_date_time_new_now_utc();
733 show_date
= (log
->type
== PURPLE_LOG_SYSTEM
) || (g_date_time_difference(dt
, when
) > 20L * G_TIME_SPAN_MINUTE
);
734 g_date_time_unref(dt
);
736 date
= purple_signal_emit_return_1(purple_log_get_handle(),
738 log
, when
, show_date
);
742 dt
= g_date_time_to_local(when
);
744 date
= g_date_time_format(dt
, _("%x %X"));
746 date
= g_date_time_format(dt
, "%X");
747 g_date_time_unref(dt
);
752 /* NOTE: This can return msg (which you may or may not want to g_free())
753 * NOTE: or a newly allocated string which you MUST g_free().
754 * TODO: XXX: does it really works?
757 convert_image_tags(const PurpleLog
*log
, const char *msg
)
763 GString
*newmsg
= NULL
;
767 while (purple_markup_find_tag("img", tmp
, &start
, &end
, &attributes
)) {
772 newmsg
= g_string_new("");
774 /* copy any text before the img tag */
776 g_string_append_len(newmsg
, tmp
, start
- tmp
);
778 if ((idstr
= g_datalist_get_data(&attributes
, "id")) != NULL
)
786 gconstpointer image_data
;
787 const gchar
*new_filename
= NULL
;
789 size_t image_byte_count
;
791 image
= purple_image_store_get(imgid
);
794 /* This should never happen. */
795 /* This *does* happen for failed Direct-IMs -DAA */
796 g_string_free(newmsg
, TRUE
);
797 g_return_val_if_reached((char *)msg
);
800 image_data
= purple_image_get_data(image
);
801 image_byte_count
= purple_image_get_data_size(image
);
802 dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
803 new_filename
= purple_image_generate_filename(image
);
805 path
= g_build_filename(dir
, new_filename
, NULL
);
807 /* Only save unique files. */
808 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
810 if ((image_file
= g_fopen(path
, "wb")) != NULL
)
812 if (!fwrite(image_data
, image_byte_count
, 1, image_file
))
814 purple_debug_error("log", "Error writing %s: %s\n",
815 path
, g_strerror(errno
));
818 /* Attempt to not leave half-written files around. */
819 if (g_unlink(path
)) {
820 purple_debug_error("log", "Error deleting partial "
821 "file %s: %s\n", path
, g_strerror(errno
));
826 purple_debug_info("log", "Wrote image file: %s\n", path
);
832 purple_debug_error("log", "Unable to create file %s: %s\n",
833 path
, g_strerror(errno
));
837 /* Write the new image tag */
838 g_string_append_printf(newmsg
, "<img src=\"%s\">", new_filename
);
842 /* Continue from the end of the tag */
848 /* No images were found to change. */
852 /* Append any remaining message data */
853 g_string_append(newmsg
, tmp
);
855 return g_string_free(newmsg
, FALSE
);
858 void purple_log_common_writer(PurpleLog
*log
, const char *ext
)
860 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
864 /* This log is new */
872 dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
876 purple_build_dir (dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
878 dt
= g_date_time_to_local(log
->time
);
879 tz
= purple_escape_filename(g_date_time_get_timezone_abbreviation(dt
));
880 date
= g_date_time_format(dt
, "%Y-%m-%d.%H%M%S%z");
881 g_date_time_unref(dt
);
883 filename
= g_strdup_printf("%s%s%s", date
, tz
, ext
? ext
: "");
885 path
= g_build_filename(dir
, filename
, NULL
);
890 log
->logger_data
= data
= g_slice_new0(PurpleLogCommonLoggerData
);
892 data
->file
= g_fopen(path
, "a");
893 if (data
->file
== NULL
)
895 purple_debug(PURPLE_DEBUG_ERROR
, "log",
896 "Could not create log file %s\n", path
);
898 if (log
->conv
!= NULL
)
899 purple_conversation_write_system_message(log
->conv
,
900 _("Logging of this conversation failed."),
901 PURPLE_MESSAGE_ERROR
);
910 GList
*purple_log_common_lister(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
, PurpleLogLogger
*logger
)
914 const char *filename
;
920 path
= purple_log_get_log_dir(type
, name
, account
);
924 if (!(dir
= g_dir_open(path
, 0, NULL
)))
930 while ((filename
= g_dir_read_name(dir
)))
932 if (purple_str_has_suffix(filename
, ext
) &&
933 strlen(filename
) >= (17 + strlen(ext
)))
936 PurpleLogCommonLoggerData
*data
;
937 GDateTime
*stamp
= purple_str_to_date_time(purple_unescape_filename(filename
), FALSE
);
939 log
= purple_log_new(type
, name
, account
, NULL
, stamp
);
940 log
->logger
= logger
;
941 log
->logger_data
= data
= g_slice_new0(PurpleLogCommonLoggerData
);
943 data
->path
= g_build_filename(path
, filename
, NULL
);
944 list
= g_list_prepend(list
, log
);
946 g_date_time_unref(stamp
);
954 int purple_log_common_total_sizer(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
)
958 const char *filename
;
964 path
= purple_log_get_log_dir(type
, name
, account
);
968 if (!(dir
= g_dir_open(path
, 0, NULL
)))
974 while ((filename
= g_dir_read_name(dir
)))
976 if (purple_str_has_suffix(filename
, ext
) &&
977 strlen(filename
) >= (17 + strlen(ext
)))
979 char *tmp
= g_build_filename(path
, filename
, NULL
);
981 if (g_stat(tmp
, &st
))
983 purple_debug_error("log", "Error stating log file: %s\n", tmp
);
996 int purple_log_common_sizer(PurpleLog
*log
)
999 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1001 g_return_val_if_fail(data
!= NULL
, 0);
1003 if (!data
->path
|| g_stat(data
->path
, &st
))
1009 /* This will build log sets for all loggers that use the common logger
1010 * functions because they use the same directory structure. */
1011 static void log_get_log_sets_common(GHashTable
*sets
)
1013 gchar
*log_path
= g_build_filename(purple_data_dir(), "logs", NULL
);
1014 GDir
*log_dir
= g_dir_open(log_path
, 0, NULL
);
1015 const gchar
*protocol
;
1017 if (log_dir
== NULL
) {
1022 while ((protocol
= g_dir_read_name(log_dir
)) != NULL
) {
1023 gchar
*protocol_path
= g_build_filename(log_path
, protocol
, NULL
);
1025 const gchar
*username
;
1026 gchar
*protocol_unescaped
;
1027 GList
*account_iter
;
1028 GList
*accounts
= NULL
;
1030 if ((protocol_dir
= g_dir_open(protocol_path
, 0, NULL
)) == NULL
) {
1031 g_free(protocol_path
);
1035 /* Using g_strdup() to cover the one-in-a-million chance that a
1036 * protocol's list_icon function uses purple_unescape_filename(). */
1037 protocol_unescaped
= g_strdup(purple_unescape_filename(protocol
));
1039 /* Find all the accounts for protocol. */
1040 for (account_iter
= purple_accounts_get_all() ; account_iter
!= NULL
; account_iter
= account_iter
->next
) {
1041 PurpleProtocol
*protocol
;
1043 protocol
= purple_protocols_find(purple_account_get_protocol_id((PurpleAccount
*)account_iter
->data
));
1047 if (purple_strequal(protocol_unescaped
, purple_protocol_class_list_icon(protocol
, (PurpleAccount
*)account_iter
->data
, NULL
)))
1048 accounts
= g_list_prepend(accounts
, account_iter
->data
);
1050 g_free(protocol_unescaped
);
1052 while ((username
= g_dir_read_name(protocol_dir
)) != NULL
) {
1053 gchar
*username_path
= g_build_filename(protocol_path
, username
, NULL
);
1055 const gchar
*username_unescaped
;
1056 PurpleAccount
*account
= NULL
;
1059 if ((username_dir
= g_dir_open(username_path
, 0, NULL
)) == NULL
) {
1060 g_free(username_path
);
1064 /* Find the account for username in the list of accounts for protocol. */
1065 username_unescaped
= purple_unescape_filename(username
);
1066 for (account_iter
= g_list_first(accounts
) ; account_iter
!= NULL
; account_iter
= account_iter
->next
) {
1067 if (purple_strequal(purple_account_get_username((PurpleAccount
*)account_iter
->data
), username_unescaped
)) {
1068 account
= account_iter
->data
;
1073 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
1074 while ((name
= (gchar
*)g_dir_read_name(username_dir
)) != NULL
) {
1078 /* IMPORTANT: Always initialize all members of PurpleLogSet */
1079 set
= g_slice_new(PurpleLogSet
);
1081 /* Unescape the filename. */
1082 name
= g_strdup(purple_unescape_filename(name
));
1084 /* Get the (possibly new) length of name. */
1087 set
->type
= PURPLE_LOG_IM
;
1089 set
->account
= account
;
1090 /* set->buddy is always set below */
1091 set
->normalized_name
= g_strdup(purple_normalize(account
, name
));
1093 /* Check for .chat or .system at the end of the name to determine the type. */
1095 gchar
*tmp
= &name
[len
- 7];
1096 if (purple_strequal(tmp
, ".system")) {
1097 set
->type
= PURPLE_LOG_SYSTEM
;
1102 gchar
*tmp
= &name
[len
- 5];
1103 if (purple_strequal(tmp
, ".chat")) {
1104 set
->type
= PURPLE_LOG_CHAT
;
1109 /* Determine if this (account, name) combination exists as a buddy. */
1110 if (account
!= NULL
&& *name
!= '\0')
1111 set
->buddy
= (purple_blist_find_buddy(account
, name
) != NULL
);
1115 log_add_log_set_to_hash(sets
, set
);
1117 g_free(username_path
);
1118 g_dir_close(username_dir
);
1120 g_free(protocol_path
);
1121 g_list_free(accounts
);
1122 g_dir_close(protocol_dir
);
1125 g_dir_close(log_dir
);
1128 gboolean
purple_log_common_deleter(PurpleLog
*log
)
1130 PurpleLogCommonLoggerData
*data
;
1133 g_return_val_if_fail(log
!= NULL
, FALSE
);
1135 data
= log
->logger_data
;
1139 if (data
->path
== NULL
)
1142 ret
= g_unlink(data
->path
);
1147 purple_debug_error("log", "Failed to delete: %s - %s\n", data
->path
, g_strerror(errno
));
1151 /* I'm not sure that g_unlink() will ever return
1152 * something other than 0 or -1. -- rlaager */
1153 purple_debug_error("log", "Failed to delete: %s\n", data
->path
);
1159 gboolean
purple_log_common_is_deletable(PurpleLog
*log
)
1161 PurpleLogCommonLoggerData
*data
;
1166 g_return_val_if_fail(log
!= NULL
, FALSE
);
1168 data
= log
->logger_data
;
1172 if (data
->path
== NULL
)
1176 dirname
= g_path_get_dirname(data
->path
);
1177 if (g_access(dirname
, W_OK
) == 0)
1182 purple_debug_info("log", "access(%s) failed: %s\n", dirname
, g_strerror(errno
));
1185 /* Unless and until someone writes equivalent win32 code,
1186 * we'll assume the file is deletable. */
1193 static char *process_txt_log(char *txt
, char *to_free
)
1197 /* The to_free argument allows us to save a
1198 * g_strdup() in some cases. */
1200 if (to_free
== NULL
)
1203 /* g_markup_escape_text requires valid UTF-8 */
1204 if (!g_utf8_validate(txt
, -1, NULL
))
1206 tmp
= purple_utf8_salvage(txt
);
1208 to_free
= txt
= tmp
;
1211 tmp
= g_markup_escape_text(txt
, -1);
1213 txt
= purple_markup_linkify(tmp
);
1219 /****************************
1220 ** HTML LOGGER *************
1221 ****************************/
1223 static gsize
html_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
1224 const char *from
, GDateTime
*time
, const char *message
)
1227 char *image_corrected_msg
;
1231 PurpleProtocol
*protocol
=
1232 purple_protocols_find(purple_account_get_protocol_id(log
->account
));
1233 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1237 const char *proto
= purple_protocol_class_list_icon(protocol
, log
->account
, NULL
);
1240 purple_log_common_writer(log
, ".html");
1242 data
= log
->logger_data
;
1244 /* if we can't write to the file, give up before we hurt ourselves */
1248 dt
= g_date_time_to_local(log
->time
);
1249 date
= g_date_time_format(dt
, "%c");
1250 g_date_time_unref(dt
);
1252 written
+= fprintf(data
->file
, "<html><head>");
1253 written
+= fprintf(data
->file
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
1254 written
+= fprintf(data
->file
, "<title>");
1255 if (log
->type
== PURPLE_LOG_SYSTEM
)
1256 header
= g_strdup_printf("System log for account %s (%s) connected at %s",
1257 purple_account_get_username(log
->account
), proto
, date
);
1259 header
= g_strdup_printf("Conversation with %s at %s on %s (%s)",
1260 log
->name
, date
, purple_account_get_username(log
->account
), proto
);
1262 written
+= fprintf(data
->file
, "%s", header
);
1263 written
+= fprintf(data
->file
, "</title></head><body>");
1264 written
+= fprintf(data
->file
, "<h3>%s</h3>\n", header
);
1269 /* if we can't write to the file, give up before we hurt ourselves */
1273 escaped_from
= g_markup_escape_text(from
!= NULL
? from
: "<NULL>",
1276 image_corrected_msg
= convert_image_tags(log
, message
);
1277 purple_markup_html_to_xhtml(image_corrected_msg
, &msg_fixed
, NULL
);
1279 /* Yes, this breaks encapsulation. But it's a static function and
1280 * this saves a needless strdup(). */
1281 if (image_corrected_msg
!= message
)
1282 g_free(image_corrected_msg
);
1284 date
= log_get_timestamp(log
, time
);
1286 if(log
->type
== PURPLE_LOG_SYSTEM
){
1287 written
+= fprintf(data
->file
, "---- %s @ %s ----<br/>\n", msg_fixed
, date
);
1289 if (type
& PURPLE_MESSAGE_SYSTEM
)
1290 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date
, msg_fixed
);
1291 else if (type
& PURPLE_MESSAGE_RAW
)
1292 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font> %s<br/>\n", date
, msg_fixed
);
1293 else if (type
& PURPLE_MESSAGE_ERROR
)
1294 written
+= fprintf(data
->file
, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date
, msg_fixed
);
1295 else if (type
& PURPLE_MESSAGE_AUTO_RESP
) {
1296 if (type
& PURPLE_MESSAGE_SEND
)
1297 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
);
1298 else if (type
& PURPLE_MESSAGE_RECV
)
1299 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
);
1300 } else if (type
& PURPLE_MESSAGE_RECV
) {
1301 if(purple_message_meify(msg_fixed
, -1))
1302 written
+= fprintf(data
->file
, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1303 date
, escaped_from
, msg_fixed
);
1305 written
+= fprintf(data
->file
, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1306 date
, escaped_from
, msg_fixed
);
1307 } else if (type
& PURPLE_MESSAGE_SEND
) {
1308 if(purple_message_meify(msg_fixed
, -1))
1309 written
+= fprintf(data
->file
, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1310 date
, escaped_from
, msg_fixed
);
1312 written
+= fprintf(data
->file
, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1313 date
, escaped_from
, msg_fixed
);
1315 purple_debug_error("log", "Unhandled message type.\n");
1316 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1317 date
, escaped_from
, msg_fixed
);
1322 g_free(escaped_from
);
1328 static void html_logger_finalize(PurpleLog
*log
)
1330 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1333 fprintf(data
->file
, "</body></html>\n");
1338 g_slice_free(PurpleLogCommonLoggerData
, data
);
1342 static GList
*html_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1344 return purple_log_common_lister(type
, sn
, account
, ".html", html_logger
);
1347 static GList
*html_logger_list_syslog(PurpleAccount
*account
)
1349 return purple_log_common_lister(PURPLE_LOG_SYSTEM
, ".system", account
, ".html", html_logger
);
1352 static char *html_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1355 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1356 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
1357 if (!data
|| !data
->path
)
1358 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1359 if (g_file_get_contents(data
->path
, &read
, NULL
, NULL
)) {
1360 char *minus_header
= strchr(read
, '\n');
1365 minus_header
= g_strdup(minus_header
+ 1);
1368 return minus_header
;
1370 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data
->path
);
1373 static int html_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1375 return purple_log_common_total_sizer(type
, name
, account
, ".html");
1379 /****************************
1380 ** PLAIN TEXT LOGGER *******
1381 ****************************/
1383 static gsize
txt_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
1384 const char *from
, GDateTime
*time
, const char *message
)
1387 PurpleProtocol
*protocol
=
1388 purple_protocols_find(purple_account_get_protocol_id(log
->account
));
1389 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1390 char *stripped
= NULL
;
1395 /* This log is new. We could use the loggers 'new' function, but
1396 * creating a new file there would result in empty files in the case
1397 * that you open a convo with someone, but don't say anything.
1399 const char *proto
= purple_protocol_class_list_icon(protocol
, log
->account
, NULL
);
1402 purple_log_common_writer(log
, ".txt");
1404 data
= log
->logger_data
;
1406 /* if we can't write to the file, give up before we hurt ourselves */
1407 if(!data
|| !data
->file
)
1410 dt
= g_date_time_to_local(log
->time
);
1411 date
= g_date_time_format(dt
, "%c");
1412 if (log
->type
== PURPLE_LOG_SYSTEM
)
1413 written
+= fprintf(data
->file
, "System log for account %s (%s) connected at %s\n",
1414 purple_account_get_username(log
->account
), proto
,
1417 written
+= fprintf(data
->file
, "Conversation with %s at %s on %s (%s)\n",
1419 purple_account_get_username(log
->account
), proto
);
1421 g_date_time_unref(dt
);
1424 /* if we can't write to the file, give up before we hurt ourselves */
1428 stripped
= purple_markup_strip_html(message
);
1429 date
= log_get_timestamp(log
, time
);
1431 if(log
->type
== PURPLE_LOG_SYSTEM
){
1432 written
+= fprintf(data
->file
, "---- %s @ %s ----\n", stripped
, date
);
1434 if (type
& PURPLE_MESSAGE_SEND
||
1435 type
& PURPLE_MESSAGE_RECV
) {
1436 if (type
& PURPLE_MESSAGE_AUTO_RESP
) {
1437 written
+= fprintf(data
->file
, _("(%s) %s <AUTO-REPLY>: %s\n"), date
,
1440 if(purple_message_meify(stripped
, -1))
1441 written
+= fprintf(data
->file
, "(%s) ***%s %s\n", date
, from
,
1444 written
+= fprintf(data
->file
, "(%s) %s: %s\n", date
, from
,
1447 } else if (type
& PURPLE_MESSAGE_SYSTEM
||
1448 type
& PURPLE_MESSAGE_ERROR
||
1449 type
& PURPLE_MESSAGE_RAW
)
1450 written
+= fprintf(data
->file
, "(%s) %s\n", date
, stripped
);
1451 else if (type
& PURPLE_MESSAGE_NO_LOG
) {
1452 /* This shouldn't happen */
1456 written
+= fprintf(data
->file
, "(%s) %s%s %s\n", date
, from
? from
: "",
1457 from
? ":" : "", stripped
);
1466 static void txt_logger_finalize(PurpleLog
*log
)
1468 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1474 g_slice_free(PurpleLogCommonLoggerData
, data
);
1478 static GList
*txt_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1480 return purple_log_common_lister(type
, sn
, account
, ".txt", txt_logger
);
1483 static GList
*txt_logger_list_syslog(PurpleAccount
*account
)
1485 return purple_log_common_lister(PURPLE_LOG_SYSTEM
, ".system", account
, ".txt", txt_logger
);
1488 static char *txt_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1490 char *read
, *minus_header
;
1491 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1493 if (!data
|| !data
->path
)
1494 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1495 if (g_file_get_contents(data
->path
, &read
, NULL
, NULL
)) {
1496 minus_header
= strchr(read
, '\n');
1499 return process_txt_log(minus_header
+ 1, read
);
1501 return process_txt_log(read
, NULL
);
1503 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data
->path
);
1506 static int txt_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1508 return purple_log_common_total_sizer(type
, name
, account
, ".txt");
1516 /* The old logger doesn't write logs, only reads them. This is to include
1517 * old logs in the log viewer transparently.
1520 struct old_logger_data
{
1521 PurpleStringref
*pathref
;
1526 static GList
*old_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1528 char *logfile
= g_strdup_printf("%s.log", purple_normalize(account
, sn
));
1529 char *pathstr
= g_build_filename(purple_data_dir(), "logs", logfile
, NULL
);
1530 PurpleStringref
*pathref
= purple_stringref_new(pathstr
);
1532 time_t log_last_modified
;
1535 int file_fd
, index_fd
;
1538 gint year
, month
, day
, hour
, minute
, second
;
1540 struct old_logger_data
*data
= NULL
;
1544 GDateTime
*lasttime
= NULL
;
1546 PurpleLog
*log
= NULL
;
1551 file_fd
= g_open(purple_stringref_value(pathref
), 0, O_RDONLY
);
1552 if (file_fd
== -1 || (file
= fdopen(file_fd
, "rb")) == NULL
) {
1553 purple_debug_error("log",
1554 "Failed to open log file \"%s\" for reading: %s\n",
1555 purple_stringref_value(pathref
), g_strerror(errno
));
1556 purple_stringref_unref(pathref
);
1560 if (_purple_fstat(file_fd
, &st
) == -1) {
1561 purple_stringref_unref(pathref
);
1566 log_last_modified
= st
.st_mtime
;
1568 /* Change the .log extension to .idx */
1569 strcpy(pathstr
+ strlen(pathstr
) - 3, "idx");
1571 index_fd
= g_open(pathstr
, 0, O_RDONLY
);
1572 if (index_fd
!= -1) {
1573 if (_purple_fstat(index_fd
, &st
) != 0) {
1579 if (index_fd
!= -1) {
1580 if (st
.st_mtime
< log_last_modified
)
1582 purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr
);
1587 /* The index file exists and is at least as new as the log, so open it. */
1588 if (!(index
= fdopen(index_fd
, "rb"))) {
1589 purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
1590 pathstr
, g_strerror(errno
));
1592 /* Fall through so that we'll parse the log file. */
1594 purple_debug_info("log", "Using index: %s\n", pathstr
);
1596 while (fgets(buf
, BUF_LONG
, index
))
1598 unsigned long idx_time
;
1599 if (sscanf(buf
, "%d\t%d\t%lu", &lastoff
, &newlen
, &idx_time
) == 3)
1601 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, NULL
);
1602 log
->logger
= old_logger
;
1603 log
->time
= g_date_time_new_from_unix_local(idx_time
);
1605 /* IMPORTANT: Always set all members of struct old_logger_data */
1606 data
= g_slice_new(struct old_logger_data
);
1608 data
->pathref
= purple_stringref_ref(pathref
);
1609 data
->offset
= lastoff
;
1610 data
->length
= newlen
;
1612 log
->logger_data
= data
;
1613 list
= g_list_prepend(list
, log
);
1617 purple_stringref_unref(pathref
);
1625 index_tmp
= g_strdup_printf("%s.XXXXXX", pathstr
);
1626 if ((index_fd
= g_mkstemp(index_tmp
)) == -1) {
1627 purple_debug_error("log", "Failed to open index temp file: %s\n",
1633 if ((index
= fdopen(index_fd
, "wb")) == NULL
)
1635 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1638 if (index_tmp
!= NULL
)
1640 g_unlink(index_tmp
);
1647 while (fgets(buf
, BUF_LONG
, file
)) {
1648 if (strstr(buf
, "---- New C") != NULL
) {
1651 char convostart
[32];
1652 char *temp
= strchr(buf
, '@');
1654 if (temp
== NULL
|| strlen(temp
) < 2)
1658 length
= strcspn(temp
, "-");
1659 if (length
> 31) length
= 31;
1661 offset
= ftell(file
);
1664 newlen
= offset
- lastoff
- length
;
1665 if(strstr(buf
, "----</H3><BR>")) {
1667 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1668 sizeof("----</H3><BR>") - 2;
1671 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1674 if(strchr(buf
, '\r'))
1678 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, lasttime
);
1679 log
->logger
= old_logger
;
1681 /* IMPORTANT: Always set all members of struct old_logger_data */
1682 data
= g_slice_new(struct old_logger_data
);
1684 data
->pathref
= purple_stringref_ref(pathref
);
1685 data
->offset
= lastoff
;
1686 data
->length
= newlen
;
1688 log
->logger_data
= data
;
1689 list
= g_list_prepend(list
, log
);
1692 fprintf(index
, "%d\t%d\t%lu\n", data
->offset
, data
->length
, (unsigned long)log
->time
);
1699 g_snprintf(convostart
, length
, "%s", temp
);
1700 year
= month
= day
= hour
= minute
= second
= 0;
1701 if (sscanf(convostart
, "%*s %3s %d %d:%d:%d %d", month_str
,
1702 &day
, &hour
, &minute
, &second
, &year
) != 6)
1704 purple_debug_warning("log", "invalid date format\n");
1706 /* Ugly hack, in case current locale is not English */
1707 if (purple_strequal(month_str
, "Jan")) {
1709 } else if (purple_strequal(month_str
, "Feb")) {
1711 } else if (purple_strequal(month_str
, "Mar")) {
1713 } else if (purple_strequal(month_str
, "Apr")) {
1715 } else if (purple_strequal(month_str
, "May")) {
1717 } else if (purple_strequal(month_str
, "Jun")) {
1719 } else if (purple_strequal(month_str
, "Jul")) {
1721 } else if (purple_strequal(month_str
, "Aug")) {
1723 } else if (purple_strequal(month_str
, "Sep")) {
1725 } else if (purple_strequal(month_str
, "Oct")) {
1727 } else if (purple_strequal(month_str
, "Nov")) {
1729 } else if (purple_strequal(month_str
, "Dec")) {
1733 g_date_time_unref(lasttime
);
1734 lasttime
= g_date_time_new_local(year
, month
, day
,
1735 hour
, minute
, second
);
1740 if ((newlen
= ftell(file
) - lastoff
) != 0) {
1741 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, lasttime
);
1742 log
->logger
= old_logger
;
1744 /* IMPORTANT: Always set all members of struct old_logger_data */
1745 data
= g_slice_new(struct old_logger_data
);
1747 data
->pathref
= purple_stringref_ref(pathref
);
1748 data
->offset
= lastoff
;
1749 data
->length
= newlen
;
1751 log
->logger_data
= data
;
1752 list
= g_list_prepend(list
, log
);
1755 fprintf(index
, "%d\t%d\t%lu\n", data
->offset
, data
->length
, (unsigned long)log
->time
);
1760 g_date_time_unref(lasttime
);
1761 purple_stringref_unref(pathref
);
1767 if (index_tmp
== NULL
)
1770 g_return_val_if_reached(list
);
1773 if (g_rename(index_tmp
, pathstr
))
1775 purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
1776 index_tmp
, pathstr
, g_strerror(errno
));
1777 g_unlink(index_tmp
);
1780 purple_debug_info("log", "Built index: %s\n", pathstr
);
1788 static int old_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1790 char *logfile
= g_strdup_printf("%s.log", purple_normalize(account
, name
));
1791 char *pathstr
= g_build_filename(purple_data_dir(), "logs", logfile
, NULL
);
1795 if (g_stat(pathstr
, &st
))
1806 static char * old_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1809 struct old_logger_data
*data
= log
->logger_data
;
1810 const char *path
= purple_stringref_value(data
->pathref
);
1811 FILE *file
= g_fopen(path
, "rb");
1814 g_return_val_if_fail(file
, g_strdup(""));
1815 read
= g_malloc(data
->length
+ 1);
1817 if (fseek(file
, data
->offset
, SEEK_SET
) != 0)
1820 result
= fread(read
, data
->length
, 1, file
);
1822 purple_debug_error("log", "Unable to read from log file: %s\n", path
);
1824 read
[data
->length
] = '\0';
1826 if (strstr(read
, "<BR>"))
1828 *flags
|= PURPLE_LOG_READ_NO_NEWLINE
;
1832 return process_txt_log(read
, NULL
);
1835 static int old_logger_size (PurpleLog
*log
)
1837 struct old_logger_data
*data
= log
->logger_data
;
1838 return data
? data
->length
: 0;
1841 static void old_logger_get_log_sets(PurpleLogSetCallback cb
, GHashTable
*sets
)
1843 char *log_path
= g_build_filename(purple_data_dir(), "logs", NULL
);
1844 GDir
*log_dir
= g_dir_open(log_path
, 0, NULL
);
1846 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
1849 if (log_dir
== NULL
)
1852 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
1853 while ((name
= (gchar
*)g_dir_read_name(log_dir
)) != NULL
) {
1857 gboolean found
= FALSE
;
1859 /* Unescape the filename. */
1860 name
= g_strdup(purple_unescape_filename(name
));
1862 /* Get the (possibly new) length of name. */
1870 /* Make sure we're dealing with a log file. */
1871 ext
= &name
[len
- 4];
1872 if (!purple_strequal(ext
, ".log")) {
1877 /* IMPORTANT: Always set all members of PurpleLogSet */
1878 set
= g_slice_new(PurpleLogSet
);
1880 /* Chat for .chat at the end of the name to determine the type. */
1882 set
->type
= PURPLE_LOG_IM
;
1884 char *tmp
= &name
[len
- 9];
1885 if (purple_strequal(tmp
, ".chat")) {
1886 set
->type
= PURPLE_LOG_CHAT
;
1891 set
->name
= set
->normalized_name
= name
;
1893 /* Search the buddy list to find the account and to determine if this is a buddy. */
1894 for (gnode
= purple_blist_get_root();
1895 !found
&& gnode
!= NULL
;
1896 gnode
= purple_blist_node_get_sibling_next(gnode
))
1898 if (!PURPLE_IS_GROUP(gnode
))
1901 for (cnode
= purple_blist_node_get_first_child(gnode
);
1902 !found
&& cnode
!= NULL
;
1903 cnode
= purple_blist_node_get_sibling_next(cnode
))
1905 if (!PURPLE_IS_CONTACT(cnode
))
1908 for (bnode
= purple_blist_node_get_first_child(cnode
);
1909 !found
&& bnode
!= NULL
;
1910 bnode
= purple_blist_node_get_sibling_next(bnode
))
1912 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
1914 if (purple_strequal(purple_buddy_get_name(buddy
), name
)) {
1915 set
->account
= purple_buddy_get_account(buddy
);
1925 set
->account
= NULL
;
1931 g_dir_close(log_dir
);
1934 static void old_logger_finalize(PurpleLog
*log
)
1936 struct old_logger_data
*data
= log
->logger_data
;
1937 purple_stringref_unref(data
->pathref
);
1938 g_slice_free(struct old_logger_data
, data
);