libpurple/plugins/log_reader.c

branch
cpw.khc.msnp14
changeset 20481
65485e2ed8a3
parent 20472
6a6d2ef151e6
parent 20478
46933dc62880
equal deleted inserted replaced
20480:df9df972434f 20481:65485e2ed8a3
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <stdio.h>
6
7 #ifndef PURPLE_PLUGINS
8 # define PURPLE_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 Purple header included. */
24 #ifdef _WIN32
25 #include "win32dep.h"
26 #endif
27
28 /* Where is the Windows partition mounted? */
29 #ifndef PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
30 #define PURPLE_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 PurpleLogLogger *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(PurpleLogType type, const char *sn, PurpleAccount *account)
61 {
62 GList *list = NULL;
63 const char *logdir;
64 PurplePlugin *plugin;
65 PurplePluginProtocolInfo *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 = purple_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 = purple_find_prpl(purple_account_get_protocol_id(account));
81 if (!plugin)
82 return NULL;
83
84 prpl_info = PURPLE_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 (!purple_str_has_prefix(file, sn))
100 continue;
101 if (purple_str_has_suffix(file, ".html") || purple_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 purple_debug(PURPLE_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 PurpleLog *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 purple_debug(PURPLE_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 = purple_log_new(PURPLE_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 (purple_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 purple_debug(PURPLE_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 PurpleLog *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 purple_debug(PURPLE_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 = purple_log_new(PURPLE_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 (PurpleLog *log, PurpleLogReadFlags *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 purple_debug(PURPLE_DEBUG_INFO, "Adium log read",
252 "Reading %s\n", data->path);
253 if (!g_file_get_contents(data->path, &read, &length, &error)) {
254 purple_debug(PURPLE_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 (purple_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 (PurpleLog *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 (purple_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(PurpleLog *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 PurpleLogLogger *fire_logger;
336
337 struct fire_logger_data {
338 };
339
340 static GList *fire_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
341 {
342 /* TODO: Do something here. */
343 return NULL;
344 }
345
346 static char * fire_logger_read (PurpleLog *log, PurpleLogReadFlags *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 (PurpleLog *log)
359 {
360 g_return_val_if_fail(log != NULL, 0);
361
362 if (purple_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(PurpleLog *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 PurpleLogLogger *messenger_plus_logger;
388
389 struct messenger_plus_logger_data {
390 };
391
392 static GList *messenger_plus_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
393 {
394 /* TODO: Do something here. */
395 return NULL;
396 }
397
398 static char * messenger_plus_logger_read (PurpleLog *log, PurpleLogReadFlags *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 (PurpleLog *log)
411 {
412 g_return_val_if_fail(log != NULL, 0);
413
414 if (purple_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(PurpleLog *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 PurpleLogLogger *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 /* This function is really confusing. It makes baby rlaager cry...
449 In other news: "You lost a lot of blood but we found most of it."
450 */
451 static time_t msn_logger_parse_timestamp(xmlnode *message, struct tm **tm_out)
452 {
453 const char *datetime;
454 static struct tm tm2;
455 time_t stamp;
456 const char *date;
457 const char *time;
458 int month;
459 int day;
460 int year;
461 int hour;
462 int min;
463 int sec;
464 char am_pm;
465 char *str;
466 static struct tm tm;
467 time_t t;
468 time_t diff;
469
470 #ifndef G_DISABLE_CHECKS
471 if (message != NULL)
472 {
473 *tm_out = NULL;
474
475 /* Trigger the usual warning. */
476 g_return_val_if_fail(message != NULL, (time_t)0);
477 }
478 #endif
479
480 datetime = xmlnode_get_attrib(message, "DateTime");
481 if (!(datetime && *datetime))
482 {
483 purple_debug_error("MSN log timestamp parse",
484 "Attribute missing: %s\n", "DateTime");
485 return (time_t)0;
486 }
487
488 stamp = purple_str_to_time(datetime, TRUE, &tm2, NULL, NULL);
489 #ifdef HAVE_TM_GMTOFF
490 tm2.tm_gmtoff = 0;
491 #endif
492 #ifdef HAVE_STRUCT_TM_TM_ZONE
493 /* This is used in the place of a timezone abbreviation if the
494 * offset is way off. The user should never really see it, but
495 * it's here just in case. The parens are to make it clear it's
496 * not a real timezone. */
497 tm2.tm_zone = _("(UTC)");
498 #endif
499
500
501 date = xmlnode_get_attrib(message, "Date");
502 if (!(date && *date))
503 {
504 purple_debug_error("MSN log timestamp parse",
505 "Attribute missing: %s\n", "Date");
506 *tm_out = &tm2;
507 return stamp;
508 }
509
510 time = xmlnode_get_attrib(message, "Time");
511 if (!(time && *time))
512 {
513 purple_debug_error("MSN log timestamp parse",
514 "Attribute missing: %s\n", "Time");
515 *tm_out = &tm2;
516 return stamp;
517 }
518
519 if (sscanf(date, "%u/%u/%u", &month, &day, &year) != 3)
520 {
521 purple_debug_error("MSN log timestamp parse",
522 "%s parsing error\n", "Date");
523 *tm_out = &tm2;
524 return stamp;
525 }
526 else
527 {
528 if (month > 12)
529 {
530 int tmp = day;
531 day = month;
532 month = tmp;
533 }
534 }
535
536 if (sscanf(time, "%u:%u:%u %c", &hour, &min, &sec, &am_pm) != 4)
537 {
538 purple_debug_error("MSN log timestamp parse",
539 "%s parsing error\n", "Time");
540 *tm_out = &tm2;
541 return stamp;
542 }
543
544 if (am_pm == 'P') {
545 hour += 12;
546 } else if (hour == 12) {
547 /* 12 AM = 00 hr */
548 hour = 0;
549 }
550
551 str = g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year, month, day, hour, min, sec);
552 t = purple_str_to_time(str, TRUE, &tm, NULL, NULL);
553
554
555 if (stamp > t)
556 diff = stamp - t;
557 else
558 diff = t - stamp;
559
560 if (diff > (14 * 60 * 60))
561 {
562 if (day <= 12)
563 {
564 /* Swap day & month variables, to see if it's a non-US date. */
565 g_free(str);
566 str = g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year, month, day, hour, min, sec);
567 t = purple_str_to_time(str, TRUE, &tm, NULL, NULL);
568
569 if (stamp > t)
570 diff = stamp - t;
571 else
572 diff = t - stamp;
573
574 if (diff > (14 * 60 * 60))
575 {
576 /* We got a time, it's not impossible, but
577 * the diff is too large. Display the UTC time. */
578 g_free(str);
579 *tm_out = &tm2;
580 return stamp;
581 }
582 else
583 {
584 /* Legal time */
585 /* Fall out */
586 }
587 }
588 else
589 {
590 /* We got a time, it's not impossible, but
591 * the diff is too large. Display the UTC time. */
592 g_free(str);
593 *tm_out = &tm2;
594 return stamp;
595 }
596 }
597
598 /* If we got here, the time is legal with a reasonable offset.
599 * Let's find out if it's in our TZ. */
600 if (purple_str_to_time(str, FALSE, &tm, NULL, NULL) == stamp)
601 {
602 g_free(str);
603 *tm_out = &tm;
604 return stamp;
605 }
606 g_free(str);
607
608 /* The time isn't in our TZ, but it's reasonable. */
609 #ifdef HAVE_STRUCT_TM_TM_ZONE
610 tm.tm_zone = " ";
611 #endif
612 *tm_out = &tm;
613 return stamp;
614 }
615
616 static GList *msn_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
617 {
618 GList *list = NULL;
619 char *username;
620 PurpleBuddy *buddy;
621 const char *logdir;
622 const char *savedfilename = NULL;
623 char *logfile;
624 char *path;
625 GError *error = NULL;
626 gchar *contents = NULL;
627 gsize length;
628 xmlnode *root;
629 xmlnode *message;
630 const char *old_session_id = "";
631 struct msn_logger_data *data = NULL;
632
633 g_return_val_if_fail(sn != NULL, list);
634 g_return_val_if_fail(account != NULL, list);
635
636 if (strcmp(account->protocol_id, "prpl-msn"))
637 return list;
638
639 logdir = purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory");
640
641 /* By clearing the log directory path, this logger can be (effectively) disabled. */
642 if (!*logdir)
643 return list;
644
645 buddy = purple_find_buddy(account, sn);
646
647 if ((username = g_strdup(purple_account_get_string(
648 account, "log_reader_msn_log_folder", NULL)))) {
649 /* As a special case, we allow the null string to kill the parsing
650 * straight away. This would allow the user to deal with the case
651 * when two account have the same username at different domains and
652 * only one has logs stored.
653 */
654 if (!*username) {
655 g_free(username);
656 return list;
657 }
658 } else {
659 username = g_strdup(purple_normalize(account, account->username));
660 }
661
662 if (buddy)
663 savedfilename = purple_blist_node_get_string(&buddy->node, "log_reader_msn_log_filename");
664
665 if (savedfilename) {
666 /* As a special case, we allow the null string to kill the parsing
667 * straight away. This would allow the user to deal with the case
668 * when two buddies have the same username at different domains and
669 * only one has logs stored.
670 */
671 if (!*savedfilename) {
672 g_free(username);
673 return list;
674 }
675
676 logfile = g_strdup(savedfilename);
677 } else {
678 logfile = g_strdup_printf("%s.xml", purple_normalize(account, sn));
679 }
680
681 path = g_build_filename(logdir, username, "History", logfile, NULL);
682
683 if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
684 gboolean found = FALSE;
685 char *at_sign;
686 GDir *dir;
687
688 g_free(path);
689
690 if (savedfilename) {
691 /* We had a saved filename, but it doesn't exist.
692 * Returning now is the right course of action because we don't
693 * want to detect another file incorrectly.
694 */
695 g_free(username);
696 g_free(logfile);
697 return list;
698 }
699
700 /* Perhaps we're using a new version of MSN with the weird numbered folders.
701 * I don't know how the numbers are calculated, so I'm going to attempt to
702 * find logs by pattern matching...
703 */
704
705 at_sign = g_strrstr(username, "@");
706 if (at_sign)
707 *at_sign = '\0';
708
709 dir = g_dir_open(logdir, 0, NULL);
710 if (dir) {
711 const gchar *name;
712
713 while ((name = g_dir_read_name(dir))) {
714 const char *c = name;
715
716 if (!purple_str_has_prefix(c, username))
717 continue;
718
719 c += strlen(username);
720 while (*c) {
721 if (!g_ascii_isdigit(*c))
722 break;
723
724 c++;
725 }
726
727 path = g_build_filename(logdir, name, NULL);
728 /* The !c makes sure we got to the end of the while loop above. */
729 if (!*c && g_file_test(path, G_FILE_TEST_IS_DIR)) {
730 char *history_path = g_build_filename(
731 path, "History", NULL);
732 if (g_file_test(history_path, G_FILE_TEST_IS_DIR)) {
733 purple_account_set_string(account,
734 "log_reader_msn_log_folder", name);
735 g_free(path);
736 path = history_path;
737 found = TRUE;
738 break;
739 }
740 g_free(path);
741 g_free(history_path);
742 }
743 else
744 g_free(path);
745 }
746 g_dir_close(dir);
747 }
748 g_free(username);
749
750 if (!found) {
751 g_free(logfile);
752 return list;
753 }
754
755 /* If we've reached this point, we've found a History folder. */
756
757 username = g_strdup(purple_normalize(account, sn));
758 at_sign = g_strrstr(username, "@");
759 if (at_sign)
760 *at_sign = '\0';
761
762 found = FALSE;
763 dir = g_dir_open(path, 0, NULL);
764 if (dir) {
765 const gchar *name;
766
767 while ((name = g_dir_read_name(dir))) {
768 const char *c = name;
769
770 if (!purple_str_has_prefix(c, username))
771 continue;
772
773 c += strlen(username);
774 while (*c) {
775 if (!g_ascii_isdigit(*c))
776 break;
777
778 c++;
779 }
780
781 path = g_build_filename(path, name, NULL);
782 if (!strcmp(c, ".xml") &&
783 g_file_test(path, G_FILE_TEST_EXISTS)) {
784 found = TRUE;
785 g_free(logfile);
786 logfile = g_strdup(name);
787 break;
788 }
789 else
790 g_free(path);
791 }
792 g_dir_close(dir);
793 }
794 g_free(username);
795
796 if (!found) {
797 g_free(logfile);
798 return list;
799 }
800 } else {
801 g_free(username);
802 g_free(logfile);
803 logfile = NULL; /* No sense saving the obvious buddy@domain.com. */
804 }
805
806 purple_debug(PURPLE_DEBUG_INFO, "MSN log read",
807 "Reading %s\n", path);
808 if (!g_file_get_contents(path, &contents, &length, &error)) {
809 g_free(path);
810 purple_debug(PURPLE_DEBUG_ERROR, "MSN log read",
811 "Error reading log\n");
812 if (error)
813 g_error_free(error);
814 return list;
815 }
816 g_free(path);
817
818 /* Reading the file was successful...
819 * Save its name if it involves the crazy numbers. The idea here is that you could
820 * then tweak the blist.xml file by hand if need be. This would be the case if two
821 * buddies have the same username at different domains. One set of logs would get
822 * detected for both buddies.
823 */
824 if (buddy && logfile) {
825 purple_blist_node_set_string(&buddy->node, "log_reader_msn_log_filename", logfile);
826 g_free(logfile);
827 }
828
829 root = xmlnode_from_str(contents, length);
830 g_free(contents);
831 if (!root)
832 return list;
833
834 for (message = xmlnode_get_child(root, "Message"); message;
835 message = xmlnode_get_next_twin(message)) {
836 const char *session_id;
837
838 session_id = xmlnode_get_attrib(message, "SessionID");
839 if (!session_id) {
840 purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
841 "Error parsing message: %s\n", "SessionID missing");
842 continue;
843 }
844
845 if (strcmp(session_id, old_session_id)) {
846 /*
847 * The session ID differs from the last message.
848 * Thus, this is the start of a new conversation.
849 */
850 struct tm *tm;
851 time_t stamp;
852 PurpleLog *log;
853
854 data = g_new0(struct msn_logger_data, 1);
855 data->root = root;
856 data->message = message;
857 data->session_id = session_id;
858 data->text = NULL;
859 data->last_log = FALSE;
860
861 stamp = msn_logger_parse_timestamp(message, &tm);
862
863 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, stamp, tm);
864 log->logger = msn_logger;
865 log->logger_data = data;
866
867 list = g_list_append(list, log);
868 }
869 old_session_id = session_id;
870 }
871
872 if (data)
873 data->last_log = TRUE;
874
875 return list;
876 }
877
878 static char * msn_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
879 {
880 struct msn_logger_data *data;
881 GString *text = NULL;
882 xmlnode *message;
883
884 g_return_val_if_fail(log != NULL, g_strdup(""));
885
886 data = log->logger_data;
887
888 if (data->text) {
889 /* The GTK code which displays the logs g_free()s whatever is
890 * returned from this function. Thus, we can't reuse the str
891 * part of the GString. The only solution is to free it and
892 * start over.
893 */
894 g_string_free(data->text, FALSE);
895 }
896
897 text = g_string_new("");
898
899 if (!data->root || !data->message || !data->session_id) {
900 /* Something isn't allocated correctly. */
901 purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
902 "Error parsing message: %s\n", "Internal variables inconsistent");
903 data->text = text;
904
905 return text->str;
906 }
907
908 for (message = data->message; message;
909 message = xmlnode_get_next_twin(message)) {
910
911 const char *new_session_id;
912 xmlnode *text_node;
913 const char *from_name = NULL;
914 const char *to_name = NULL;
915 xmlnode *from;
916 xmlnode *to;
917 enum name_guesses name_guessed = NAME_GUESS_UNKNOWN;
918 const char *their_name;
919 time_t time_unix;
920 struct tm *tm;
921 char *timestamp;
922 char *tmp;
923 const char *style;
924
925 new_session_id = xmlnode_get_attrib(message, "SessionID");
926
927 /* If this triggers, something is wrong with the XML. */
928 if (!new_session_id) {
929 purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
930 "Error parsing message: %s\n", "New SessionID missing");
931 break;
932 }
933
934 if (strcmp(new_session_id, data->session_id)) {
935 /* The session ID differs from the first message.
936 * Thus, this is the start of a new conversation.
937 */
938 break;
939 }
940
941 text_node = xmlnode_get_child(message, "Text");
942 if (!text_node)
943 continue;
944
945 from = xmlnode_get_child(message, "From");
946 if (from) {
947 xmlnode *user = xmlnode_get_child(from, "User");
948
949 if (user) {
950 from_name = xmlnode_get_attrib(user, "FriendlyName");
951
952 /* This saves a check later. */
953 if (!*from_name)
954 from_name = NULL;
955 }
956 }
957
958 to = xmlnode_get_child(message, "To");
959 if (to) {
960 xmlnode *user = xmlnode_get_child(to, "User");
961 if (user) {
962 to_name = xmlnode_get_attrib(user, "FriendlyName");
963
964 /* This saves a check later. */
965 if (!*to_name)
966 to_name = NULL;
967 }
968 }
969
970 their_name = from_name;
971 if (from_name && purple_prefs_get_bool("/plugins/core/log_reader/use_name_heuristics")) {
972 const char *friendly_name = purple_connection_get_display_name(log->account->gc);
973
974 if (friendly_name != NULL) {
975 int friendly_name_length = strlen(friendly_name);
976 const char *alias;
977 int alias_length;
978 PurpleBuddy *buddy = purple_find_buddy(log->account, log->name);
979 gboolean from_name_matches;
980 gboolean to_name_matches;
981
982 if (buddy && buddy->alias)
983 their_name = buddy->alias;
984
985 if (log->account->alias)
986 {
987 alias = log->account->alias;
988 alias_length = strlen(alias);
989 }
990 else
991 {
992 alias = "";
993 alias_length = 0;
994 }
995
996 /* Try to guess which user is me.
997 * The first step is to determine if either of the names matches either my
998 * friendly name or alias. For this test, "match" is defined as:
999 * ^(friendly_name|alias)([^a-zA-Z0-9].*)?$
1000 */
1001 from_name_matches = (purple_str_has_prefix(from_name, friendly_name) &&
1002 !isalnum(*(from_name + friendly_name_length))) ||
1003 (purple_str_has_prefix(from_name, alias) &&
1004 !isalnum(*(from_name + alias_length)));
1005
1006 to_name_matches = to_name != NULL && (
1007 (purple_str_has_prefix(to_name, friendly_name) &&
1008 !isalnum(*(to_name + friendly_name_length))) ||
1009 (purple_str_has_prefix(to_name, alias) &&
1010 !isalnum(*(to_name + alias_length))));
1011
1012 if (from_name_matches) {
1013 if (!to_name_matches) {
1014 name_guessed = NAME_GUESS_ME;
1015 }
1016 } else if (to_name_matches) {
1017 name_guessed = NAME_GUESS_THEM;
1018 } else {
1019 if (buddy && buddy->alias) {
1020 char *alias = g_strdup(buddy->alias);
1021
1022 /* "Truncate" the string at the first non-alphanumeric
1023 * character. The idea is to relax the comparison.
1024 */
1025 char *temp;
1026 for (temp = alias; *temp ; temp++) {
1027 if (!isalnum(*temp)) {
1028 *temp = '\0';
1029 break;
1030 }
1031 }
1032 alias_length = strlen(alias);
1033
1034 /* Try to guess which user is them.
1035 * The first step is to determine if either of the names
1036 * matches their alias. For this test, "match" is
1037 * defined as: ^alias([^a-zA-Z0-9].*)?$
1038 */
1039 from_name_matches = (purple_str_has_prefix(
1040 from_name, alias) &&
1041 !isalnum(*(from_name +
1042 alias_length)));
1043
1044 to_name_matches = to_name && (purple_str_has_prefix(
1045 to_name, alias) &&
1046 !isalnum(*(to_name +
1047 alias_length)));
1048
1049 g_free(alias);
1050
1051 if (from_name_matches) {
1052 if (!to_name_matches) {
1053 name_guessed = NAME_GUESS_THEM;
1054 }
1055 } else if (to_name_matches) {
1056 name_guessed = NAME_GUESS_ME;
1057 } else if (buddy->server_alias) {
1058 friendly_name_length =
1059 strlen(buddy->server_alias);
1060
1061 /* Try to guess which user is them.
1062 * The first step is to determine if either of
1063 * the names matches their friendly name. For
1064 * this test, "match" is defined as:
1065 * ^friendly_name([^a-zA-Z0-9].*)?$
1066 */
1067 from_name_matches = (purple_str_has_prefix(
1068 from_name,
1069 buddy->server_alias) &&
1070 !isalnum(*(from_name +
1071 friendly_name_length)));
1072
1073 to_name_matches = to_name && (
1074 (purple_str_has_prefix(
1075 to_name, buddy->server_alias) &&
1076 !isalnum(*(to_name +
1077 friendly_name_length))));
1078
1079 if (from_name_matches) {
1080 if (!to_name_matches) {
1081 name_guessed = NAME_GUESS_THEM;
1082 }
1083 } else if (to_name_matches) {
1084 name_guessed = NAME_GUESS_ME;
1085 }
1086 }
1087 }
1088 }
1089 }
1090 }
1091
1092 if (name_guessed != NAME_GUESS_UNKNOWN) {
1093 text = g_string_append(text, "<span style=\"color: #");
1094 if (name_guessed == NAME_GUESS_ME)
1095 text = g_string_append(text, "16569E");
1096 else
1097 text = g_string_append(text, "A82F2F");
1098 text = g_string_append(text, ";\">");
1099 }
1100
1101 time_unix = msn_logger_parse_timestamp(message, &tm);
1102
1103 timestamp = g_strdup_printf("<font size=\"2\">(%02u:%02u:%02u)</font> ",
1104 tm->tm_hour, tm->tm_min, tm->tm_sec);
1105 text = g_string_append(text, timestamp);
1106 g_free(timestamp);
1107
1108 if (from_name) {
1109 text = g_string_append(text, "<b>");
1110
1111 if (name_guessed == NAME_GUESS_ME) {
1112 if (log->account->alias)
1113 text = g_string_append(text, log->account->alias);
1114 else
1115 text = g_string_append(text, log->account->username);
1116 }
1117 else if (name_guessed == NAME_GUESS_THEM)
1118 text = g_string_append(text, their_name);
1119 else
1120 text = g_string_append(text, from_name);
1121
1122 text = g_string_append(text, ":</b> ");
1123 }
1124
1125 if (name_guessed != NAME_GUESS_UNKNOWN)
1126 text = g_string_append(text, "</span>");
1127
1128 style = xmlnode_get_attrib(text_node, "Style");
1129
1130 tmp = xmlnode_get_data(text_node);
1131 if (style && *style) {
1132 text = g_string_append(text, "<span style=\"");
1133 text = g_string_append(text, style);
1134 text = g_string_append(text, "\">");
1135 text = g_string_append(text, tmp);
1136 text = g_string_append(text, "</span>\n");
1137 } else {
1138 text = g_string_append(text, tmp);
1139 text = g_string_append(text, "\n");
1140 }
1141 g_free(tmp);
1142 }
1143
1144 data->text = text;
1145
1146 return text->str;
1147 }
1148
1149 static int msn_logger_size (PurpleLog *log)
1150 {
1151 char *text;
1152 size_t size;
1153
1154 g_return_val_if_fail(log != NULL, 0);
1155
1156 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
1157 return 0;
1158
1159 text = msn_logger_read(log, NULL);
1160 size = strlen(text);
1161 g_free(text);
1162
1163 return size;
1164 }
1165
1166 static void msn_logger_finalize(PurpleLog *log)
1167 {
1168 struct msn_logger_data *data;
1169
1170 g_return_if_fail(log != NULL);
1171
1172 data = log->logger_data;
1173
1174 if (data->last_log)
1175 xmlnode_free(data->root);
1176
1177 if (data->text)
1178 g_string_free(data->text, FALSE);
1179 }
1180
1181
1182 /*****************************************************************************
1183 * Trillian Logger *
1184 *****************************************************************************/
1185
1186 /* The trillian logger doesn't write logs, only reads them. This is to include
1187 * Trillian logs in the log viewer transparently.
1188 */
1189
1190 static PurpleLogLogger *trillian_logger;
1191 static void trillian_logger_finalize(PurpleLog *log);
1192
1193 struct trillian_logger_data {
1194 char *path; /* FIXME: Change this to use PurpleStringref like log.c:old_logger_list */
1195 int offset;
1196 int length;
1197 char *their_nickname;
1198 };
1199
1200 static GList *trillian_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
1201 {
1202 GList *list = NULL;
1203 const char *logdir;
1204 PurplePlugin *plugin;
1205 PurplePluginProtocolInfo *prpl_info;
1206 char *prpl_name;
1207 const char *buddy_name;
1208 char *filename;
1209 char *path;
1210 GError *error = NULL;
1211 gchar *contents = NULL;
1212 gsize length;
1213 gchar *line;
1214 gchar *c;
1215
1216 g_return_val_if_fail(sn != NULL, list);
1217 g_return_val_if_fail(account != NULL, list);
1218
1219 logdir = purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory");
1220
1221 /* By clearing the log directory path, this logger can be (effectively) disabled. */
1222 if (!*logdir)
1223 return list;
1224
1225 plugin = purple_find_prpl(purple_account_get_protocol_id(account));
1226 if (!plugin)
1227 return NULL;
1228
1229 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1230 if (!prpl_info->list_icon)
1231 return NULL;
1232
1233 prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1);
1234
1235 buddy_name = purple_normalize(account, sn);
1236
1237 filename = g_strdup_printf("%s.log", buddy_name);
1238 path = g_build_filename(
1239 logdir, prpl_name, filename, NULL);
1240
1241 purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
1242 "Reading %s\n", path);
1243 /* FIXME: There's really no need to read the entire file at once.
1244 * See src/log.c:old_logger_list for a better approach.
1245 */
1246 if (!g_file_get_contents(path, &contents, &length, &error)) {
1247 if (error) {
1248 g_error_free(error);
1249 error = NULL;
1250 }
1251 g_free(path);
1252
1253 path = g_build_filename(
1254 logdir, prpl_name, "Query", filename, NULL);
1255 purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
1256 "Reading %s\n", path);
1257 if (!g_file_get_contents(path, &contents, &length, &error)) {
1258 if (error)
1259 g_error_free(error);
1260 }
1261 }
1262 g_free(filename);
1263
1264 if (contents) {
1265 struct trillian_logger_data *data = NULL;
1266 int offset = 0;
1267 int last_line_offset = 0;
1268
1269 line = contents;
1270 c = contents;
1271 while (*c) {
1272 offset++;
1273
1274 if (*c != '\n') {
1275 c++;
1276 continue;
1277 }
1278
1279 *c = '\0';
1280 if (purple_str_has_prefix(line, "Session Close ")) {
1281 if (data && !data->length) {
1282 if (!(data->length = last_line_offset - data->offset)) {
1283 /* This log had no data, so we remove it. */
1284 GList *last = g_list_last(list);
1285
1286 purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
1287 "Empty log. Offset %i\n", data->offset);
1288
1289 trillian_logger_finalize((PurpleLog *)last->data);
1290 list = g_list_delete_link(list, last);
1291 }
1292 }
1293 } else if (line[0] && line[1] && line [3] &&
1294 purple_str_has_prefix(&line[3], "sion Start ")) {
1295
1296 char *their_nickname = line;
1297 char *timestamp;
1298
1299 if (data && !data->length)
1300 data->length = last_line_offset - data->offset;
1301
1302 while (*their_nickname && (*their_nickname != ':'))
1303 their_nickname++;
1304 their_nickname++;
1305
1306 /* This code actually has nothing to do with
1307 * the timestamp YET. I'm simply using this
1308 * variable for now to NUL-terminate the
1309 * their_nickname string.
1310 */
1311 timestamp = their_nickname;
1312 while (*timestamp && *timestamp != ')')
1313 timestamp++;
1314
1315 if (*timestamp == ')') {
1316 char *month;
1317 struct tm tm;
1318
1319 *timestamp = '\0';
1320 if (line[0] && line[1] && line[2])
1321 timestamp += 3;
1322
1323 /* Now we start dealing with the timestamp. */
1324
1325 /* Skip over the day name. */
1326 while (*timestamp && (*timestamp != ' '))
1327 timestamp++;
1328 *timestamp = '\0';
1329 timestamp++;
1330
1331 /* Parse out the month. */
1332 month = timestamp;
1333 while (*timestamp && (*timestamp != ' '))
1334 timestamp++;
1335 *timestamp = '\0';
1336 timestamp++;
1337
1338 /* Parse the day, time, and year. */
1339 if (sscanf(timestamp, "%u %u:%u:%u %u",
1340 &tm.tm_mday, &tm.tm_hour,
1341 &tm.tm_min, &tm.tm_sec,
1342 &tm.tm_year) != 5) {
1343
1344 purple_debug(PURPLE_DEBUG_ERROR,
1345 "Trillian log timestamp parse",
1346 "Session Start parsing error\n");
1347 } else {
1348 PurpleLog *log;
1349
1350 tm.tm_year -= 1900;
1351
1352 /* Let the C library deal with
1353 * daylight savings time.
1354 */
1355 tm.tm_isdst = -1;
1356
1357 /* Ugly hack, in case current locale
1358 * is not English. This code is taken
1359 * from log.c.
1360 */
1361 if (strcmp(month, "Jan") == 0) {
1362 tm.tm_mon= 0;
1363 } else if (strcmp(month, "Feb") == 0) {
1364 tm.tm_mon = 1;
1365 } else if (strcmp(month, "Mar") == 0) {
1366 tm.tm_mon = 2;
1367 } else if (strcmp(month, "Apr") == 0) {
1368 tm.tm_mon = 3;
1369 } else if (strcmp(month, "May") == 0) {
1370 tm.tm_mon = 4;
1371 } else if (strcmp(month, "Jun") == 0) {
1372 tm.tm_mon = 5;
1373 } else if (strcmp(month, "Jul") == 0) {
1374 tm.tm_mon = 6;
1375 } else if (strcmp(month, "Aug") == 0) {
1376 tm.tm_mon = 7;
1377 } else if (strcmp(month, "Sep") == 0) {
1378 tm.tm_mon = 8;
1379 } else if (strcmp(month, "Oct") == 0) {
1380 tm.tm_mon = 9;
1381 } else if (strcmp(month, "Nov") == 0) {
1382 tm.tm_mon = 10;
1383 } else if (strcmp(month, "Dec") == 0) {
1384 tm.tm_mon = 11;
1385 }
1386
1387 data = g_new0(
1388 struct trillian_logger_data, 1);
1389 data->path = g_strdup(path);
1390 data->offset = offset;
1391 data->length = 0;
1392 data->their_nickname =
1393 g_strdup(their_nickname);
1394
1395 /* XXX: Look into this later... Should we pass in a struct tm? */
1396 log = purple_log_new(PURPLE_LOG_IM,
1397 sn, account, NULL, mktime(&tm), NULL);
1398 log->logger = trillian_logger;
1399 log->logger_data = data;
1400
1401 list = g_list_append(list, log);
1402 }
1403 }
1404 }
1405 c++;
1406 line = c;
1407 last_line_offset = offset;
1408 }
1409
1410 g_free(contents);
1411 }
1412 g_free(path);
1413
1414 g_free(prpl_name);
1415
1416 return list;
1417 }
1418
1419 static char * trillian_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
1420 {
1421 struct trillian_logger_data *data;
1422 char *read;
1423 FILE *file;
1424 PurpleBuddy *buddy;
1425 char *escaped;
1426 GString *formatted;
1427 char *c;
1428 char *line;
1429
1430 g_return_val_if_fail(log != NULL, g_strdup(""));
1431
1432 data = log->logger_data;
1433
1434 g_return_val_if_fail(data->path != NULL, g_strdup(""));
1435 g_return_val_if_fail(data->length > 0, g_strdup(""));
1436 g_return_val_if_fail(data->their_nickname != NULL, g_strdup(""));
1437
1438 purple_debug(PURPLE_DEBUG_INFO, "Trillian log read",
1439 "Reading %s\n", data->path);
1440
1441 read = g_malloc(data->length + 2);
1442
1443 file = g_fopen(data->path, "rb");
1444 fseek(file, data->offset, SEEK_SET);
1445 fread(read, data->length, 1, file);
1446 fclose(file);
1447
1448 if (read[data->length-1] == '\n') {
1449 read[data->length] = '\0';
1450 } else {
1451 read[data->length] = '\n';
1452 read[data->length+1] = '\0';
1453 }
1454
1455 /* Load miscellaneous data. */
1456 buddy = purple_find_buddy(log->account, log->name);
1457
1458 escaped = g_markup_escape_text(read, -1);
1459 g_free(read);
1460 read = escaped;
1461
1462 /* Apply formatting... */
1463 formatted = g_string_new("");
1464 c = read;
1465 line = read;
1466 while (*c)
1467 {
1468 if (*c == '\n')
1469 {
1470 char *link_temp_line;
1471 char *link;
1472 char *timestamp;
1473 char *footer = NULL;
1474 *c = '\0';
1475
1476 /* Convert links.
1477 *
1478 * The format is (Link: URL)URL
1479 * So, I want to find each occurance of "(Link: " and replace that chunk with:
1480 * <a href="
1481 * Then, replace the next ")" with:
1482 * ">
1483 * Then, replace the next " " (or add this if the end-of-line is reached) with:
1484 * </a>
1485 */
1486 link_temp_line = NULL;
1487 while ((link = g_strstr_len(line, strlen(line), "(Link: "))) {
1488 GString *temp;
1489
1490 if (!*link)
1491 continue;
1492
1493 *link = '\0';
1494 link++;
1495
1496 temp = g_string_new(line);
1497 g_string_append(temp, "<a href=\"");
1498
1499 if (strlen(link) >= 6) {
1500 link += (sizeof("(Link: ") - 1);
1501
1502 while (*link && *link != ')') {
1503 g_string_append_c(temp, *link);
1504 link++;
1505 }
1506 if (link) {
1507 link++;
1508
1509 g_string_append(temp, "\">");
1510 while (*link && *link != ' ') {
1511 g_string_append_c(temp, *link);
1512 link++;
1513 }
1514 g_string_append(temp, "</a>");
1515 }
1516
1517 g_string_append(temp, link);
1518
1519 /* Free the last round's line. */
1520 if (link_temp_line)
1521 g_free(line);
1522
1523 line = temp->str;
1524 g_string_free(temp, FALSE);
1525
1526 /* Save this memory location so we can free it later. */
1527 link_temp_line = line;
1528 }
1529 }
1530
1531 timestamp = "";
1532 if (*line == '[') {
1533 timestamp = line;
1534 while (*timestamp && *timestamp != ']')
1535 timestamp++;
1536 if (*timestamp == ']') {
1537 *timestamp = '\0';
1538 line++;
1539 /* TODO: Parse the timestamp and convert it to Purple's format. */
1540 g_string_append_printf(formatted,
1541 "<font size=\"2\">(%s)</font> ", line);
1542 line = timestamp;
1543 if (line[1] && line[2])
1544 line += 2;
1545 }
1546
1547 if (purple_str_has_prefix(line, "*** ")) {
1548 line += (sizeof("*** ") - 1);
1549 g_string_append(formatted, "<b>");
1550 footer = "</b>";
1551 if (purple_str_has_prefix(line, "NOTE: This user is offline.")) {
1552 line = _("User is offline.");
1553 } else if (purple_str_has_prefix(line,
1554 "NOTE: Your status is currently set to ")) {
1555
1556 line += (sizeof("NOTE: ") - 1);
1557 } else if (purple_str_has_prefix(line, "Auto-response sent to ")) {
1558 g_string_append(formatted, _("Auto-response sent:"));
1559 while (*line && *line != ':')
1560 line++;
1561 if (*line)
1562 line++;
1563 g_string_append(formatted, "</b>");
1564 footer = NULL;
1565 } else if (strstr(line, " signed off ")) {
1566 if (buddy != NULL && buddy->alias)
1567 g_string_append_printf(formatted,
1568 _("%s has signed off."), buddy->alias);
1569 else
1570 g_string_append_printf(formatted,
1571 _("%s has signed off."), log->name);
1572 line = "";
1573 } else if (strstr(line, " signed on ")) {
1574 if (buddy != NULL && buddy->alias)
1575 g_string_append(formatted, buddy->alias);
1576 else
1577 g_string_append(formatted, log->name);
1578 line = " logged in.";
1579 } else if (purple_str_has_prefix(line,
1580 "One or more messages may have been undeliverable.")) {
1581
1582 g_string_append(formatted,
1583 "<span style=\"color: #ff0000;\">");
1584 g_string_append(formatted,
1585 _("One or more messages may have been "
1586 "undeliverable."));
1587 line = "";
1588 footer = "</span></b>";
1589 } else if (purple_str_has_prefix(line,
1590 "You have been disconnected.")) {
1591
1592 g_string_append(formatted,
1593 "<span style=\"color: #ff0000;\">");
1594 g_string_append(formatted,
1595 _("You were disconnected from the server."));
1596 line = "";
1597 footer = "</span></b>";
1598 } else if (purple_str_has_prefix(line,
1599 "You are currently disconnected.")) {
1600
1601 g_string_append(formatted,
1602 "<span style=\"color: #ff0000;\">");
1603 line = _("You are currently disconnected. Messages "
1604 "will not be received unless you are "
1605 "logged in.");
1606 footer = "</span></b>";
1607 } else if (purple_str_has_prefix(line,
1608 "Your previous message has not been sent.")) {
1609
1610 g_string_append(formatted,
1611 "<span style=\"color: #ff0000;\">");
1612
1613 if (purple_str_has_prefix(line,
1614 "Your previous message has not been sent. "
1615 "Reason: Maximum length exceeded.")) {
1616
1617 g_string_append(formatted,
1618 _("Message could not be sent because "
1619 "the maximum length was exceeded."));
1620 line = "";
1621 } else {
1622 g_string_append(formatted,
1623 _("Message could not be sent."));
1624 line += (sizeof(
1625 "Your previous message "
1626 "has not been sent. ") - 1);
1627 }
1628
1629 footer = "</span></b>";
1630 }
1631 } else if (purple_str_has_prefix(line, data->their_nickname)) {
1632 if (buddy != NULL && buddy->alias) {
1633 line += strlen(data->their_nickname) + 2;
1634 g_string_append_printf(formatted,
1635 "<span style=\"color: #A82F2F;\">"
1636 "<b>%s</b></span>: ", buddy->alias);
1637 }
1638 } else {
1639 char *line2 = line;
1640 while (*line2 && *line2 != ':')
1641 line2++;
1642 if (*line2 == ':') {
1643 const char *acct_name;
1644 line2++;
1645 line = line2;
1646 acct_name = purple_account_get_alias(log->account);
1647 if (!acct_name)
1648 acct_name = purple_account_get_username(log->account);
1649
1650 g_string_append_printf(formatted,
1651 "<span style=\"color: #16569E;\">"
1652 "<b>%s</b></span>:", acct_name);
1653 }
1654 }
1655 }
1656
1657 g_string_append(formatted, line);
1658
1659 if (footer)
1660 g_string_append(formatted, footer);
1661
1662 g_string_append_c(formatted, '\n');
1663
1664 if (link_temp_line)
1665 g_free(link_temp_line);
1666
1667 c++;
1668 line = c;
1669 } else
1670 c++;
1671 }
1672
1673 g_free(read);
1674 read = formatted->str;
1675 g_string_free(formatted, FALSE);
1676
1677 return read;
1678 }
1679
1680 static int trillian_logger_size (PurpleLog *log)
1681 {
1682 struct trillian_logger_data *data;
1683 char *text;
1684 size_t size;
1685
1686 g_return_val_if_fail(log != NULL, 0);
1687
1688 data = log->logger_data;
1689
1690 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
1691 return data ? data->length : 0;
1692 }
1693
1694 text = trillian_logger_read(log, NULL);
1695 size = strlen(text);
1696 g_free(text);
1697
1698 return size;
1699 }
1700
1701 static void trillian_logger_finalize(PurpleLog *log)
1702 {
1703 struct trillian_logger_data *data;
1704
1705 g_return_if_fail(log != NULL);
1706
1707 data = log->logger_data;
1708
1709 g_free(data->path);
1710 g_free(data->their_nickname);
1711
1712 }
1713
1714
1715 /*****************************************************************************
1716 * Plugin Code *
1717 *****************************************************************************/
1718
1719 static void
1720 init_plugin(PurplePlugin *plugin)
1721 {
1722 char *path;
1723 #ifdef _WIN32
1724 char *folder;
1725 gboolean found = FALSE;
1726 #endif
1727
1728 g_return_if_fail(plugin != NULL);
1729
1730 purple_prefs_add_none("/plugins/core/log_reader");
1731
1732
1733 /* Add general preferences. */
1734
1735 purple_prefs_add_bool("/plugins/core/log_reader/fast_sizes", FALSE);
1736 purple_prefs_add_bool("/plugins/core/log_reader/use_name_heuristics", TRUE);
1737
1738
1739 /* Add Adium log directory preference. */
1740 purple_prefs_add_none("/plugins/core/log_reader/adium");
1741
1742 /* Calculate default Adium log directory. */
1743 #ifdef _WIN32
1744 path = "";
1745 #else
1746 path = g_build_filename(purple_home_dir(), "Library", "Application Support",
1747 "Adium 2.0", "Users", "Default", "Logs", NULL);
1748 #endif
1749
1750 purple_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path);
1751
1752 #ifndef _WIN32
1753 g_free(path);
1754 #endif
1755
1756
1757 /* Add Fire log directory preference. */
1758 purple_prefs_add_none("/plugins/core/log_reader/fire");
1759
1760 /* Calculate default Fire log directory. */
1761 #ifdef _WIN32
1762 path = "";
1763 #else
1764 path = g_build_filename(purple_home_dir(), "Library", "Application Support",
1765 "Fire", "Sessions", NULL);
1766 #endif
1767
1768 purple_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path);
1769
1770 #ifndef _WIN32
1771 g_free(path);
1772 #endif
1773
1774
1775 /* Add Messenger Plus! log directory preference. */
1776 purple_prefs_add_none("/plugins/core/log_reader/messenger_plus");
1777
1778 /* Calculate default Messenger Plus! log directory. */
1779 #ifdef _WIN32
1780 folder = wpurple_get_special_folder(CSIDL_PERSONAL);
1781 if (folder) {
1782 #endif
1783 path = g_build_filename(
1784 #ifdef _WIN32
1785 folder,
1786 #else
1787 PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
1788 g_get_user_name(), "My Documents",
1789 #endif
1790 "My Chat Logs", NULL);
1791 #ifdef _WIN32
1792 g_free(folder);
1793 } else /* !folder */
1794 path = g_strdup("");
1795 #endif
1796
1797 purple_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path);
1798 g_free(path);
1799
1800
1801 /* Add MSN Messenger log directory preference. */
1802 purple_prefs_add_none("/plugins/core/log_reader/msn");
1803
1804 /* Calculate default MSN message history directory. */
1805 #ifdef _WIN32
1806 folder = wpurple_get_special_folder(CSIDL_PERSONAL);
1807 if (folder) {
1808 #endif
1809 path = g_build_filename(
1810 #ifdef _WIN32
1811 folder,
1812 #else
1813 PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
1814 g_get_user_name(), "My Documents",
1815 #endif
1816 "My Received Files", NULL);
1817 #ifdef _WIN32
1818 g_free(folder);
1819 } else /* !folder */
1820 path = g_strdup("");
1821 #endif
1822
1823 purple_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path);
1824 g_free(path);
1825
1826
1827 /* Add Trillian log directory preference. */
1828 purple_prefs_add_none("/plugins/core/log_reader/trillian");
1829
1830 #ifdef _WIN32
1831 /* XXX: While a major hack, this is the most reliable way I could
1832 * think of to determine the Trillian installation directory.
1833 */
1834
1835 path = NULL;
1836 if ((folder = wpurple_read_reg_string(HKEY_CLASSES_ROOT, "Trillian.SkinZip\\shell\\Add\\command\\", NULL))) {
1837 char *value = folder;
1838 char *temp;
1839
1840 /* Break apart buffer. */
1841 if (*value == '"') {
1842 value++;
1843 temp = value;
1844 while (*temp && *temp != '"')
1845 temp++;
1846 } else {
1847 temp = value;
1848 while (*temp && *temp != ' ')
1849 temp++;
1850 }
1851 *temp = '\0';
1852
1853 /* Set path. */
1854 if (purple_str_has_suffix(value, "trillian.exe")) {
1855 value[strlen(value) - (sizeof("trillian.exe") - 1)] = '\0';
1856 path = g_build_filename(value, "users", "default", "talk.ini", NULL);
1857 }
1858 g_free(folder);
1859 }
1860
1861 if (!path) {
1862 char *folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES);
1863 if (folder) {
1864 path = g_build_filename(folder, "Trillian",
1865 "users", "default", "talk.ini", NULL);
1866 g_free(folder);
1867 }
1868 }
1869
1870 if (path) {
1871 /* Read talk.ini file to find the log directory. */
1872 GError *error = NULL;
1873
1874 #if 0 && GLIB_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */
1875 GKeyFile *key_file;
1876
1877 purple_debug(PURPLE_DEBUG_INFO, "Trillian talk.ini read",
1878 "Reading %s\n", path);
1879 if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, GError &error)) {
1880 purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
1881 "Error reading talk.ini\n");
1882 if (error)
1883 g_error_free(error);
1884 } else {
1885 char *logdir = g_key_file_get_string(key_file, "Logging", "Directory", &error);
1886 if (error) {
1887 purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
1888 "Error reading Directory value from Logging section\n");
1889 g_error_free(error);
1890 }
1891
1892 if (logdir) {
1893 g_strchomp(logdir);
1894 purple_prefs_add_string(
1895 "/plugins/core/log_reader/trillian/log_directory", logdir);
1896 found = TRUE;
1897 }
1898
1899 g_key_file_free(key_file);
1900 }
1901 #else /* !GLIB_CHECK_VERSION(2,6,0) */
1902 gsize length;
1903 gchar *contents = NULL;
1904
1905 purple_debug(PURPLE_DEBUG_INFO, "Trillian talk.ini read",
1906 "Reading %s\n", path);
1907 if (!g_file_get_contents(path, &contents, &length, &error)) {
1908 purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
1909 "Error reading talk.ini\n");
1910 if (error)
1911 g_error_free(error);
1912 } else {
1913 char *line = contents;
1914 while (*contents) {
1915 if (*contents == '\n') {
1916 *contents = '\0';
1917
1918 /* XXX: This assumes the first Directory key is under [Logging]. */
1919 if (purple_str_has_prefix(line, "Directory=")) {
1920 line += (sizeof("Directory=") - 1);
1921 g_strchomp(line);
1922 purple_prefs_add_string(
1923 "/plugins/core/log_reader/trillian/log_directory",
1924 line);
1925 found = TRUE;
1926 }
1927
1928 contents++;
1929 line = contents;
1930 } else
1931 contents++;
1932 }
1933 g_free(path);
1934 g_free(contents);
1935 }
1936 #endif /* !GTK_CHECK_VERSION(2,6,0) */
1937 } /* path */
1938
1939 if (!found) {
1940 #endif /* defined(_WIN32) */
1941
1942 /* Calculate default Trillian log directory. */
1943 #ifdef _WIN32
1944 folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES);
1945 if (folder) {
1946 #endif
1947 path = g_build_filename(
1948 #ifdef _WIN32
1949 folder,
1950 #else
1951 PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files",
1952 #endif
1953 "Trillian", "users", "default", "logs", NULL);
1954 #ifdef _WIN32
1955 g_free(folder);
1956 } else /* !folder */
1957 path = g_strdup("");
1958 #endif
1959
1960 purple_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", path);
1961 g_free(path);
1962
1963 #ifdef _WIN32
1964 } /* !found */
1965 #endif
1966 }
1967
1968 static gboolean
1969 plugin_load(PurplePlugin *plugin)
1970 {
1971 g_return_val_if_fail(plugin != NULL, FALSE);
1972
1973 /* The names of IM clients are marked for translation at the request of
1974 translators who wanted to transliterate them. Many translators
1975 choose to leave them alone. Choose what's best for your language. */
1976 adium_logger = purple_log_logger_new("adium", _("Adium"), 6,
1977 NULL,
1978 NULL,
1979 adium_logger_finalize,
1980 adium_logger_list,
1981 adium_logger_read,
1982 adium_logger_size);
1983 purple_log_logger_add(adium_logger);
1984
1985 #if 0
1986 /* The names of IM clients are marked for translation at the request of
1987 translators who wanted to transliterate them. Many translators
1988 choose to leave them alone. Choose what's best for your language. */
1989 fire_logger = purple_log_logger_new("fire", _("Fire"), 6,
1990 NULL,
1991 NULL,
1992 fire_logger_finalize,
1993 fire_logger_list,
1994 fire_logger_read,
1995 fire_logger_size);
1996 purple_log_logger_add(fire_logger);
1997
1998 /* The names of IM clients are marked for translation at the request of
1999 translators who wanted to transliterate them. Many translators
2000 choose to leave them alone. Choose what's best for your language. */
2001 messenger_plus_logger = purple_log_logger_new("messenger_plus", _("Messenger Plus!"), 6,
2002 NULL,
2003 NULL,
2004 messenger_plus_logger_finalize,
2005 messenger_plus_logger_list,
2006 messenger_plus_logger_read,
2007 messenger_plus_logger_size);
2008 purple_log_logger_add(messenger_plus_logger);
2009 #endif
2010
2011 /* The names of IM clients are marked for translation at the request of
2012 translators who wanted to transliterate them. Many translators
2013 choose to leave them alone. Choose what's best for your language. */
2014 msn_logger = purple_log_logger_new("msn", _("MSN Messenger"), 6,
2015 NULL,
2016 NULL,
2017 msn_logger_finalize,
2018 msn_logger_list,
2019 msn_logger_read,
2020 msn_logger_size);
2021 purple_log_logger_add(msn_logger);
2022
2023 /* The names of IM clients are marked for translation at the request of
2024 translators who wanted to transliterate them. Many translators
2025 choose to leave them alone. Choose what's best for your language. */
2026 trillian_logger = purple_log_logger_new("trillian", _("Trillian"), 6,
2027 NULL,
2028 NULL,
2029 trillian_logger_finalize,
2030 trillian_logger_list,
2031 trillian_logger_read,
2032 trillian_logger_size);
2033 purple_log_logger_add(trillian_logger);
2034
2035 return TRUE;
2036 }
2037
2038 static gboolean
2039 plugin_unload(PurplePlugin *plugin)
2040 {
2041 g_return_val_if_fail(plugin != NULL, FALSE);
2042
2043 purple_log_logger_remove(adium_logger);
2044 #if 0
2045 purple_log_logger_remove(fire_logger);
2046 purple_log_logger_remove(messenger_plus_logger);
2047 #endif
2048 purple_log_logger_remove(msn_logger);
2049 purple_log_logger_remove(trillian_logger);
2050
2051 return TRUE;
2052 }
2053
2054 static PurplePluginPrefFrame *
2055 get_plugin_pref_frame(PurplePlugin *plugin)
2056 {
2057 PurplePluginPrefFrame *frame;
2058 PurplePluginPref *ppref;
2059
2060 g_return_val_if_fail(plugin != NULL, FALSE);
2061
2062 frame = purple_plugin_pref_frame_new();
2063
2064
2065 /* Add general preferences. */
2066
2067 ppref = purple_plugin_pref_new_with_label(_("General Log Reading Configuration"));
2068 purple_plugin_pref_frame_add(frame, ppref);
2069
2070 ppref = purple_plugin_pref_new_with_name_and_label(
2071 "/plugins/core/log_reader/fast_sizes", _("Fast size calculations"));
2072 purple_plugin_pref_frame_add(frame, ppref);
2073
2074 ppref = purple_plugin_pref_new_with_name_and_label(
2075 "/plugins/core/log_reader/use_name_heuristics", _("Use name heuristics"));
2076 purple_plugin_pref_frame_add(frame, ppref);
2077
2078
2079 /* Add Log Directory preferences. */
2080
2081 ppref = purple_plugin_pref_new_with_label(_("Log Directory"));
2082 purple_plugin_pref_frame_add(frame, ppref);
2083
2084 ppref = purple_plugin_pref_new_with_name_and_label(
2085 "/plugins/core/log_reader/adium/log_directory", _("Adium"));
2086 purple_plugin_pref_frame_add(frame, ppref);
2087
2088 #if 0
2089 ppref = purple_plugin_pref_new_with_name_and_label(
2090 "/plugins/core/log_reader/fire/log_directory", _("Fire"));
2091 purple_plugin_pref_frame_add(frame, ppref);
2092
2093 ppref = purple_plugin_pref_new_with_name_and_label(
2094 "/plugins/core/log_reader/messenger_plus/log_directory", _("Messenger Plus!"));
2095 purple_plugin_pref_frame_add(frame, ppref);
2096 #endif
2097
2098 ppref = purple_plugin_pref_new_with_name_and_label(
2099 "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
2100 purple_plugin_pref_frame_add(frame, ppref);
2101
2102 ppref = purple_plugin_pref_new_with_name_and_label(
2103 "/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
2104 purple_plugin_pref_frame_add(frame, ppref);
2105
2106 return frame;
2107 }
2108
2109 static PurplePluginUiInfo prefs_info = {
2110 get_plugin_pref_frame,
2111 0, /* page_num (reserved) */
2112 NULL /* frame (reserved) */
2113 };
2114
2115 static PurplePluginInfo info =
2116 {
2117 PURPLE_PLUGIN_MAGIC,
2118 PURPLE_MAJOR_VERSION,
2119 PURPLE_MINOR_VERSION,
2120 PURPLE_PLUGIN_STANDARD, /**< type */
2121 NULL, /**< ui_requirement */
2122 0, /**< flags */
2123 NULL, /**< dependencies */
2124 PURPLE_PRIORITY_DEFAULT, /**< priority */
2125 "core-log_reader", /**< id */
2126 N_("Log Reader"), /**< name */
2127 VERSION, /**< version */
2128
2129 /** summary */
2130 N_("Includes other IM clients' logs in the "
2131 "log viewer."),
2132
2133 /** description */
2134 N_("When viewing logs, this plugin will include "
2135 "logs from other IM clients. Currently, this "
2136 "includes Adium, MSN Messenger, and Trillian.\n\n"
2137 "WARNING: This plugin is still alpha code and "
2138 "may crash frequently. Use it at your own risk!"),
2139
2140 "Richard Laager <rlaager@pidgin.im>", /**< author */
2141 PURPLE_WEBSITE, /**< homepage */
2142 plugin_load, /**< load */
2143 plugin_unload, /**< unload */
2144 NULL, /**< destroy */
2145 NULL, /**< ui_info */
2146 NULL, /**< extra_info */
2147 &prefs_info, /**< prefs_info */
2148 NULL /**< actions */
2149 };
2150
2151 PURPLE_INIT_PLUGIN(log_reader, init_plugin, info)

mercurial