gtk/plugins/log_reader.c

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

mercurial