| |
1 /** |
| |
2 * @file log.c Logging API |
| |
3 * @ingroup core |
| |
4 * |
| |
5 * purple |
| |
6 * |
| |
7 * Purple is the legal property of its developers, whose names are too numerous |
| |
8 * to list here. Please refer to the COPYRIGHT file distributed with this |
| |
9 * source distribution. |
| |
10 * |
| |
11 * This program is free software; you can redistribute it and/or modify |
| |
12 * it under the terms of the GNU General Public License as published by |
| |
13 * the Free Software Foundation; either version 2 of the License, or |
| |
14 * (at your option) any later version. |
| |
15 * |
| |
16 * This program is distributed in the hope that it will be useful, |
| |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
19 * GNU General Public License for more details. |
| |
20 * |
| |
21 * You should have received a copy of the GNU General Public License |
| |
22 * along with this program; if not, write to the Free Software |
| |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
24 */ |
| |
25 |
| |
26 #include "internal.h" |
| |
27 #include "account.h" |
| |
28 #include "dbus-maybe.h" |
| |
29 #include "debug.h" |
| |
30 #include "internal.h" |
| |
31 #include "log.h" |
| |
32 #include "prefs.h" |
| |
33 #include "util.h" |
| |
34 #include "stringref.h" |
| |
35 |
| |
36 static GSList *loggers = NULL; |
| |
37 |
| |
38 static PurpleLogLogger *html_logger; |
| |
39 static PurpleLogLogger *txt_logger; |
| |
40 static PurpleLogLogger *old_logger; |
| |
41 |
| |
42 struct _purple_logsize_user { |
| |
43 char *name; |
| |
44 PurpleAccount *account; |
| |
45 }; |
| |
46 static GHashTable *logsize_users = NULL; |
| |
47 |
| |
48 static void log_get_log_sets_common(GHashTable *sets); |
| |
49 |
| |
50 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type, |
| |
51 const char *from, time_t time, const char *message); |
| |
52 static void html_logger_finalize(PurpleLog *log); |
| |
53 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account); |
| |
54 static GList *html_logger_list_syslog(PurpleAccount *account); |
| |
55 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags); |
| |
56 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account); |
| |
57 |
| |
58 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account); |
| |
59 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account); |
| |
60 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags); |
| |
61 static int old_logger_size (PurpleLog *log); |
| |
62 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets); |
| |
63 static void old_logger_finalize(PurpleLog *log); |
| |
64 |
| |
65 static gsize txt_logger_write(PurpleLog *log, |
| |
66 PurpleMessageFlags type, |
| |
67 const char *from, time_t time, const char *message); |
| |
68 static void txt_logger_finalize(PurpleLog *log); |
| |
69 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account); |
| |
70 static GList *txt_logger_list_syslog(PurpleAccount *account); |
| |
71 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags); |
| |
72 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account); |
| |
73 |
| |
74 /************************************************************************** |
| |
75 * PUBLIC LOGGING FUNCTIONS *********************************************** |
| |
76 **************************************************************************/ |
| |
77 |
| |
78 PurpleLog *purple_log_new(PurpleLogType type, const char *name, PurpleAccount *account, |
| |
79 PurpleConversation *conv, time_t time, const struct tm *tm) |
| |
80 { |
| |
81 PurpleLog *log; |
| |
82 |
| |
83 /* IMPORTANT: Make sure to initialize all the members of PurpleLog */ |
| |
84 log = g_slice_new(PurpleLog); |
| |
85 PURPLE_DBUS_REGISTER_POINTER(log, PurpleLog); |
| |
86 |
| |
87 log->type = type; |
| |
88 log->name = g_strdup(purple_normalize(account, name)); |
| |
89 log->account = account; |
| |
90 log->conv = conv; |
| |
91 log->time = time; |
| |
92 log->logger = purple_log_logger_get(); |
| |
93 log->logger_data = NULL; |
| |
94 |
| |
95 if (tm == NULL) |
| |
96 log->tm = NULL; |
| |
97 else |
| |
98 { |
| |
99 /* There's no need to zero this as we immediately do a direct copy. */ |
| |
100 log->tm = g_slice_new(struct tm); |
| |
101 |
| |
102 *(log->tm) = *tm; |
| |
103 |
| |
104 #ifdef HAVE_STRUCT_TM_TM_ZONE |
| |
105 /* XXX: This is so wrong... */ |
| |
106 if (log->tm->tm_zone != NULL) |
| |
107 { |
| |
108 char *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL); |
| |
109 if (tmp != NULL) |
| |
110 log->tm->tm_zone = tmp; |
| |
111 else |
| |
112 /* Just shove the UTF-8 bytes in and hope... */ |
| |
113 log->tm->tm_zone = g_strdup(log->tm->tm_zone); |
| |
114 } |
| |
115 #endif |
| |
116 } |
| |
117 |
| |
118 if (log->logger && log->logger->create) |
| |
119 log->logger->create(log); |
| |
120 return log; |
| |
121 } |
| |
122 |
| |
123 void purple_log_free(PurpleLog *log) |
| |
124 { |
| |
125 g_return_if_fail(log); |
| |
126 if (log->logger && log->logger->finalize) |
| |
127 log->logger->finalize(log); |
| |
128 g_free(log->name); |
| |
129 |
| |
130 if (log->tm != NULL) |
| |
131 { |
| |
132 #ifdef HAVE_STRUCT_TM_TM_ZONE |
| |
133 /* XXX: This is so wrong... */ |
| |
134 g_free((char *)log->tm->tm_zone); |
| |
135 #endif |
| |
136 g_slice_free(struct tm, log->tm); |
| |
137 } |
| |
138 |
| |
139 PURPLE_DBUS_UNREGISTER_POINTER(log); |
| |
140 g_slice_free(PurpleLog, log); |
| |
141 } |
| |
142 |
| |
143 void purple_log_write(PurpleLog *log, PurpleMessageFlags type, |
| |
144 const char *from, time_t time, const char *message) |
| |
145 { |
| |
146 struct _purple_logsize_user *lu; |
| |
147 gsize written, total = 0; |
| |
148 gpointer ptrsize; |
| |
149 |
| |
150 g_return_if_fail(log); |
| |
151 g_return_if_fail(log->logger); |
| |
152 g_return_if_fail(log->logger->write); |
| |
153 |
| |
154 written = (log->logger->write)(log, type, from, time, message); |
| |
155 |
| |
156 lu = g_new(struct _purple_logsize_user, 1); |
| |
157 |
| |
158 lu->name = g_strdup(purple_normalize(log->account, log->name)); |
| |
159 lu->account = log->account; |
| |
160 |
| |
161 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { |
| |
162 total = GPOINTER_TO_INT(ptrsize); |
| |
163 total += written; |
| |
164 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total)); |
| |
165 } else { |
| |
166 g_free(lu->name); |
| |
167 g_free(lu); |
| |
168 } |
| |
169 |
| |
170 } |
| |
171 |
| |
172 char *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags) |
| |
173 { |
| |
174 PurpleLogReadFlags mflags; |
| |
175 g_return_val_if_fail(log && log->logger, NULL); |
| |
176 if (log->logger->read) { |
| |
177 char *ret = (log->logger->read)(log, flags ? flags : &mflags); |
| |
178 purple_str_strip_char(ret, '\r'); |
| |
179 return ret; |
| |
180 } |
| |
181 return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>")); |
| |
182 } |
| |
183 |
| |
184 int purple_log_get_size(PurpleLog *log) |
| |
185 { |
| |
186 g_return_val_if_fail(log && log->logger, 0); |
| |
187 |
| |
188 if (log->logger->size) |
| |
189 return log->logger->size(log); |
| |
190 return 0; |
| |
191 } |
| |
192 |
| |
193 static guint _purple_logsize_user_hash(struct _purple_logsize_user *lu) |
| |
194 { |
| |
195 return g_str_hash(lu->name); |
| |
196 } |
| |
197 |
| |
198 static guint _purple_logsize_user_equal(struct _purple_logsize_user *lu1, |
| |
199 struct _purple_logsize_user *lu2) |
| |
200 { |
| |
201 return (lu1->account == lu2->account && (!strcmp(lu1->name, lu2->name))); |
| |
202 } |
| |
203 |
| |
204 static void _purple_logsize_user_free_key(struct _purple_logsize_user *lu) |
| |
205 { |
| |
206 g_free(lu->name); |
| |
207 g_free(lu); |
| |
208 } |
| |
209 |
| |
210 int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
211 { |
| |
212 gpointer ptrsize; |
| |
213 int size = 0; |
| |
214 GSList *n; |
| |
215 struct _purple_logsize_user *lu; |
| |
216 |
| |
217 lu = g_new(struct _purple_logsize_user, 1); |
| |
218 lu->name = g_strdup(purple_normalize(account, name)); |
| |
219 lu->account = account; |
| |
220 |
| |
221 if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { |
| |
222 size = GPOINTER_TO_INT(ptrsize); |
| |
223 g_free(lu->name); |
| |
224 g_free(lu); |
| |
225 } else { |
| |
226 for (n = loggers; n; n = n->next) { |
| |
227 PurpleLogLogger *logger = n->data; |
| |
228 |
| |
229 if(logger->total_size){ |
| |
230 size += (logger->total_size)(type, name, account); |
| |
231 } else if(logger->list) { |
| |
232 GList *logs = (logger->list)(type, name, account); |
| |
233 int this_size = 0; |
| |
234 |
| |
235 while (logs) { |
| |
236 PurpleLog *log = (PurpleLog*)(logs->data); |
| |
237 this_size += purple_log_get_size(log); |
| |
238 purple_log_free(log); |
| |
239 logs = g_list_delete_link(logs, logs); |
| |
240 } |
| |
241 |
| |
242 size += this_size; |
| |
243 } |
| |
244 } |
| |
245 |
| |
246 g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size)); |
| |
247 } |
| |
248 return size; |
| |
249 } |
| |
250 |
| |
251 gboolean purple_log_is_deletable(PurpleLog *log) |
| |
252 { |
| |
253 g_return_val_if_fail(log != NULL, FALSE); |
| |
254 g_return_val_if_fail(log->logger != NULL, FALSE); |
| |
255 |
| |
256 if (log->logger->remove == NULL) |
| |
257 return FALSE; |
| |
258 |
| |
259 if (log->logger->is_deletable != NULL) |
| |
260 return log->logger->is_deletable(log); |
| |
261 |
| |
262 return TRUE; |
| |
263 } |
| |
264 |
| |
265 gboolean purple_log_delete(PurpleLog *log) |
| |
266 { |
| |
267 g_return_val_if_fail(log != NULL, FALSE); |
| |
268 g_return_val_if_fail(log->logger != NULL, FALSE); |
| |
269 |
| |
270 if (log->logger->remove != NULL) |
| |
271 return log->logger->remove(log); |
| |
272 |
| |
273 return FALSE; |
| |
274 } |
| |
275 |
| |
276 char * |
| |
277 purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
278 { |
| |
279 PurplePlugin *prpl; |
| |
280 PurplePluginProtocolInfo *prpl_info; |
| |
281 const char *prpl_name; |
| |
282 char *acct_name; |
| |
283 const char *target; |
| |
284 char *dir; |
| |
285 |
| |
286 prpl = purple_find_prpl(purple_account_get_protocol_id(account)); |
| |
287 if (!prpl) |
| |
288 return NULL; |
| |
289 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); |
| |
290 prpl_name = prpl_info->list_icon(account, NULL); |
| |
291 |
| |
292 acct_name = g_strdup(purple_escape_filename(purple_normalize(account, |
| |
293 purple_account_get_username(account)))); |
| |
294 |
| |
295 if (type == PURPLE_LOG_CHAT) { |
| |
296 char *temp = g_strdup_printf("%s.chat", purple_normalize(account, name)); |
| |
297 target = purple_escape_filename(temp); |
| |
298 g_free(temp); |
| |
299 } else if(type == PURPLE_LOG_SYSTEM) { |
| |
300 target = ".system"; |
| |
301 } else { |
| |
302 target = purple_escape_filename(purple_normalize(account, name)); |
| |
303 } |
| |
304 |
| |
305 dir = g_build_filename(purple_user_dir(), "logs", prpl_name, acct_name, target, NULL); |
| |
306 |
| |
307 g_free(acct_name); |
| |
308 |
| |
309 return dir; |
| |
310 } |
| |
311 |
| |
312 /**************************************************************************** |
| |
313 * LOGGER FUNCTIONS ********************************************************* |
| |
314 ****************************************************************************/ |
| |
315 |
| |
316 static PurpleLogLogger *current_logger = NULL; |
| |
317 |
| |
318 static void logger_pref_cb(const char *name, PurplePrefType type, |
| |
319 gconstpointer value, gpointer data) |
| |
320 { |
| |
321 PurpleLogLogger *logger; |
| |
322 GSList *l = loggers; |
| |
323 while (l) { |
| |
324 logger = l->data; |
| |
325 if (!strcmp(logger->id, value)) { |
| |
326 purple_log_logger_set(logger); |
| |
327 return; |
| |
328 } |
| |
329 l = l->next; |
| |
330 } |
| |
331 purple_log_logger_set(txt_logger); |
| |
332 } |
| |
333 |
| |
334 |
| |
335 PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...) |
| |
336 { |
| |
337 #if 0 |
| |
338 void(*create)(PurpleLog *), |
| |
339 gsize(*write)(PurpleLog *, PurpleMessageFlags, const char *, time_t, const char *), |
| |
340 void(*finalize)(PurpleLog *), |
| |
341 GList*(*list)(PurpleLogType type, const char*, PurpleAccount*), |
| |
342 char*(*read)(PurpleLog*, PurpleLogReadFlags*), |
| |
343 int(*size)(PurpleLog*), |
| |
344 int(*total_size)(PurpleLogType type, const char *name, PurpleAccount *account), |
| |
345 GList*(*list_syslog)(PurpleAccount *account), |
| |
346 void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets), |
| |
347 gboolean(*remove)(PurpleLog *log), |
| |
348 gboolean(*is_deletable)(PurpleLog *log)) |
| |
349 { |
| |
350 #endif |
| |
351 PurpleLogLogger *logger; |
| |
352 va_list args; |
| |
353 |
| |
354 g_return_val_if_fail(id != NULL, NULL); |
| |
355 g_return_val_if_fail(name != NULL, NULL); |
| |
356 g_return_val_if_fail(functions >= 1, NULL); |
| |
357 |
| |
358 logger = g_new0(PurpleLogLogger, 1); |
| |
359 logger->id = g_strdup(id); |
| |
360 logger->name = g_strdup(name); |
| |
361 |
| |
362 va_start(args, functions); |
| |
363 |
| |
364 if (functions >= 1) |
| |
365 logger->create = va_arg(args, void *); |
| |
366 if (functions >= 2) |
| |
367 logger->write = va_arg(args, void *); |
| |
368 if (functions >= 3) |
| |
369 logger->finalize = va_arg(args, void *); |
| |
370 if (functions >= 4) |
| |
371 logger->list = va_arg(args, void *); |
| |
372 if (functions >= 5) |
| |
373 logger->read = va_arg(args, void *); |
| |
374 if (functions >= 6) |
| |
375 logger->size = va_arg(args, void *); |
| |
376 if (functions >= 7) |
| |
377 logger->total_size = va_arg(args, void *); |
| |
378 if (functions >= 8) |
| |
379 logger->list_syslog = va_arg(args, void *); |
| |
380 if (functions >= 9) |
| |
381 logger->get_log_sets = va_arg(args, void *); |
| |
382 if (functions >= 10) |
| |
383 logger->remove = va_arg(args, void *); |
| |
384 if (functions >= 11) |
| |
385 logger->is_deletable = va_arg(args, void *); |
| |
386 |
| |
387 if (functions >= 12) |
| |
388 purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id); |
| |
389 |
| |
390 va_end(args); |
| |
391 |
| |
392 return logger; |
| |
393 } |
| |
394 |
| |
395 void purple_log_logger_free(PurpleLogLogger *logger) |
| |
396 { |
| |
397 g_free(logger->name); |
| |
398 g_free(logger->id); |
| |
399 g_free(logger); |
| |
400 } |
| |
401 |
| |
402 void purple_log_logger_add (PurpleLogLogger *logger) |
| |
403 { |
| |
404 g_return_if_fail(logger); |
| |
405 if (g_slist_find(loggers, logger)) |
| |
406 return; |
| |
407 loggers = g_slist_append(loggers, logger); |
| |
408 } |
| |
409 |
| |
410 void purple_log_logger_remove (PurpleLogLogger *logger) |
| |
411 { |
| |
412 g_return_if_fail(logger); |
| |
413 loggers = g_slist_remove(loggers, logger); |
| |
414 } |
| |
415 |
| |
416 void purple_log_logger_set (PurpleLogLogger *logger) |
| |
417 { |
| |
418 g_return_if_fail(logger); |
| |
419 current_logger = logger; |
| |
420 } |
| |
421 |
| |
422 PurpleLogLogger *purple_log_logger_get() |
| |
423 { |
| |
424 return current_logger; |
| |
425 } |
| |
426 |
| |
427 GList *purple_log_logger_get_options(void) |
| |
428 { |
| |
429 GSList *n; |
| |
430 GList *list = NULL; |
| |
431 PurpleLogLogger *data; |
| |
432 |
| |
433 for (n = loggers; n; n = n->next) { |
| |
434 data = n->data; |
| |
435 if (!data->write) |
| |
436 continue; |
| |
437 list = g_list_append(list, data->name); |
| |
438 list = g_list_append(list, data->id); |
| |
439 } |
| |
440 |
| |
441 return list; |
| |
442 } |
| |
443 |
| |
444 gint purple_log_compare(gconstpointer y, gconstpointer z) |
| |
445 { |
| |
446 const PurpleLog *a = y; |
| |
447 const PurpleLog *b = z; |
| |
448 |
| |
449 return b->time - a->time; |
| |
450 } |
| |
451 |
| |
452 GList *purple_log_get_logs(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
453 { |
| |
454 GList *logs = NULL; |
| |
455 GSList *n; |
| |
456 for (n = loggers; n; n = n->next) { |
| |
457 PurpleLogLogger *logger = n->data; |
| |
458 if (!logger->list) |
| |
459 continue; |
| |
460 logs = g_list_concat(logger->list(type, name, account), logs); |
| |
461 } |
| |
462 |
| |
463 return g_list_sort(logs, purple_log_compare); |
| |
464 } |
| |
465 |
| |
466 gint purple_log_set_compare(gconstpointer y, gconstpointer z) |
| |
467 { |
| |
468 const PurpleLogSet *a = y; |
| |
469 const PurpleLogSet *b = z; |
| |
470 gint ret = 0; |
| |
471 |
| |
472 /* This logic seems weird at first... |
| |
473 * If either account is NULL, we pretend the accounts are |
| |
474 * equal. This allows us to detect duplicates that will |
| |
475 * exist if one logger knows the account and another |
| |
476 * doesn't. */ |
| |
477 if (a->account != NULL && b->account != NULL) { |
| |
478 ret = strcmp(purple_account_get_username(a->account), purple_account_get_username(b->account)); |
| |
479 if (ret != 0) |
| |
480 return ret; |
| |
481 } |
| |
482 |
| |
483 ret = strcmp(a->normalized_name, b->normalized_name); |
| |
484 if (ret != 0) |
| |
485 return ret; |
| |
486 |
| |
487 return (gint)b->type - (gint)a->type; |
| |
488 } |
| |
489 |
| |
490 static guint |
| |
491 log_set_hash(gconstpointer key) |
| |
492 { |
| |
493 const PurpleLogSet *set = key; |
| |
494 |
| |
495 /* The account isn't hashed because we need PurpleLogSets with NULL accounts |
| |
496 * to be found when we search by a PurpleLogSet that has a non-NULL account |
| |
497 * but the same type and name. */ |
| |
498 return g_int_hash(&set->type) + g_str_hash(set->name); |
| |
499 } |
| |
500 |
| |
501 static gboolean |
| |
502 log_set_equal(gconstpointer a, gconstpointer b) |
| |
503 { |
| |
504 /* I realize that the choices made for GList and GHashTable |
| |
505 * make sense for those data types, but I wish the comparison |
| |
506 * functions were compatible. */ |
| |
507 return !purple_log_set_compare(a, b); |
| |
508 } |
| |
509 |
| |
510 static void |
| |
511 log_add_log_set_to_hash(GHashTable *sets, PurpleLogSet *set) |
| |
512 { |
| |
513 PurpleLogSet *existing_set = g_hash_table_lookup(sets, set); |
| |
514 |
| |
515 if (existing_set == NULL) |
| |
516 g_hash_table_insert(sets, set, set); |
| |
517 else if (existing_set->account == NULL && set->account != NULL) |
| |
518 g_hash_table_replace(sets, set, set); |
| |
519 else |
| |
520 purple_log_set_free(set); |
| |
521 } |
| |
522 |
| |
523 GHashTable *purple_log_get_log_sets(void) |
| |
524 { |
| |
525 GSList *n; |
| |
526 GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal, |
| |
527 (GDestroyNotify)purple_log_set_free, NULL); |
| |
528 |
| |
529 /* Get the log sets from all the loggers. */ |
| |
530 for (n = loggers; n; n = n->next) { |
| |
531 PurpleLogLogger *logger = n->data; |
| |
532 |
| |
533 if (!logger->get_log_sets) |
| |
534 continue; |
| |
535 |
| |
536 logger->get_log_sets(log_add_log_set_to_hash, sets); |
| |
537 } |
| |
538 |
| |
539 log_get_log_sets_common(sets); |
| |
540 |
| |
541 /* Return the GHashTable of unique PurpleLogSets. */ |
| |
542 return sets; |
| |
543 } |
| |
544 |
| |
545 void purple_log_set_free(PurpleLogSet *set) |
| |
546 { |
| |
547 g_return_if_fail(set != NULL); |
| |
548 |
| |
549 g_free(set->name); |
| |
550 if (set->normalized_name != set->name) |
| |
551 g_free(set->normalized_name); |
| |
552 |
| |
553 g_slice_free(PurpleLogSet, set); |
| |
554 } |
| |
555 |
| |
556 GList *purple_log_get_system_logs(PurpleAccount *account) |
| |
557 { |
| |
558 GList *logs = NULL; |
| |
559 GSList *n; |
| |
560 for (n = loggers; n; n = n->next) { |
| |
561 PurpleLogLogger *logger = n->data; |
| |
562 if (!logger->list_syslog) |
| |
563 continue; |
| |
564 logs = g_list_concat(logger->list_syslog(account), logs); |
| |
565 } |
| |
566 |
| |
567 return g_list_sort(logs, purple_log_compare); |
| |
568 } |
| |
569 |
| |
570 /**************************************************************************** |
| |
571 * LOG SUBSYSTEM ************************************************************ |
| |
572 ****************************************************************************/ |
| |
573 |
| |
574 void * |
| |
575 purple_log_get_handle(void) |
| |
576 { |
| |
577 static int handle; |
| |
578 |
| |
579 return &handle; |
| |
580 } |
| |
581 |
| |
582 void purple_log_init(void) |
| |
583 { |
| |
584 void *handle = purple_log_get_handle(); |
| |
585 |
| |
586 purple_prefs_add_none("/core/logging"); |
| |
587 purple_prefs_add_bool("/core/logging/log_ims", FALSE); |
| |
588 purple_prefs_add_bool("/core/logging/log_chats", FALSE); |
| |
589 purple_prefs_add_bool("/core/logging/log_system", FALSE); |
| |
590 |
| |
591 purple_prefs_add_string("/core/logging/format", "txt"); |
| |
592 |
| |
593 html_logger = purple_log_logger_new("html", _("HTML"), 11, |
| |
594 NULL, |
| |
595 html_logger_write, |
| |
596 html_logger_finalize, |
| |
597 html_logger_list, |
| |
598 html_logger_read, |
| |
599 purple_log_common_sizer, |
| |
600 html_logger_total_size, |
| |
601 html_logger_list_syslog, |
| |
602 NULL, |
| |
603 purple_log_common_deleter, |
| |
604 purple_log_common_is_deletable); |
| |
605 purple_log_logger_add(html_logger); |
| |
606 |
| |
607 txt_logger = purple_log_logger_new("txt", _("Plain text"), 11, |
| |
608 NULL, |
| |
609 txt_logger_write, |
| |
610 txt_logger_finalize, |
| |
611 txt_logger_list, |
| |
612 txt_logger_read, |
| |
613 purple_log_common_sizer, |
| |
614 txt_logger_total_size, |
| |
615 txt_logger_list_syslog, |
| |
616 NULL, |
| |
617 purple_log_common_deleter, |
| |
618 purple_log_common_is_deletable); |
| |
619 purple_log_logger_add(txt_logger); |
| |
620 |
| |
621 old_logger = purple_log_logger_new("old", _("Old flat format"), 9, |
| |
622 NULL, |
| |
623 NULL, |
| |
624 old_logger_finalize, |
| |
625 old_logger_list, |
| |
626 old_logger_read, |
| |
627 old_logger_size, |
| |
628 old_logger_total_size, |
| |
629 NULL, |
| |
630 old_logger_get_log_sets); |
| |
631 purple_log_logger_add(old_logger); |
| |
632 |
| |
633 purple_signal_register(handle, "log-timestamp", |
| |
634 #if SIZEOF_TIME_T == 4 |
| |
635 purple_marshal_POINTER__POINTER_INT_BOOLEAN, |
| |
636 #elif SIZEOF_TIME_T == 8 |
| |
637 purple_marshal_POINTER__POINTER_INT64_BOOLEAN, |
| |
638 #else |
| |
639 #error Unknown size of time_t |
| |
640 #endif |
| |
641 purple_value_new(PURPLE_TYPE_STRING), 3, |
| |
642 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
643 PURPLE_SUBTYPE_LOG), |
| |
644 #if SIZEOF_TIME_T == 4 |
| |
645 purple_value_new(PURPLE_TYPE_INT), |
| |
646 #elif SIZEOF_TIME_T == 8 |
| |
647 purple_value_new(PURPLE_TYPE_INT64), |
| |
648 #else |
| |
649 # error Unknown size of time_t |
| |
650 #endif |
| |
651 purple_value_new(PURPLE_TYPE_BOOLEAN)); |
| |
652 |
| |
653 purple_prefs_connect_callback(NULL, "/core/logging/format", |
| |
654 logger_pref_cb, NULL); |
| |
655 purple_prefs_trigger_callback("/core/logging/format"); |
| |
656 |
| |
657 logsize_users = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash, |
| |
658 (GEqualFunc)_purple_logsize_user_equal, |
| |
659 (GDestroyNotify)_purple_logsize_user_free_key, NULL); |
| |
660 } |
| |
661 |
| |
662 void |
| |
663 purple_log_uninit(void) |
| |
664 { |
| |
665 purple_signals_unregister_by_instance(purple_log_get_handle()); |
| |
666 } |
| |
667 |
| |
668 /**************************************************************************** |
| |
669 * LOGGERS ****************************************************************** |
| |
670 ****************************************************************************/ |
| |
671 |
| |
672 static char *log_get_timestamp(PurpleLog *log, time_t when) |
| |
673 { |
| |
674 gboolean show_date; |
| |
675 char *date; |
| |
676 struct tm tm; |
| |
677 |
| |
678 show_date = (log->type == PURPLE_LOG_SYSTEM) || (time(NULL) > when + 20*60); |
| |
679 |
| |
680 date = purple_signal_emit_return_1(purple_log_get_handle(), |
| |
681 "log-timestamp", |
| |
682 log, when, show_date); |
| |
683 if (date != NULL) |
| |
684 return date; |
| |
685 |
| |
686 tm = *(localtime(&when)); |
| |
687 if (show_date) |
| |
688 return g_strdup(purple_date_format_long(&tm)); |
| |
689 else |
| |
690 return g_strdup(purple_time_format(&tm)); |
| |
691 } |
| |
692 |
| |
693 void purple_log_common_writer(PurpleLog *log, const char *ext) |
| |
694 { |
| |
695 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
696 |
| |
697 if (data == NULL) |
| |
698 { |
| |
699 /* This log is new */ |
| |
700 char *dir; |
| |
701 struct tm *tm; |
| |
702 const char *tz; |
| |
703 const char *date; |
| |
704 char *filename; |
| |
705 char *path; |
| |
706 |
| |
707 dir = purple_log_get_log_dir(log->type, log->name, log->account); |
| |
708 if (dir == NULL) |
| |
709 return; |
| |
710 |
| |
711 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); |
| |
712 |
| |
713 tm = localtime(&log->time); |
| |
714 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm)); |
| |
715 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); |
| |
716 |
| |
717 filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); |
| |
718 |
| |
719 path = g_build_filename(dir, filename, NULL); |
| |
720 g_free(dir); |
| |
721 g_free(filename); |
| |
722 |
| |
723 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData); |
| |
724 |
| |
725 data->file = g_fopen(path, "a"); |
| |
726 if (data->file == NULL) |
| |
727 { |
| |
728 purple_debug(PURPLE_DEBUG_ERROR, "log", |
| |
729 "Could not create log file %s\n", path); |
| |
730 |
| |
731 if (log->conv != NULL) |
| |
732 purple_conversation_write(log->conv, NULL, _("Logging of this conversation failed."), |
| |
733 PURPLE_MESSAGE_ERROR, time(NULL)); |
| |
734 |
| |
735 g_free(path); |
| |
736 return; |
| |
737 } |
| |
738 g_free(path); |
| |
739 } |
| |
740 } |
| |
741 |
| |
742 GList *purple_log_common_lister(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext, PurpleLogLogger *logger) |
| |
743 { |
| |
744 GDir *dir; |
| |
745 GList *list = NULL; |
| |
746 const char *filename; |
| |
747 char *path; |
| |
748 |
| |
749 if(!account) |
| |
750 return NULL; |
| |
751 |
| |
752 path = purple_log_get_log_dir(type, name, account); |
| |
753 if (path == NULL) |
| |
754 return NULL; |
| |
755 |
| |
756 if (!(dir = g_dir_open(path, 0, NULL))) |
| |
757 { |
| |
758 g_free(path); |
| |
759 return NULL; |
| |
760 } |
| |
761 |
| |
762 while ((filename = g_dir_read_name(dir))) |
| |
763 { |
| |
764 if (purple_str_has_suffix(filename, ext) && |
| |
765 strlen(filename) >= (17 + strlen(ext))) |
| |
766 { |
| |
767 PurpleLog *log; |
| |
768 PurpleLogCommonLoggerData *data; |
| |
769 struct tm tm; |
| |
770 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE) |
| |
771 long tz_off; |
| |
772 const char *rest; |
| |
773 time_t stamp = purple_str_to_time(purple_unescape_filename(filename), FALSE, &tm, &tz_off, &rest); |
| |
774 char *end; |
| |
775 |
| |
776 /* As zero is a valid offset, PURPLE_NO_TZ_OFF means no offset was |
| |
777 * provided. See util.h. Yes, it's kinda ugly. */ |
| |
778 if (tz_off != PURPLE_NO_TZ_OFF) |
| |
779 tm.tm_gmtoff = tz_off - tm.tm_gmtoff; |
| |
780 |
| |
781 if (rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL) |
| |
782 { |
| |
783 log = purple_log_new(type, name, account, NULL, stamp, NULL); |
| |
784 } |
| |
785 else |
| |
786 { |
| |
787 char *tmp = g_strndup(rest, end - rest); |
| |
788 tm.tm_zone = tmp; |
| |
789 log = purple_log_new(type, name, account, NULL, stamp, &tm); |
| |
790 g_free(tmp); |
| |
791 } |
| |
792 #else |
| |
793 time_t stamp = purple_str_to_time(filename, FALSE, &tm, NULL, NULL); |
| |
794 |
| |
795 log = purple_log_new(type, name, account, NULL, stamp, &tm); |
| |
796 #endif |
| |
797 |
| |
798 log->logger = logger; |
| |
799 log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData); |
| |
800 |
| |
801 data->path = g_build_filename(path, filename, NULL); |
| |
802 list = g_list_prepend(list, log); |
| |
803 } |
| |
804 } |
| |
805 g_dir_close(dir); |
| |
806 g_free(path); |
| |
807 return list; |
| |
808 } |
| |
809 |
| |
810 int purple_log_common_total_sizer(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext) |
| |
811 { |
| |
812 GDir *dir; |
| |
813 int size = 0; |
| |
814 const char *filename; |
| |
815 char *path; |
| |
816 |
| |
817 if(!account) |
| |
818 return 0; |
| |
819 |
| |
820 path = purple_log_get_log_dir(type, name, account); |
| |
821 if (path == NULL) |
| |
822 return 0; |
| |
823 |
| |
824 if (!(dir = g_dir_open(path, 0, NULL))) |
| |
825 { |
| |
826 g_free(path); |
| |
827 return 0; |
| |
828 } |
| |
829 |
| |
830 while ((filename = g_dir_read_name(dir))) |
| |
831 { |
| |
832 if (purple_str_has_suffix(filename, ext) && |
| |
833 strlen(filename) >= (17 + strlen(ext))) |
| |
834 { |
| |
835 char *tmp = g_build_filename(path, filename, NULL); |
| |
836 struct stat st; |
| |
837 if (g_stat(tmp, &st)) |
| |
838 { |
| |
839 purple_debug_error("log", "Error stating log file: %s\n", tmp); |
| |
840 g_free(tmp); |
| |
841 continue; |
| |
842 } |
| |
843 g_free(tmp); |
| |
844 size += st.st_size; |
| |
845 } |
| |
846 } |
| |
847 g_dir_close(dir); |
| |
848 g_free(path); |
| |
849 return size; |
| |
850 } |
| |
851 |
| |
852 int purple_log_common_sizer(PurpleLog *log) |
| |
853 { |
| |
854 struct stat st; |
| |
855 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
856 |
| |
857 g_return_val_if_fail(data != NULL, 0); |
| |
858 |
| |
859 if (!data->path || g_stat(data->path, &st)) |
| |
860 st.st_size = 0; |
| |
861 |
| |
862 return st.st_size; |
| |
863 } |
| |
864 |
| |
865 /* This will build log sets for all loggers that use the common logger |
| |
866 * functions because they use the same directory structure. */ |
| |
867 static void log_get_log_sets_common(GHashTable *sets) |
| |
868 { |
| |
869 gchar *log_path = g_build_filename(purple_user_dir(), "logs", NULL); |
| |
870 GDir *log_dir = g_dir_open(log_path, 0, NULL); |
| |
871 const gchar *protocol; |
| |
872 |
| |
873 if (log_dir == NULL) { |
| |
874 g_free(log_path); |
| |
875 return; |
| |
876 } |
| |
877 |
| |
878 while ((protocol = g_dir_read_name(log_dir)) != NULL) { |
| |
879 gchar *protocol_path = g_build_filename(log_path, protocol, NULL); |
| |
880 GDir *protocol_dir; |
| |
881 const gchar *username; |
| |
882 gchar *protocol_unescaped; |
| |
883 GList *account_iter; |
| |
884 GList *accounts = NULL; |
| |
885 |
| |
886 if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) { |
| |
887 g_free(protocol_path); |
| |
888 continue; |
| |
889 } |
| |
890 |
| |
891 /* Using g_strdup() to cover the one-in-a-million chance that a |
| |
892 * prpl's list_icon function uses purple_unescape_filename(). */ |
| |
893 protocol_unescaped = g_strdup(purple_unescape_filename(protocol)); |
| |
894 |
| |
895 /* Find all the accounts for protocol. */ |
| |
896 for (account_iter = purple_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) { |
| |
897 PurplePlugin *prpl; |
| |
898 PurplePluginProtocolInfo *prpl_info; |
| |
899 |
| |
900 prpl = purple_find_prpl(purple_account_get_protocol_id((PurpleAccount *)account_iter->data)); |
| |
901 if (!prpl) |
| |
902 continue; |
| |
903 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); |
| |
904 |
| |
905 if (!strcmp(protocol_unescaped, prpl_info->list_icon((PurpleAccount *)account_iter->data, NULL))) |
| |
906 accounts = g_list_prepend(accounts, account_iter->data); |
| |
907 } |
| |
908 g_free(protocol_unescaped); |
| |
909 |
| |
910 while ((username = g_dir_read_name(protocol_dir)) != NULL) { |
| |
911 gchar *username_path = g_build_filename(protocol_path, username, NULL); |
| |
912 GDir *username_dir; |
| |
913 const gchar *username_unescaped; |
| |
914 PurpleAccount *account = NULL; |
| |
915 gchar *name; |
| |
916 |
| |
917 if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) { |
| |
918 g_free(username_path); |
| |
919 continue; |
| |
920 } |
| |
921 |
| |
922 /* Find the account for username in the list of accounts for protocol. */ |
| |
923 username_unescaped = purple_unescape_filename(username); |
| |
924 for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) { |
| |
925 if (!strcmp(((PurpleAccount *)account_iter->data)->username, username_unescaped)) { |
| |
926 account = account_iter->data; |
| |
927 break; |
| |
928 } |
| |
929 } |
| |
930 |
| |
931 /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */ |
| |
932 while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) { |
| |
933 size_t len; |
| |
934 PurpleLogSet *set; |
| |
935 |
| |
936 /* IMPORTANT: Always initialize all members of PurpleLogSet */ |
| |
937 set = g_slice_new(PurpleLogSet); |
| |
938 |
| |
939 /* Unescape the filename. */ |
| |
940 name = g_strdup(purple_unescape_filename(name)); |
| |
941 |
| |
942 /* Get the (possibly new) length of name. */ |
| |
943 len = strlen(name); |
| |
944 |
| |
945 set->type = PURPLE_LOG_IM; |
| |
946 set->name = name; |
| |
947 set->account = account; |
| |
948 /* set->buddy is always set below */ |
| |
949 set->normalized_name = g_strdup(purple_normalize(account, name)); |
| |
950 |
| |
951 /* Chat for .chat or .system at the end of the name to determine the type. */ |
| |
952 if (len > 7) { |
| |
953 gchar *tmp = &name[len - 7]; |
| |
954 if (!strcmp(tmp, ".system")) { |
| |
955 set->type = PURPLE_LOG_SYSTEM; |
| |
956 *tmp = '\0'; |
| |
957 } |
| |
958 } |
| |
959 if (len > 5) { |
| |
960 gchar *tmp = &name[len - 5]; |
| |
961 if (!strcmp(tmp, ".chat")) { |
| |
962 set->type = PURPLE_LOG_CHAT; |
| |
963 *tmp = '\0'; |
| |
964 } |
| |
965 } |
| |
966 |
| |
967 /* Determine if this (account, name) combination exists as a buddy. */ |
| |
968 if (account != NULL) |
| |
969 set->buddy = (purple_find_buddy(account, name) != NULL); |
| |
970 else |
| |
971 set->buddy = FALSE; |
| |
972 |
| |
973 log_add_log_set_to_hash(sets, set); |
| |
974 } |
| |
975 g_free(username_path); |
| |
976 g_dir_close(username_dir); |
| |
977 } |
| |
978 g_free(protocol_path); |
| |
979 g_dir_close(protocol_dir); |
| |
980 } |
| |
981 g_free(log_path); |
| |
982 g_dir_close(log_dir); |
| |
983 } |
| |
984 |
| |
985 gboolean purple_log_common_deleter(PurpleLog *log) |
| |
986 { |
| |
987 PurpleLogCommonLoggerData *data; |
| |
988 int ret; |
| |
989 |
| |
990 g_return_val_if_fail(log != NULL, FALSE); |
| |
991 |
| |
992 data = log->logger_data; |
| |
993 if (data == NULL) |
| |
994 return FALSE; |
| |
995 |
| |
996 if (data->path == NULL) |
| |
997 return FALSE; |
| |
998 |
| |
999 ret = g_unlink(data->path); |
| |
1000 if (ret == 0) |
| |
1001 return TRUE; |
| |
1002 else if (ret == -1) |
| |
1003 { |
| |
1004 purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, strerror(errno)); |
| |
1005 } |
| |
1006 else |
| |
1007 { |
| |
1008 /* I'm not sure that g_unlink() will ever return |
| |
1009 * something other than 0 or -1. -- rlaager */ |
| |
1010 purple_debug_error("log", "Failed to delete: %s\n", data->path); |
| |
1011 } |
| |
1012 |
| |
1013 return FALSE; |
| |
1014 } |
| |
1015 |
| |
1016 gboolean purple_log_common_is_deletable(PurpleLog *log) |
| |
1017 { |
| |
1018 PurpleLogCommonLoggerData *data; |
| |
1019 #ifndef _WIN32 |
| |
1020 gchar *dirname; |
| |
1021 #endif |
| |
1022 |
| |
1023 g_return_val_if_fail(log != NULL, FALSE); |
| |
1024 |
| |
1025 data = log->logger_data; |
| |
1026 if (data == NULL) |
| |
1027 return FALSE; |
| |
1028 |
| |
1029 if (data->path == NULL) |
| |
1030 return FALSE; |
| |
1031 |
| |
1032 #ifndef _WIN32 |
| |
1033 dirname = g_path_get_dirname(data->path); |
| |
1034 if (g_access(dirname, W_OK) == 0) |
| |
1035 { |
| |
1036 g_free(dirname); |
| |
1037 return TRUE; |
| |
1038 } |
| |
1039 purple_debug_info("log", "access(%s) failed: %s\n", dirname, strerror(errno)); |
| |
1040 g_free(dirname); |
| |
1041 #else |
| |
1042 /* Unless and until someone writes equivalent win32 code, |
| |
1043 * we'll assume the file is deletable. */ |
| |
1044 return TRUE; |
| |
1045 #endif |
| |
1046 |
| |
1047 return FALSE; |
| |
1048 } |
| |
1049 |
| |
1050 static char *process_txt_log(char *txt, char *to_free) |
| |
1051 { |
| |
1052 char *tmp; |
| |
1053 |
| |
1054 /* The to_free argument allows us to save a |
| |
1055 * g_strdup() in some cases. */ |
| |
1056 |
| |
1057 if (to_free == NULL) |
| |
1058 to_free = txt; |
| |
1059 |
| |
1060 /* g_markup_escape_text requires valid UTF-8 */ |
| |
1061 if (!g_utf8_validate(txt, -1, NULL)) |
| |
1062 { |
| |
1063 tmp = purple_utf8_salvage(txt); |
| |
1064 g_free(to_free); |
| |
1065 to_free = txt = tmp; |
| |
1066 } |
| |
1067 |
| |
1068 tmp = g_markup_escape_text(txt, -1); |
| |
1069 g_free(to_free); |
| |
1070 txt = purple_markup_linkify(tmp); |
| |
1071 g_free(tmp); |
| |
1072 |
| |
1073 return txt; |
| |
1074 } |
| |
1075 |
| |
1076 #if 0 /* Maybe some other time. */ |
| |
1077 /**************** |
| |
1078 ** XML LOGGER ** |
| |
1079 ****************/ |
| |
1080 |
| |
1081 static const char *str_from_msg_type (PurpleMessageFlags type) |
| |
1082 { |
| |
1083 |
| |
1084 return ""; |
| |
1085 |
| |
1086 } |
| |
1087 |
| |
1088 static void xml_logger_write(PurpleLog *log, |
| |
1089 PurpleMessageFlags type, |
| |
1090 const char *from, time_t time, const char *message) |
| |
1091 { |
| |
1092 char *xhtml = NULL; |
| |
1093 |
| |
1094 if (!log->logger_data) { |
| |
1095 /* This log is new. We could use the loggers 'new' function, but |
| |
1096 * creating a new file there would result in empty files in the case |
| |
1097 * that you open a convo with someone, but don't say anything. |
| |
1098 */ |
| |
1099 struct tm *tm; |
| |
1100 const char *tz; |
| |
1101 const char *date; |
| |
1102 char *dir = purple_log_get_log_dir(log->type, log->name, log->account); |
| |
1103 char *name; |
| |
1104 char *filename; |
| |
1105 |
| |
1106 if (dir == NULL) |
| |
1107 return; |
| |
1108 |
| |
1109 tm = localtime(&log->time); |
| |
1110 tz = purple_escape_filename(purple_utf8_strftime("%Z", tm); |
| |
1111 date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); |
| |
1112 |
| |
1113 name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); |
| |
1114 |
| |
1115 purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); |
| |
1116 |
| |
1117 filename = g_build_filename(dir, name, NULL); |
| |
1118 g_free(dir); |
| |
1119 g_free(name); |
| |
1120 |
| |
1121 log->logger_data = g_fopen(filename, "a"); |
| |
1122 if (!log->logger_data) { |
| |
1123 purple_debug(PURPLE_DEBUG_ERROR, "log", "Could not create log file %s\n", filename); |
| |
1124 g_free(filename); |
| |
1125 return; |
| |
1126 } |
| |
1127 g_free(filename); |
| |
1128 fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n" |
| |
1129 "<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n"); |
| |
1130 |
| |
1131 date = purple_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time)); |
| |
1132 fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n", |
| |
1133 date, log->name, prpl); |
| |
1134 } |
| |
1135 |
| |
1136 /* if we can't write to the file, give up before we hurt ourselves */ |
| |
1137 if(!data->file) |
| |
1138 return; |
| |
1139 |
| |
1140 date = log_get_timestamp(log, time); |
| |
1141 |
| |
1142 purple_markup_html_to_xhtml(message, &xhtml, NULL); |
| |
1143 if (from) |
| |
1144 fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n", |
| |
1145 str_from_msg_type(type), |
| |
1146 type & PURPLE_MESSAGE_SEND ? "direction='sent'" : |
| |
1147 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "", |
| |
1148 from, date, xhtml); |
| |
1149 else |
| |
1150 fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n", |
| |
1151 str_from_msg_type(type), |
| |
1152 type & PURPLE_MESSAGE_SEND ? "direction='sent'" : |
| |
1153 type & PURPLE_MESSAGE_RECV ? "direction='received'" : "", |
| |
1154 date, xhtml): |
| |
1155 fflush(log->logger_data); |
| |
1156 g_free(date); |
| |
1157 g_free(xhtml); |
| |
1158 } |
| |
1159 |
| |
1160 static void xml_logger_finalize(PurpleLog *log) |
| |
1161 { |
| |
1162 if (log->logger_data) { |
| |
1163 fprintf(log->logger_data, "</conversation>\n"); |
| |
1164 fclose(log->logger_data); |
| |
1165 log->logger_data = NULL; |
| |
1166 } |
| |
1167 } |
| |
1168 |
| |
1169 static GList *xml_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) |
| |
1170 { |
| |
1171 return purple_log_common_lister(type, sn, account, ".xml", &xml_logger); |
| |
1172 } |
| |
1173 |
| |
1174 static PurpleLogLogger xml_logger = { |
| |
1175 N_("XML"), "xml", |
| |
1176 NULL, |
| |
1177 xml_logger_write, |
| |
1178 xml_logger_finalize, |
| |
1179 xml_logger_list, |
| |
1180 NULL, |
| |
1181 NULL, |
| |
1182 NULL |
| |
1183 }; |
| |
1184 #endif |
| |
1185 |
| |
1186 /**************************** |
| |
1187 ** HTML LOGGER ************* |
| |
1188 ****************************/ |
| |
1189 |
| |
1190 static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type, |
| |
1191 const char *from, time_t time, const char *message) |
| |
1192 { |
| |
1193 char *msg_fixed; |
| |
1194 char *date; |
| |
1195 char *header; |
| |
1196 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account)); |
| |
1197 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1198 gsize written = 0; |
| |
1199 |
| |
1200 if(!data) { |
| |
1201 const char *prpl = |
| |
1202 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); |
| |
1203 const char *date; |
| |
1204 purple_log_common_writer(log, ".html"); |
| |
1205 |
| |
1206 data = log->logger_data; |
| |
1207 |
| |
1208 /* if we can't write to the file, give up before we hurt ourselves */ |
| |
1209 if(!data->file) |
| |
1210 return 0; |
| |
1211 |
| |
1212 date = purple_date_format_full(localtime(&log->time)); |
| |
1213 |
| |
1214 written += fprintf(data->file, "<html><head>"); |
| |
1215 written += fprintf(data->file, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"); |
| |
1216 written += fprintf(data->file, "<title>"); |
| |
1217 if (log->type == PURPLE_LOG_SYSTEM) |
| |
1218 header = g_strdup_printf("System log for account %s (%s) connected at %s", |
| |
1219 purple_account_get_username(log->account), prpl, date); |
| |
1220 else |
| |
1221 header = g_strdup_printf("Conversation with %s at %s on %s (%s)", |
| |
1222 log->name, date, purple_account_get_username(log->account), prpl); |
| |
1223 |
| |
1224 written += fprintf(data->file, "%s", header); |
| |
1225 written += fprintf(data->file, "</title></head><body>"); |
| |
1226 written += fprintf(data->file, "<h3>%s</h3>\n", header); |
| |
1227 g_free(header); |
| |
1228 } |
| |
1229 |
| |
1230 /* if we can't write to the file, give up before we hurt ourselves */ |
| |
1231 if(!data->file) |
| |
1232 return 0; |
| |
1233 |
| |
1234 purple_markup_html_to_xhtml(message, &msg_fixed, NULL); |
| |
1235 date = log_get_timestamp(log, time); |
| |
1236 |
| |
1237 if(log->type == PURPLE_LOG_SYSTEM){ |
| |
1238 written += fprintf(data->file, "---- %s @ %s ----<br/>\n", msg_fixed, date); |
| |
1239 } else { |
| |
1240 if (type & PURPLE_MESSAGE_SYSTEM) |
| |
1241 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed); |
| |
1242 else if (type & PURPLE_MESSAGE_RAW) |
| |
1243 written += fprintf(data->file, "<font size=\"2\">(%s)</font> %s<br/>\n", date, msg_fixed); |
| |
1244 else if (type & PURPLE_MESSAGE_ERROR) |
| |
1245 written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed); |
| |
1246 else if (type & PURPLE_MESSAGE_WHISPER) |
| |
1247 written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", |
| |
1248 date, from, msg_fixed); |
| |
1249 else if (type & PURPLE_MESSAGE_AUTO_RESP) { |
| |
1250 if (type & PURPLE_MESSAGE_SEND) |
| |
1251 written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); |
| |
1252 else if (type & PURPLE_MESSAGE_RECV) |
| |
1253 written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); |
| |
1254 } else if (type & PURPLE_MESSAGE_RECV) { |
| |
1255 if(purple_message_meify(msg_fixed, -1)) |
| |
1256 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", |
| |
1257 date, from, msg_fixed); |
| |
1258 else |
| |
1259 written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", |
| |
1260 date, from, msg_fixed); |
| |
1261 } else if (type & PURPLE_MESSAGE_SEND) { |
| |
1262 if(purple_message_meify(msg_fixed, -1)) |
| |
1263 written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", |
| |
1264 date, from, msg_fixed); |
| |
1265 else |
| |
1266 written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", |
| |
1267 date, from, msg_fixed); |
| |
1268 } else { |
| |
1269 purple_debug_error("log", "Unhandled message type."); |
| |
1270 written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", |
| |
1271 date, from, msg_fixed); |
| |
1272 } |
| |
1273 } |
| |
1274 g_free(date); |
| |
1275 g_free(msg_fixed); |
| |
1276 fflush(data->file); |
| |
1277 |
| |
1278 return written; |
| |
1279 } |
| |
1280 |
| |
1281 static void html_logger_finalize(PurpleLog *log) |
| |
1282 { |
| |
1283 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1284 if (data) { |
| |
1285 if(data->file) { |
| |
1286 fprintf(data->file, "</body></html>\n"); |
| |
1287 fclose(data->file); |
| |
1288 } |
| |
1289 g_free(data->path); |
| |
1290 |
| |
1291 g_slice_free(PurpleLogCommonLoggerData, data); |
| |
1292 } |
| |
1293 } |
| |
1294 |
| |
1295 static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) |
| |
1296 { |
| |
1297 return purple_log_common_lister(type, sn, account, ".html", html_logger); |
| |
1298 } |
| |
1299 |
| |
1300 static GList *html_logger_list_syslog(PurpleAccount *account) |
| |
1301 { |
| |
1302 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger); |
| |
1303 } |
| |
1304 |
| |
1305 static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags) |
| |
1306 { |
| |
1307 char *read; |
| |
1308 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1309 *flags = PURPLE_LOG_READ_NO_NEWLINE; |
| |
1310 if (!data || !data->path) |
| |
1311 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>")); |
| |
1312 if (g_file_get_contents(data->path, &read, NULL, NULL)) { |
| |
1313 char *minus_header = strchr(read, '\n'); |
| |
1314 |
| |
1315 if (!minus_header) |
| |
1316 return read; |
| |
1317 |
| |
1318 minus_header = g_strdup(minus_header + 1); |
| |
1319 g_free(read); |
| |
1320 |
| |
1321 return minus_header; |
| |
1322 } |
| |
1323 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path); |
| |
1324 } |
| |
1325 |
| |
1326 static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
1327 { |
| |
1328 return purple_log_common_total_sizer(type, name, account, ".html"); |
| |
1329 } |
| |
1330 |
| |
1331 |
| |
1332 /**************************** |
| |
1333 ** PLAIN TEXT LOGGER ******* |
| |
1334 ****************************/ |
| |
1335 |
| |
1336 static gsize txt_logger_write(PurpleLog *log, |
| |
1337 PurpleMessageFlags type, |
| |
1338 const char *from, time_t time, const char *message) |
| |
1339 { |
| |
1340 char *date; |
| |
1341 PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account)); |
| |
1342 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1343 char *stripped = NULL; |
| |
1344 |
| |
1345 gsize written = 0; |
| |
1346 |
| |
1347 if (data == NULL) { |
| |
1348 /* This log is new. We could use the loggers 'new' function, but |
| |
1349 * creating a new file there would result in empty files in the case |
| |
1350 * that you open a convo with someone, but don't say anything. |
| |
1351 */ |
| |
1352 const char *prpl = |
| |
1353 PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); |
| |
1354 purple_log_common_writer(log, ".txt"); |
| |
1355 |
| |
1356 data = log->logger_data; |
| |
1357 |
| |
1358 /* if we can't write to the file, give up before we hurt ourselves */ |
| |
1359 if(!data->file) |
| |
1360 return 0; |
| |
1361 |
| |
1362 if (log->type == PURPLE_LOG_SYSTEM) |
| |
1363 written += fprintf(data->file, "System log for account %s (%s) connected at %s\n", |
| |
1364 purple_account_get_username(log->account), prpl, |
| |
1365 purple_date_format_full(localtime(&log->time))); |
| |
1366 else |
| |
1367 written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n", |
| |
1368 log->name, purple_date_format_full(localtime(&log->time)), |
| |
1369 purple_account_get_username(log->account), prpl); |
| |
1370 } |
| |
1371 |
| |
1372 /* if we can't write to the file, give up before we hurt ourselves */ |
| |
1373 if(!data->file) |
| |
1374 return 0; |
| |
1375 |
| |
1376 stripped = purple_markup_strip_html(message); |
| |
1377 date = log_get_timestamp(log, time); |
| |
1378 |
| |
1379 if(log->type == PURPLE_LOG_SYSTEM){ |
| |
1380 written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date); |
| |
1381 } else { |
| |
1382 if (type & PURPLE_MESSAGE_SEND || |
| |
1383 type & PURPLE_MESSAGE_RECV) { |
| |
1384 if (type & PURPLE_MESSAGE_AUTO_RESP) { |
| |
1385 written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date, |
| |
1386 from, stripped); |
| |
1387 } else { |
| |
1388 if(purple_message_meify(stripped, -1)) |
| |
1389 written += fprintf(data->file, "(%s) ***%s %s\n", date, from, |
| |
1390 stripped); |
| |
1391 else |
| |
1392 written += fprintf(data->file, "(%s) %s: %s\n", date, from, |
| |
1393 stripped); |
| |
1394 } |
| |
1395 } else if (type & PURPLE_MESSAGE_SYSTEM || |
| |
1396 type & PURPLE_MESSAGE_ERROR || |
| |
1397 type & PURPLE_MESSAGE_RAW) |
| |
1398 written += fprintf(data->file, "(%s) %s\n", date, stripped); |
| |
1399 else if (type & PURPLE_MESSAGE_NO_LOG) { |
| |
1400 /* This shouldn't happen */ |
| |
1401 g_free(stripped); |
| |
1402 return written; |
| |
1403 } else if (type & PURPLE_MESSAGE_WHISPER) |
| |
1404 written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped); |
| |
1405 else |
| |
1406 written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "", |
| |
1407 from ? ":" : "", stripped); |
| |
1408 } |
| |
1409 g_free(date); |
| |
1410 g_free(stripped); |
| |
1411 fflush(data->file); |
| |
1412 |
| |
1413 return written; |
| |
1414 } |
| |
1415 |
| |
1416 static void txt_logger_finalize(PurpleLog *log) |
| |
1417 { |
| |
1418 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1419 if (data) { |
| |
1420 if(data->file) |
| |
1421 fclose(data->file); |
| |
1422 g_free(data->path); |
| |
1423 |
| |
1424 g_slice_free(PurpleLogCommonLoggerData, data); |
| |
1425 } |
| |
1426 } |
| |
1427 |
| |
1428 static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) |
| |
1429 { |
| |
1430 return purple_log_common_lister(type, sn, account, ".txt", txt_logger); |
| |
1431 } |
| |
1432 |
| |
1433 static GList *txt_logger_list_syslog(PurpleAccount *account) |
| |
1434 { |
| |
1435 return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger); |
| |
1436 } |
| |
1437 |
| |
1438 static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags) |
| |
1439 { |
| |
1440 char *read, *minus_header; |
| |
1441 PurpleLogCommonLoggerData *data = log->logger_data; |
| |
1442 *flags = 0; |
| |
1443 if (!data || !data->path) |
| |
1444 return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>")); |
| |
1445 if (g_file_get_contents(data->path, &read, NULL, NULL)) { |
| |
1446 minus_header = strchr(read, '\n'); |
| |
1447 |
| |
1448 if (minus_header) |
| |
1449 return process_txt_log(minus_header + 1, read); |
| |
1450 else |
| |
1451 return process_txt_log(read, NULL); |
| |
1452 } |
| |
1453 return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path); |
| |
1454 } |
| |
1455 |
| |
1456 static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
1457 { |
| |
1458 return purple_log_common_total_sizer(type, name, account, ".txt"); |
| |
1459 } |
| |
1460 |
| |
1461 |
| |
1462 /**************** |
| |
1463 * OLD LOGGER *** |
| |
1464 ****************/ |
| |
1465 |
| |
1466 /* The old logger doesn't write logs, only reads them. This is to include |
| |
1467 * old logs in the log viewer transparently. |
| |
1468 */ |
| |
1469 |
| |
1470 struct old_logger_data { |
| |
1471 PurpleStringref *pathref; |
| |
1472 int offset; |
| |
1473 int length; |
| |
1474 }; |
| |
1475 |
| |
1476 static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) |
| |
1477 { |
| |
1478 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, sn)); |
| |
1479 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL); |
| |
1480 PurpleStringref *pathref = purple_stringref_new(pathstr); |
| |
1481 struct stat st; |
| |
1482 time_t log_last_modified; |
| |
1483 FILE *index; |
| |
1484 FILE *file; |
| |
1485 int index_fd; |
| |
1486 char *index_tmp; |
| |
1487 char buf[BUF_LONG]; |
| |
1488 struct tm tm; |
| |
1489 char month[4]; |
| |
1490 struct old_logger_data *data = NULL; |
| |
1491 char *newlog; |
| |
1492 int logfound = 0; |
| |
1493 int lastoff = 0; |
| |
1494 int newlen; |
| |
1495 time_t lasttime = 0; |
| |
1496 |
| |
1497 PurpleLog *log = NULL; |
| |
1498 GList *list = NULL; |
| |
1499 |
| |
1500 g_free(logfile); |
| |
1501 |
| |
1502 if (g_stat(purple_stringref_value(pathref), &st)) |
| |
1503 { |
| |
1504 purple_stringref_unref(pathref); |
| |
1505 g_free(pathstr); |
| |
1506 return NULL; |
| |
1507 } |
| |
1508 else |
| |
1509 log_last_modified = st.st_mtime; |
| |
1510 |
| |
1511 /* Change the .log extension to .idx */ |
| |
1512 strcpy(pathstr + strlen(pathstr) - 3, "idx"); |
| |
1513 |
| |
1514 if (g_stat(pathstr, &st) == 0) |
| |
1515 { |
| |
1516 if (st.st_mtime < log_last_modified) |
| |
1517 { |
| |
1518 purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr); |
| |
1519 } |
| |
1520 else |
| |
1521 { |
| |
1522 /* The index file exists and is at least as new as the log, so open it. */ |
| |
1523 if (!(index = g_fopen(pathstr, "rb"))) |
| |
1524 { |
| |
1525 purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n", |
| |
1526 pathstr, strerror(errno)); |
| |
1527 |
| |
1528 /* Fall through so that we'll parse the log file. */ |
| |
1529 } |
| |
1530 else |
| |
1531 { |
| |
1532 purple_debug_info("log", "Using index: %s\n", pathstr); |
| |
1533 g_free(pathstr); |
| |
1534 while (fgets(buf, BUF_LONG, index)) |
| |
1535 { |
| |
1536 unsigned long idx_time; |
| |
1537 if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3) |
| |
1538 { |
| |
1539 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL); |
| |
1540 log->logger = old_logger; |
| |
1541 log->time = (time_t)idx_time; |
| |
1542 |
| |
1543 /* IMPORTANT: Always set all members of struct old_logger_data */ |
| |
1544 data = g_slice_new(struct old_logger_data); |
| |
1545 |
| |
1546 data->pathref = purple_stringref_ref(pathref); |
| |
1547 data->offset = lastoff; |
| |
1548 data->length = newlen; |
| |
1549 |
| |
1550 log->logger_data = data; |
| |
1551 list = g_list_prepend(list, log); |
| |
1552 } |
| |
1553 } |
| |
1554 fclose(index); |
| |
1555 |
| |
1556 return list; |
| |
1557 } |
| |
1558 } |
| |
1559 } |
| |
1560 |
| |
1561 if (!(file = g_fopen(purple_stringref_value(pathref), "rb"))) { |
| |
1562 purple_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n", |
| |
1563 purple_stringref_value(pathref), strerror(errno)); |
| |
1564 purple_stringref_unref(pathref); |
| |
1565 g_free(pathstr); |
| |
1566 return NULL; |
| |
1567 } |
| |
1568 |
| |
1569 index_tmp = g_strdup_printf("%s.XXXXXX", pathstr); |
| |
1570 if ((index_fd = g_mkstemp(index_tmp)) == -1) { |
| |
1571 purple_debug_error("log", "Failed to open index temp file: %s\n", |
| |
1572 strerror(errno)); |
| |
1573 g_free(pathstr); |
| |
1574 g_free(index_tmp); |
| |
1575 index = NULL; |
| |
1576 } else { |
| |
1577 if ((index = fdopen(index_fd, "wb")) == NULL) |
| |
1578 { |
| |
1579 purple_debug_error("log", "Failed to fdopen() index temp file: %s\n", |
| |
1580 strerror(errno)); |
| |
1581 close(index_fd); |
| |
1582 if (index_tmp != NULL) |
| |
1583 { |
| |
1584 g_unlink(index_tmp); |
| |
1585 g_free(index_tmp); |
| |
1586 } |
| |
1587 g_free(pathstr); |
| |
1588 } |
| |
1589 } |
| |
1590 |
| |
1591 while (fgets(buf, BUF_LONG, file)) { |
| |
1592 if ((newlog = strstr(buf, "---- New C"))) { |
| |
1593 int length; |
| |
1594 int offset; |
| |
1595 char convostart[32]; |
| |
1596 char *temp = strchr(buf, '@'); |
| |
1597 |
| |
1598 if (temp == NULL || strlen(temp) < 2) |
| |
1599 continue; |
| |
1600 |
| |
1601 temp++; |
| |
1602 length = strcspn(temp, "-"); |
| |
1603 if (length > 31) length = 31; |
| |
1604 |
| |
1605 offset = ftell(file); |
| |
1606 |
| |
1607 if (logfound) { |
| |
1608 newlen = offset - lastoff - length; |
| |
1609 if(strstr(buf, "----</H3><BR>")) { |
| |
1610 newlen -= |
| |
1611 sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") + |
| |
1612 sizeof("----</H3><BR>") - 2; |
| |
1613 } else { |
| |
1614 newlen -= |
| |
1615 sizeof("---- New Conversation @ ") + sizeof("----") - 2; |
| |
1616 } |
| |
1617 |
| |
1618 if(strchr(buf, '\r')) |
| |
1619 newlen--; |
| |
1620 |
| |
1621 if (newlen != 0) { |
| |
1622 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL); |
| |
1623 log->logger = old_logger; |
| |
1624 log->time = lasttime; |
| |
1625 |
| |
1626 /* IMPORTANT: Always set all members of struct old_logger_data */ |
| |
1627 data = g_slice_new(struct old_logger_data); |
| |
1628 |
| |
1629 data->pathref = purple_stringref_ref(pathref); |
| |
1630 data->offset = lastoff; |
| |
1631 data->length = newlen; |
| |
1632 |
| |
1633 log->logger_data = data; |
| |
1634 list = g_list_prepend(list, log); |
| |
1635 |
| |
1636 /* XXX: There is apparently Is there a proper way to print a time_t? */ |
| |
1637 if (index != NULL) |
| |
1638 fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time); |
| |
1639 } |
| |
1640 } |
| |
1641 |
| |
1642 logfound = 1; |
| |
1643 lastoff = offset; |
| |
1644 |
| |
1645 g_snprintf(convostart, length, "%s", temp); |
| |
1646 sscanf(convostart, "%*s %s %d %d:%d:%d %d", |
| |
1647 month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year); |
| |
1648 /* Ugly hack, in case current locale is not English */ |
| |
1649 if (strcmp(month, "Jan") == 0) { |
| |
1650 tm.tm_mon= 0; |
| |
1651 } else if (strcmp(month, "Feb") == 0) { |
| |
1652 tm.tm_mon = 1; |
| |
1653 } else if (strcmp(month, "Mar") == 0) { |
| |
1654 tm.tm_mon = 2; |
| |
1655 } else if (strcmp(month, "Apr") == 0) { |
| |
1656 tm.tm_mon = 3; |
| |
1657 } else if (strcmp(month, "May") == 0) { |
| |
1658 tm.tm_mon = 4; |
| |
1659 } else if (strcmp(month, "Jun") == 0) { |
| |
1660 tm.tm_mon = 5; |
| |
1661 } else if (strcmp(month, "Jul") == 0) { |
| |
1662 tm.tm_mon = 6; |
| |
1663 } else if (strcmp(month, "Aug") == 0) { |
| |
1664 tm.tm_mon = 7; |
| |
1665 } else if (strcmp(month, "Sep") == 0) { |
| |
1666 tm.tm_mon = 8; |
| |
1667 } else if (strcmp(month, "Oct") == 0) { |
| |
1668 tm.tm_mon = 9; |
| |
1669 } else if (strcmp(month, "Nov") == 0) { |
| |
1670 tm.tm_mon = 10; |
| |
1671 } else if (strcmp(month, "Dec") == 0) { |
| |
1672 tm.tm_mon = 11; |
| |
1673 } |
| |
1674 tm.tm_year -= 1900; |
| |
1675 lasttime = mktime(&tm); |
| |
1676 } |
| |
1677 } |
| |
1678 |
| |
1679 if (logfound) { |
| |
1680 if ((newlen = ftell(file) - lastoff) != 0) { |
| |
1681 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL); |
| |
1682 log->logger = old_logger; |
| |
1683 log->time = lasttime; |
| |
1684 |
| |
1685 /* IMPORTANT: Always set all members of struct old_logger_data */ |
| |
1686 data = g_slice_new(struct old_logger_data); |
| |
1687 |
| |
1688 data->pathref = purple_stringref_ref(pathref); |
| |
1689 data->offset = lastoff; |
| |
1690 data->length = newlen; |
| |
1691 |
| |
1692 log->logger_data = data; |
| |
1693 list = g_list_prepend(list, log); |
| |
1694 |
| |
1695 /* XXX: Is there a proper way to print a time_t? */ |
| |
1696 if (index != NULL) |
| |
1697 fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time); |
| |
1698 } |
| |
1699 } |
| |
1700 |
| |
1701 purple_stringref_unref(pathref); |
| |
1702 fclose(file); |
| |
1703 if (index != NULL) |
| |
1704 { |
| |
1705 fclose(index); |
| |
1706 |
| |
1707 if (index_tmp == NULL) |
| |
1708 { |
| |
1709 g_free(pathstr); |
| |
1710 g_return_val_if_reached(list); |
| |
1711 } |
| |
1712 |
| |
1713 if (g_rename(index_tmp, pathstr)) |
| |
1714 { |
| |
1715 purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n", |
| |
1716 index_tmp, pathstr, strerror(errno)); |
| |
1717 g_unlink(index_tmp); |
| |
1718 g_free(index_tmp); |
| |
1719 } |
| |
1720 else |
| |
1721 purple_debug_info("log", "Built index: %s\n", pathstr); |
| |
1722 |
| |
1723 g_free(pathstr); |
| |
1724 } |
| |
1725 return list; |
| |
1726 } |
| |
1727 |
| |
1728 static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account) |
| |
1729 { |
| |
1730 char *logfile = g_strdup_printf("%s.log", purple_normalize(account, name)); |
| |
1731 char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL); |
| |
1732 int size; |
| |
1733 struct stat st; |
| |
1734 |
| |
1735 if (g_stat(pathstr, &st)) |
| |
1736 size = 0; |
| |
1737 else |
| |
1738 size = st.st_size; |
| |
1739 |
| |
1740 g_free(logfile); |
| |
1741 g_free(pathstr); |
| |
1742 |
| |
1743 return size; |
| |
1744 } |
| |
1745 |
| |
1746 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags) |
| |
1747 { |
| |
1748 struct old_logger_data *data = log->logger_data; |
| |
1749 FILE *file = g_fopen(purple_stringref_value(data->pathref), "rb"); |
| |
1750 char *read = g_malloc(data->length + 1); |
| |
1751 fseek(file, data->offset, SEEK_SET); |
| |
1752 fread(read, data->length, 1, file); |
| |
1753 fclose(file); |
| |
1754 read[data->length] = '\0'; |
| |
1755 *flags = 0; |
| |
1756 if (strstr(read, "<BR>")) |
| |
1757 { |
| |
1758 *flags |= PURPLE_LOG_READ_NO_NEWLINE; |
| |
1759 return read; |
| |
1760 } |
| |
1761 |
| |
1762 return process_txt_log(read, NULL); |
| |
1763 } |
| |
1764 |
| |
1765 static int old_logger_size (PurpleLog *log) |
| |
1766 { |
| |
1767 struct old_logger_data *data = log->logger_data; |
| |
1768 return data ? data->length : 0; |
| |
1769 } |
| |
1770 |
| |
1771 static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets) |
| |
1772 { |
| |
1773 char *log_path = g_build_filename(purple_user_dir(), "logs", NULL); |
| |
1774 GDir *log_dir = g_dir_open(log_path, 0, NULL); |
| |
1775 gchar *name; |
| |
1776 PurpleBlistNode *gnode, *cnode, *bnode; |
| |
1777 |
| |
1778 g_free(log_path); |
| |
1779 if (log_dir == NULL) |
| |
1780 return; |
| |
1781 |
| |
1782 /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */ |
| |
1783 while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) { |
| |
1784 size_t len; |
| |
1785 gchar *ext; |
| |
1786 PurpleLogSet *set; |
| |
1787 gboolean found = FALSE; |
| |
1788 |
| |
1789 /* Unescape the filename. */ |
| |
1790 name = g_strdup(purple_unescape_filename(name)); |
| |
1791 |
| |
1792 /* Get the (possibly new) length of name. */ |
| |
1793 len = strlen(name); |
| |
1794 |
| |
1795 if (len < 5) { |
| |
1796 g_free(name); |
| |
1797 continue; |
| |
1798 } |
| |
1799 |
| |
1800 /* Make sure we're dealing with a log file. */ |
| |
1801 ext = &name[len - 4]; |
| |
1802 if (strcmp(ext, ".log")) { |
| |
1803 g_free(name); |
| |
1804 continue; |
| |
1805 } |
| |
1806 |
| |
1807 /* IMPORTANT: Always set all members of PurpleLogSet */ |
| |
1808 set = g_slice_new(PurpleLogSet); |
| |
1809 |
| |
1810 /* Chat for .chat at the end of the name to determine the type. */ |
| |
1811 *ext = '\0'; |
| |
1812 set->type = PURPLE_LOG_IM; |
| |
1813 if (len > 9) { |
| |
1814 char *tmp = &name[len - 9]; |
| |
1815 if (!strcmp(tmp, ".chat")) { |
| |
1816 set->type = PURPLE_LOG_CHAT; |
| |
1817 *tmp = '\0'; |
| |
1818 } |
| |
1819 } |
| |
1820 |
| |
1821 set->name = set->normalized_name = name; |
| |
1822 |
| |
1823 /* Search the buddy list to find the account and to determine if this is a buddy. */ |
| |
1824 for (gnode = purple_get_blist()->root; !found && gnode != NULL; gnode = gnode->next) |
| |
1825 { |
| |
1826 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) |
| |
1827 continue; |
| |
1828 |
| |
1829 for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next) |
| |
1830 { |
| |
1831 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) |
| |
1832 continue; |
| |
1833 |
| |
1834 for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next) |
| |
1835 { |
| |
1836 PurpleBuddy *buddy = (PurpleBuddy *)bnode; |
| |
1837 |
| |
1838 if (!strcmp(buddy->name, name)) { |
| |
1839 set->account = buddy->account; |
| |
1840 set->buddy = TRUE; |
| |
1841 found = TRUE; |
| |
1842 } |
| |
1843 } |
| |
1844 } |
| |
1845 } |
| |
1846 |
| |
1847 if (!found) |
| |
1848 { |
| |
1849 set->account = NULL; |
| |
1850 set->buddy = FALSE; |
| |
1851 } |
| |
1852 |
| |
1853 cb(sets, set); |
| |
1854 } |
| |
1855 g_dir_close(log_dir); |
| |
1856 } |
| |
1857 |
| |
1858 static void old_logger_finalize(PurpleLog *log) |
| |
1859 { |
| |
1860 struct old_logger_data *data = log->logger_data; |
| |
1861 purple_stringref_unref(data->pathref); |
| |
1862 g_slice_free(struct old_logger_data, data); |
| |
1863 } |