comparison libgaim/plugins/log_reader.c @ 14235:a54ff7cafc2a

[gaim-migrate @ 16917] * Enable the log reader plugin. I'm really going to try to work on this some more, but making it available (with a warning) will help shame me into fixing it. * De-activate the stub loggers in the log reader plugin for Fire and Messenger Plus! * Fix up logs of svn:ignore properties so that svn status comes up clean. committer: Tailor Script <tailor@pidgin.im>
author Richard Laager <rlaager@wiktel.com>
date Sun, 20 Aug 2006 20:14:12 +0000
parents
children 7635195195c0
comparison
equal deleted inserted replaced
14234:04bdbbefbd1d 14235:a54ff7cafc2a
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <stdio.h>
6
7 #ifndef GAIM_PLUGINS
8 # define GAIM_PLUGINS
9 #endif
10
11 #include "internal.h"
12
13 #include "debug.h"
14 #include "log.h"
15 #include "plugin.h"
16 #include "pluginpref.h"
17 #include "prefs.h"
18 #include "stringref.h"
19 #include "util.h"
20 #include "version.h"
21 #include "xmlnode.h"
22
23 /* This must be the last Gaim header included. */
24 #ifdef _WIN32
25 #include "win32dep.h"
26 #endif
27
28 /* Where is the Windows partition mounted? */
29 #ifndef GAIM_LOG_READER_WINDOWS_MOUNT_POINT
30 #define GAIM_LOG_READER_WINDOWS_MOUNT_POINT "/mnt/windows"
31 #endif
32
33 enum name_guesses {
34 NAME_GUESS_UNKNOWN,
35 NAME_GUESS_ME,
36 NAME_GUESS_THEM
37 };
38
39
40 /*****************************************************************************
41 * Adium Logger *
42 *****************************************************************************/
43
44 /* The adium logger doesn't write logs, only reads them. This is to include
45 * Adium logs in the log viewer transparently.
46 */
47
48 static GaimLogLogger *adium_logger;
49
50 enum adium_log_type {
51 ADIUM_HTML,
52 ADIUM_TEXT,
53 };
54
55 struct adium_logger_data {
56 char *path;
57 enum adium_log_type type;
58 };
59
60 static GList *adium_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
61 {
62 GList *list = NULL;
63 const char *logdir;
64 GaimPlugin *plugin;
65 GaimPluginProtocolInfo *prpl_info;
66 char *prpl_name;
67 char *temp;
68 char *path;
69 GDir *dir;
70
71 g_return_val_if_fail(sn != NULL, list);
72 g_return_val_if_fail(account != NULL, list);
73
74 logdir = gaim_prefs_get_string("/plugins/core/log_reader/adium/log_directory");
75
76 /* By clearing the log directory path, this logger can be (effectively) disabled. */
77 if (!*logdir)
78 return list;
79
80 plugin = gaim_find_prpl(gaim_account_get_protocol_id(account));
81 if (!plugin)
82 return NULL;
83
84 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
85 if (!prpl_info->list_icon)
86 return NULL;
87
88 prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1);
89
90 temp = g_strdup_printf("%s.%s", prpl_name, account->username);
91 path = g_build_filename(logdir, temp, sn, NULL);
92 g_free(temp);
93
94 dir = g_dir_open(path, 0, NULL);
95 if (dir) {
96 const gchar *file;
97
98 while ((file = g_dir_read_name(dir))) {
99 if (!gaim_str_has_prefix(file, sn))
100 continue;
101 if (gaim_str_has_suffix(file, ".html") || gaim_str_has_suffix(file, ".AdiumHTMLLog")) {
102 struct tm tm;
103 const char *date = file;
104
105 date += strlen(sn) + 2;
106 if (sscanf(date, "%u|%u|%u",
107 &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
108
109 gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
110 "Filename timestamp parsing error\n");
111 } else {
112 char *filename = g_build_filename(path, file, NULL);
113 FILE *handle = g_fopen(filename, "rb");
114 char *contents;
115 char *contents2;
116 struct adium_logger_data *data;
117 GaimLog *log;
118
119 if (!handle) {
120 g_free(filename);
121 continue;
122 }
123
124 /* XXX: This is really inflexible. */
125 contents = g_malloc(57);
126 fread(contents, 56, 1, handle);
127 fclose(handle);
128 contents[56] = '\0';
129
130 /* XXX: This is fairly inflexible. */
131 contents2 = contents;
132 while (*contents2 && *contents2 != '>')
133 contents2++;
134 if (*contents2)
135 contents2++;
136 while (*contents2 && *contents2 != '>')
137 contents2++;
138 if (*contents2)
139 contents2++;
140
141 if (sscanf(contents2, "%u.%u.%u",
142 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
143
144 gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
145 "Contents timestamp parsing error\n");
146 g_free(contents);
147 g_free(filename);
148 continue;
149 }
150 g_free(contents);
151
152 data = g_new0(struct adium_logger_data, 1);
153 data->path = filename;
154 data->type = ADIUM_HTML;
155
156 tm.tm_year -= 1900;
157 tm.tm_mon -= 1;
158
159 /* XXX: Look into this later... Should we pass in a struct tm? */
160 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
161 log->logger = adium_logger;
162 log->logger_data = data;
163
164 list = g_list_append(list, log);
165 }
166 } else if (gaim_str_has_suffix(file, ".adiumLog")) {
167 struct tm tm;
168 const char *date = file;
169
170 date += strlen(sn) + 2;
171 if (sscanf(date, "%u|%u|%u",
172 &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
173
174 gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
175 "Filename timestamp parsing error\n");
176 } else {
177 char *filename = g_build_filename(path, file, NULL);
178 FILE *handle = g_fopen(filename, "rb");
179 char *contents;
180 char *contents2;
181 struct adium_logger_data *data;
182 GaimLog *log;
183
184 if (!handle) {
185 g_free(filename);
186 continue;
187 }
188
189 /* XXX: This is really inflexible. */
190 contents = g_malloc(14);
191 fread(contents, 13, 1, handle);
192 fclose(handle);
193 contents[13] = '\0';
194
195 contents2 = contents;
196 while (*contents2 && *contents2 != '(')
197 contents2++;
198 if (*contents2)
199 contents2++;
200
201 if (sscanf(contents2, "%u.%u.%u",
202 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
203
204 gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
205 "Contents timestamp parsing error\n");
206 g_free(contents);
207 g_free(filename);
208 continue;
209 }
210
211 g_free(contents);
212
213 tm.tm_year -= 1900;
214 tm.tm_mon -= 1;
215
216 data = g_new0(struct adium_logger_data, 1);
217 data->path = filename;
218 data->type = ADIUM_TEXT;
219
220 /* XXX: Look into this later... Should we pass in a struct tm? */
221 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
222 log->logger = adium_logger;
223 log->logger_data = data;
224
225 list = g_list_append(list, log);
226 }
227 }
228 }
229 g_dir_close(dir);
230 }
231
232 g_free(prpl_name);
233 g_free(path);
234
235 return list;
236 }
237
238 static char *adium_logger_read (GaimLog *log, GaimLogReadFlags *flags)
239 {
240 struct adium_logger_data *data;
241 GError *error = NULL;
242 gchar *read = NULL;
243 gsize length;
244
245 g_return_val_if_fail(log != NULL, g_strdup(""));
246
247 data = log->logger_data;
248
249 g_return_val_if_fail(data->path != NULL, g_strdup(""));
250
251 gaim_debug(GAIM_DEBUG_INFO, "Adium log read",
252 "Reading %s\n", data->path);
253 if (!g_file_get_contents(data->path, &read, &length, &error)) {
254 gaim_debug(GAIM_DEBUG_ERROR, "Adium log read",
255 "Error reading log\n");
256 if (error)
257 g_error_free(error);
258 return g_strdup("");
259 }
260
261 if (data->type != ADIUM_HTML) {
262 char *escaped = g_markup_escape_text(read, -1);
263 g_free(read);
264 read = escaped;
265 }
266
267 #ifdef WIN32
268 /* This problem only seems to show up on Windows.
269 * The BOM is displaying as a space at the beginning of the log.
270 */
271 if (gaim_str_has_prefix(read, "\xef\xbb\xbf"))
272 {
273 /* FIXME: This feels so wrong... */
274 char *temp = g_strdup(&(read[3]));
275 g_free(read);
276 read = temp;
277 }
278 #endif
279
280 /* TODO: Apply formatting.
281 * Replace the above hack with something better, since we'll
282 * be looping over the entire log file contents anyway.
283 */
284
285 return read;
286 }
287
288 static int adium_logger_size (GaimLog *log)
289 {
290 struct adium_logger_data *data;
291 char *text;
292 size_t size;
293
294 g_return_val_if_fail(log != NULL, 0);
295
296 data = log->logger_data;
297
298 if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
299 struct stat st;
300
301 if (!data->path || stat(data->path, &st))
302 st.st_size = 0;
303
304 return st.st_size;
305 }
306
307 text = adium_logger_read(log, NULL);
308 size = strlen(text);
309 g_free(text);
310
311 return size;
312 }
313
314 static void adium_logger_finalize(GaimLog *log)
315 {
316 struct adium_logger_data *data;
317
318 g_return_if_fail(log != NULL);
319
320 data = log->logger_data;
321
322 g_free(data->path);
323 }
324
325
326 /*****************************************************************************
327 * Fire Logger *
328 *****************************************************************************/
329
330 #if 0
331 /* The fire logger doesn't write logs, only reads them. This is to include
332 * Fire logs in the log viewer transparently.
333 */
334
335 static GaimLogLogger *fire_logger;
336
337 struct fire_logger_data {
338 };
339
340 static GList *fire_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
341 {
342 /* TODO: Do something here. */
343 return NULL;
344 }
345
346 static char * fire_logger_read (GaimLog *log, GaimLogReadFlags *flags)
347 {
348 struct fire_logger_data *data;
349
350 g_return_val_if_fail(log != NULL, g_strdup(""));
351
352 data = log->logger_data;
353
354 /* TODO: Do something here. */
355 return g_strdup("");
356 }
357
358 static int fire_logger_size (GaimLog *log)
359 {
360 g_return_val_if_fail(log != NULL, 0);
361
362 if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
363 return 0;
364
365 /* TODO: Do something here. */
366 return 0;
367 }
368
369 static void fire_logger_finalize(GaimLog *log)
370 {
371 g_return_if_fail(log != NULL);
372
373 /* TODO: Do something here. */
374 }
375 #endif
376
377
378 /*****************************************************************************
379 * Messenger Plus! Logger *
380 *****************************************************************************/
381
382 #if 0
383 /* The messenger_plus logger doesn't write logs, only reads them. This is to include
384 * Messenger Plus! logs in the log viewer transparently.
385 */
386
387 static GaimLogLogger *messenger_plus_logger;
388
389 struct messenger_plus_logger_data {
390 };
391
392 static GList *messenger_plus_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
393 {
394 /* TODO: Do something here. */
395 return NULL;
396 }
397
398 static char * messenger_plus_logger_read (GaimLog *log, GaimLogReadFlags *flags)
399 {
400 struct messenger_plus_logger_data *data = log->logger_data;
401
402 g_return_val_if_fail(log != NULL, g_strdup(""));
403
404 data = log->logger_data;
405
406 /* TODO: Do something here. */
407 return g_strdup("");
408 }
409
410 static int messenger_plus_logger_size (GaimLog *log)
411 {
412 g_return_val_if_fail(log != NULL, 0);
413
414 if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
415 return 0;
416
417 /* TODO: Do something here. */
418 return 0;
419 }
420
421 static void messenger_plus_logger_finalize(GaimLog *log)
422 {
423 g_return_if_fail(log != NULL);
424
425 /* TODO: Do something here. */
426 }
427 #endif
428
429
430 /*****************************************************************************
431 * MSN Messenger Logger *
432 *****************************************************************************/
433
434 /* The msn logger doesn't write logs, only reads them. This is to include
435 * MSN Messenger message histories in the log viewer transparently.
436 */
437
438 static GaimLogLogger *msn_logger;
439
440 struct msn_logger_data {
441 xmlnode *root;
442 xmlnode *message;
443 const char *session_id;
444 int last_log;
445 GString *text;
446 };
447
448 static time_t msn_logger_parse_timestamp(xmlnode *message)
449 {
450 const char *date;
451 const char *time;
452 struct tm tm;
453 char am_pm;
454
455 g_return_val_if_fail(message != NULL, (time_t)0);
456
457 date = xmlnode_get_attrib(message, "Date");
458 if (!(date && *date)) {
459 gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse",
460 "Attribute missing: %s\n", "Date");
461 return (time_t)0;
462 }
463
464 time = xmlnode_get_attrib(message, "Time");
465 if (!(time && *time)) {
466 gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse",
467 "Attribute missing: %s\n", "Time");
468 return (time_t)0;
469 }
470
471 if (sscanf(date, "%u/%u/%u", &tm.tm_mon, &tm.tm_mday, &tm.tm_year) != 3)
472 gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse",
473 "%s parsing error\n", "Date");
474
475 if (sscanf(time, "%u:%u:%u %c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &am_pm) != 4)
476 gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse",
477 "%s parsing error\n", "Time");
478
479 tm.tm_year -= 1900;
480 tm.tm_mon -= 1;
481 if (am_pm == 'P') {
482 tm.tm_hour += 12;
483 } else if (tm.tm_hour == 12) {
484 /* 12 AM = 00 hr */
485 tm.tm_hour = 0;
486 }
487 /* Let the C library deal with daylight savings time. */
488 tm.tm_isdst = -1;
489
490 return mktime(&tm);
491 }
492
493
494 static GList *msn_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
495 {
496 GList *list = NULL;
497 char *username;
498 GaimBuddy *buddy;
499 const char *logdir;
500 const char *savedfilename = NULL;
501 char *logfile;
502 char *path;
503 GError *error = NULL;
504 gchar *contents = NULL;
505 gsize length;
506 xmlnode *root;
507 xmlnode *message;
508 const char *old_session_id = "";
509 struct msn_logger_data *data = NULL;
510
511 g_return_val_if_fail(sn != NULL, list);
512 g_return_val_if_fail(account != NULL, list);
513
514 if (strcmp(account->protocol_id, "prpl-msn"))
515 return list;
516
517 logdir = gaim_prefs_get_string("/plugins/core/log_reader/msn/log_directory");
518
519 /* By clearing the log directory path, this logger can be (effectively) disabled. */
520 if (!*logdir)
521 return list;
522
523 buddy = gaim_find_buddy(account, sn);
524
525 if ((username = g_strdup(gaim_account_get_string(
526 account, "log_reader_msn_log_folder", NULL)))) {
527 /* As a special case, we allow the null string to kill the parsing
528 * straight away. This would allow the user to deal with the case
529 * when two account have the same username at different domains and
530 * only one has logs stored.
531 */
532 if (!*username) {
533 g_free(username);
534 return list;
535 }
536 } else {
537 username = g_strdup(gaim_normalize(account, account->username));
538 }
539
540 if (buddy)
541 savedfilename = gaim_blist_node_get_string(&buddy->node, "log_reader_msn_log_filename");
542
543 if (savedfilename) {
544 /* As a special case, we allow the null string to kill the parsing
545 * straight away. This would allow the user to deal with the case
546 * when two buddies have the same username at different domains and
547 * only one has logs stored.
548 */
549 if (!*savedfilename) {
550 g_free(username);
551 return list;
552 }
553
554 logfile = g_strdup(savedfilename);
555 } else {
556 logfile = g_strdup_printf("%s.xml", gaim_normalize(account, sn));
557 }
558
559 path = g_build_filename(logdir, username, "History", logfile, NULL);
560
561 if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
562 gboolean found = FALSE;
563 char *at_sign;
564 GDir *dir;
565
566 g_free(path);
567
568 if (savedfilename) {
569 /* We had a saved filename, but it doesn't exist.
570 * Returning now is the right course of action because we don't
571 * want to detect another file incorrectly.
572 */
573 g_free(username);
574 g_free(logfile);
575 return list;
576 }
577
578 /* Perhaps we're using a new version of MSN with the weird numbered folders.
579 * I don't know how the numbers are calculated, so I'm going to attempt to
580 * find logs by pattern matching...
581 */
582
583 at_sign = g_strrstr(username, "@");
584 if (at_sign)
585 *at_sign = '\0';
586
587 dir = g_dir_open(logdir, 0, NULL);
588 if (dir) {
589 const gchar *name;
590
591 while ((name = g_dir_read_name(dir))) {
592 const char *c = name;
593
594 if (!gaim_str_has_prefix(c, username))
595 continue;
596
597 c += strlen(username);
598 while (*c) {
599 if (!g_ascii_isdigit(*c))
600 break;
601
602 c++;
603 }
604
605 path = g_build_filename(logdir, name, NULL);
606 /* The !c makes sure we got to the end of the while loop above. */
607 if (!*c && g_file_test(path, G_FILE_TEST_IS_DIR)) {
608 char *history_path = g_build_filename(
609 path, "History", NULL);
610 if (g_file_test(history_path, G_FILE_TEST_IS_DIR)) {
611 gaim_account_set_string(account,
612 "log_reader_msn_log_folder", name);
613 g_free(path);
614 path = history_path;
615 found = TRUE;
616 break;
617 }
618 g_free(path);
619 g_free(history_path);
620 }
621 else
622 g_free(path);
623 }
624 g_dir_close(dir);
625 }
626 g_free(username);
627
628 if (!found) {
629 g_free(logfile);
630 return list;
631 }
632
633 /* If we've reached this point, we've found a History folder. */
634
635 username = g_strdup(gaim_normalize(account, sn));
636 at_sign = g_strrstr(username, "@");
637 if (at_sign)
638 *at_sign = '\0';
639
640 found = FALSE;
641 dir = g_dir_open(path, 0, NULL);
642 if (dir) {
643 const gchar *name;
644
645 while ((name = g_dir_read_name(dir))) {
646 const char *c = name;
647
648 if (!gaim_str_has_prefix(c, username))
649 continue;
650
651 c += strlen(username);
652 while (*c) {
653 if (!g_ascii_isdigit(*c))
654 break;
655
656 c++;
657 }
658
659 path = g_build_filename(path, name, NULL);
660 if (!strcmp(c, ".xml") &&
661 g_file_test(path, G_FILE_TEST_EXISTS)) {
662 found = TRUE;
663 g_free(logfile);
664 logfile = g_strdup(name);
665 break;
666 }
667 else
668 g_free(path);
669 }
670 g_dir_close(dir);
671 }
672 g_free(username);
673
674 if (!found) {
675 g_free(logfile);
676 return list;
677 }
678 } else {
679 g_free(username);
680 g_free(logfile);
681 logfile = NULL; /* No sense saving the obvious buddy@domain.com. */
682 }
683
684 gaim_debug(GAIM_DEBUG_INFO, "MSN log read",
685 "Reading %s\n", path);
686 if (!g_file_get_contents(path, &contents, &length, &error)) {
687 g_free(path);
688 gaim_debug(GAIM_DEBUG_ERROR, "MSN log read",
689 "Error reading log\n");
690 if (error)
691 g_error_free(error);
692 return list;
693 }
694 g_free(path);
695
696 /* Reading the file was successful...
697 * Save its name if it involves the crazy numbers. The idea here is that you could
698 * then tweak the blist.xml file by hand if need be. This would be the case if two
699 * buddies have the same username at different domains. One set of logs would get
700 * detected for both buddies.
701 */
702 if (buddy && logfile) {
703 gaim_blist_node_set_string(&buddy->node, "log_reader_msn_log_filename", logfile);
704 g_free(logfile);
705 }
706
707 root = xmlnode_from_str(contents, length);
708 g_free(contents);
709 if (!root)
710 return list;
711
712 for (message = xmlnode_get_child(root, "Message"); message;
713 message = xmlnode_get_next_twin(message)) {
714 const char *session_id;
715
716 session_id = xmlnode_get_attrib(message, "SessionID");
717 if (!session_id) {
718 gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
719 "Error parsing message: %s\n", "SessionID missing");
720 continue;
721 }
722
723 if (strcmp(session_id, old_session_id)) {
724 /*
725 * The session ID differs from the last message.
726 * Thus, this is the start of a new conversation.
727 */
728 GaimLog *log;
729
730 data = g_new0(struct msn_logger_data, 1);
731 data->root = root;
732 data->message = message;
733 data->session_id = session_id;
734 data->text = NULL;
735 data->last_log = FALSE;
736
737 /* XXX: Look into this later... Should we pass in a struct tm? */
738 log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, msn_logger_parse_timestamp(message), NULL);
739 log->logger = msn_logger;
740 log->logger_data = data;
741
742 list = g_list_append(list, log);
743 }
744 old_session_id = session_id;
745 }
746
747 if (data)
748 data->last_log = TRUE;
749
750 return list;
751 }
752
753 static char * msn_logger_read (GaimLog *log, GaimLogReadFlags *flags)
754 {
755 struct msn_logger_data *data;
756 GString *text = NULL;
757 xmlnode *message;
758
759 g_return_val_if_fail(log != NULL, g_strdup(""));
760
761 data = log->logger_data;
762
763 if (data->text) {
764 /* The GTK code which displays the logs g_free()s whatever is
765 * returned from this function. Thus, we can't reuse the str
766 * part of the GString. The only solution is to free it and
767 * start over.
768 */
769 g_string_free(data->text, FALSE);
770 }
771
772 text = g_string_new("");
773
774 if (!data->root || !data->message || !data->session_id) {
775 /* Something isn't allocated correctly. */
776 gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
777 "Error parsing message: %s\n", "Internal variables inconsistent");
778 data->text = text;
779
780 return text->str;
781 }
782
783 for (message = data->message; message;
784 message = xmlnode_get_next_twin(message)) {
785
786 const char *new_session_id;
787 xmlnode *text_node;
788 const char *from_name = NULL;
789 const char *to_name = NULL;
790 xmlnode *from;
791 xmlnode *to;
792 enum name_guesses name_guessed = NAME_GUESS_UNKNOWN;
793 const char *their_name;
794 time_t time_unix;
795 struct tm *tm_new;
796 char *timestamp;
797 char *tmp;
798 const char *style;
799
800 new_session_id = xmlnode_get_attrib(message, "SessionID");
801
802 /* If this triggers, something is wrong with the XML. */
803 if (!new_session_id) {
804 gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
805 "Error parsing message: %s\n", "New SessionID missing");
806 break;
807 }
808
809 if (strcmp(new_session_id, data->session_id)) {
810 /* The session ID differs from the first message.
811 * Thus, this is the start of a new conversation.
812 */
813 break;
814 }
815
816 text_node = xmlnode_get_child(message, "Text");
817 if (!text_node)
818 continue;
819
820 from = xmlnode_get_child(message, "From");
821 if (from) {
822 xmlnode *user = xmlnode_get_child(from, "User");
823
824 if (user) {
825 from_name = xmlnode_get_attrib(user, "FriendlyName");
826
827 /* This saves a check later. */
828 if (!*from_name)
829 from_name = NULL;
830 }
831 }
832
833 to = xmlnode_get_child(message, "To");
834 if (to) {
835 xmlnode *user = xmlnode_get_child(to, "User");
836 if (user) {
837 to_name = xmlnode_get_attrib(user, "FriendlyName");
838
839 /* This saves a check later. */
840 if (!*to_name)
841 to_name = NULL;
842 }
843 }
844
845 their_name = from_name;
846 if (from_name && gaim_prefs_get_bool("/plugins/core/log_reader/use_name_heuristics")) {
847 const char *friendly_name = gaim_connection_get_display_name(log->account->gc);
848
849 if (friendly_name != NULL) {
850 int friendly_name_length = strlen(friendly_name);
851 int alias_length = strlen(log->account->alias);
852 GaimBuddy *buddy = gaim_find_buddy(log->account, log->name);
853 gboolean from_name_matches;
854 gboolean to_name_matches;
855
856 if (buddy && buddy->alias)
857 their_name = buddy->alias;
858
859 /* Try to guess which user is me.
860 * The first step is to determine if either of the names matches either my
861 * friendly name or alias. For this test, "match" is defined as:
862 * ^(friendly_name|alias)([^a-zA-Z0-9].*)?$
863 */
864 from_name_matches = (gaim_str_has_prefix(from_name, friendly_name) &&
865 !isalnum(*(from_name + friendly_name_length))) ||
866 (gaim_str_has_prefix(from_name, log->account->alias) &&
867 !isalnum(*(from_name + alias_length)));
868
869 to_name_matches = to_name != NULL && (
870 (gaim_str_has_prefix(to_name, friendly_name) &&
871 !isalnum(*(to_name + friendly_name_length))) ||
872 (gaim_str_has_prefix(to_name, log->account->alias) &&
873 !isalnum(*(to_name + alias_length))));
874
875 if (from_name_matches) {
876 if (!to_name_matches) {
877 name_guessed = NAME_GUESS_ME;
878 }
879 } else if (to_name_matches) {
880 name_guessed = NAME_GUESS_THEM;
881 } else {
882 if (buddy && buddy->alias) {
883 char *alias = g_strdup(buddy->alias);
884
885 /* "Truncate" the string at the first non-alphanumeric
886 * character. The idea is to relax the comparison.
887 */
888 char *temp;
889 for (temp = alias; *temp ; temp++) {
890 if (!isalnum(*temp)) {
891 *temp = '\0';
892 break;
893 }
894 }
895 alias_length = strlen(alias);
896
897 /* Try to guess which user is them.
898 * The first step is to determine if either of the names
899 * matches their alias. For this test, "match" is
900 * defined as: ^alias([^a-zA-Z0-9].*)?$
901 */
902 from_name_matches = (gaim_str_has_prefix(
903 from_name, alias) &&
904 !isalnum(*(from_name +
905 alias_length)));
906
907 to_name_matches = to_name && (gaim_str_has_prefix(
908 to_name, alias) &&
909 !isalnum(*(to_name +
910 alias_length)));
911
912 g_free(alias);
913
914 if (from_name_matches) {
915 if (!to_name_matches) {
916 name_guessed = NAME_GUESS_THEM;
917 }
918 } else if (to_name_matches) {
919 name_guessed = NAME_GUESS_ME;
920 } else if (buddy->server_alias) {
921 friendly_name_length =
922 strlen(buddy->server_alias);
923
924 /* Try to guess which user is them.
925 * The first step is to determine if either of
926 * the names matches their friendly name. For
927 * this test, "match" is defined as:
928 * ^friendly_name([^a-zA-Z0-9].*)?$
929 */
930 from_name_matches = (gaim_str_has_prefix(
931 from_name,
932 buddy->server_alias) &&
933 !isalnum(*(from_name +
934 friendly_name_length)));
935
936 to_name_matches = to_name && (
937 (gaim_str_has_prefix(
938 to_name, buddy->server_alias) &&
939 !isalnum(*(to_name +
940 friendly_name_length))));
941
942 if (from_name_matches) {
943 if (!to_name_matches) {
944 name_guessed = NAME_GUESS_THEM;
945 }
946 } else if (to_name_matches) {
947 name_guessed = NAME_GUESS_ME;
948 }
949 }
950 }
951 }
952 }
953 }
954
955 if (name_guessed != NAME_GUESS_UNKNOWN) {
956 text = g_string_append(text, "<span style=\"color: #");
957 if (name_guessed == NAME_GUESS_ME)
958 text = g_string_append(text, "16569E");
959 else
960 text = g_string_append(text, "A82F2F");
961 text = g_string_append(text, ";\">");
962 }
963
964 time_unix = msn_logger_parse_timestamp(message);
965 tm_new = localtime(&time_unix);
966
967 timestamp = g_strdup_printf("<font size=\"2\">(%02u:%02u:%02u)</font> ",
968 tm_new->tm_hour, tm_new->tm_min, tm_new->tm_sec);
969 text = g_string_append(text, timestamp);
970 g_free(timestamp);
971
972 if (from_name) {
973 text = g_string_append(text, "<b>");
974
975 if (name_guessed == NAME_GUESS_ME)
976 text = g_string_append(text, log->account->alias);
977 else if (name_guessed == NAME_GUESS_THEM)
978 text = g_string_append(text, their_name);
979 else
980 text = g_string_append(text, from_name);
981
982 text = g_string_append(text, ":</b> ");
983 }
984
985 if (name_guessed != NAME_GUESS_UNKNOWN)
986 text = g_string_append(text, "</span>");
987
988 style = xmlnode_get_attrib(text_node, "Style");
989
990 tmp = xmlnode_get_data(text_node);
991 if (style && *style) {
992 text = g_string_append(text, "<span style=\"");
993 text = g_string_append(text, style);
994 text = g_string_append(text, "\">");
995 text = g_string_append(text, tmp);
996 text = g_string_append(text, "</span>\n");
997 } else {
998 text = g_string_append(text, tmp);
999 text = g_string_append(text, "\n");
1000 }
1001 g_free(tmp);
1002 }
1003
1004 data->text = text;
1005
1006 return text->str;
1007 }
1008
1009 static int msn_logger_size (GaimLog *log)
1010 {
1011 char *text;
1012 size_t size;
1013
1014 g_return_val_if_fail(log != NULL, 0);
1015
1016 if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
1017 return 0;
1018
1019 text = msn_logger_read(log, NULL);
1020 size = strlen(text);
1021 g_free(text);
1022
1023 return size;
1024 }
1025
1026 static void msn_logger_finalize(GaimLog *log)
1027 {
1028 struct msn_logger_data *data;
1029
1030 g_return_if_fail(log != NULL);
1031
1032 data = log->logger_data;
1033
1034 if (data->last_log)
1035 xmlnode_free(data->root);
1036
1037 if (data->text)
1038 g_string_free(data->text, FALSE);
1039 }
1040
1041
1042 /*****************************************************************************
1043 * Trillian Logger *
1044 *****************************************************************************/
1045
1046 /* The trillian logger doesn't write logs, only reads them. This is to include
1047 * Trillian logs in the log viewer transparently.
1048 */
1049
1050 static GaimLogLogger *trillian_logger;
1051 static void trillian_logger_finalize(GaimLog *log);
1052
1053 struct trillian_logger_data {
1054 char *path; /* FIXME: Change this to use GaimStringref like log.c:old_logger_list */
1055 int offset;
1056 int length;
1057 char *their_nickname;
1058 };
1059
1060 static GList *trillian_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
1061 {
1062 GList *list = NULL;
1063 const char *logdir;
1064 GaimPlugin *plugin;
1065 GaimPluginProtocolInfo *prpl_info;
1066 char *prpl_name;
1067 const char *buddy_name;
1068 char *filename;
1069 char *path;
1070 GError *error = NULL;
1071 gchar *contents = NULL;
1072 gsize length;
1073 gchar *line;
1074 gchar *c;
1075
1076 g_return_val_if_fail(sn != NULL, list);
1077 g_return_val_if_fail(account != NULL, list);
1078
1079 logdir = gaim_prefs_get_string("/plugins/core/log_reader/trillian/log_directory");
1080
1081 /* By clearing the log directory path, this logger can be (effectively) disabled. */
1082 if (!*logdir)
1083 return list;
1084
1085 plugin = gaim_find_prpl(gaim_account_get_protocol_id(account));
1086 if (!plugin)
1087 return NULL;
1088
1089 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
1090 if (!prpl_info->list_icon)
1091 return NULL;
1092
1093 prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1);
1094
1095 buddy_name = gaim_normalize(account, sn);
1096
1097 filename = g_strdup_printf("%s.log", buddy_name);
1098 path = g_build_filename(
1099 logdir, prpl_name, filename, NULL);
1100
1101 gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
1102 "Reading %s\n", path);
1103 /* FIXME: There's really no need to read the entire file at once.
1104 * See src/log.c:old_logger_list for a better approach.
1105 */
1106 if (!g_file_get_contents(path, &contents, &length, &error)) {
1107 if (error) {
1108 g_error_free(error);
1109 error = NULL;
1110 }
1111 g_free(path);
1112
1113 path = g_build_filename(
1114 logdir, prpl_name, "Query", filename, NULL);
1115 gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
1116 "Reading %s\n", path);
1117 if (!g_file_get_contents(path, &contents, &length, &error)) {
1118 if (error)
1119 g_error_free(error);
1120 }
1121 }
1122 g_free(filename);
1123
1124 if (contents) {
1125 struct trillian_logger_data *data = NULL;
1126 int offset = 0;
1127 int last_line_offset = 0;
1128
1129 line = contents;
1130 c = contents;
1131 while (*c) {
1132 offset++;
1133
1134 if (*c != '\n') {
1135 c++;
1136 continue;
1137 }
1138
1139 *c = '\0';
1140 if (gaim_str_has_prefix(line, "Session Close ")) {
1141 if (data && !data->length) {
1142 if (!(data->length = last_line_offset - data->offset)) {
1143 /* This log had no data, so we remove it. */
1144 GList *last = g_list_last(list);
1145
1146 gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
1147 "Empty log. Offset %i\n", data->offset);
1148
1149 trillian_logger_finalize((GaimLog *)last->data);
1150 list = g_list_delete_link(list, last);
1151 }
1152 }
1153 } else if (line[0] && line[1] && line [3] &&
1154 gaim_str_has_prefix(&line[3], "sion Start ")) {
1155
1156 char *their_nickname = line;
1157 char *timestamp;
1158
1159 if (data && !data->length)
1160 data->length = last_line_offset - data->offset;
1161
1162 while (*their_nickname && (*their_nickname != ':'))
1163 their_nickname++;
1164 their_nickname++;
1165
1166 /* This code actually has nothing to do with
1167 * the timestamp YET. I'm simply using this
1168 * variable for now to NUL-terminate the
1169 * their_nickname string.
1170 */
1171 timestamp = their_nickname;
1172 while (*timestamp && *timestamp != ')')
1173 timestamp++;
1174
1175 if (*timestamp == ')') {
1176 char *month;
1177 struct tm tm;
1178
1179 *timestamp = '\0';
1180 if (line[0] && line[1] && line[2])
1181 timestamp += 3;
1182
1183 /* Now we start dealing with the timestamp. */
1184
1185 /* Skip over the day name. */
1186 while (*timestamp && (*timestamp != ' '))
1187 timestamp++;
1188 *timestamp = '\0';
1189 timestamp++;
1190
1191 /* Parse out the month. */
1192 month = timestamp;
1193 while (*timestamp && (*timestamp != ' '))
1194 timestamp++;
1195 *timestamp = '\0';
1196 timestamp++;
1197
1198 /* Parse the day, time, and year. */
1199 if (sscanf(timestamp, "%u %u:%u:%u %u",
1200 &tm.tm_mday, &tm.tm_hour,
1201 &tm.tm_min, &tm.tm_sec,
1202 &tm.tm_year) != 5) {
1203
1204 gaim_debug(GAIM_DEBUG_ERROR,
1205 "Trillian log timestamp parse",
1206 "Session Start parsing error\n");
1207 } else {
1208 GaimLog *log;
1209
1210 tm.tm_year -= 1900;
1211
1212 /* Let the C library deal with
1213 * daylight savings time.
1214 */
1215 tm.tm_isdst = -1;
1216
1217 /* Ugly hack, in case current locale
1218 * is not English. This code is taken
1219 * from log.c.
1220 */
1221 if (strcmp(month, "Jan") == 0) {
1222 tm.tm_mon= 0;
1223 } else if (strcmp(month, "Feb") == 0) {
1224 tm.tm_mon = 1;
1225 } else if (strcmp(month, "Mar") == 0) {
1226 tm.tm_mon = 2;
1227 } else if (strcmp(month, "Apr") == 0) {
1228 tm.tm_mon = 3;
1229 } else if (strcmp(month, "May") == 0) {
1230 tm.tm_mon = 4;
1231 } else if (strcmp(month, "Jun") == 0) {
1232 tm.tm_mon = 5;
1233 } else if (strcmp(month, "Jul") == 0) {
1234 tm.tm_mon = 6;
1235 } else if (strcmp(month, "Aug") == 0) {
1236 tm.tm_mon = 7;
1237 } else if (strcmp(month, "Sep") == 0) {
1238 tm.tm_mon = 8;
1239 } else if (strcmp(month, "Oct") == 0) {
1240 tm.tm_mon = 9;
1241 } else if (strcmp(month, "Nov") == 0) {
1242 tm.tm_mon = 10;
1243 } else if (strcmp(month, "Dec") == 0) {
1244 tm.tm_mon = 11;
1245 }
1246
1247 data = g_new0(
1248 struct trillian_logger_data, 1);
1249 data->path = g_strdup(path);
1250 data->offset = offset;
1251 data->length = 0;
1252 data->their_nickname =
1253 g_strdup(their_nickname);
1254
1255 /* XXX: Look into this later... Should we pass in a struct tm? */
1256 log = gaim_log_new(GAIM_LOG_IM,
1257 sn, account, NULL, mktime(&tm), NULL);
1258 log->logger = trillian_logger;
1259 log->logger_data = data;
1260
1261 list = g_list_append(list, log);
1262 }
1263 }
1264 }
1265 c++;
1266 line = c;
1267 last_line_offset = offset;
1268 }
1269
1270 g_free(contents);
1271 }
1272 g_free(path);
1273
1274 g_free(prpl_name);
1275
1276 return list;
1277 }
1278
1279 static char * trillian_logger_read (GaimLog *log, GaimLogReadFlags *flags)
1280 {
1281 struct trillian_logger_data *data;
1282 char *read;
1283 FILE *file;
1284 GaimBuddy *buddy;
1285 char *escaped;
1286 GString *formatted;
1287 char *c;
1288 char *line;
1289
1290 g_return_val_if_fail(log != NULL, g_strdup(""));
1291
1292 data = log->logger_data;
1293
1294 g_return_val_if_fail(data->path != NULL, g_strdup(""));
1295 g_return_val_if_fail(data->length > 0, g_strdup(""));
1296 g_return_val_if_fail(data->their_nickname != NULL, g_strdup(""));
1297
1298 gaim_debug(GAIM_DEBUG_INFO, "Trillian log read",
1299 "Reading %s\n", data->path);
1300
1301 read = g_malloc(data->length + 2);
1302
1303 file = g_fopen(data->path, "rb");
1304 fseek(file, data->offset, SEEK_SET);
1305 fread(read, data->length, 1, file);
1306 fclose(file);
1307
1308 if (read[data->length-1] == '\n') {
1309 read[data->length] = '\0';
1310 } else {
1311 read[data->length] = '\n';
1312 read[data->length+1] = '\0';
1313 }
1314
1315 /* Load miscellaneous data. */
1316 buddy = gaim_find_buddy(log->account, log->name);
1317
1318 escaped = g_markup_escape_text(read, -1);
1319 g_free(read);
1320 read = escaped;
1321
1322 /* Apply formatting... */
1323 formatted = g_string_new("");
1324 c = read;
1325 line = read;
1326 while (*c)
1327 {
1328 if (*c == '\n')
1329 {
1330 char *link_temp_line;
1331 char *link;
1332 char *timestamp;
1333 char *footer = NULL;
1334 *c = '\0';
1335
1336 /* Convert links.
1337 *
1338 * The format is (Link: URL)URL
1339 * So, I want to find each occurance of "(Link: " and replace that chunk with:
1340 * <a href="
1341 * Then, replace the next ")" with:
1342 * ">
1343 * Then, replace the next " " (or add this if the end-of-line is reached) with:
1344 * </a>
1345 */
1346 link_temp_line = NULL;
1347 while ((link = g_strstr_len(line, strlen(line), "(Link: "))) {
1348 GString *temp;
1349
1350 if (!*link)
1351 continue;
1352
1353 *link = '\0';
1354 link++;
1355
1356 temp = g_string_new(line);
1357 g_string_append(temp, "<a href=\"");
1358
1359 if (strlen(link) >= 6) {
1360 link += (sizeof("(Link: ") - 1);
1361
1362 while (*link && *link != ')') {
1363 g_string_append_c(temp, *link);
1364 link++;
1365 }
1366 if (link) {
1367 link++;
1368
1369 g_string_append(temp, "\">");
1370 while (*link && *link != ' ') {
1371 g_string_append_c(temp, *link);
1372 link++;
1373 }
1374 g_string_append(temp, "</a>");
1375 }
1376
1377 g_string_append(temp, link);
1378
1379 /* Free the last round's line. */
1380 if (link_temp_line)
1381 g_free(line);
1382
1383 line = temp->str;
1384 g_string_free(temp, FALSE);
1385
1386 /* Save this memory location so we can free it later. */
1387 link_temp_line = line;
1388 }
1389 }
1390
1391 timestamp = "";
1392 if (*line == '[') {
1393 timestamp = line;
1394 while (*timestamp && *timestamp != ']')
1395 timestamp++;
1396 if (*timestamp == ']') {
1397 *timestamp = '\0';
1398 line++;
1399 /* TODO: Parse the timestamp and convert it to Gaim's format. */
1400 g_string_append_printf(formatted,
1401 "<font size=\"2\">(%s)</font> ", line);
1402 line = timestamp;
1403 if (line[1] && line[2])
1404 line += 2;
1405 }
1406
1407 if (gaim_str_has_prefix(line, "*** ")) {
1408 line += (sizeof("*** ") - 1);
1409 g_string_append(formatted, "<b>");
1410 footer = "</b>";
1411 if (gaim_str_has_prefix(line, "NOTE: This user is offline.")) {
1412 line = _("User is offline.");
1413 } else if (gaim_str_has_prefix(line,
1414 "NOTE: Your status is currently set to ")) {
1415
1416 line += (sizeof("NOTE: ") - 1);
1417 } else if (gaim_str_has_prefix(line, "Auto-response sent to ")) {
1418 g_string_append(formatted, _("Auto-response sent:"));
1419 while (*line && *line != ':')
1420 line++;
1421 if (*line)
1422 line++;
1423 g_string_append(formatted, "</b>");
1424 footer = NULL;
1425 } else if (strstr(line, " signed off ")) {
1426 if (buddy != NULL && buddy->alias)
1427 g_string_append_printf(formatted,
1428 _("%s has signed off."), buddy->alias);
1429 else
1430 g_string_append_printf(formatted,
1431 _("%s has signed off."), log->name);
1432 line = "";
1433 } else if (strstr(line, " signed on ")) {
1434 if (buddy != NULL && buddy->alias)
1435 g_string_append(formatted, buddy->alias);
1436 else
1437 g_string_append(formatted, log->name);
1438 line = " logged in.";
1439 } else if (gaim_str_has_prefix(line,
1440 "One or more messages may have been undeliverable.")) {
1441
1442 g_string_append(formatted,
1443 "<span style=\"color: #ff0000;\">");
1444 g_string_append(formatted,
1445 _("One or more messages may have been "
1446 "undeliverable."));
1447 line = "";
1448 footer = "</span></b>";
1449 } else if (gaim_str_has_prefix(line,
1450 "You have been disconnected.")) {
1451
1452 g_string_append(formatted,
1453 "<span style=\"color: #ff0000;\">");
1454 g_string_append(formatted,
1455 _("You were disconnected from the server."));
1456 line = "";
1457 footer = "</span></b>";
1458 } else if (gaim_str_has_prefix(line,
1459 "You are currently disconnected.")) {
1460
1461 g_string_append(formatted,
1462 "<span style=\"color: #ff0000;\">");
1463 line = _("You are currently disconnected. Messages "
1464 "will not be received unless you are "
1465 "logged in.");
1466 footer = "</span></b>";
1467 } else if (gaim_str_has_prefix(line,
1468 "Your previous message has not been sent.")) {
1469
1470 g_string_append(formatted,
1471 "<span style=\"color: #ff0000;\">");
1472
1473 if (gaim_str_has_prefix(line,
1474 "Your previous message has not been sent. "
1475 "Reason: Maximum length exceeded.")) {
1476
1477 g_string_append(formatted,
1478 _("Message could not be sent because "
1479 "the maximum length was exceeded."));
1480 line = "";
1481 } else {
1482 g_string_append(formatted,
1483 _("Message could not be sent."));
1484 line += (sizeof(
1485 "Your previous message "
1486 "has not been sent. ") - 1);
1487 }
1488
1489 footer = "</span></b>";
1490 }
1491 } else if (gaim_str_has_prefix(line, data->their_nickname)) {
1492 if (buddy != NULL && buddy->alias) {
1493 line += strlen(data->their_nickname) + 2;
1494 g_string_append_printf(formatted,
1495 "<span style=\"color: #A82F2F;\">"
1496 "<b>%s</b></span>: ", buddy->alias);
1497 }
1498 } else {
1499 char *line2 = line;
1500 while (*line2 && *line2 != ':')
1501 line2++;
1502 if (*line2 == ':') {
1503 line2++;
1504 line = line2;
1505 g_string_append_printf(formatted,
1506 "<span style=\"color: #16569E;\">"
1507 "<b>%s</b></span>:", log->account->alias);
1508 }
1509 }
1510 }
1511
1512 g_string_append(formatted, line);
1513
1514 if (footer)
1515 g_string_append(formatted, footer);
1516
1517 g_string_append_c(formatted, '\n');
1518
1519 if (link_temp_line)
1520 g_free(link_temp_line);
1521
1522 c++;
1523 line = c;
1524 } else
1525 c++;
1526 }
1527
1528 g_free(read);
1529 read = formatted->str;
1530 g_string_free(formatted, FALSE);
1531
1532 return read;
1533 }
1534
1535 static int trillian_logger_size (GaimLog *log)
1536 {
1537 struct trillian_logger_data *data;
1538 char *text;
1539 size_t size;
1540
1541 g_return_val_if_fail(log != NULL, 0);
1542
1543 data = log->logger_data;
1544
1545 if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
1546 return data ? data->length : 0;
1547 }
1548
1549 text = trillian_logger_read(log, NULL);
1550 size = strlen(text);
1551 g_free(text);
1552
1553 return size;
1554 }
1555
1556 static void trillian_logger_finalize(GaimLog *log)
1557 {
1558 struct trillian_logger_data *data;
1559
1560 g_return_if_fail(log != NULL);
1561
1562 data = log->logger_data;
1563
1564 g_free(data->path);
1565 g_free(data->their_nickname);
1566
1567 }
1568
1569
1570 /*****************************************************************************
1571 * Plugin Code *
1572 *****************************************************************************/
1573
1574 static void
1575 init_plugin(GaimPlugin *plugin)
1576 {
1577 char *path;
1578 #ifdef _WIN32
1579 char *folder;
1580 #endif
1581
1582 g_return_if_fail(plugin != NULL);
1583
1584 gaim_prefs_add_none("/plugins/core/log_reader");
1585
1586
1587 /* Add general preferences. */
1588
1589 gaim_prefs_add_bool("/plugins/core/log_reader/fast_sizes", FALSE);
1590 gaim_prefs_add_bool("/plugins/core/log_reader/use_name_heuristics", TRUE);
1591
1592
1593 /* Add Adium log directory preference. */
1594 gaim_prefs_add_none("/plugins/core/log_reader/adium");
1595
1596 /* Calculate default Adium log directory. */
1597 #ifdef _WIN32
1598 path = "";
1599 #else
1600 path = g_build_filename(gaim_home_dir(), "Library", "Application Support",
1601 "Adium 2.0", "Users", "Default", "Logs", NULL);
1602 #endif
1603
1604 gaim_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path);
1605
1606 #ifndef _WIN32
1607 g_free(path);
1608 #endif
1609
1610
1611 /* Add Fire log directory preference. */
1612 gaim_prefs_add_none("/plugins/core/log_reader/fire");
1613
1614 /* Calculate default Fire log directory. */
1615 #ifdef _WIN32
1616 path = "";
1617 #else
1618 path = g_build_filename(gaim_home_dir(), "Library", "Application Support",
1619 "Fire", "Sessions", NULL);
1620 #endif
1621
1622 gaim_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path);
1623
1624 #ifndef _WIN32
1625 g_free(path);
1626 #endif
1627
1628
1629 /* Add Messenger Plus! log directory preference. */
1630 gaim_prefs_add_none("/plugins/core/log_reader/messenger_plus");
1631
1632 /* Calculate default Messenger Plus! log directory. */
1633 #ifdef _WIN32
1634 folder = wgaim_get_special_folder(CSIDL_PERSONAL);
1635 if (folder) {
1636 #endif
1637 path = g_build_filename(
1638 #ifdef _WIN32
1639 folder,
1640 #else
1641 GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
1642 g_get_user_name(), "My Documents",
1643 #endif
1644 "My Chat Logs", NULL);
1645 #ifdef _WIN32
1646 g_free(folder);
1647 } else /* !folder */
1648 path = g_strdup("");
1649 #endif
1650
1651 gaim_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path);
1652 g_free(path);
1653
1654
1655 /* Add MSN Messenger log directory preference. */
1656 gaim_prefs_add_none("/plugins/core/log_reader/msn");
1657
1658 /* Calculate default MSN message history directory. */
1659 #ifdef _WIN32
1660 folder = wgaim_get_special_folder(CSIDL_PERSONAL);
1661 if (folder) {
1662 #endif
1663 path = g_build_filename(
1664 #ifdef _WIN32
1665 folder,
1666 #else
1667 GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
1668 g_get_user_name(), "My Documents",
1669 #endif
1670 "My Received Files", NULL);
1671 #ifdef _WIN32
1672 g_free(folder);
1673 } else /* !folder */
1674 path = g_strdup("");
1675 #endif
1676
1677 gaim_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path);
1678 g_free(path);
1679
1680
1681 /* Add Trillian log directory preference. */
1682 gaim_prefs_add_none("/plugins/core/log_reader/trillian");
1683
1684 #ifdef _WIN32
1685 /* XXX: While a major hack, this is the most reliable way I could
1686 * think of to determine the Trillian installation directory.
1687 */
1688 HKEY hKey;
1689 char buffer[1024] = "";
1690 DWORD size = (sizeof(buffer) - 1);
1691 DWORD type;
1692 gboolean found = FALSE;
1693
1694 path = NULL;
1695 /* TODO: Test this after removing the trailing "\\". */
1696 if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, "Trillian.SkinZip\\shell\\Add\\command\\",
1697 0, KEY_QUERY_VALUE, &hKey)) {
1698
1699 if(ERROR_SUCCESS == RegQueryValueEx(hKey, "", NULL, &type, (LPBYTE)buffer, &size)) {
1700 char *value = buffer;
1701 char *temp;
1702
1703 /* Ensure the data is null terminated. */
1704 value[size] = '\0';
1705
1706 /* Break apart buffer. */
1707 if (*value == '"') {
1708 value++;
1709 temp = value;
1710 while (*temp && *temp != '"')
1711 temp++;
1712 } else {
1713 temp = value;
1714 while (*temp && *temp != ' ')
1715 temp++;
1716 }
1717 *temp = '\0';
1718
1719 /* Set path. */
1720 if (gaim_str_has_suffix(value, "trillian.exe"))
1721 {
1722 value[strlen(value) - (sizeof("trillian.exe") - 1)] = '\0';
1723 path = g_build_filename(value, "users", "default", "talk.ini", NULL);
1724 }
1725 }
1726 RegCloseKey(hKey);
1727 }
1728
1729 if (!path) {
1730 char *folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES);
1731 if (folder) {
1732 path = g_build_filename(folder, "Trillian",
1733 "users", "default", "talk.ini", NULL);
1734 g_free(folder);
1735 }
1736 }
1737
1738 if (path) {
1739 /* Read talk.ini file to find the log directory. */
1740 GError *error = NULL;
1741
1742 #if 0 && GLIB_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */
1743 GKeyFile *key_file;
1744
1745 gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read",
1746 "Reading %s\n", path);
1747 if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, GError &error)) {
1748 gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
1749 "Error reading talk.ini\n");
1750 if (error)
1751 g_error_free(error);
1752 } else {
1753 char *logdir = g_key_file_get_string(key_file, "Logging", "Directory", &error);
1754 if (error) {
1755 gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
1756 "Error reading Directory value from Logging section\n");
1757 g_error_free(error);
1758 }
1759
1760 if (logdir) {
1761 g_strchomp(logdir);
1762 gaim_prefs_add_string(
1763 "/plugins/core/log_reader/trillian/log_directory", logdir);
1764 found = TRUE;
1765 }
1766
1767 g_key_file_free(key_file);
1768 }
1769 #else /* !GLIB_CHECK_VERSION(2,6,0) */
1770 gsize length;
1771 gchar *contents = NULL;
1772
1773 gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read",
1774 "Reading %s\n", path);
1775 if (!g_file_get_contents(path, &contents, &length, &error)) {
1776 gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
1777 "Error reading talk.ini\n");
1778 if (error)
1779 g_error_free(error);
1780 } else {
1781 char *line = contents;
1782 while (*contents) {
1783 if (*contents == '\n') {
1784 *contents = '\0';
1785
1786 /* XXX: This assumes the first Directory key is under [Logging]. */
1787 if (gaim_str_has_prefix(line, "Directory=")) {
1788 line += (sizeof("Directory=") - 1);
1789 g_strchomp(line);
1790 gaim_prefs_add_string(
1791 "/plugins/core/log_reader/trillian/log_directory",
1792 line);
1793 found = TRUE;
1794 }
1795
1796 contents++;
1797 line = contents;
1798 } else
1799 contents++;
1800 }
1801 g_free(path);
1802 g_free(contents);
1803 }
1804 #endif /* !GTK_CHECK_VERSION(2,6,0) */
1805 } /* path */
1806
1807 if (!found) {
1808 #endif /* defined(_WIN32) */
1809
1810 /* Calculate default Trillian log directory. */
1811 #ifdef _WIN32
1812 folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES);
1813 if (folder) {
1814 #endif
1815 path = g_build_filename(
1816 #ifdef _WIN32
1817 folder,
1818 #else
1819 GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files",
1820 #endif
1821 "Trillian", "users", "default", "logs", NULL);
1822 #ifdef _WIN32
1823 g_free(folder);
1824 } else /* !folder */
1825 path = g_strdup("");
1826 #endif
1827
1828 gaim_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", path);
1829 g_free(path);
1830
1831 #ifdef _WIN32
1832 } /* !found */
1833 #endif
1834 }
1835
1836 static gboolean
1837 plugin_load(GaimPlugin *plugin)
1838 {
1839 g_return_val_if_fail(plugin != NULL, FALSE);
1840
1841 /* The names of IM clients are marked for translation at the request of
1842 translators who wanted to transliterate them. Many translators
1843 choose to leave them alone. Choose what's best for your language. */
1844 adium_logger = gaim_log_logger_new("adium", _("Adium"), 6,
1845 NULL,
1846 NULL,
1847 adium_logger_finalize,
1848 adium_logger_list,
1849 adium_logger_read,
1850 adium_logger_size);
1851 gaim_log_logger_add(adium_logger);
1852
1853 #if 0
1854 /* The names of IM clients are marked for translation at the request of
1855 translators who wanted to transliterate them. Many translators
1856 choose to leave them alone. Choose what's best for your language. */
1857 fire_logger = gaim_log_logger_new("fire", _("Fire"), 6,
1858 NULL,
1859 NULL,
1860 fire_logger_finalize,
1861 fire_logger_list,
1862 fire_logger_read,
1863 fire_logger_size);
1864 gaim_log_logger_add(fire_logger);
1865
1866 /* The names of IM clients are marked for translation at the request of
1867 translators who wanted to transliterate them. Many translators
1868 choose to leave them alone. Choose what's best for your language. */
1869 messenger_plus_logger = gaim_log_logger_new("messenger_plus", _("Messenger Plus!"), 6,
1870 NULL,
1871 NULL,
1872 messenger_plus_logger_finalize,
1873 messenger_plus_logger_list,
1874 messenger_plus_logger_read,
1875 messenger_plus_logger_size);
1876 gaim_log_logger_add(messenger_plus_logger);
1877 #endif
1878
1879 /* The names of IM clients are marked for translation at the request of
1880 translators who wanted to transliterate them. Many translators
1881 choose to leave them alone. Choose what's best for your language. */
1882 msn_logger = gaim_log_logger_new("msn", _("MSN Messenger"), 6,
1883 NULL,
1884 NULL,
1885 msn_logger_finalize,
1886 msn_logger_list,
1887 msn_logger_read,
1888 msn_logger_size);
1889 gaim_log_logger_add(msn_logger);
1890
1891 /* The names of IM clients are marked for translation at the request of
1892 translators who wanted to transliterate them. Many translators
1893 choose to leave them alone. Choose what's best for your language. */
1894 trillian_logger = gaim_log_logger_new("trillian", _("Trillian"), 6,
1895 NULL,
1896 NULL,
1897 trillian_logger_finalize,
1898 trillian_logger_list,
1899 trillian_logger_read,
1900 trillian_logger_size);
1901 gaim_log_logger_add(trillian_logger);
1902
1903 return TRUE;
1904 }
1905
1906 static gboolean
1907 plugin_unload(GaimPlugin *plugin)
1908 {
1909 g_return_val_if_fail(plugin != NULL, FALSE);
1910
1911 gaim_log_logger_remove(adium_logger);
1912 #if 0
1913 gaim_log_logger_remove(fire_logger);
1914 gaim_log_logger_remove(messenger_plus_logger);
1915 #endif
1916 gaim_log_logger_remove(msn_logger);
1917 gaim_log_logger_remove(trillian_logger);
1918
1919 return TRUE;
1920 }
1921
1922 static GaimPluginPrefFrame *
1923 get_plugin_pref_frame(GaimPlugin *plugin)
1924 {
1925 GaimPluginPrefFrame *frame;
1926 GaimPluginPref *ppref;
1927
1928 g_return_val_if_fail(plugin != NULL, FALSE);
1929
1930 frame = gaim_plugin_pref_frame_new();
1931
1932
1933 /* Add general preferences. */
1934
1935 ppref = gaim_plugin_pref_new_with_label(_("General Log Reading Configuration"));
1936 gaim_plugin_pref_frame_add(frame, ppref);
1937
1938 ppref = gaim_plugin_pref_new_with_name_and_label(
1939 "/plugins/core/log_reader/fast_sizes", _("Fast size calculations"));
1940 gaim_plugin_pref_frame_add(frame, ppref);
1941
1942 ppref = gaim_plugin_pref_new_with_name_and_label(
1943 "/plugins/core/log_reader/use_name_heuristics", _("Use name heuristics"));
1944 gaim_plugin_pref_frame_add(frame, ppref);
1945
1946
1947 /* Add Log Directory preferences. */
1948
1949 ppref = gaim_plugin_pref_new_with_label(_("Log Directory"));
1950 gaim_plugin_pref_frame_add(frame, ppref);
1951
1952 ppref = gaim_plugin_pref_new_with_name_and_label(
1953 "/plugins/core/log_reader/adium/log_directory", _("Adium"));
1954 gaim_plugin_pref_frame_add(frame, ppref);
1955
1956 #if 0
1957 ppref = gaim_plugin_pref_new_with_name_and_label(
1958 "/plugins/core/log_reader/fire/log_directory", _("Fire"));
1959 gaim_plugin_pref_frame_add(frame, ppref);
1960
1961 ppref = gaim_plugin_pref_new_with_name_and_label(
1962 "/plugins/core/log_reader/messenger_plus/log_directory", _("Messenger Plus!"));
1963 gaim_plugin_pref_frame_add(frame, ppref);
1964 #endif
1965
1966 ppref = gaim_plugin_pref_new_with_name_and_label(
1967 "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
1968 gaim_plugin_pref_frame_add(frame, ppref);
1969
1970 ppref = gaim_plugin_pref_new_with_name_and_label(
1971 "/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
1972 gaim_plugin_pref_frame_add(frame, ppref);
1973
1974 return frame;
1975 }
1976
1977 static GaimPluginUiInfo prefs_info = {
1978 get_plugin_pref_frame,
1979 0, /* page_num (reserved) */
1980 NULL /* frame (reserved) */
1981 };
1982
1983 static GaimPluginInfo info =
1984 {
1985 GAIM_PLUGIN_MAGIC,
1986 GAIM_MAJOR_VERSION,
1987 GAIM_MINOR_VERSION,
1988 GAIM_PLUGIN_STANDARD, /**< type */
1989 NULL, /**< ui_requirement */
1990 0, /**< flags */
1991 NULL, /**< dependencies */
1992 GAIM_PRIORITY_DEFAULT, /**< priority */
1993 "core-log_reader", /**< id */
1994 N_("Log Reader"), /**< name */
1995 VERSION, /**< version */
1996
1997 /** summary */
1998 N_("Includes other IM clients' logs in the "
1999 "log viewer."),
2000
2001 /** description */
2002 N_("When viewing logs, this plugin will include "
2003 "logs from other IM clients. Currently, this "
2004 "includes Adium, MSN Messenger, and Trillian.\n\n"
2005 "WARNING: This plugin is still alpha code and "
2006 "may crash frequently. Use it at your own risk!"),
2007
2008 "Richard Laager <rlaager@users.sf.net>", /**< author */
2009 GAIM_WEBSITE, /**< homepage */
2010 plugin_load, /**< load */
2011 plugin_unload, /**< unload */
2012 NULL, /**< destroy */
2013 NULL, /**< ui_info */
2014 NULL, /**< extra_info */
2015 &prefs_info, /**< prefs_info */
2016 NULL /**< actions */
2017 };
2018
2019 GAIM_INIT_PLUGIN(log_reader, init_plugin, info)