prefs: Convert Status/Idle page to glade.
[pidgin-git.git] / libpurple / log.c
blobdf1ee0ff346adafd1a7acc2a6836942cbdda4b9b
1 /* purple
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
5 * source distribution.
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
22 #include "internal.h"
23 #include "account.h"
24 #include "dbus-maybe.h"
25 #include "debug.h"
26 #include "glibcompat.h"
27 #include "image-store.h"
28 #include "log.h"
29 #include "prefs.h"
30 #include "util.h"
31 #include "stringref.h"
32 #include "time.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 {
41 char *name;
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)
79 PurpleLog *log;
81 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */
82 log = g_slice_new(PurpleLog);
83 PURPLE_DBUS_REGISTER_POINTER(log, PurpleLog);
85 log->type = type;
86 log->name = g_strdup(purple_normalize(account, name));
87 log->account = account;
88 log->conv = conv;
89 if (time)
90 log->time = g_date_time_ref(time);
91 else
92 log->time = NULL;
93 log->logger = purple_log_logger_get();
94 log->logger_data = NULL;
96 if (log->logger && log->logger->create)
97 log->logger->create(log);
98 return 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);
106 g_free(log->name);
107 if (log->time)
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;
119 gpointer ptrsize;
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);
136 total += written;
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);
148 total += written;
149 g_hash_table_replace(logsize_users_decayed, lu, GINT_TO_POINTER(total));
150 } else {
151 g_free(lu->name);
152 g_free(lu);
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');
163 return ret;
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);
174 return 0;
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)
190 g_free(lu->name);
191 g_free(lu);
194 int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
196 gpointer ptrsize;
197 int size = 0;
198 GSList *n;
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);
207 g_free(lu->name);
208 g_free(lu);
209 } else {
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);
217 int this_size = 0;
219 while (logs) {
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);
226 size += this_size;
230 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
232 return size;
235 gint purple_log_get_activity_score(PurpleLogType type, const char *name, PurpleAccount *account)
237 gpointer ptrscore;
238 int score;
239 GSList *n;
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);
248 g_free(lu->name);
249 g_free(lu);
250 } else {
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;
256 if(logger->list) {
257 GList *logs = (logger->list)(type, name, account);
259 while (logs) {
260 PurpleLog *log = (PurpleLog*)(logs->data);
261 if (!log) {
262 g_warn_if_reached();
263 continue;
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));
279 return 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)
288 return FALSE;
290 if (log->logger->is_deletable != NULL)
291 return log->logger->is_deletable(log);
293 return TRUE;
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);
304 return FALSE;
307 char *
308 purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account)
310 PurpleProtocol *protocol;
311 const char *protocol_name;
312 char *acct_name;
313 const char *target;
314 char *dir;
316 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
317 if (!protocol)
318 return NULL;
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);
328 g_free(temp);
329 } else if(type == PURPLE_LOG_SYSTEM) {
330 target = ".system";
331 } else {
332 target = purple_escape_filename(purple_normalize(account, name));
335 dir = g_build_filename(purple_data_dir(), "logs", protocol_name, acct_name, target, NULL);
337 g_free(acct_name);
339 return dir;
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;
352 GSList *l = loggers;
353 while (l) {
354 logger = l->data;
355 if (purple_strequal(logger->id, value)) {
356 purple_log_logger_set(logger);
357 return;
359 l = l->next;
361 purple_log_logger_set(txt_logger);
365 PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...)
367 PurpleLogLogger *logger;
368 va_list args;
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);
380 if (functions >= 1)
381 logger->create = va_arg(args, void *);
382 if (functions >= 2)
383 logger->write = va_arg(args, void *);
384 if (functions >= 3)
385 logger->finalize = va_arg(args, void *);
386 if (functions >= 4)
387 logger->list = va_arg(args, void *);
388 if (functions >= 5)
389 logger->read = va_arg(args, void *);
390 if (functions >= 6)
391 logger->size = va_arg(args, void *);
392 if (functions >= 7)
393 logger->total_size = va_arg(args, void *);
394 if (functions >= 8)
395 logger->list_syslog = va_arg(args, void *);
396 if (functions >= 9)
397 logger->get_log_sets = va_arg(args, void *);
398 if (functions >= 10)
399 logger->remove = va_arg(args, void *);
400 if (functions >= 11)
401 logger->is_deletable = va_arg(args, void *);
403 if (functions >= 12)
404 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id);
406 va_end(args);
408 return logger;
411 void purple_log_logger_free(PurpleLogLogger *logger)
413 if (!logger)
414 return;
415 g_free(logger->name);
416 g_free(logger->id);
417 g_free(logger);
420 void purple_log_logger_add (PurpleLogLogger *logger)
422 g_return_if_fail(logger);
423 if (g_slist_find(loggers, logger))
424 return;
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)
450 GSList *n;
451 GList *list = NULL;
452 PurpleLogLogger *data;
454 for (n = loggers; n; n = n->next) {
455 data = n->data;
456 if (!data->write)
457 continue;
458 list = g_list_append(list, data->name);
459 list = g_list_append(list, data->id);
462 return list;
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)
476 GList *logs = NULL;
477 GSList *n;
478 for (n = loggers; n; n = n->next) {
479 PurpleLogLogger *logger = n->data;
480 if (!logger->list)
481 continue;
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;
492 gint ret = 0;
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
498 * doesn't. */
499 if (a->account != NULL && b->account != NULL) {
500 ret = strcmp(purple_account_get_username(a->account), purple_account_get_username(b->account));
501 if (ret != 0)
502 return ret;
505 ret = strcmp(a->normalized_name, b->normalized_name);
506 if (ret != 0)
507 return ret;
509 return (gint)b->type - (gint)a->type;
512 static guint
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);
523 static gboolean
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);
532 static void
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);
541 else
542 purple_log_set_free(set);
545 GHashTable *purple_log_get_log_sets(void)
547 GSList *n;
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)
556 continue;
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. */
564 return sets;
567 void purple_log_set_free(PurpleLogSet *set)
569 g_return_if_fail(set != NULL);
571 g_free(set->name);
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)
580 GList *logs = NULL;
581 GSList *n;
582 for (n = loggers; n; n = n->next) {
583 PurpleLogLogger *logger = n->data;
584 if (!logger->list_syslog)
585 continue;
586 logs = g_list_concat(logger->list_syslog(account), logs);
589 return g_list_sort(logs, purple_log_compare);
592 /****************************************************************************
593 * LOG SUBSYSTEM ************************************************************
594 ****************************************************************************/
596 void *
597 purple_log_get_handle(void)
599 static int handle;
601 return &handle;
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,
616 NULL,
617 html_logger_write,
618 html_logger_finalize,
619 html_logger_list,
620 html_logger_read,
621 purple_log_common_sizer,
622 html_logger_total_size,
623 html_logger_list_syslog,
624 NULL,
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,
630 NULL,
631 txt_logger_write,
632 txt_logger_finalize,
633 txt_logger_list,
634 txt_logger_read,
635 purple_log_common_sizer,
636 txt_logger_total_size,
637 txt_logger_list_syslog,
638 NULL,
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,
644 NULL,
645 NULL,
646 old_logger_finalize,
647 old_logger_list,
648 old_logger_read,
649 old_logger_size,
650 old_logger_total_size,
651 NULL,
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,
657 G_TYPE_STRING, 3,
658 PURPLE_TYPE_LOG,
659 G_TYPE_OBJECT,
660 G_TYPE_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);
674 void
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);
681 html_logger = NULL;
683 purple_log_logger_remove(txt_logger);
684 purple_log_logger_free(txt_logger);
685 txt_logger = NULL;
687 purple_log_logger_remove(old_logger);
688 purple_log_logger_free(old_logger);
689 old_logger = NULL;
691 g_hash_table_destroy(logsize_users);
692 g_hash_table_destroy(logsize_users_decayed);
695 static PurpleLog *
696 purple_log_copy(PurpleLog *log)
698 PurpleLog *log_copy;
700 g_return_val_if_fail(log != NULL, NULL);
702 log_copy = g_new(PurpleLog, 1);
703 *log_copy = *log;
705 return log_copy;
708 GType
709 purple_log_get_type(void)
711 static GType type = 0;
713 if (type == 0) {
714 type = g_boxed_type_register_static("PurpleLog",
715 (GBoxedCopyFunc)purple_log_copy,
716 (GBoxedFreeFunc)g_free);
719 return type;
722 /****************************************************************************
723 * LOGGERS ******************************************************************
724 ****************************************************************************/
726 static char *log_get_timestamp(PurpleLog *log, GDateTime *when)
728 gboolean show_date;
729 char *date;
730 GDateTime *dt;
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(),
737 "log-timestamp",
738 log, when, show_date);
739 if (date != NULL)
740 return date;
742 dt = g_date_time_to_local(when);
743 if (show_date)
744 date = g_date_time_format(dt, _("%x %X"));
745 else
746 date = g_date_time_format(dt, "%X");
747 g_date_time_unref(dt);
749 return date;
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?
756 static char *
757 convert_image_tags(const PurpleLog *log, const char *msg)
759 const char *tmp;
760 const char *start;
761 const char *end;
762 GData *attributes;
763 GString *newmsg = NULL;
765 tmp = msg;
767 while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) {
768 int imgid = 0;
769 char *idstr = NULL;
771 if (newmsg == NULL)
772 newmsg = g_string_new("");
774 /* copy any text before the img tag */
775 if (tmp < start)
776 g_string_append_len(newmsg, tmp, start - tmp);
778 if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL)
779 imgid = atoi(idstr);
781 if (imgid != 0)
783 FILE *image_file;
784 char *dir;
785 PurpleImage *image;
786 gconstpointer image_data;
787 const gchar *new_filename = NULL;
788 char *path = NULL;
789 size_t image_byte_count;
791 image = purple_image_store_get(imgid);
792 if (image == NULL)
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));
816 fclose(image_file);
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));
824 else
826 purple_debug_info("log", "Wrote image file: %s\n", path);
827 fclose(image_file);
830 else
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);
839 g_free(path);
842 /* Continue from the end of the tag */
843 tmp = end + 1;
846 if (newmsg == NULL)
848 /* No images were found to change. */
849 return (char *)msg;
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;
862 if (data == NULL)
864 /* This log is new */
865 char *dir;
866 GDateTime *dt;
867 const char *tz;
868 gchar *date;
869 char *filename;
870 char *path;
872 dir = purple_log_get_log_dir(log->type, log->name, log->account);
873 if (dir == NULL)
874 return;
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);
886 g_free(dir);
887 g_free(date);
888 g_free(filename);
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);
903 g_free(path);
904 return;
906 g_free(path);
910 GList *purple_log_common_lister(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext, PurpleLogLogger *logger)
912 GDir *dir;
913 GList *list = NULL;
914 const char *filename;
915 char *path;
917 if(!account)
918 return NULL;
920 path = purple_log_get_log_dir(type, name, account);
921 if (path == NULL)
922 return NULL;
924 if (!(dir = g_dir_open(path, 0, NULL)))
926 g_free(path);
927 return NULL;
930 while ((filename = g_dir_read_name(dir)))
932 if (purple_str_has_suffix(filename, ext) &&
933 strlen(filename) >= (17 + strlen(ext)))
935 PurpleLog *log;
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);
949 g_dir_close(dir);
950 g_free(path);
951 return list;
954 int purple_log_common_total_sizer(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext)
956 GDir *dir;
957 int size = 0;
958 const char *filename;
959 char *path;
961 if(!account)
962 return 0;
964 path = purple_log_get_log_dir(type, name, account);
965 if (path == NULL)
966 return 0;
968 if (!(dir = g_dir_open(path, 0, NULL)))
970 g_free(path);
971 return 0;
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);
980 GStatBuf st;
981 if (g_stat(tmp, &st))
983 purple_debug_error("log", "Error stating log file: %s\n", tmp);
984 g_free(tmp);
985 continue;
987 g_free(tmp);
988 size += st.st_size;
991 g_dir_close(dir);
992 g_free(path);
993 return size;
996 int purple_log_common_sizer(PurpleLog *log)
998 GStatBuf st;
999 PurpleLogCommonLoggerData *data = log->logger_data;
1001 g_return_val_if_fail(data != NULL, 0);
1003 if (!data->path || g_stat(data->path, &st))
1004 st.st_size = 0;
1006 return st.st_size;
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) {
1018 g_free(log_path);
1019 return;
1022 while ((protocol = g_dir_read_name(log_dir)) != NULL) {
1023 gchar *protocol_path = g_build_filename(log_path, protocol, NULL);
1024 GDir *protocol_dir;
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);
1032 continue;
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));
1044 if (!protocol)
1045 continue;
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);
1054 GDir *username_dir;
1055 const gchar *username_unescaped;
1056 PurpleAccount *account = NULL;
1057 gchar *name;
1059 if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
1060 g_free(username_path);
1061 continue;
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;
1069 break;
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) {
1075 size_t len;
1076 PurpleLogSet *set;
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. */
1085 len = strlen(name);
1087 set->type = PURPLE_LOG_IM;
1088 set->name = name;
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. */
1094 if (len >= 7) {
1095 gchar *tmp = &name[len - 7];
1096 if (purple_strequal(tmp, ".system")) {
1097 set->type = PURPLE_LOG_SYSTEM;
1098 *tmp = '\0';
1101 if (len > 5) {
1102 gchar *tmp = &name[len - 5];
1103 if (purple_strequal(tmp, ".chat")) {
1104 set->type = PURPLE_LOG_CHAT;
1105 *tmp = '\0';
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);
1112 else
1113 set->buddy = FALSE;
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);
1124 g_free(log_path);
1125 g_dir_close(log_dir);
1128 gboolean purple_log_common_deleter(PurpleLog *log)
1130 PurpleLogCommonLoggerData *data;
1131 int ret;
1133 g_return_val_if_fail(log != NULL, FALSE);
1135 data = log->logger_data;
1136 if (data == NULL)
1137 return FALSE;
1139 if (data->path == NULL)
1140 return FALSE;
1142 ret = g_unlink(data->path);
1143 if (ret == 0)
1144 return TRUE;
1145 else if (ret == -1)
1147 purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, g_strerror(errno));
1149 else
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);
1156 return FALSE;
1159 gboolean purple_log_common_is_deletable(PurpleLog *log)
1161 PurpleLogCommonLoggerData *data;
1162 #ifndef _WIN32
1163 gchar *dirname;
1164 #endif
1166 g_return_val_if_fail(log != NULL, FALSE);
1168 data = log->logger_data;
1169 if (data == NULL)
1170 return FALSE;
1172 if (data->path == NULL)
1173 return FALSE;
1175 #ifndef _WIN32
1176 dirname = g_path_get_dirname(data->path);
1177 if (g_access(dirname, W_OK) == 0)
1179 g_free(dirname);
1180 return TRUE;
1182 purple_debug_info("log", "access(%s) failed: %s\n", dirname, g_strerror(errno));
1183 g_free(dirname);
1184 #else
1185 /* Unless and until someone writes equivalent win32 code,
1186 * we'll assume the file is deletable. */
1187 return TRUE;
1188 #endif
1190 return FALSE;
1193 static char *process_txt_log(char *txt, char *to_free)
1195 char *tmp;
1197 /* The to_free argument allows us to save a
1198 * g_strdup() in some cases. */
1200 if (to_free == NULL)
1201 to_free = txt;
1203 /* g_markup_escape_text requires valid UTF-8 */
1204 if (!g_utf8_validate(txt, -1, NULL))
1206 tmp = purple_utf8_salvage(txt);
1207 g_free(to_free);
1208 to_free = txt = tmp;
1211 tmp = g_markup_escape_text(txt, -1);
1212 g_free(to_free);
1213 txt = purple_markup_linkify(tmp);
1214 g_free(tmp);
1216 return txt;
1219 /****************************
1220 ** HTML LOGGER *************
1221 ****************************/
1223 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
1224 const char *from, GDateTime *time, const char *message)
1226 char *msg_fixed;
1227 char *image_corrected_msg;
1228 char *date;
1229 char *header;
1230 char *escaped_from;
1231 PurpleProtocol *protocol =
1232 purple_protocols_find(purple_account_get_protocol_id(log->account));
1233 PurpleLogCommonLoggerData *data = log->logger_data;
1234 gsize written = 0;
1236 if(!data) {
1237 const char *proto = purple_protocol_class_list_icon(protocol, log->account, NULL);
1238 GDateTime *dt;
1239 gchar *date;
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 */
1245 if(!data->file)
1246 return 0;
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);
1258 else
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);
1265 g_free(date);
1266 g_free(header);
1269 /* if we can't write to the file, give up before we hurt ourselves */
1270 if(!data->file)
1271 return 0;
1273 escaped_from = g_markup_escape_text(from != NULL ? from : "<NULL>",
1274 -1);
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);
1288 } else {
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 &lt;AUTO-REPLY&gt;:</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 &lt;AUTO-REPLY&gt;:</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);
1304 else
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);
1311 else
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);
1314 } else {
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);
1320 g_free(date);
1321 g_free(msg_fixed);
1322 g_free(escaped_from);
1323 fflush(data->file);
1325 return written;
1328 static void html_logger_finalize(PurpleLog *log)
1330 PurpleLogCommonLoggerData *data = log->logger_data;
1331 if (data) {
1332 if(data->file) {
1333 fprintf(data->file, "</body></html>\n");
1334 fclose(data->file);
1336 g_free(data->path);
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)
1354 char *read;
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');
1362 if (!minus_header)
1363 return read;
1365 minus_header = g_strdup(minus_header + 1);
1366 g_free(read);
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)
1386 char *date;
1387 PurpleProtocol *protocol =
1388 purple_protocols_find(purple_account_get_protocol_id(log->account));
1389 PurpleLogCommonLoggerData *data = log->logger_data;
1390 char *stripped = NULL;
1392 gsize written = 0;
1394 if (data == 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);
1400 GDateTime *dt;
1401 gchar *date;
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)
1408 return 0;
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,
1415 date);
1416 else
1417 written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n",
1418 log->name, date,
1419 purple_account_get_username(log->account), proto);
1420 g_free(date);
1421 g_date_time_unref(dt);
1424 /* if we can't write to the file, give up before we hurt ourselves */
1425 if(!data->file)
1426 return 0;
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);
1433 } else {
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,
1438 from, stripped);
1439 } else {
1440 if(purple_message_meify(stripped, -1))
1441 written += fprintf(data->file, "(%s) ***%s %s\n", date, from,
1442 stripped);
1443 else
1444 written += fprintf(data->file, "(%s) %s: %s\n", date, from,
1445 stripped);
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 */
1453 g_free(stripped);
1454 return written;
1455 } else
1456 written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "",
1457 from ? ":" : "", stripped);
1459 g_free(date);
1460 g_free(stripped);
1461 fflush(data->file);
1463 return written;
1466 static void txt_logger_finalize(PurpleLog *log)
1468 PurpleLogCommonLoggerData *data = log->logger_data;
1469 if (data) {
1470 if(data->file)
1471 fclose(data->file);
1472 g_free(data->path);
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;
1492 *flags = 0;
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');
1498 if (minus_header)
1499 return process_txt_log(minus_header + 1, read);
1500 else
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");
1512 /****************
1513 * OLD LOGGER ***
1514 ****************/
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;
1522 int offset;
1523 int length;
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);
1531 GStatBuf st;
1532 time_t log_last_modified;
1533 FILE *index;
1534 FILE *file;
1535 int file_fd, index_fd;
1536 char *index_tmp;
1537 char buf[BUF_LONG];
1538 gint year, month, day, hour, minute, second;
1539 char month_str[4];
1540 struct old_logger_data *data = NULL;
1541 int logfound = 0;
1542 int lastoff = 0;
1543 int newlen;
1544 GDateTime *lasttime = NULL;
1546 PurpleLog *log = NULL;
1547 GList *list = NULL;
1549 g_free(logfile);
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);
1557 g_free(pathstr);
1558 return NULL;
1560 if (_purple_fstat(file_fd, &st) == -1) {
1561 purple_stringref_unref(pathref);
1562 g_free(pathstr);
1563 fclose(file);
1564 return NULL;
1565 } else
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) {
1574 close(index_fd);
1575 index_fd = -1;
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);
1583 close(index_fd);
1585 else
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. */
1593 } else {
1594 purple_debug_info("log", "Using index: %s\n", pathstr);
1595 g_free(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);
1616 fclose(index);
1617 purple_stringref_unref(pathref);
1619 fclose(file);
1620 return list;
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",
1628 g_strerror(errno));
1629 g_free(pathstr);
1630 g_free(index_tmp);
1631 index = NULL;
1632 } else {
1633 if ((index = fdopen(index_fd, "wb")) == NULL)
1635 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
1636 g_strerror(errno));
1637 close(index_fd);
1638 if (index_tmp != NULL)
1640 g_unlink(index_tmp);
1641 g_free(index_tmp);
1643 g_free(pathstr);
1647 while (fgets(buf, BUF_LONG, file)) {
1648 if (strstr(buf, "---- New C") != NULL) {
1649 int length;
1650 int offset;
1651 char convostart[32];
1652 char *temp = strchr(buf, '@');
1654 if (temp == NULL || strlen(temp) < 2)
1655 continue;
1657 temp++;
1658 length = strcspn(temp, "-");
1659 if (length > 31) length = 31;
1661 offset = ftell(file);
1663 if (logfound) {
1664 newlen = offset - lastoff - length;
1665 if(strstr(buf, "----</H3><BR>")) {
1666 newlen -=
1667 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
1668 sizeof("----</H3><BR>") - 2;
1669 } else {
1670 newlen -=
1671 sizeof("---- New Conversation @ ") + sizeof("----") - 2;
1674 if(strchr(buf, '\r'))
1675 newlen--;
1677 if (newlen != 0) {
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);
1691 if (index != NULL)
1692 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time);
1696 logfound = 1;
1697 lastoff = offset;
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")) {
1708 month = 1;
1709 } else if (purple_strequal(month_str, "Feb")) {
1710 month = 2;
1711 } else if (purple_strequal(month_str, "Mar")) {
1712 month = 3;
1713 } else if (purple_strequal(month_str, "Apr")) {
1714 month = 4;
1715 } else if (purple_strequal(month_str, "May")) {
1716 month = 5;
1717 } else if (purple_strequal(month_str, "Jun")) {
1718 month = 6;
1719 } else if (purple_strequal(month_str, "Jul")) {
1720 month = 7;
1721 } else if (purple_strequal(month_str, "Aug")) {
1722 month = 8;
1723 } else if (purple_strequal(month_str, "Sep")) {
1724 month = 9;
1725 } else if (purple_strequal(month_str, "Oct")) {
1726 month = 10;
1727 } else if (purple_strequal(month_str, "Nov")) {
1728 month = 11;
1729 } else if (purple_strequal(month_str, "Dec")) {
1730 month = 12;
1732 if (lasttime)
1733 g_date_time_unref(lasttime);
1734 lasttime = g_date_time_new_local(year, month, day,
1735 hour, minute, second);
1739 if (logfound) {
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);
1754 if (index != NULL)
1755 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time);
1759 if (lasttime)
1760 g_date_time_unref(lasttime);
1761 purple_stringref_unref(pathref);
1762 fclose(file);
1763 if (index != NULL)
1765 fclose(index);
1767 if (index_tmp == NULL)
1769 g_free(pathstr);
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);
1779 else
1780 purple_debug_info("log", "Built index: %s\n", pathstr);
1782 g_free(index_tmp);
1783 g_free(pathstr);
1785 return list;
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);
1792 int size;
1793 GStatBuf st;
1795 if (g_stat(pathstr, &st))
1796 size = 0;
1797 else
1798 size = st.st_size;
1800 g_free(logfile);
1801 g_free(pathstr);
1803 return size;
1806 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
1808 size_t result;
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");
1812 char *read;
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)
1818 result = 0;
1819 else
1820 result = fread(read, data->length, 1, file);
1821 if (result != 1)
1822 purple_debug_error("log", "Unable to read from log file: %s\n", path);
1823 fclose(file);
1824 read[data->length] = '\0';
1825 *flags = 0;
1826 if (strstr(read, "<BR>"))
1828 *flags |= PURPLE_LOG_READ_NO_NEWLINE;
1829 return read;
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);
1845 gchar *name;
1846 PurpleBlistNode *gnode, *cnode, *bnode;
1848 g_free(log_path);
1849 if (log_dir == NULL)
1850 return;
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) {
1854 size_t len;
1855 gchar *ext;
1856 PurpleLogSet *set;
1857 gboolean found = FALSE;
1859 /* Unescape the filename. */
1860 name = g_strdup(purple_unescape_filename(name));
1862 /* Get the (possibly new) length of name. */
1863 len = strlen(name);
1865 if (len < 5) {
1866 g_free(name);
1867 continue;
1870 /* Make sure we're dealing with a log file. */
1871 ext = &name[len - 4];
1872 if (!purple_strequal(ext, ".log")) {
1873 g_free(name);
1874 continue;
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. */
1881 *ext = '\0';
1882 set->type = PURPLE_LOG_IM;
1883 if (len > 9) {
1884 char *tmp = &name[len - 9];
1885 if (purple_strequal(tmp, ".chat")) {
1886 set->type = PURPLE_LOG_CHAT;
1887 *tmp = '\0';
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))
1899 continue;
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))
1906 continue;
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);
1916 set->buddy = TRUE;
1917 found = TRUE;
1923 if (!found)
1925 set->account = NULL;
1926 set->buddy = FALSE;
1929 cb(sets, set);
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);