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