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
25 #include "glibcompat.h"
26 #include "image-store.h"
30 #include "stringref.h"
33 static GSList
*loggers
= NULL
;
35 static PurpleLogLogger
*html_logger
;
36 static PurpleLogLogger
*txt_logger
;
37 static PurpleLogLogger
*old_logger
;
39 struct _purple_logsize_user
{
41 PurpleAccount
*account
;
43 static GHashTable
*logsize_users
= NULL
;
44 static GHashTable
*logsize_users_decayed
= NULL
;
46 static void log_get_log_sets_common(GHashTable
*sets
);
48 static gsize
html_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
49 const char *from
, GDateTime
*time
, const char *message
);
50 static void html_logger_finalize(PurpleLog
*log
);
51 static GList
*html_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
52 static GList
*html_logger_list_syslog(PurpleAccount
*account
);
53 static char *html_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
);
54 static int html_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
56 static GList
*old_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
57 static int old_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
58 static char * old_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
);
59 static int old_logger_size (PurpleLog
*log
);
60 static void old_logger_get_log_sets(PurpleLogSetCallback cb
, GHashTable
*sets
);
61 static void old_logger_finalize(PurpleLog
*log
);
63 static gsize
txt_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
64 const char *from
, GDateTime
*time
, const char *message
);
65 static void txt_logger_finalize(PurpleLog
*log
);
66 static GList
*txt_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
);
67 static GList
*txt_logger_list_syslog(PurpleAccount
*account
);
68 static char *txt_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
);
69 static int txt_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
);
71 /**************************************************************************
72 * PUBLIC LOGGING FUNCTIONS ***********************************************
73 **************************************************************************/
75 PurpleLog
*purple_log_new(PurpleLogType type
, const char *name
, PurpleAccount
*account
,
76 PurpleConversation
*conv
, GDateTime
*time
)
80 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
81 log
= g_slice_new(PurpleLog
);
84 log
->name
= g_strdup(purple_normalize(account
, name
));
85 log
->account
= account
;
88 log
->time
= g_date_time_ref(time
);
91 log
->logger
= purple_log_logger_get();
92 log
->logger_data
= NULL
;
94 if (log
->logger
&& log
->logger
->create
)
95 log
->logger
->create(log
);
99 void purple_log_free(PurpleLog
*log
)
101 g_return_if_fail(log
);
102 if (log
->logger
&& log
->logger
->finalize
)
103 log
->logger
->finalize(log
);
106 g_date_time_unref(log
->time
);
108 g_slice_free(PurpleLog
, log
);
111 void purple_log_write(PurpleLog
*log
, PurpleMessageFlags type
,
112 const char *from
, GDateTime
*time
, const char *message
)
114 struct _purple_logsize_user
*lu
;
115 gsize written
, total
= 0;
118 g_return_if_fail(log
);
119 g_return_if_fail(log
->logger
);
120 g_return_if_fail(log
->logger
->write
);
122 written
= (log
->logger
->write
)(log
, type
, from
, time
, message
);
124 lu
= g_new(struct _purple_logsize_user
, 1);
126 lu
->name
= g_strdup(purple_normalize(log
->account
, log
->name
));
127 lu
->account
= log
->account
;
129 if(g_hash_table_lookup_extended(logsize_users
, lu
, NULL
, &ptrsize
)) {
130 char *tmp
= lu
->name
;
132 total
= GPOINTER_TO_INT(ptrsize
);
134 g_hash_table_replace(logsize_users
, lu
, GINT_TO_POINTER(total
));
136 /* The hash table takes ownership of lu, so create a new one
137 * for the logsize_users_decayed check below. */
138 lu
= g_new(struct _purple_logsize_user
, 1);
139 lu
->name
= g_strdup(tmp
);
140 lu
->account
= log
->account
;
143 if(g_hash_table_lookup_extended(logsize_users_decayed
, lu
, NULL
, &ptrsize
)) {
144 total
= GPOINTER_TO_INT(ptrsize
);
146 g_hash_table_replace(logsize_users_decayed
, lu
, GINT_TO_POINTER(total
));
153 char *purple_log_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
155 PurpleLogReadFlags mflags
;
156 g_return_val_if_fail(log
&& log
->logger
, NULL
);
157 if (log
->logger
->read
) {
158 char *ret
= (log
->logger
->read
)(log
, flags
? flags
: &mflags
);
159 purple_str_strip_char(ret
, '\r');
162 return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>"));
165 int purple_log_get_size(PurpleLog
*log
)
167 g_return_val_if_fail(log
&& log
->logger
, 0);
169 if (log
->logger
->size
)
170 return log
->logger
->size(log
);
174 static guint
_purple_logsize_user_hash(struct _purple_logsize_user
*lu
)
176 return g_str_hash(lu
->name
);
179 static guint
_purple_logsize_user_equal(struct _purple_logsize_user
*lu1
,
180 struct _purple_logsize_user
*lu2
)
182 return (lu1
->account
== lu2
->account
&& purple_strequal(lu1
->name
, lu2
->name
));
185 static void _purple_logsize_user_free_key(struct _purple_logsize_user
*lu
)
191 int purple_log_get_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
196 struct _purple_logsize_user
*lu
;
198 lu
= g_new(struct _purple_logsize_user
, 1);
199 lu
->name
= g_strdup(purple_normalize(account
, name
));
200 lu
->account
= account
;
202 if(g_hash_table_lookup_extended(logsize_users
, lu
, NULL
, &ptrsize
)) {
203 size
= GPOINTER_TO_INT(ptrsize
);
207 for (n
= loggers
; n
; n
= n
->next
) {
208 PurpleLogLogger
*logger
= n
->data
;
210 if(logger
->total_size
){
211 size
+= (logger
->total_size
)(type
, name
, account
);
212 } else if(logger
->list
) {
213 GList
*logs
= (logger
->list
)(type
, name
, account
);
217 PurpleLog
*log
= (PurpleLog
*)(logs
->data
);
218 this_size
+= purple_log_get_size(log
);
219 purple_log_free(log
);
220 logs
= g_list_delete_link(logs
, logs
);
227 g_hash_table_replace(logsize_users
, lu
, GINT_TO_POINTER(size
));
232 gint
purple_log_get_activity_score(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
237 struct _purple_logsize_user
*lu
;
239 lu
= g_new(struct _purple_logsize_user
, 1);
240 lu
->name
= g_strdup(purple_normalize(account
, name
));
241 lu
->account
= account
;
243 if(g_hash_table_lookup_extended(logsize_users_decayed
, lu
, NULL
, &ptrscore
)) {
244 score
= GPOINTER_TO_INT(ptrscore
);
248 GDateTime
*now
= g_date_time_new_now_utc();
249 double score_double
= 0.0;
250 for (n
= loggers
; n
; n
= n
->next
) {
251 PurpleLogLogger
*logger
= n
->data
;
254 GList
*logs
= (logger
->list
)(type
, name
, account
);
257 PurpleLog
*log
= (PurpleLog
*)(logs
->data
);
262 /* Activity score counts bytes in the log, exponentially
263 decayed with a half-life of 14 days. */
264 score_double
+= purple_log_get_size(log
) *
265 pow(0.5, g_date_time_difference(now
, log
->time
)/(14LL*G_TIME_SPAN_DAY
));
266 purple_log_free(log
);
267 logs
= g_list_delete_link(logs
, logs
);
271 g_date_time_unref(now
);
273 score
= (gint
) ceil(score_double
);
274 g_hash_table_replace(logsize_users_decayed
, lu
, GINT_TO_POINTER(score
));
279 gboolean
purple_log_is_deletable(PurpleLog
*log
)
281 g_return_val_if_fail(log
!= NULL
, FALSE
);
282 g_return_val_if_fail(log
->logger
!= NULL
, FALSE
);
284 if (log
->logger
->remove
== NULL
)
287 if (log
->logger
->is_deletable
!= NULL
)
288 return log
->logger
->is_deletable(log
);
293 gboolean
purple_log_delete(PurpleLog
*log
)
295 g_return_val_if_fail(log
!= NULL
, FALSE
);
296 g_return_val_if_fail(log
->logger
!= NULL
, FALSE
);
298 if (log
->logger
->remove
!= NULL
)
299 return log
->logger
->remove(log
);
305 purple_log_get_log_dir(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
307 PurpleProtocol
*protocol
;
308 const char *protocol_name
;
313 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
317 protocol_name
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
319 acct_name
= g_strdup(purple_escape_filename(purple_normalize(account
,
320 purple_account_get_username(account
))));
322 if (type
== PURPLE_LOG_CHAT
) {
323 char *temp
= g_strdup_printf("%s.chat", purple_normalize(account
, name
));
324 target
= purple_escape_filename(temp
);
326 } else if(type
== PURPLE_LOG_SYSTEM
) {
329 target
= purple_escape_filename(purple_normalize(account
, name
));
332 dir
= g_build_filename(purple_data_dir(), "logs", protocol_name
, acct_name
, target
, NULL
);
339 /****************************************************************************
340 * LOGGER FUNCTIONS *********************************************************
341 ****************************************************************************/
343 static PurpleLogLogger
*current_logger
= NULL
;
345 static void logger_pref_cb(const char *name
, PurplePrefType type
,
346 gconstpointer value
, gpointer data
)
348 PurpleLogLogger
*logger
;
352 if (purple_strequal(logger
->id
, value
)) {
353 purple_log_logger_set(logger
);
358 purple_log_logger_set(txt_logger
);
362 PurpleLogLogger
*purple_log_logger_new(const char *id
, const char *name
, int functions
, ...)
364 PurpleLogLogger
*logger
;
367 g_return_val_if_fail(id
!= NULL
, NULL
);
368 g_return_val_if_fail(name
!= NULL
, NULL
);
369 g_return_val_if_fail(functions
>= 1, NULL
);
371 logger
= g_new0(PurpleLogLogger
, 1);
372 logger
->id
= g_strdup(id
);
373 logger
->name
= g_strdup(name
);
375 va_start(args
, functions
);
378 logger
->create
= va_arg(args
, void *);
380 logger
->write
= va_arg(args
, void *);
382 logger
->finalize
= va_arg(args
, void *);
384 logger
->list
= va_arg(args
, void *);
386 logger
->read
= va_arg(args
, void *);
388 logger
->size
= va_arg(args
, void *);
390 logger
->total_size
= va_arg(args
, void *);
392 logger
->list_syslog
= va_arg(args
, void *);
394 logger
->get_log_sets
= va_arg(args
, void *);
396 logger
->remove
= va_arg(args
, void *);
398 logger
->is_deletable
= va_arg(args
, void *);
401 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name
, id
);
408 void purple_log_logger_free(PurpleLogLogger
*logger
)
412 g_free(logger
->name
);
417 void purple_log_logger_add (PurpleLogLogger
*logger
)
419 g_return_if_fail(logger
);
420 if (g_slist_find(loggers
, logger
))
422 loggers
= g_slist_append(loggers
, logger
);
423 if (purple_strequal(purple_prefs_get_string("/purple/logging/format"), logger
->id
)) {
424 purple_prefs_trigger_callback("/purple/logging/format");
428 void purple_log_logger_remove (PurpleLogLogger
*logger
)
430 g_return_if_fail(logger
);
431 loggers
= g_slist_remove(loggers
, logger
);
434 void purple_log_logger_set (PurpleLogLogger
*logger
)
436 g_return_if_fail(logger
);
437 current_logger
= logger
;
440 PurpleLogLogger
*purple_log_logger_get()
442 return current_logger
;
445 GList
*purple_log_logger_get_options(void)
449 PurpleLogLogger
*data
;
451 for (n
= loggers
; n
; n
= n
->next
) {
455 list
= g_list_append(list
, data
->name
);
456 list
= g_list_append(list
, data
->id
);
462 gint
purple_log_compare(gconstpointer y
, gconstpointer z
)
464 const PurpleLog
*a
= y
;
465 const PurpleLog
*b
= z
;
467 /* Sort in reverse order. */
468 return g_date_time_compare(b
->time
, a
->time
);
471 GList
*purple_log_get_logs(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
475 for (n
= loggers
; n
; n
= n
->next
) {
476 PurpleLogLogger
*logger
= n
->data
;
479 logs
= g_list_concat(logger
->list(type
, name
, account
), logs
);
482 return g_list_sort(logs
, purple_log_compare
);
485 gint
purple_log_set_compare(gconstpointer y
, gconstpointer z
)
487 const PurpleLogSet
*a
= y
;
488 const PurpleLogSet
*b
= z
;
491 /* This logic seems weird at first...
492 * If either account is NULL, we pretend the accounts are
493 * equal. This allows us to detect duplicates that will
494 * exist if one logger knows the account and another
496 if (a
->account
!= NULL
&& b
->account
!= NULL
) {
497 ret
= strcmp(purple_account_get_username(a
->account
), purple_account_get_username(b
->account
));
502 ret
= strcmp(a
->normalized_name
, b
->normalized_name
);
506 return (gint
)b
->type
- (gint
)a
->type
;
510 log_set_hash(gconstpointer key
)
512 const PurpleLogSet
*set
= key
;
514 /* The account isn't hashed because we need PurpleLogSets with NULL accounts
515 * to be found when we search by a PurpleLogSet that has a non-NULL account
516 * but the same type and name. */
517 return g_int_hash(&set
->type
) + g_str_hash(set
->name
);
521 log_set_equal(gconstpointer a
, gconstpointer b
)
523 /* I realize that the choices made for GList and GHashTable
524 * make sense for those data types, but I wish the comparison
525 * functions were compatible. */
526 return !purple_log_set_compare(a
, b
);
530 log_add_log_set_to_hash(GHashTable
*sets
, PurpleLogSet
*set
)
532 PurpleLogSet
*existing_set
= g_hash_table_lookup(sets
, set
);
534 if (existing_set
== NULL
)
535 g_hash_table_insert(sets
, set
, set
);
536 else if (existing_set
->account
== NULL
&& set
->account
!= NULL
)
537 g_hash_table_replace(sets
, set
, set
);
539 purple_log_set_free(set
);
542 GHashTable
*purple_log_get_log_sets(void)
545 GHashTable
*sets
= g_hash_table_new_full(log_set_hash
, log_set_equal
,
546 (GDestroyNotify
)purple_log_set_free
, NULL
);
548 /* Get the log sets from all the loggers. */
549 for (n
= loggers
; n
; n
= n
->next
) {
550 PurpleLogLogger
*logger
= n
->data
;
552 if (!logger
->get_log_sets
)
555 logger
->get_log_sets(log_add_log_set_to_hash
, sets
);
558 log_get_log_sets_common(sets
);
560 /* Return the GHashTable of unique PurpleLogSets. */
564 void purple_log_set_free(PurpleLogSet
*set
)
566 g_return_if_fail(set
!= NULL
);
569 if (set
->normalized_name
!= set
->name
)
570 g_free(set
->normalized_name
);
572 g_slice_free(PurpleLogSet
, set
);
575 GList
*purple_log_get_system_logs(PurpleAccount
*account
)
579 for (n
= loggers
; n
; n
= n
->next
) {
580 PurpleLogLogger
*logger
= n
->data
;
581 if (!logger
->list_syslog
)
583 logs
= g_list_concat(logger
->list_syslog(account
), logs
);
586 return g_list_sort(logs
, purple_log_compare
);
589 /****************************************************************************
590 * LOG SUBSYSTEM ************************************************************
591 ****************************************************************************/
594 purple_log_get_handle(void)
601 void purple_log_init(void)
603 void *handle
= purple_log_get_handle();
605 purple_prefs_add_none("/purple/logging");
606 purple_prefs_add_bool("/purple/logging/log_ims", TRUE
);
607 purple_prefs_add_bool("/purple/logging/log_chats", TRUE
);
608 purple_prefs_add_bool("/purple/logging/log_system", FALSE
);
610 purple_prefs_add_string("/purple/logging/format", "html");
612 html_logger
= purple_log_logger_new("html", _("HTML"), 11,
615 html_logger_finalize
,
618 purple_log_common_sizer
,
619 html_logger_total_size
,
620 html_logger_list_syslog
,
622 purple_log_common_deleter
,
623 purple_log_common_is_deletable
);
624 purple_log_logger_add(html_logger
);
626 txt_logger
= purple_log_logger_new("txt", _("Plain text"), 11,
632 purple_log_common_sizer
,
633 txt_logger_total_size
,
634 txt_logger_list_syslog
,
636 purple_log_common_deleter
,
637 purple_log_common_is_deletable
);
638 purple_log_logger_add(txt_logger
);
640 old_logger
= purple_log_logger_new("old", _("Old flat format"), 9,
647 old_logger_total_size
,
649 old_logger_get_log_sets
);
650 purple_log_logger_add(old_logger
);
652 purple_signal_register(handle
, "log-timestamp",
653 purple_marshal_POINTER__POINTER_POINTER_BOOLEAN
,
659 purple_prefs_connect_callback(NULL
, "/purple/logging/format",
660 logger_pref_cb
, NULL
);
661 purple_prefs_trigger_callback("/purple/logging/format");
663 logsize_users
= g_hash_table_new_full((GHashFunc
)_purple_logsize_user_hash
,
664 (GEqualFunc
)_purple_logsize_user_equal
,
665 (GDestroyNotify
)_purple_logsize_user_free_key
, NULL
);
666 logsize_users_decayed
= g_hash_table_new_full((GHashFunc
)_purple_logsize_user_hash
,
667 (GEqualFunc
)_purple_logsize_user_equal
,
668 (GDestroyNotify
)_purple_logsize_user_free_key
, NULL
);
672 purple_log_uninit(void)
674 purple_signals_unregister_by_instance(purple_log_get_handle());
676 purple_log_logger_remove(html_logger
);
677 purple_log_logger_free(html_logger
);
680 purple_log_logger_remove(txt_logger
);
681 purple_log_logger_free(txt_logger
);
684 purple_log_logger_remove(old_logger
);
685 purple_log_logger_free(old_logger
);
688 g_hash_table_destroy(logsize_users
);
689 g_hash_table_destroy(logsize_users_decayed
);
693 purple_log_copy(PurpleLog
*log
)
697 g_return_val_if_fail(log
!= NULL
, NULL
);
699 log_copy
= g_new(PurpleLog
, 1);
706 purple_log_get_type(void)
708 static GType type
= 0;
711 type
= g_boxed_type_register_static("PurpleLog",
712 (GBoxedCopyFunc
)purple_log_copy
,
713 (GBoxedFreeFunc
)g_free
);
719 /****************************************************************************
720 * LOGGERS ******************************************************************
721 ****************************************************************************/
723 static char *log_get_timestamp(PurpleLog
*log
, GDateTime
*when
)
729 dt
= g_date_time_new_now_utc();
730 show_date
= (log
->type
== PURPLE_LOG_SYSTEM
) || (g_date_time_difference(dt
, when
) > 20L * G_TIME_SPAN_MINUTE
);
731 g_date_time_unref(dt
);
733 date
= purple_signal_emit_return_1(purple_log_get_handle(),
735 log
, when
, show_date
);
739 dt
= g_date_time_to_local(when
);
741 date
= g_date_time_format(dt
, _("%x %X"));
743 date
= g_date_time_format(dt
, "%X");
744 g_date_time_unref(dt
);
749 /* NOTE: This can return msg (which you may or may not want to g_free())
750 * NOTE: or a newly allocated string which you MUST g_free().
751 * TODO: XXX: does it really works?
754 convert_image_tags(const PurpleLog
*log
, const char *msg
)
760 GString
*newmsg
= NULL
;
764 while (purple_markup_find_tag("img", tmp
, &start
, &end
, &attributes
)) {
769 newmsg
= g_string_new("");
771 /* copy any text before the img tag */
773 g_string_append_len(newmsg
, tmp
, start
- tmp
);
775 if ((idstr
= g_datalist_get_data(&attributes
, "id")) != NULL
)
783 gconstpointer image_data
;
784 const gchar
*new_filename
= NULL
;
786 size_t image_byte_count
;
788 image
= purple_image_store_get(imgid
);
791 /* This should never happen. */
792 /* This *does* happen for failed Direct-IMs -DAA */
793 g_string_free(newmsg
, TRUE
);
794 g_return_val_if_reached((char *)msg
);
797 image_data
= purple_image_get_data(image
);
798 image_byte_count
= purple_image_get_data_size(image
);
799 dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
800 new_filename
= purple_image_generate_filename(image
);
802 path
= g_build_filename(dir
, new_filename
, NULL
);
804 /* Only save unique files. */
805 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
807 if ((image_file
= g_fopen(path
, "wb")) != NULL
)
809 if (!fwrite(image_data
, image_byte_count
, 1, image_file
))
811 purple_debug_error("log", "Error writing %s: %s\n",
812 path
, g_strerror(errno
));
815 /* Attempt to not leave half-written files around. */
816 if (g_unlink(path
)) {
817 purple_debug_error("log", "Error deleting partial "
818 "file %s: %s\n", path
, g_strerror(errno
));
823 purple_debug_info("log", "Wrote image file: %s\n", path
);
829 purple_debug_error("log", "Unable to create file %s: %s\n",
830 path
, g_strerror(errno
));
834 /* Write the new image tag */
835 g_string_append_printf(newmsg
, "<img src=\"%s\">", new_filename
);
840 /* Continue from the end of the tag */
846 /* No images were found to change. */
850 /* Append any remaining message data */
851 g_string_append(newmsg
, tmp
);
853 return g_string_free(newmsg
, FALSE
);
856 void purple_log_common_writer(PurpleLog
*log
, const char *ext
)
858 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
862 /* This log is new */
870 dir
= purple_log_get_log_dir(log
->type
, log
->name
, log
->account
);
874 purple_build_dir (dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
876 dt
= g_date_time_to_local(log
->time
);
877 tz
= purple_escape_filename(g_date_time_get_timezone_abbreviation(dt
));
878 date
= g_date_time_format(dt
, "%Y-%m-%d.%H%M%S%z");
879 g_date_time_unref(dt
);
881 filename
= g_strdup_printf("%s%s%s", date
, tz
, ext
? ext
: "");
883 path
= g_build_filename(dir
, filename
, NULL
);
888 log
->logger_data
= data
= g_slice_new0(PurpleLogCommonLoggerData
);
890 data
->file
= g_fopen(path
, "a");
891 if (data
->file
== NULL
)
893 purple_debug(PURPLE_DEBUG_ERROR
, "log",
894 "Could not create log file %s\n", path
);
896 if (log
->conv
!= NULL
)
897 purple_conversation_write_system_message(log
->conv
,
898 _("Logging of this conversation failed."),
899 PURPLE_MESSAGE_ERROR
);
908 GList
*purple_log_common_lister(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
, PurpleLogLogger
*logger
)
912 const char *filename
;
918 path
= purple_log_get_log_dir(type
, name
, account
);
922 if (!(dir
= g_dir_open(path
, 0, NULL
)))
928 while ((filename
= g_dir_read_name(dir
)))
930 if (purple_str_has_suffix(filename
, ext
) &&
931 strlen(filename
) >= (17 + strlen(ext
)))
934 PurpleLogCommonLoggerData
*data
;
935 GDateTime
*stamp
= purple_str_to_date_time(purple_unescape_filename(filename
), FALSE
);
937 log
= purple_log_new(type
, name
, account
, NULL
, stamp
);
938 log
->logger
= logger
;
939 log
->logger_data
= data
= g_slice_new0(PurpleLogCommonLoggerData
);
941 data
->path
= g_build_filename(path
, filename
, NULL
);
942 list
= g_list_prepend(list
, log
);
944 g_date_time_unref(stamp
);
952 int purple_log_common_total_sizer(PurpleLogType type
, const char *name
, PurpleAccount
*account
, const char *ext
)
956 const char *filename
;
962 path
= purple_log_get_log_dir(type
, name
, account
);
966 if (!(dir
= g_dir_open(path
, 0, NULL
)))
972 while ((filename
= g_dir_read_name(dir
)))
974 if (purple_str_has_suffix(filename
, ext
) &&
975 strlen(filename
) >= (17 + strlen(ext
)))
977 char *tmp
= g_build_filename(path
, filename
, NULL
);
979 if (g_stat(tmp
, &st
))
981 purple_debug_error("log", "Error stating log file: %s\n", tmp
);
994 int purple_log_common_sizer(PurpleLog
*log
)
997 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
999 g_return_val_if_fail(data
!= NULL
, 0);
1001 if (!data
->path
|| g_stat(data
->path
, &st
))
1007 /* This will build log sets for all loggers that use the common logger
1008 * functions because they use the same directory structure. */
1009 static void log_get_log_sets_common(GHashTable
*sets
)
1011 gchar
*log_path
= g_build_filename(purple_data_dir(), "logs", NULL
);
1012 GDir
*log_dir
= g_dir_open(log_path
, 0, NULL
);
1013 const gchar
*protocol
;
1015 if (log_dir
== NULL
) {
1020 while ((protocol
= g_dir_read_name(log_dir
)) != NULL
) {
1021 gchar
*protocol_path
= g_build_filename(log_path
, protocol
, NULL
);
1023 const gchar
*username
;
1024 gchar
*protocol_unescaped
;
1025 GList
*account_iter
;
1026 GList
*accounts
= NULL
;
1028 if ((protocol_dir
= g_dir_open(protocol_path
, 0, NULL
)) == NULL
) {
1029 g_free(protocol_path
);
1033 /* Using g_strdup() to cover the one-in-a-million chance that a
1034 * protocol's list_icon function uses purple_unescape_filename(). */
1035 protocol_unescaped
= g_strdup(purple_unescape_filename(protocol
));
1037 /* Find all the accounts for protocol. */
1038 for (account_iter
= purple_accounts_get_all() ; account_iter
!= NULL
; account_iter
= account_iter
->next
) {
1039 PurpleProtocol
*protocol
;
1041 protocol
= purple_protocols_find(purple_account_get_protocol_id((PurpleAccount
*)account_iter
->data
));
1045 if (purple_strequal(protocol_unescaped
, purple_protocol_class_list_icon(protocol
, (PurpleAccount
*)account_iter
->data
, NULL
)))
1046 accounts
= g_list_prepend(accounts
, account_iter
->data
);
1048 g_free(protocol_unescaped
);
1050 while ((username
= g_dir_read_name(protocol_dir
)) != NULL
) {
1051 gchar
*username_path
= g_build_filename(protocol_path
, username
, NULL
);
1053 const gchar
*username_unescaped
;
1054 PurpleAccount
*account
= NULL
;
1057 if ((username_dir
= g_dir_open(username_path
, 0, NULL
)) == NULL
) {
1058 g_free(username_path
);
1062 /* Find the account for username in the list of accounts for protocol. */
1063 username_unescaped
= purple_unescape_filename(username
);
1064 for (account_iter
= g_list_first(accounts
) ; account_iter
!= NULL
; account_iter
= account_iter
->next
) {
1065 if (purple_strequal(purple_account_get_username((PurpleAccount
*)account_iter
->data
), username_unescaped
)) {
1066 account
= account_iter
->data
;
1071 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
1072 while ((name
= (gchar
*)g_dir_read_name(username_dir
)) != NULL
) {
1076 /* IMPORTANT: Always initialize all members of PurpleLogSet */
1077 set
= g_slice_new(PurpleLogSet
);
1079 /* Unescape the filename. */
1080 name
= g_strdup(purple_unescape_filename(name
));
1082 /* Get the (possibly new) length of name. */
1085 set
->type
= PURPLE_LOG_IM
;
1087 set
->account
= account
;
1088 /* set->buddy is always set below */
1089 set
->normalized_name
= g_strdup(purple_normalize(account
, name
));
1091 /* Check for .chat or .system at the end of the name to determine the type. */
1093 gchar
*tmp
= &name
[len
- 7];
1094 if (purple_strequal(tmp
, ".system")) {
1095 set
->type
= PURPLE_LOG_SYSTEM
;
1100 gchar
*tmp
= &name
[len
- 5];
1101 if (purple_strequal(tmp
, ".chat")) {
1102 set
->type
= PURPLE_LOG_CHAT
;
1107 /* Determine if this (account, name) combination exists as a buddy. */
1108 if (account
!= NULL
&& *name
!= '\0')
1109 set
->buddy
= (purple_blist_find_buddy(account
, name
) != NULL
);
1113 log_add_log_set_to_hash(sets
, set
);
1115 g_free(username_path
);
1116 g_dir_close(username_dir
);
1118 g_free(protocol_path
);
1119 g_list_free(accounts
);
1120 g_dir_close(protocol_dir
);
1123 g_dir_close(log_dir
);
1126 gboolean
purple_log_common_deleter(PurpleLog
*log
)
1128 PurpleLogCommonLoggerData
*data
;
1131 g_return_val_if_fail(log
!= NULL
, FALSE
);
1133 data
= log
->logger_data
;
1137 if (data
->path
== NULL
)
1140 ret
= g_unlink(data
->path
);
1145 purple_debug_error("log", "Failed to delete: %s - %s\n", data
->path
, g_strerror(errno
));
1149 /* I'm not sure that g_unlink() will ever return
1150 * something other than 0 or -1. -- rlaager */
1151 purple_debug_error("log", "Failed to delete: %s\n", data
->path
);
1157 gboolean
purple_log_common_is_deletable(PurpleLog
*log
)
1159 PurpleLogCommonLoggerData
*data
;
1164 g_return_val_if_fail(log
!= NULL
, FALSE
);
1166 data
= log
->logger_data
;
1170 if (data
->path
== NULL
)
1174 dirname
= g_path_get_dirname(data
->path
);
1175 if (g_access(dirname
, W_OK
) == 0)
1180 purple_debug_info("log", "access(%s) failed: %s\n", dirname
, g_strerror(errno
));
1183 /* Unless and until someone writes equivalent win32 code,
1184 * we'll assume the file is deletable. */
1191 static char *process_txt_log(char *txt
, char *to_free
)
1195 /* The to_free argument allows us to save a
1196 * g_strdup() in some cases. */
1198 if (to_free
== NULL
)
1201 /* g_markup_escape_text requires valid UTF-8 */
1202 if (!g_utf8_validate(txt
, -1, NULL
))
1204 tmp
= purple_utf8_salvage(txt
);
1206 to_free
= txt
= tmp
;
1209 tmp
= g_markup_escape_text(txt
, -1);
1211 txt
= purple_markup_linkify(tmp
);
1217 /****************************
1218 ** HTML LOGGER *************
1219 ****************************/
1221 static gsize
html_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
1222 const char *from
, GDateTime
*time
, const char *message
)
1225 char *image_corrected_msg
;
1229 PurpleProtocol
*protocol
=
1230 purple_protocols_find(purple_account_get_protocol_id(log
->account
));
1231 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1235 const char *proto
= purple_protocol_class_list_icon(protocol
, log
->account
, NULL
);
1238 purple_log_common_writer(log
, ".html");
1240 data
= log
->logger_data
;
1242 /* if we can't write to the file, give up before we hurt ourselves */
1246 dt
= g_date_time_to_local(log
->time
);
1247 date
= g_date_time_format(dt
, "%c");
1248 g_date_time_unref(dt
);
1250 written
+= fprintf(data
->file
, "<html><head>");
1251 written
+= fprintf(data
->file
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
1252 written
+= fprintf(data
->file
, "<title>");
1253 if (log
->type
== PURPLE_LOG_SYSTEM
)
1254 header
= g_strdup_printf("System log for account %s (%s) connected at %s",
1255 purple_account_get_username(log
->account
), proto
, date
);
1257 header
= g_strdup_printf("Conversation with %s at %s on %s (%s)",
1258 log
->name
, date
, purple_account_get_username(log
->account
), proto
);
1260 written
+= fprintf(data
->file
, "%s", header
);
1261 written
+= fprintf(data
->file
, "</title></head><body>");
1262 written
+= fprintf(data
->file
, "<h3>%s</h3>\n", header
);
1267 /* if we can't write to the file, give up before we hurt ourselves */
1271 escaped_from
= g_markup_escape_text(from
!= NULL
? from
: "<NULL>",
1274 image_corrected_msg
= convert_image_tags(log
, message
);
1275 purple_markup_html_to_xhtml(image_corrected_msg
, &msg_fixed
, NULL
);
1277 /* Yes, this breaks encapsulation. But it's a static function and
1278 * this saves a needless strdup(). */
1279 if (image_corrected_msg
!= message
)
1280 g_free(image_corrected_msg
);
1282 date
= log_get_timestamp(log
, time
);
1284 if(log
->type
== PURPLE_LOG_SYSTEM
){
1285 written
+= fprintf(data
->file
, "---- %s @ %s ----<br/>\n", msg_fixed
, date
);
1287 if (type
& PURPLE_MESSAGE_SYSTEM
)
1288 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date
, msg_fixed
);
1289 else if (type
& PURPLE_MESSAGE_RAW
)
1290 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font> %s<br/>\n", date
, msg_fixed
);
1291 else if (type
& PURPLE_MESSAGE_ERROR
)
1292 written
+= fprintf(data
->file
, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date
, msg_fixed
);
1293 else if (type
& PURPLE_MESSAGE_AUTO_RESP
) {
1294 if (type
& PURPLE_MESSAGE_SEND
)
1295 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
);
1296 else if (type
& PURPLE_MESSAGE_RECV
)
1297 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
);
1298 } else if (type
& PURPLE_MESSAGE_RECV
) {
1299 if(purple_message_meify(msg_fixed
, -1))
1300 written
+= fprintf(data
->file
, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1301 date
, escaped_from
, msg_fixed
);
1303 written
+= fprintf(data
->file
, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1304 date
, escaped_from
, msg_fixed
);
1305 } else if (type
& PURPLE_MESSAGE_SEND
) {
1306 if(purple_message_meify(msg_fixed
, -1))
1307 written
+= fprintf(data
->file
, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
1308 date
, escaped_from
, msg_fixed
);
1310 written
+= fprintf(data
->file
, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
1311 date
, escaped_from
, msg_fixed
);
1313 purple_debug_error("log", "Unhandled message type.\n");
1314 written
+= fprintf(data
->file
, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
1315 date
, escaped_from
, msg_fixed
);
1320 g_free(escaped_from
);
1326 static void html_logger_finalize(PurpleLog
*log
)
1328 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1331 fprintf(data
->file
, "</body></html>\n");
1336 g_slice_free(PurpleLogCommonLoggerData
, data
);
1340 static GList
*html_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1342 return purple_log_common_lister(type
, sn
, account
, ".html", html_logger
);
1345 static GList
*html_logger_list_syslog(PurpleAccount
*account
)
1347 return purple_log_common_lister(PURPLE_LOG_SYSTEM
, ".system", account
, ".html", html_logger
);
1350 static char *html_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1353 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1354 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
1355 if (!data
|| !data
->path
)
1356 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1357 if (g_file_get_contents(data
->path
, &read
, NULL
, NULL
)) {
1358 char *minus_header
= strchr(read
, '\n');
1363 minus_header
= g_strdup(minus_header
+ 1);
1366 return minus_header
;
1368 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data
->path
);
1371 static int html_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1373 return purple_log_common_total_sizer(type
, name
, account
, ".html");
1377 /****************************
1378 ** PLAIN TEXT LOGGER *******
1379 ****************************/
1381 static gsize
txt_logger_write(PurpleLog
*log
, PurpleMessageFlags type
,
1382 const char *from
, GDateTime
*time
, const char *message
)
1385 PurpleProtocol
*protocol
=
1386 purple_protocols_find(purple_account_get_protocol_id(log
->account
));
1387 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1388 char *stripped
= NULL
;
1393 /* This log is new. We could use the loggers 'new' function, but
1394 * creating a new file there would result in empty files in the case
1395 * that you open a convo with someone, but don't say anything.
1397 const char *proto
= purple_protocol_class_list_icon(protocol
, log
->account
, NULL
);
1400 purple_log_common_writer(log
, ".txt");
1402 data
= log
->logger_data
;
1404 /* if we can't write to the file, give up before we hurt ourselves */
1405 if(!data
|| !data
->file
)
1408 dt
= g_date_time_to_local(log
->time
);
1409 date
= g_date_time_format(dt
, "%c");
1410 if (log
->type
== PURPLE_LOG_SYSTEM
)
1411 written
+= fprintf(data
->file
, "System log for account %s (%s) connected at %s\n",
1412 purple_account_get_username(log
->account
), proto
,
1415 written
+= fprintf(data
->file
, "Conversation with %s at %s on %s (%s)\n",
1417 purple_account_get_username(log
->account
), proto
);
1419 g_date_time_unref(dt
);
1422 /* if we can't write to the file, give up before we hurt ourselves */
1426 stripped
= purple_markup_strip_html(message
);
1427 date
= log_get_timestamp(log
, time
);
1429 if(log
->type
== PURPLE_LOG_SYSTEM
){
1430 written
+= fprintf(data
->file
, "---- %s @ %s ----\n", stripped
, date
);
1432 if (type
& PURPLE_MESSAGE_SEND
||
1433 type
& PURPLE_MESSAGE_RECV
) {
1434 if (type
& PURPLE_MESSAGE_AUTO_RESP
) {
1435 written
+= fprintf(data
->file
, _("(%s) %s <AUTO-REPLY>: %s\n"), date
,
1438 if(purple_message_meify(stripped
, -1))
1439 written
+= fprintf(data
->file
, "(%s) ***%s %s\n", date
, from
,
1442 written
+= fprintf(data
->file
, "(%s) %s: %s\n", date
, from
,
1445 } else if (type
& PURPLE_MESSAGE_SYSTEM
||
1446 type
& PURPLE_MESSAGE_ERROR
||
1447 type
& PURPLE_MESSAGE_RAW
)
1448 written
+= fprintf(data
->file
, "(%s) %s\n", date
, stripped
);
1449 else if (type
& PURPLE_MESSAGE_NO_LOG
) {
1450 /* This shouldn't happen */
1454 written
+= fprintf(data
->file
, "(%s) %s%s %s\n", date
, from
? from
: "",
1455 from
? ":" : "", stripped
);
1464 static void txt_logger_finalize(PurpleLog
*log
)
1466 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1472 g_slice_free(PurpleLogCommonLoggerData
, data
);
1476 static GList
*txt_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1478 return purple_log_common_lister(type
, sn
, account
, ".txt", txt_logger
);
1481 static GList
*txt_logger_list_syslog(PurpleAccount
*account
)
1483 return purple_log_common_lister(PURPLE_LOG_SYSTEM
, ".system", account
, ".txt", txt_logger
);
1486 static char *txt_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1488 char *read
, *minus_header
;
1489 PurpleLogCommonLoggerData
*data
= log
->logger_data
;
1491 if (!data
|| !data
->path
)
1492 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
1493 if (g_file_get_contents(data
->path
, &read
, NULL
, NULL
)) {
1494 minus_header
= strchr(read
, '\n');
1497 return process_txt_log(minus_header
+ 1, read
);
1499 return process_txt_log(read
, NULL
);
1501 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data
->path
);
1504 static int txt_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1506 return purple_log_common_total_sizer(type
, name
, account
, ".txt");
1514 /* The old logger doesn't write logs, only reads them. This is to include
1515 * old logs in the log viewer transparently.
1518 struct old_logger_data
{
1519 PurpleStringref
*pathref
;
1524 static GList
*old_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1526 char *logfile
= g_strdup_printf("%s.log", purple_normalize(account
, sn
));
1527 char *pathstr
= g_build_filename(purple_data_dir(), "logs", logfile
, NULL
);
1528 PurpleStringref
*pathref
= purple_stringref_new(pathstr
);
1530 time_t log_last_modified
;
1533 int file_fd
, index_fd
;
1536 gint year
, month
, day
, hour
, minute
, second
;
1538 struct old_logger_data
*data
= NULL
;
1542 GDateTime
*lasttime
= NULL
;
1544 PurpleLog
*log
= NULL
;
1549 file_fd
= g_open(purple_stringref_value(pathref
), 0, O_RDONLY
);
1550 if (file_fd
== -1 || (file
= fdopen(file_fd
, "rb")) == NULL
) {
1551 purple_debug_error("log",
1552 "Failed to open log file \"%s\" for reading: %s\n",
1553 purple_stringref_value(pathref
), g_strerror(errno
));
1554 purple_stringref_unref(pathref
);
1558 if (_purple_fstat(file_fd
, &st
) == -1) {
1559 purple_stringref_unref(pathref
);
1564 log_last_modified
= st
.st_mtime
;
1566 /* Change the .log extension to .idx */
1567 strcpy(pathstr
+ strlen(pathstr
) - 3, "idx");
1569 index_fd
= g_open(pathstr
, 0, O_RDONLY
);
1570 if (index_fd
!= -1) {
1571 if (_purple_fstat(index_fd
, &st
) != 0) {
1577 if (index_fd
!= -1) {
1578 if (st
.st_mtime
< log_last_modified
)
1580 purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr
);
1585 /* The index file exists and is at least as new as the log, so open it. */
1586 if (!(index
= fdopen(index_fd
, "rb"))) {
1587 purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
1588 pathstr
, g_strerror(errno
));
1590 /* Fall through so that we'll parse the log file. */
1592 purple_debug_info("log", "Using index: %s\n", pathstr
);
1594 while (fgets(buf
, BUF_LONG
, index
))
1596 unsigned long idx_time
;
1597 if (sscanf(buf
, "%d\t%d\t%lu", &lastoff
, &newlen
, &idx_time
) == 3)
1599 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, NULL
);
1600 log
->logger
= old_logger
;
1601 log
->time
= g_date_time_new_from_unix_local(idx_time
);
1603 /* IMPORTANT: Always set all members of struct old_logger_data */
1604 data
= g_slice_new(struct old_logger_data
);
1606 data
->pathref
= purple_stringref_ref(pathref
);
1607 data
->offset
= lastoff
;
1608 data
->length
= newlen
;
1610 log
->logger_data
= data
;
1611 list
= g_list_prepend(list
, log
);
1615 purple_stringref_unref(pathref
);
1623 index_tmp
= g_strdup_printf("%s.XXXXXX", pathstr
);
1624 if ((index_fd
= g_mkstemp(index_tmp
)) == -1) {
1625 purple_debug_error("log", "Failed to open index temp file: %s\n",
1631 if ((index
= fdopen(index_fd
, "wb")) == NULL
)
1633 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1636 if (index_tmp
!= NULL
)
1638 g_unlink(index_tmp
);
1645 while (fgets(buf
, BUF_LONG
, file
)) {
1646 if (strstr(buf
, "---- New C") != NULL
) {
1649 char convostart
[32];
1650 char *temp
= strchr(buf
, '@');
1652 if (temp
== NULL
|| strlen(temp
) < 2)
1656 length
= strcspn(temp
, "-");
1657 if (length
> 31) length
= 31;
1659 offset
= ftell(file
);
1662 newlen
= offset
- lastoff
- length
;
1663 if(strstr(buf
, "----</H3><BR>")) {
1665 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1666 sizeof("----</H3><BR>") - 2;
1669 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1672 if(strchr(buf
, '\r'))
1676 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, lasttime
);
1677 log
->logger
= old_logger
;
1679 /* IMPORTANT: Always set all members of struct old_logger_data */
1680 data
= g_slice_new(struct old_logger_data
);
1682 data
->pathref
= purple_stringref_ref(pathref
);
1683 data
->offset
= lastoff
;
1684 data
->length
= newlen
;
1686 log
->logger_data
= data
;
1687 list
= g_list_prepend(list
, log
);
1690 fprintf(index
, "%d\t%d\t%lu\n", data
->offset
, data
->length
, (unsigned long)g_date_time_to_unix(log
->time
));
1697 g_snprintf(convostart
, length
, "%s", temp
);
1698 year
= month
= day
= hour
= minute
= second
= 0;
1699 if (sscanf(convostart
, "%*s %3s %d %d:%d:%d %d", month_str
,
1700 &day
, &hour
, &minute
, &second
, &year
) != 6)
1702 purple_debug_warning("log", "invalid date format\n");
1704 /* Ugly hack, in case current locale is not English */
1705 if (purple_strequal(month_str
, "Jan")) {
1707 } else if (purple_strequal(month_str
, "Feb")) {
1709 } else if (purple_strequal(month_str
, "Mar")) {
1711 } else if (purple_strequal(month_str
, "Apr")) {
1713 } else if (purple_strequal(month_str
, "May")) {
1715 } else if (purple_strequal(month_str
, "Jun")) {
1717 } else if (purple_strequal(month_str
, "Jul")) {
1719 } else if (purple_strequal(month_str
, "Aug")) {
1721 } else if (purple_strequal(month_str
, "Sep")) {
1723 } else if (purple_strequal(month_str
, "Oct")) {
1725 } else if (purple_strequal(month_str
, "Nov")) {
1727 } else if (purple_strequal(month_str
, "Dec")) {
1731 g_date_time_unref(lasttime
);
1732 lasttime
= g_date_time_new_local(year
, month
, day
,
1733 hour
, minute
, second
);
1738 if ((newlen
= ftell(file
) - lastoff
) != 0) {
1739 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, lasttime
);
1740 log
->logger
= old_logger
;
1742 /* IMPORTANT: Always set all members of struct old_logger_data */
1743 data
= g_slice_new(struct old_logger_data
);
1745 data
->pathref
= purple_stringref_ref(pathref
);
1746 data
->offset
= lastoff
;
1747 data
->length
= newlen
;
1749 log
->logger_data
= data
;
1750 list
= g_list_prepend(list
, log
);
1753 fprintf(index
, "%d\t%d\t%lu\n", data
->offset
, data
->length
, (unsigned long)log
->time
);
1758 g_date_time_unref(lasttime
);
1759 purple_stringref_unref(pathref
);
1765 if (index_tmp
== NULL
)
1768 g_return_val_if_reached(list
);
1771 if (g_rename(index_tmp
, pathstr
))
1773 purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
1774 index_tmp
, pathstr
, g_strerror(errno
));
1775 g_unlink(index_tmp
);
1778 purple_debug_info("log", "Built index: %s\n", pathstr
);
1786 static int old_logger_total_size(PurpleLogType type
, const char *name
, PurpleAccount
*account
)
1788 char *logfile
= g_strdup_printf("%s.log", purple_normalize(account
, name
));
1789 char *pathstr
= g_build_filename(purple_data_dir(), "logs", logfile
, NULL
);
1793 if (g_stat(pathstr
, &st
))
1804 static char * old_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1807 struct old_logger_data
*data
= log
->logger_data
;
1808 const char *path
= purple_stringref_value(data
->pathref
);
1809 FILE *file
= g_fopen(path
, "rb");
1812 g_return_val_if_fail(file
, g_strdup(""));
1813 read
= g_malloc(data
->length
+ 1);
1815 if (fseek(file
, data
->offset
, SEEK_SET
) != 0)
1818 result
= fread(read
, data
->length
, 1, file
);
1820 purple_debug_error("log", "Unable to read from log file: %s\n", path
);
1822 read
[data
->length
] = '\0';
1824 if (strstr(read
, "<BR>"))
1826 *flags
|= PURPLE_LOG_READ_NO_NEWLINE
;
1830 return process_txt_log(read
, NULL
);
1833 static int old_logger_size (PurpleLog
*log
)
1835 struct old_logger_data
*data
= log
->logger_data
;
1836 return data
? data
->length
: 0;
1839 static void old_logger_get_log_sets(PurpleLogSetCallback cb
, GHashTable
*sets
)
1841 char *log_path
= g_build_filename(purple_data_dir(), "logs", NULL
);
1842 GDir
*log_dir
= g_dir_open(log_path
, 0, NULL
);
1844 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
1847 if (log_dir
== NULL
)
1850 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
1851 while ((name
= (gchar
*)g_dir_read_name(log_dir
)) != NULL
) {
1855 gboolean found
= FALSE
;
1857 /* Unescape the filename. */
1858 name
= g_strdup(purple_unescape_filename(name
));
1860 /* Get the (possibly new) length of name. */
1868 /* Make sure we're dealing with a log file. */
1869 ext
= &name
[len
- 4];
1870 if (!purple_strequal(ext
, ".log")) {
1875 /* IMPORTANT: Always set all members of PurpleLogSet */
1876 set
= g_slice_new(PurpleLogSet
);
1878 /* Chat for .chat at the end of the name to determine the type. */
1880 set
->type
= PURPLE_LOG_IM
;
1882 char *tmp
= &name
[len
- 9];
1883 if (purple_strequal(tmp
, ".chat")) {
1884 set
->type
= PURPLE_LOG_CHAT
;
1889 set
->name
= set
->normalized_name
= name
;
1891 /* Search the buddy list to find the account and to determine if this is a buddy. */
1892 for (gnode
= purple_blist_get_default_root();
1893 !found
&& gnode
!= NULL
;
1894 gnode
= purple_blist_node_get_sibling_next(gnode
)) {
1895 if (!PURPLE_IS_GROUP(gnode
))
1898 for (cnode
= purple_blist_node_get_first_child(gnode
);
1899 !found
&& cnode
!= NULL
;
1900 cnode
= purple_blist_node_get_sibling_next(cnode
))
1902 if (!PURPLE_IS_CONTACT(cnode
))
1905 for (bnode
= purple_blist_node_get_first_child(cnode
);
1906 !found
&& bnode
!= NULL
;
1907 bnode
= purple_blist_node_get_sibling_next(bnode
))
1909 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
1911 if (purple_strequal(purple_buddy_get_name(buddy
), name
)) {
1912 set
->account
= purple_buddy_get_account(buddy
);
1922 set
->account
= NULL
;
1928 g_dir_close(log_dir
);
1931 static void old_logger_finalize(PurpleLog
*log
)
1933 struct old_logger_data
*data
= log
->logger_data
;
1934 purple_stringref_unref(data
->pathref
);
1935 g_slice_free(struct old_logger_data
, data
);