Tagged for release 2.26.0.
[empathy-mirror.git] / gnome-2-26 / libempathy / empathy-log-store-empathy.c
blob3414d666fa9ab6dec1e705be85557c9f9c0fae98
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.
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.
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.
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.
21 * Authors: Xavier Claessens <xclaesse@gmail.com>
22 * Jonny Lamb <jonny.lamb@collabora.co.uk>
25 #include <config.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <glib/gstdio.h>
32 #include "empathy-log-store.h"
33 #include "empathy-log-store-empathy.h"
34 #include "empathy-log-manager.h"
35 #include "empathy-contact.h"
36 #include "empathy-time.h"
37 #include "empathy-utils.h"
39 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
40 #include "empathy-debug.h"
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"
53 #define LOG_FOOTER \
54 "</log>\n"
57 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLogStoreEmpathy)
58 typedef struct
60 gchar *basedir;
61 gchar *name;
62 } EmpathyLogStoreEmpathyPriv;
64 static void log_store_iface_init (gpointer g_iface,gpointer iface_data);
66 G_DEFINE_TYPE_WITH_CODE (EmpathyLogStoreEmpathy, empathy_log_store_empathy,
67 G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_LOG_STORE,
68 log_store_iface_init));
70 static void
71 log_store_empathy_finalize (GObject *object)
73 EmpathyLogStoreEmpathy *self = EMPATHY_LOG_STORE_EMPATHY (object);
74 EmpathyLogStoreEmpathyPriv *priv = GET_PRIV (self);
76 g_free (priv->basedir);
77 g_free (priv->name);
80 static void
81 empathy_log_store_empathy_class_init (EmpathyLogStoreEmpathyClass *klass)
83 GObjectClass *object_class = G_OBJECT_CLASS (klass);
85 object_class->finalize = log_store_empathy_finalize;
87 g_type_class_add_private (object_class, sizeof (EmpathyLogStoreEmpathyPriv));
90 static void
91 empathy_log_store_empathy_init (EmpathyLogStoreEmpathy *self)
93 EmpathyLogStoreEmpathyPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
94 EMPATHY_TYPE_LOG_STORE_EMPATHY, EmpathyLogStoreEmpathyPriv);
96 self->priv = priv;
98 priv->basedir = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (),
99 ".gnome2", PACKAGE_NAME, "logs", NULL);
101 priv->name = g_strdup ("Empathy");
104 static gchar *
105 log_store_empathy_get_dir (EmpathyLogStore *self,
106 McAccount *account,
107 const gchar *chat_id,
108 gboolean chatroom)
110 const gchar *account_id;
111 gchar *basedir;
112 EmpathyLogStoreEmpathyPriv *priv;
114 priv = GET_PRIV (self);
116 account_id = mc_account_get_unique_name (account);
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);
125 return basedir;
128 static gchar *
129 log_store_empathy_get_timestamp_filename (void)
131 time_t t;
132 gchar *time_str;
133 gchar *filename;
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);
139 g_free (time_str);
141 return filename;
144 static gchar *
145 log_store_empathy_get_timestamp_from_message (EmpathyMessage *message)
147 time_t t;
149 t = empathy_message_get_timestamp (message);
151 /* We keep the timestamps in the messages as UTC. */
152 return empathy_time_to_string_utc (t, LOG_TIME_FORMAT_FULL);
155 static gchar *
156 log_store_empathy_get_filename (EmpathyLogStore *self,
157 McAccount *account,
158 const gchar *chat_id,
159 gboolean chatroom)
161 gchar *basedir;
162 gchar *timestamp;
163 gchar *filename;
165 basedir = log_store_empathy_get_dir (self, account, chat_id, chatroom);
166 timestamp = log_store_empathy_get_timestamp_filename ();
167 filename = g_build_filename (basedir, timestamp, NULL);
169 g_free (basedir);
170 g_free (timestamp);
172 return filename;
175 static gboolean
176 log_store_empathy_add_message (EmpathyLogStore *self,
177 const gchar *chat_id,
178 gboolean chatroom,
179 EmpathyMessage *message,
180 GError **error)
182 FILE *file;
183 McAccount *account;
184 EmpathyContact *sender;
185 const gchar *body_str;
186 const gchar *str;
187 EmpathyAvatar *avatar;
188 gchar *avatar_token = NULL;
189 gchar *filename;
190 gchar *basedir;
191 gchar *body;
192 gchar *timestamp;
193 gchar *contact_name;
194 gchar *contact_id;
195 TpChannelTextMessageType msg_type;
197 g_return_val_if_fail (EMPATHY_IS_LOG_STORE (self), FALSE);
198 g_return_val_if_fail (chat_id != NULL, FALSE);
199 g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
201 sender = empathy_message_get_sender (message);
202 account = empathy_contact_get_account (sender);
203 body_str = empathy_message_get_body (message);
204 msg_type = empathy_message_get_tptype (message);
206 if (EMP_STR_EMPTY (body_str))
207 return FALSE;
209 filename = log_store_empathy_get_filename (self, account, chat_id, chatroom);
210 basedir = g_path_get_dirname (filename);
211 if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
213 DEBUG ("Creating directory:'%s'", basedir);
214 g_mkdir_with_parents (basedir, LOG_DIR_CREATE_MODE);
216 g_free (basedir);
218 DEBUG ("Adding message: '%s' to file: '%s'", body_str, filename);
220 if (!g_file_test (filename, G_FILE_TEST_EXISTS))
222 file = g_fopen (filename, "w+");
223 if (file != NULL)
224 g_fprintf (file, LOG_HEADER);
226 g_chmod (filename, LOG_FILE_CREATE_MODE);
228 else
230 file = g_fopen (filename, "r+");
231 if (file != NULL)
232 fseek (file, - strlen (LOG_FOOTER), SEEK_END);
235 body = g_markup_escape_text (body_str, -1);
236 timestamp = log_store_empathy_get_timestamp_from_message (message);
238 str = empathy_contact_get_name (sender);
239 contact_name = g_markup_escape_text (str, -1);
241 str = empathy_contact_get_id (sender);
242 contact_id = g_markup_escape_text (str, -1);
244 avatar = empathy_contact_get_avatar (sender);
245 if (avatar != NULL)
246 avatar_token = g_markup_escape_text (avatar->token, -1);
248 g_fprintf (file,
249 "<message time='%s' cm_id='%d' id='%s' name='%s' token='%s' isuser='%s' type='%s'>"
250 "%s</message>\n" LOG_FOOTER, timestamp,
251 empathy_message_get_id (message),
252 contact_id, contact_name,
253 avatar_token ? avatar_token : "",
254 empathy_contact_is_user (sender) ? "true" : "false",
255 empathy_message_type_to_str (msg_type), body);
257 fclose (file);
258 g_free (filename);
259 g_free (contact_id);
260 g_free (contact_name);
261 g_free (timestamp);
262 g_free (body);
263 g_free (avatar_token);
265 return TRUE;
268 static gboolean
269 log_store_empathy_exists (EmpathyLogStore *self,
270 McAccount *account,
271 const gchar *chat_id,
272 gboolean chatroom)
274 gchar *dir;
275 gboolean exists;
277 dir = log_store_empathy_get_dir (self, account, chat_id, chatroom);
278 exists = g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
279 g_free (dir);
281 return exists;
284 static GList *
285 log_store_empathy_get_dates (EmpathyLogStore *self,
286 McAccount *account,
287 const gchar *chat_id,
288 gboolean chatroom)
290 GList *dates = NULL;
291 gchar *date;
292 gchar *directory;
293 GDir *dir;
294 const gchar *filename;
295 const gchar *p;
297 g_return_val_if_fail (EMPATHY_IS_LOG_STORE (self), NULL);
298 g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
299 g_return_val_if_fail (chat_id != NULL, NULL);
301 directory = log_store_empathy_get_dir (self, account, chat_id, chatroom);
302 dir = g_dir_open (directory, 0, NULL);
303 if (!dir)
305 DEBUG ("Could not open directory:'%s'", directory);
306 g_free (directory);
307 return NULL;
310 DEBUG ("Collating a list of dates in:'%s'", directory);
312 while ((filename = g_dir_read_name (dir)) != NULL)
314 if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX))
315 continue;
317 p = strstr (filename, LOG_FILENAME_SUFFIX);
318 date = g_strndup (filename, p - filename);
320 if (!date)
321 continue;
323 if (!g_regex_match_simple ("\\d{8}", date, 0, 0))
324 continue;
326 dates = g_list_insert_sorted (dates, date, (GCompareFunc) strcmp);
329 g_free (directory);
330 g_dir_close (dir);
332 DEBUG ("Parsed %d dates", g_list_length (dates));
334 return dates;
337 static gchar *
338 log_store_empathy_get_filename_for_date (EmpathyLogStore *self,
339 McAccount *account,
340 const gchar *chat_id,
341 gboolean chatroom,
342 const gchar *date)
344 gchar *basedir;
345 gchar *timestamp;
346 gchar *filename;
348 basedir = log_store_empathy_get_dir (self, account, chat_id, chatroom);
349 timestamp = g_strconcat (date, LOG_FILENAME_SUFFIX, NULL);
350 filename = g_build_filename (basedir, timestamp, NULL);
352 g_free (basedir);
353 g_free (timestamp);
355 return filename;
358 static EmpathyLogSearchHit *
359 log_store_empathy_search_hit_new (EmpathyLogStore *self,
360 const gchar *filename)
362 EmpathyLogSearchHit *hit;
363 const gchar *account_name;
364 const gchar *end;
365 gchar **strv;
366 guint len;
368 if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX))
369 return NULL;
371 strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
372 len = g_strv_length (strv);
374 hit = g_slice_new0 (EmpathyLogSearchHit);
376 end = strstr (strv[len-1], LOG_FILENAME_SUFFIX);
377 hit->date = g_strndup (strv[len-1], end - strv[len-1]);
378 hit->chat_id = g_strdup (strv[len-2]);
379 hit->is_chatroom = (strcmp (strv[len-3], LOG_DIR_CHATROOMS) == 0);
381 if (hit->is_chatroom)
382 account_name = strv[len-4];
383 else
384 account_name = strv[len-3];
386 hit->account = mc_account_lookup (account_name);
387 hit->filename = g_strdup (filename);
389 g_strfreev (strv);
391 return hit;
394 static GList *
395 log_store_empathy_get_messages_for_file (EmpathyLogStore *self,
396 const gchar *filename)
398 GList *messages = NULL;
399 xmlParserCtxtPtr ctxt;
400 xmlDocPtr doc;
401 xmlNodePtr log_node;
402 xmlNodePtr node;
403 EmpathyLogSearchHit *hit;
404 McAccount *account;
406 g_return_val_if_fail (EMPATHY_IS_LOG_STORE (self), NULL);
407 g_return_val_if_fail (filename != NULL, NULL);
409 DEBUG ("Attempting to parse filename:'%s'...", filename);
411 if (!g_file_test (filename, G_FILE_TEST_EXISTS))
413 DEBUG ("Filename:'%s' does not exist", filename);
414 return NULL;
417 /* Get the account from the filename */
418 hit = log_store_empathy_search_hit_new (self, filename);
419 account = g_object_ref (hit->account);
420 empathy_log_manager_search_hit_free (hit);
422 /* Create parser. */
423 ctxt = xmlNewParserCtxt ();
425 /* Parse and validate the file. */
426 doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
427 if (!doc)
429 g_warning ("Failed to parse file:'%s'", filename);
430 xmlFreeParserCtxt (ctxt);
431 return NULL;
434 /* The root node, presets. */
435 log_node = xmlDocGetRootElement (doc);
436 if (!log_node)
438 xmlFreeDoc (doc);
439 xmlFreeParserCtxt (ctxt);
440 return NULL;
443 /* Now get the messages. */
444 for (node = log_node->children; node; node = node->next)
446 EmpathyMessage *message;
447 EmpathyContact *sender;
448 gchar *time;
449 time_t t;
450 gchar *sender_id;
451 gchar *sender_name;
452 gchar *sender_avatar_token;
453 gchar *body;
454 gchar *is_user_str;
455 gboolean is_user = FALSE;
456 gchar *msg_type_str;
457 gchar *cm_id_str;
458 guint cm_id;
459 TpChannelTextMessageType msg_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
461 if (strcmp (node->name, "message") != 0)
462 continue;
464 body = xmlNodeGetContent (node);
465 time = xmlGetProp (node, "time");
466 sender_id = xmlGetProp (node, "id");
467 sender_name = xmlGetProp (node, "name");
468 sender_avatar_token = xmlGetProp (node, "token");
469 is_user_str = xmlGetProp (node, "isuser");
470 msg_type_str = xmlGetProp (node, "type");
471 cm_id_str = xmlGetProp (node, "cm_id");
473 if (is_user_str)
474 is_user = strcmp (is_user_str, "true") == 0;
476 if (msg_type_str)
477 msg_type = empathy_message_type_from_str (msg_type_str);
479 if (cm_id_str)
480 cm_id = atoi (cm_id_str);
482 t = empathy_time_parse (time);
484 sender = empathy_contact_new_full (account, sender_id, sender_name);
485 empathy_contact_set_is_user (sender, is_user);
486 if (!EMP_STR_EMPTY (sender_avatar_token))
487 empathy_contact_load_avatar_cache (sender,
488 sender_avatar_token);
490 message = empathy_message_new (body);
491 empathy_message_set_sender (message, sender);
492 empathy_message_set_timestamp (message, t);
493 empathy_message_set_tptype (message, msg_type);
495 if (cm_id_str)
496 empathy_message_set_id (message, cm_id);
498 messages = g_list_append (messages, message);
500 g_object_unref (sender);
501 xmlFree (time);
502 xmlFree (sender_id);
503 xmlFree (sender_name);
504 xmlFree (body);
505 xmlFree (is_user_str);
506 xmlFree (msg_type_str);
507 xmlFree (cm_id_str);
510 DEBUG ("Parsed %d messages", g_list_length (messages));
512 xmlFreeDoc (doc);
513 xmlFreeParserCtxt (ctxt);
515 return messages;
518 static GList *
519 log_store_empathy_get_all_files (EmpathyLogStore *self,
520 const gchar *dir)
522 GDir *gdir;
523 GList *files = NULL;
524 const gchar *name;
525 const gchar *basedir;
526 EmpathyLogStoreEmpathyPriv *priv;
528 priv = GET_PRIV (self);
530 basedir = dir ? dir : priv->basedir;
532 gdir = g_dir_open (basedir, 0, NULL);
533 if (!gdir)
534 return NULL;
536 while ((name = g_dir_read_name (gdir)) != NULL)
538 gchar *filename;
540 filename = g_build_filename (basedir, name, NULL);
541 if (g_str_has_suffix (filename, LOG_FILENAME_SUFFIX))
543 files = g_list_prepend (files, filename);
544 continue;
547 if (g_file_test (filename, G_FILE_TEST_IS_DIR))
549 /* Recursively get all log files */
550 files = g_list_concat (files,
551 log_store_empathy_get_all_files (self, filename));
554 g_free (filename);
557 g_dir_close (gdir);
559 return files;
562 static GList *
563 log_store_empathy_search_new (EmpathyLogStore *self,
564 const gchar *text)
566 GList *files, *l;
567 GList *hits = NULL;
568 gchar *text_casefold;
570 g_return_val_if_fail (EMPATHY_IS_LOG_STORE (self), NULL);
571 g_return_val_if_fail (!EMP_STR_EMPTY (text), NULL);
573 text_casefold = g_utf8_casefold (text, -1);
575 files = log_store_empathy_get_all_files (self, NULL);
576 DEBUG ("Found %d log files in total", g_list_length (files));
578 for (l = files; l; l = g_list_next (l))
580 gchar *filename;
581 GMappedFile *file;
582 gsize length;
583 gchar *contents;
584 gchar *contents_casefold;
586 filename = l->data;
588 file = g_mapped_file_new (filename, FALSE, NULL);
589 if (!file)
590 continue;
592 length = g_mapped_file_get_length (file);
593 contents = g_mapped_file_get_contents (file);
594 contents_casefold = g_utf8_casefold (contents, length);
596 g_mapped_file_free (file);
598 if (strstr (contents_casefold, text_casefold))
600 EmpathyLogSearchHit *hit;
602 hit = log_store_empathy_search_hit_new (self, filename);
604 if (hit)
606 hits = g_list_prepend (hits, hit);
607 DEBUG ("Found text:'%s' in file:'%s' on date:'%s'",
608 text, hit->filename, hit->date);
612 g_free (contents_casefold);
613 g_free (filename);
616 g_list_free (files);
617 g_free (text_casefold);
619 return hits;
622 static GList *
623 log_store_empathy_get_chats_for_dir (EmpathyLogStore *self,
624 const gchar *dir,
625 gboolean is_chatroom)
627 GDir *gdir;
628 GList *hits = NULL;
629 const gchar *name;
630 GError *error = NULL;
632 gdir = g_dir_open (dir, 0, &error);
633 if (!gdir)
635 DEBUG ("Failed to open directory: %s, error: %s", dir, error->message);
636 g_error_free (error);
637 return NULL;
640 while ((name = g_dir_read_name (gdir)) != NULL)
642 EmpathyLogSearchHit *hit;
643 gchar *filename;
645 filename = g_build_filename (dir, name, NULL);
646 if (!is_chatroom && strcmp (name, LOG_DIR_CHATROOMS) == 0)
648 hits = g_list_concat (hits, log_store_empathy_get_chats_for_dir (
649 self, filename, TRUE));
650 g_free (filename);
651 continue;
654 hit = g_slice_new0 (EmpathyLogSearchHit);
655 hit->chat_id = g_strdup (name);
656 hit->is_chatroom = is_chatroom;
658 hits = g_list_prepend (hits, hit);
661 g_dir_close (gdir);
663 return hits;
667 static GList *
668 log_store_empathy_get_messages_for_date (EmpathyLogStore *self,
669 McAccount *account,
670 const gchar *chat_id,
671 gboolean chatroom,
672 const gchar *date)
674 gchar *filename;
675 GList *messages;
677 g_return_val_if_fail (EMPATHY_IS_LOG_STORE (self), NULL);
678 g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
679 g_return_val_if_fail (chat_id != NULL, NULL);
681 filename = log_store_empathy_get_filename_for_date (self, account,
682 chat_id, chatroom, date);
683 messages = log_store_empathy_get_messages_for_file (self, filename);
684 g_free (filename);
686 return messages;
689 static GList *
690 log_store_empathy_get_chats (EmpathyLogStore *self,
691 McAccount *account)
693 gchar *dir;
694 GList *hits;
695 EmpathyLogStoreEmpathyPriv *priv;
697 priv = GET_PRIV (self);
699 dir = g_build_filename (priv->basedir,
700 mc_account_get_unique_name (account), NULL);
702 hits = log_store_empathy_get_chats_for_dir (self, dir, FALSE);
704 g_free (dir);
706 return hits;
709 static const gchar *
710 log_store_empathy_get_name (EmpathyLogStore *self)
712 EmpathyLogStoreEmpathyPriv *priv = GET_PRIV (self);
714 return priv->name;
717 static GList *
718 log_store_empathy_get_filtered_messages (EmpathyLogStore *self,
719 McAccount *account,
720 const gchar *chat_id,
721 gboolean chatroom,
722 guint num_messages,
723 EmpathyLogMessageFilter filter,
724 gpointer user_data)
726 GList *dates, *l, *messages = NULL;
727 guint i = 0;
729 dates = log_store_empathy_get_dates (self, account, chat_id, chatroom);
731 for (l = g_list_last (dates); l && i < num_messages; l = g_list_previous (l))
733 GList *new_messages, *n, *next;
735 /* FIXME: We should really restrict the message parsing to get only
736 * the newest num_messages. */
737 new_messages = log_store_empathy_get_messages_for_date (self, account,
738 chat_id, chatroom, l->data);
740 n = new_messages;
741 while (n != NULL)
743 next = g_list_next (n);
744 if (!filter (n->data, user_data))
746 g_object_unref (n->data);
747 new_messages = g_list_delete_link (new_messages, n);
749 else
751 i++;
753 n = next;
755 messages = g_list_concat (messages, new_messages);
758 g_list_foreach (dates, (GFunc) g_free, NULL);
759 g_list_free (dates);
761 return messages;
764 static void
765 log_store_iface_init (gpointer g_iface,
766 gpointer iface_data)
768 EmpathyLogStoreInterface *iface = (EmpathyLogStoreInterface *) g_iface;
770 iface->get_name = log_store_empathy_get_name;
771 iface->exists = log_store_empathy_exists;
772 iface->add_message = log_store_empathy_add_message;
773 iface->get_dates = log_store_empathy_get_dates;
774 iface->get_messages_for_date = log_store_empathy_get_messages_for_date;
775 iface->get_chats = log_store_empathy_get_chats;
776 iface->search_new = log_store_empathy_search_new;
777 iface->ack_message = NULL;
778 iface->get_filtered_messages = log_store_empathy_get_filtered_messages;