]> git.0d.be Git - empathy.git/blob - libempathy/empathy-log-manager.c
New window for viewing logs.
[empathy.git] / libempathy / empathy-log-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  * 
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <stdio.h>
27 #include <glib/gstdio.h>
28
29 #include "empathy-log-manager.h"
30 #include "gossip-contact.h"
31 #include "gossip-time.h"
32 #include "gossip-debug.h"
33 #include "gossip-utils.h"
34
35 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
36                        EMPATHY_TYPE_LOG_MANAGER, EmpathyLogManagerPriv))
37
38 #define DEBUG_DOMAIN "LogManager"
39
40 #define LOG_DIR_CREATE_MODE       (S_IRUSR | S_IWUSR | S_IXUSR)
41 #define LOG_FILE_CREATE_MODE      (S_IRUSR | S_IWUSR)
42 #define LOG_DIR_CHATROOMS         "chatrooms"
43 #define LOG_FILENAME_SUFFIX       ".log"
44 #define LOG_TIME_FORMAT_FULL      "%Y%m%dT%H:%M:%S"
45 #define LOG_TIME_FORMAT           "%Y%m%d"
46 #define LOG_HEADER \
47     "<?xml version='1.0' encoding='utf-8'?>\n" \
48     "<?xml-stylesheet type=\"text/xsl\" href=\"gossip-log.xsl\"?>\n" \
49     "<log>\n"
50
51 #define LOG_FOOTER \
52     "</log>\n"
53
54 struct _EmpathyLogManagerPriv {
55         gchar *basedir;
56 };
57
58 static void                 empathy_log_manager_class_init         (EmpathyLogManagerClass *klass);
59 static void                 empathy_log_manager_init               (EmpathyLogManager      *manager);
60 static void                 log_manager_finalize                   (GObject                *object);
61 static const gchar *        log_manager_get_basedir                (EmpathyLogManager      *manager);
62 static GList *              log_manager_get_all_files              (EmpathyLogManager      *manager,
63                                                                     const gchar            *dir);
64 static GList *              log_manager_get_chats                  (EmpathyLogManager      *manager,
65                                                                     const gchar            *dir,
66                                                                     gboolean                is_chatroom);
67 static gchar *              log_manager_get_dir                    (EmpathyLogManager      *manager,
68                                                                     McAccount              *account,
69                                                                     const gchar            *chat_id,
70                                                                     gboolean                chatroom);
71 static gchar *              log_manager_get_filename               (EmpathyLogManager      *manager,
72                                                                     McAccount              *account,
73                                                                     const gchar            *chat_id,
74                                                                     gboolean                chatroom);
75 static gchar *              log_manager_get_filename_for_date      (EmpathyLogManager      *manager,
76                                                                     McAccount              *account,
77                                                                     const gchar            *chat_id,
78                                                                     gboolean                chatroom,
79                                                                     const gchar            *date);
80 static gchar *              log_manager_get_timestamp_filename     (void);
81 static gchar *              log_manager_get_timestamp_from_message (GossipMessage          *message);
82 static EmpathyLogSearchHit *log_manager_search_hit_new             (EmpathyLogManager      *manager,
83                                                                     const gchar            *filename);
84
85 G_DEFINE_TYPE (EmpathyLogManager, empathy_log_manager, G_TYPE_OBJECT);
86
87 static void
88 empathy_log_manager_class_init (EmpathyLogManagerClass *klass)
89 {
90         GObjectClass *object_class = G_OBJECT_CLASS (klass);
91
92         object_class->finalize = log_manager_finalize;
93
94         g_type_class_add_private (object_class, sizeof (EmpathyLogManagerPriv));
95 }
96
97 static void
98 empathy_log_manager_init (EmpathyLogManager *manager)
99 {
100 }
101
102 static void
103 log_manager_finalize (GObject *object)
104 {
105         EmpathyLogManagerPriv *priv;
106
107         priv = GET_PRIV (object);
108
109         g_free (priv->basedir);
110 }
111
112 EmpathyLogManager *
113 empathy_log_manager_new (void)
114 {
115         static EmpathyLogManager *manager = NULL;
116
117         if (!manager) {
118                 manager = g_object_new (EMPATHY_TYPE_LOG_MANAGER, NULL);
119                 g_object_add_weak_pointer (G_OBJECT (manager), (gpointer) &manager);
120         } else {
121                 g_object_ref (manager);
122         }
123
124         return manager;
125 }
126
127 void
128 empathy_log_manager_add_message (EmpathyLogManager *manager,
129                                  const gchar       *chat_id,
130                                  gboolean           chatroom,
131                                  GossipMessage     *message)
132 {
133         FILE          *file;
134         McAccount     *account;
135         GossipContact *sender;
136         const gchar   *body_str;
137         const gchar   *str;
138         gchar         *filename;
139         gchar         *body;
140         gchar         *timestamp;
141         gchar         *contact_name;
142         gchar         *contact_id;
143
144         g_return_if_fail (EMPATHY_IS_LOG_MANAGER (manager));
145         g_return_if_fail (chat_id != NULL);
146         g_return_if_fail (GOSSIP_IS_MESSAGE (message));
147
148         sender = gossip_message_get_sender (message);
149         account = gossip_contact_get_account (sender);
150         body_str = gossip_message_get_body (message);
151
152         if (G_STR_EMPTY (body_str)) {
153                 return;
154         }
155
156         filename = log_manager_get_filename (manager, account, chat_id, chatroom);
157
158         gossip_debug (DEBUG_DOMAIN, "Adding message: '%s' to file: '%s'",
159                       body_str, filename);
160
161         if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
162                 file = g_fopen (filename, "w+");
163                 if (file) {
164                         g_fprintf (file, LOG_HEADER);
165                 }
166                 g_chmod (filename, LOG_FILE_CREATE_MODE);
167         } else {
168                 file = g_fopen (filename, "r+");
169                 if (file) {
170                         fseek (file, - strlen (LOG_FOOTER), SEEK_END);
171                 }
172         }
173
174         body = g_markup_escape_text (body_str, -1);
175         timestamp = log_manager_get_timestamp_from_message (message);
176
177         str = gossip_contact_get_name (sender);
178         contact_name = g_markup_escape_text (str, -1);
179
180         str = gossip_contact_get_id (sender);
181         contact_id = g_markup_escape_text (str, -1);
182
183         g_fprintf (file,
184                    "<message time='%s' id='%s' name='%s' isuser='%s'>%s</message>\n" LOG_FOOTER,
185                    timestamp,
186                    contact_id,
187                    contact_name,
188                    gossip_contact_is_user (sender) ? "true" : "false",
189                    body);
190
191         fclose (file);
192         g_free (filename);
193         g_free (contact_id);
194         g_free (contact_name);
195         g_free (timestamp);
196         g_free (body);
197 }
198
199 GList *
200 empathy_log_manager_get_dates (EmpathyLogManager *manager,
201                                McAccount         *account,
202                                const gchar       *chat_id,
203                                gboolean           chatroom)
204 {
205         GList       *dates = NULL;
206         gchar       *date;
207         gchar       *directory;
208         GDir        *dir;
209         const gchar *filename;
210         const gchar *p;
211
212         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
213         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
214         g_return_val_if_fail (chat_id != NULL, NULL);
215
216         directory = log_manager_get_dir (manager, account, chat_id, chatroom);
217         if (!directory) {
218                 return NULL;
219         }
220
221         dir = g_dir_open (directory, 0, NULL);
222         if (!dir) {
223                 gossip_debug (DEBUG_DOMAIN, "Could not open directory:'%s'", directory);
224                 g_free (directory);
225                 return NULL;
226         }
227
228         gossip_debug (DEBUG_DOMAIN, "Collating a list of dates in:'%s'", directory);
229
230         while ((filename = g_dir_read_name (dir)) != NULL) {
231                 if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) {
232                         continue;
233                 }
234
235                 p = strstr (filename, LOG_FILENAME_SUFFIX);
236                 date = g_strndup (filename, p - filename);
237                 if (!date) {
238                         continue;
239                 }
240
241                 dates = g_list_insert_sorted (dates, date, (GCompareFunc) strcmp);
242         }
243
244         g_free (directory);
245         g_dir_close (dir);
246
247         gossip_debug (DEBUG_DOMAIN, "Parsed %d dates", g_list_length (dates));
248
249         return dates;
250 }
251
252 GList *
253 empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager,
254                                            McAccount         *account,
255                                            const gchar       *chat_id,
256                                            gboolean           chatroom,
257                                            const gchar       *date)
258 {
259         gchar            *filename;
260         GList            *messages = NULL;
261         xmlParserCtxtPtr  ctxt;
262         xmlDocPtr         doc;
263         xmlNodePtr        log_node;
264         xmlNodePtr        node;
265
266         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
267         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
268         g_return_val_if_fail (chat_id != NULL, NULL);
269
270         filename = log_manager_get_filename_for_date (manager, account, chat_id, chatroom, date);
271
272         gossip_debug (DEBUG_DOMAIN, "Attempting to parse filename:'%s'...", filename);
273
274         if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
275                 gossip_debug (DEBUG_DOMAIN, "Filename:'%s' does not exist", filename);
276                 g_free (filename);
277                 return NULL;
278         }
279
280         /* Create parser. */
281         ctxt = xmlNewParserCtxt ();
282
283         /* Parse and validate the file. */
284         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
285         if (!doc) {
286                 g_warning ("Failed to parse file:'%s'", filename);
287                 g_free (filename);
288                 xmlFreeParserCtxt (ctxt);
289                 return NULL;
290         }
291
292         /* The root node, presets. */
293         log_node = xmlDocGetRootElement (doc);
294         if (!log_node) {
295                 g_free (filename);
296                 xmlFreeDoc (doc);
297                 xmlFreeParserCtxt (ctxt);
298                 return NULL;
299         }
300
301         /* Now get the messages. */
302         for (node = log_node->children; node; node = node->next) {
303                 GossipMessage *message;
304                 GossipContact *sender;
305                 gchar         *time;
306                 GossipTime     t;
307                 gchar         *sender_id;
308                 gchar         *sender_name;
309                 gchar         *body;
310                 gchar         *is_user_str;
311                 gboolean       is_user = FALSE;
312
313                 if (strcmp (node->name, "message") != 0) {
314                         continue;
315                 }
316
317                 body = xmlNodeGetContent (node);
318                 time = xmlGetProp (node, "time");
319                 sender_id = xmlGetProp (node, "id");
320                 sender_name = xmlGetProp (node, "name");
321                 is_user_str = xmlGetProp (node, "isuser");
322
323                 if (is_user_str) {
324                         is_user = strcmp (is_user_str, "true") == 0;
325                 }
326
327                 t = gossip_time_parse (time);
328
329                 sender = gossip_contact_new_full (account, sender_id, sender_name);
330                 gossip_contact_set_is_user (sender, is_user);
331                 message = gossip_message_new (body);
332                 gossip_message_set_sender (message, sender);
333                 gossip_message_set_timestamp (message, t);
334
335                 messages = g_list_append (messages, message);
336
337                 g_object_unref (sender);
338                 xmlFree (time);
339                 xmlFree (sender_id);
340                 xmlFree (sender_name);
341                 xmlFree (body);
342         }
343
344         gossip_debug (DEBUG_DOMAIN, "Parsed %d messages", g_list_length (messages));
345
346         g_free (filename);
347         xmlFreeDoc (doc);
348         xmlFreeParserCtxt (ctxt);
349
350         return messages;
351 }
352
353 GList *
354 empathy_log_manager_get_last_messages (EmpathyLogManager *manager,
355                                        McAccount         *account,
356                                        const gchar       *chat_id,
357                                        gboolean           chatroom)
358 {
359         GList *messages = NULL;
360         GList *dates;
361         GList *l;
362
363         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
364         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
365         g_return_val_if_fail (chat_id != NULL, NULL);
366
367         dates = empathy_log_manager_get_dates (manager, account, chat_id, chatroom);
368
369         l = g_list_last (dates);
370         if (l) {
371                 messages = empathy_log_manager_get_messages_for_date (manager,
372                                                                       account,
373                                                                       chat_id,
374                                                                       chatroom,
375                                                                       l->data);
376         }
377
378         g_list_foreach (dates, (GFunc) g_free, NULL);
379         g_list_free (dates);
380
381         return messages;
382 }
383
384 GList *
385 empathy_log_manager_get_chats (EmpathyLogManager *manager,
386                                McAccount         *account)
387 {
388         const gchar *basedir;
389         gchar       *dir;
390
391         basedir = log_manager_get_basedir (manager);
392         dir = g_build_filename (basedir,
393                                 mc_account_get_unique_name (account),
394                                 NULL);
395
396         return log_manager_get_chats (manager, dir, FALSE);
397 }
398
399 GList *
400 empathy_log_manager_search_new (EmpathyLogManager *manager,
401                                 const gchar       *text)
402 {
403         GList *files, *l;
404         GList *hits = NULL;
405         gchar *text_casefold;
406
407         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
408         g_return_val_if_fail (!G_STR_EMPTY (text), NULL);
409
410         text_casefold = g_utf8_casefold (text, -1);
411
412         files = log_manager_get_all_files (manager, NULL);
413         gossip_debug (DEBUG_DOMAIN, "Found %d log files in total",
414                       g_list_length (files));
415
416         for (l = files; l; l = l->next) {
417                 gchar       *filename;
418                 GMappedFile *file;
419                 gsize        length;
420                 gchar       *contents;
421                 gchar       *contents_casefold;
422
423                 filename = l->data;
424
425                 file = g_mapped_file_new (filename, FALSE, NULL);
426                 if (!file) {
427                         continue;
428                 }
429
430                 length = g_mapped_file_get_length (file);
431                 contents = g_mapped_file_get_contents (file);
432                 contents_casefold = g_utf8_casefold (contents, length);
433
434                 g_mapped_file_free (file);
435
436                 if (strstr (contents_casefold, text_casefold)) {
437                         EmpathyLogSearchHit *hit;
438
439                         hit = log_manager_search_hit_new (manager, filename);
440
441                         if (hit) {
442                                 hits = g_list_prepend (hits, hit);
443                                 gossip_debug (DEBUG_DOMAIN, 
444                                               "Found text:'%s' in file:'%s' on date:'%s'...",
445                                               text, hit->filename, hit->date);
446                         }
447                 }
448
449                 g_free (contents_casefold);
450                 g_free (filename);
451         }
452         g_list_free (files);
453
454         g_free (text_casefold);
455
456         return hits;
457 }
458
459 void
460 empathy_log_manager_search_free (GList *hits)
461 {
462         GList               *l;
463         EmpathyLogSearchHit *hit;
464
465         for (l = hits; l; l = l->next) {
466                 hit = l->data;
467
468                 if (hit->account) {
469                         g_object_unref (hit->account);
470                 }
471
472                 g_free (hit->date);
473                 g_free (hit->filename);
474                 g_free (hit->chat_id);
475
476                 g_slice_free (EmpathyLogSearchHit, hit);
477         }
478         
479         g_list_free (hits);
480 }
481
482 /* Format is just date, 20061201. */
483 gchar *
484 empathy_log_manager_get_date_readable (const gchar *date)
485 {
486         GossipTime t;
487
488         t = gossip_time_parse (date);
489
490         return gossip_time_to_string_local (t, "%a %d %b %Y");
491 }
492
493 static const gchar *
494 log_manager_get_basedir (EmpathyLogManager *manager)
495 {
496         EmpathyLogManagerPriv *priv;    
497
498         priv = GET_PRIV (manager);
499
500         if (priv->basedir) {
501                 return priv->basedir;
502         }
503
504         priv->basedir = g_build_path (G_DIR_SEPARATOR_S,
505                                       g_get_home_dir (),
506                                       ".gnome2",
507                                       PACKAGE_NAME,
508                                       "logs",
509                                       NULL);
510
511         return priv->basedir;
512 }
513
514 static GList *
515 log_manager_get_all_files (EmpathyLogManager *manager,
516                            const gchar       *dir)
517 {
518         GDir        *gdir;
519         GList       *files = NULL;
520         const gchar *name;
521         
522         if (!dir) {
523                 dir = log_manager_get_basedir (manager);
524         }
525
526         gdir = g_dir_open (dir, 0, NULL);
527         if (!gdir) {
528                 return NULL;
529         }
530
531         while ((name = g_dir_read_name (gdir)) != NULL) {
532                 gchar *filename;
533
534                 filename = g_build_filename (dir, name, NULL);
535                 if (g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) {
536                         files = g_list_prepend (files, filename);
537                         continue;
538                 }
539
540                 if (g_file_test (filename, G_FILE_TEST_IS_DIR)) {
541                         /* Recursively get all log files */
542                         files = g_list_concat (files, log_manager_get_all_files (manager, filename));
543                 }
544                 g_free (filename);
545         }
546
547         g_dir_close (gdir);
548
549         return files;
550 }
551
552 static GList *
553 log_manager_get_chats (EmpathyLogManager *manager,
554                        const gchar       *dir,
555                        gboolean           is_chatroom)
556 {
557         GDir        *gdir;
558         GList       *hits = NULL;
559         const gchar *name;
560
561         gdir = g_dir_open (dir, 0, NULL);
562         if (!gdir) {
563                 return NULL;
564         }
565
566         while ((name = g_dir_read_name (gdir)) != NULL) {
567                 EmpathyLogSearchHit *hit;
568                 gchar *filename;
569
570                 filename = g_build_filename (dir, name, NULL);
571                 if (strcmp (name, LOG_DIR_CHATROOMS) == 0) {
572                         hits = g_list_concat (hits, log_manager_get_chats (manager, filename, TRUE));
573                         g_free (filename);
574                         continue;
575                 }
576
577                 hit = g_slice_new0 (EmpathyLogSearchHit);
578                 hit->chat_id = g_strdup (name);
579                 hit->is_chatroom = is_chatroom;
580
581                 hits = g_list_prepend (hits, hit);
582         }
583
584         g_dir_close (gdir);
585
586         return hits;
587 }
588
589 static gchar *
590 log_manager_get_dir (EmpathyLogManager *manager,
591                      McAccount         *account,
592                      const gchar       *chat_id,
593                      gboolean           chatroom)
594 {
595         const gchar *account_id;
596         gchar       *basedir;
597         gchar       *str;
598
599         account_id = mc_account_get_unique_name (account);
600         basedir = 
601         str = g_build_path (G_DIR_SEPARATOR_S,
602                             log_manager_get_basedir (manager),
603                             account_id,
604                             chat_id,
605                             NULL);
606
607         if (chatroom) {
608                 basedir = g_build_path (G_DIR_SEPARATOR_S,
609                                         str,
610                                         LOG_DIR_CHATROOMS,
611                                         NULL);
612                 g_free (str);
613         } else {
614                 basedir = str;
615         }
616
617         if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
618                 gossip_debug (DEBUG_DOMAIN, "Creating directory:'%s'", basedir);
619
620                 g_mkdir_with_parents (basedir, LOG_DIR_CREATE_MODE);
621         }
622
623         return basedir;
624 }
625
626 static gchar *
627 log_manager_get_filename (EmpathyLogManager *manager,
628                           McAccount         *account,
629                           const gchar       *chat_id,
630                           gboolean           chatroom)
631 {
632         gchar *basedir;
633         gchar *timestamp;
634         gchar *filename;
635
636         basedir = log_manager_get_dir (manager, account, chat_id, chatroom);
637         timestamp = log_manager_get_timestamp_filename ();
638         filename = g_build_filename (basedir, timestamp, NULL);
639
640         g_free (basedir);
641         g_free (timestamp);
642
643         return filename;
644 }
645
646 static gchar *
647 log_manager_get_filename_for_date (EmpathyLogManager *manager,
648                                    McAccount         *account,
649                                    const gchar       *chat_id,
650                                    gboolean           chatroom,
651                                    const gchar       *date)
652 {
653         gchar *basedir;
654         gchar *timestamp;
655         gchar *filename;
656
657         basedir = log_manager_get_dir (manager, account, chat_id, chatroom);
658         timestamp = g_strconcat (date, LOG_FILENAME_SUFFIX, NULL);
659         filename = g_build_filename (basedir, timestamp, NULL);
660
661         g_free (basedir);
662         g_free (timestamp);
663
664         return filename;
665 }
666
667 static gchar *
668 log_manager_get_timestamp_filename (void)
669 {
670         GossipTime  t;
671         gchar      *time_str;
672         gchar      *filename;
673
674         t = gossip_time_get_current ();
675         time_str = gossip_time_to_string_local (t, LOG_TIME_FORMAT);
676         filename = g_strconcat (time_str, LOG_FILENAME_SUFFIX, NULL);
677
678         g_free (time_str);
679
680         return filename;
681 }
682
683 static gchar *
684 log_manager_get_timestamp_from_message (GossipMessage *message)
685 {
686         GossipTime t;
687
688         t = gossip_message_get_timestamp (message);
689
690         /* We keep the timestamps in the messages as UTC. */
691         return gossip_time_to_string_utc (t, LOG_TIME_FORMAT_FULL);
692 }
693
694 static EmpathyLogSearchHit *
695 log_manager_search_hit_new (EmpathyLogManager *manager,
696                             const gchar       *filename)
697 {
698         EmpathyLogSearchHit  *hit;
699         const gchar          *account_name;
700         const gchar          *end;
701         gchar               **strv;
702         guint                 len;
703
704         if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) {
705                 return NULL;
706         }
707
708         strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
709         len = g_strv_length (strv);
710
711         hit = g_slice_new0 (EmpathyLogSearchHit);
712
713         end = strstr (strv[len-1], LOG_FILENAME_SUFFIX);
714         hit->date = g_strndup (strv[len-1], end - strv[len-1]);
715         hit->chat_id = g_strdup (strv[len-2]);
716         hit->is_chatroom = (strcmp (strv[len-3], LOG_DIR_CHATROOMS) == 0);
717         if (hit->is_chatroom) {
718                 account_name = strv[len-4];
719         } else {
720                 account_name = strv[len-3];
721         }
722         hit->account = mc_account_lookup (account_name);
723         hit->filename = g_strdup (filename);
724
725         g_strfreev (strv);
726
727         return hit;
728 }
729