]> git.0d.be Git - empathy.git/blob - libempathy/empathy-log-manager.c
Log chats and display 10 last messages when opening a new chat.
[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_FILENAME_SUFFIX       ".log"
43 #define LOG_TIME_FORMAT_FULL      "%Y%m%dT%H:%M:%S"
44 #define LOG_TIME_FORMAT           "%Y%m%d"
45 #define LOG_HEADER \
46     "<?xml version='1.0' encoding='utf-8'?>\n" \
47     "<?xml-stylesheet type=\"text/xsl\" href=\"gossip-log.xsl\"?>\n" \
48     "<log>\n"
49
50 #define LOG_FOOTER \
51     "</log>\n"
52
53 struct _EmpathyLogManagerPriv {
54         gboolean dummy;
55 };
56
57 static void    empathy_log_manager_class_init         (EmpathyLogManagerClass *klass);
58 static void    empathy_log_manager_init               (EmpathyLogManager      *manager);
59 static void    log_manager_finalize                   (GObject                *object);
60 static gchar * log_manager_get_dir                    (McAccount              *account,
61                                                        const gchar            *chat_id);
62 static gchar * log_manager_get_filename               (McAccount              *account,
63                                                        const gchar            *chat_id);
64 static gchar * log_manager_get_filename_for_date      (McAccount              *account,
65                                                        const gchar            *chat_id,
66                                                        const gchar            *date);
67 static gchar * log_manager_get_timestamp_filename     (void);
68 static gchar * log_manager_get_timestamp_from_message (GossipMessage          *message);
69
70 G_DEFINE_TYPE (EmpathyLogManager, empathy_log_manager, G_TYPE_OBJECT);
71
72 static void
73 empathy_log_manager_class_init (EmpathyLogManagerClass *klass)
74 {
75         GObjectClass *object_class = G_OBJECT_CLASS (klass);
76
77         object_class->finalize = log_manager_finalize;
78
79         g_type_class_add_private (object_class, sizeof (EmpathyLogManagerPriv));
80 }
81
82 static void
83 empathy_log_manager_init (EmpathyLogManager *manager)
84 {
85 }
86
87 static void
88 log_manager_finalize (GObject *object)
89 {
90 }
91
92 EmpathyLogManager *
93 empathy_log_manager_new (void)
94 {
95         static EmpathyLogManager *manager = NULL;
96
97         if (!manager) {
98                 manager = g_object_new (EMPATHY_TYPE_LOG_MANAGER, NULL);
99                 g_object_add_weak_pointer (G_OBJECT (manager), (gpointer) &manager);
100         } else {
101                 g_object_ref (manager);
102         }
103
104         return manager;
105 }
106
107 void
108 empathy_log_manager_add_message (EmpathyLogManager *manager,
109                                  const gchar       *chat_id,
110                                  GossipMessage     *message)
111 {
112         FILE          *file;
113         McAccount     *account;
114         GossipContact *sender;
115         const gchar   *body_str;
116         const gchar   *str;
117         gchar         *filename;
118         gchar         *body;
119         gchar         *timestamp;
120         gchar         *contact_name;
121         gchar         *contact_id;
122
123         g_return_if_fail (EMPATHY_IS_LOG_MANAGER (manager));
124         g_return_if_fail (chat_id != NULL);
125         g_return_if_fail (GOSSIP_IS_MESSAGE (message));
126
127         sender = gossip_message_get_sender (message);
128         account = gossip_contact_get_account (sender);
129         body_str = gossip_message_get_body (message);
130
131         if (G_STR_EMPTY (body_str)) {
132                 return;
133         }
134
135         filename = log_manager_get_filename (account, chat_id);
136
137         gossip_debug (DEBUG_DOMAIN, "Adding message: '%s' to file: '%s'",
138                       body_str, filename);
139
140         if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
141                 file = g_fopen (filename, "w+");
142                 if (file) {
143                         g_fprintf (file, LOG_HEADER);
144                 }
145                 g_chmod (filename, LOG_FILE_CREATE_MODE);
146         } else {
147                 file = g_fopen (filename, "r+");
148                 if (file) {
149                         fseek (file, - strlen (LOG_FOOTER), SEEK_END);
150                 }
151         }
152
153         body = g_markup_escape_text (body_str, -1);
154         timestamp = log_manager_get_timestamp_from_message (message);
155
156         str = gossip_contact_get_name (sender);
157         if (!str) {
158                 contact_name = g_strdup ("");
159         } else {
160                 contact_name = g_markup_escape_text (str, -1);
161         }
162
163         str = gossip_contact_get_id (sender);
164         if (!str) {
165                 contact_id = g_strdup ("");
166         } else {
167                 contact_id = g_markup_escape_text (str, -1);
168         }
169
170         g_fprintf (file,
171                    "<message time='%s' id='%s' name='%s'>%s</message>\n" LOG_FOOTER,
172                    timestamp,
173                    contact_id,
174                    contact_name,
175                    body);
176
177         fclose (file);
178         g_free (filename);
179         g_free (contact_id);
180         g_free (contact_name);
181         g_free (timestamp);
182         g_free (body);
183 }
184
185 GList *
186 empathy_log_manager_get_dates (EmpathyLogManager *manager,
187                                McAccount         *account,
188                                const gchar       *chat_id)
189 {
190         GList       *dates = NULL;
191         gchar       *date;
192         gchar       *directory;
193         GDir        *dir;
194         const gchar *filename;
195         const gchar *p;
196
197         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
198         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
199         g_return_val_if_fail (chat_id != NULL, NULL);
200
201         directory = log_manager_get_dir (account, chat_id);
202         if (!directory) {
203                 return NULL;
204         }
205
206         dir = g_dir_open (directory, 0, NULL);
207         if (!dir) {
208                 gossip_debug (DEBUG_DOMAIN, "Could not open directory:'%s'", directory);
209                 g_free (directory);
210                 return NULL;
211         }
212
213         gossip_debug (DEBUG_DOMAIN, "Collating a list of dates in:'%s'", directory);
214
215         while ((filename = g_dir_read_name (dir)) != NULL) {
216                 if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) {
217                         continue;
218                 }
219
220                 p = strstr (filename, LOG_FILENAME_SUFFIX);
221                 date = g_strndup (filename, p - filename);
222                 if (!date) {
223                         continue;
224                 }
225
226                 dates = g_list_insert_sorted (dates, date, (GCompareFunc) strcmp);
227         }
228
229         g_free (directory);
230         g_dir_close (dir);
231
232         gossip_debug (DEBUG_DOMAIN, "Parsed %d dates", g_list_length (dates));
233
234         return dates;
235 }
236
237 GList *
238 empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager,
239                                            McAccount         *account,
240                                            const gchar       *chat_id,
241                                            const gchar       *date)
242 {
243         gchar            *filename;
244         GList            *messages = NULL;
245         xmlParserCtxtPtr  ctxt;
246         xmlDocPtr         doc;
247         xmlNodePtr        log_node;
248         xmlNodePtr        node;
249
250         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
251         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
252         g_return_val_if_fail (chat_id != NULL, NULL);
253
254         filename = log_manager_get_filename_for_date (account, chat_id, date);
255
256         gossip_debug (DEBUG_DOMAIN, "Attempting to parse filename:'%s'...", filename);
257
258         if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
259                 gossip_debug (DEBUG_DOMAIN, "Filename:'%s' does not exist", filename);
260                 g_free (filename);
261                 return NULL;
262         }
263
264         /* Create parser. */
265         ctxt = xmlNewParserCtxt ();
266
267         /* Parse and validate the file. */
268         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
269         if (!doc) {
270                 g_warning ("Failed to parse file:'%s'", filename);
271                 g_free (filename);
272                 xmlFreeParserCtxt (ctxt);
273                 return NULL;
274         }
275
276         /* The root node, presets. */
277         log_node = xmlDocGetRootElement (doc);
278         if (!log_node) {
279                 g_free (filename);
280                 xmlFreeDoc (doc);
281                 xmlFreeParserCtxt (ctxt);
282                 return NULL;
283         }
284
285         /* Now get the messages. */
286         for (node = log_node->children; node; node = node->next) {
287                 GossipMessage *message;
288                 GossipContact *sender;
289                 gchar         *time;
290                 GossipTime     t;
291                 gchar         *sender_id;
292                 gchar         *sender_name;
293                 gchar         *body;
294
295                 if (strcmp (node->name, "message") != 0) {
296                         continue;
297                 }
298
299                 body = xmlNodeGetContent (node);
300                 time = xmlGetProp (node, "time");
301                 sender_id = xmlGetProp (node, "id");
302                 sender_name = xmlGetProp (node, "name");
303
304                 t = gossip_time_parse (time);
305
306                 sender = gossip_contact_new_full (account, sender_id, sender_name);
307                 message = gossip_message_new (body);
308                 gossip_message_set_sender (message, sender);
309                 gossip_message_set_timestamp (message, t);
310
311                 messages = g_list_append (messages, message);
312
313                 g_object_unref (sender);
314                 xmlFree (time);
315                 xmlFree (sender_id);
316                 xmlFree (sender_name);
317                 xmlFree (body);
318         }
319
320         gossip_debug (DEBUG_DOMAIN, "Parsed %d messages", g_list_length (messages));
321
322         g_free (filename);
323         xmlFreeDoc (doc);
324         xmlFreeParserCtxt (ctxt);
325
326         return messages;
327 }
328
329 GList *
330 empathy_log_manager_get_last_messages (EmpathyLogManager *manager,
331                                        McAccount         *account,
332                                        const gchar       *chat_id)
333 {
334         GList *messages;
335         GList *dates;
336         GList *l;
337
338         g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL);
339         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
340         g_return_val_if_fail (chat_id != NULL, NULL);
341
342         dates = empathy_log_manager_get_dates (manager, account, chat_id);
343
344         l = g_list_last (dates);
345         messages = empathy_log_manager_get_messages_for_date (manager,
346                                                               account,
347                                                               chat_id,
348                                                               l->data);
349
350         g_list_foreach (dates, (GFunc) g_free, NULL);
351         g_list_free (dates);
352
353         return messages;
354 }
355
356 static gchar *
357 log_manager_get_dir (McAccount   *account,
358                      const gchar *chat_id)
359 {
360         const gchar *account_id;
361         gchar       *basedir;
362
363         account_id = mc_account_get_unique_name (account);
364         basedir = g_build_path (G_DIR_SEPARATOR_S,
365                                 g_get_home_dir (),
366                                 ".gnome2",
367                                 PACKAGE_NAME,
368                                 "logs",
369                                 account_id,
370                                 chat_id,
371                                 NULL);
372
373         if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
374                 gossip_debug (DEBUG_DOMAIN, "Creating directory:'%s'", basedir);
375
376                 g_mkdir_with_parents (basedir, LOG_DIR_CREATE_MODE);
377         }
378
379         return basedir;
380 }
381
382 static gchar *
383 log_manager_get_filename (McAccount   *account,
384                           const gchar *chat_id)
385 {
386         gchar *basedir;
387         gchar *timestamp;
388         gchar *filename;
389
390         basedir = log_manager_get_dir (account, chat_id);
391         timestamp = log_manager_get_timestamp_filename ();
392         filename = g_build_filename (basedir, timestamp, NULL);
393
394         g_free (basedir);
395         g_free (timestamp);
396
397         return filename;
398 }
399
400 static gchar *
401 log_manager_get_filename_for_date (McAccount   *account,
402                                    const gchar *chat_id,
403                                    const gchar *date)
404 {
405         gchar *basedir;
406         gchar *timestamp;
407         gchar *filename;
408
409         basedir = log_manager_get_dir (account, chat_id);
410         timestamp = g_strconcat (date, LOG_FILENAME_SUFFIX, NULL);
411         filename = g_build_filename (basedir, timestamp, NULL);
412
413         g_free (basedir);
414         g_free (timestamp);
415
416         return filename;
417 }
418
419 static gchar *
420 log_manager_get_timestamp_filename (void)
421 {
422         GossipTime  t;
423         gchar      *time_str;
424         gchar      *filename;
425
426         t = gossip_time_get_current ();
427         time_str = gossip_time_to_string_local (t, LOG_TIME_FORMAT);
428         filename = g_strconcat (time_str, LOG_FILENAME_SUFFIX, NULL);
429
430         g_free (time_str);
431
432         return filename;
433 }
434
435 static gchar *
436 log_manager_get_timestamp_from_message (GossipMessage *message)
437 {
438         GossipTime t;
439
440         t = gossip_message_get_timestamp (message);
441
442         /* We keep the timestamps in the messages as UTC. */
443         return gossip_time_to_string_utc (t, LOG_TIME_FORMAT_FULL);
444 }
445