]> git.0d.be Git - empathy.git/blob - libempathy/empathy-log-manager.c
Fix crash when there is no log available for a chat. Fix a crash when
[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 = NULL;
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         if (l) {
346                 messages = empathy_log_manager_get_messages_for_date (manager,
347                                                                       account,
348                                                                       chat_id,
349                                                                       l->data);
350         }
351
352         g_list_foreach (dates, (GFunc) g_free, NULL);
353         g_list_free (dates);
354
355         return messages;
356 }
357
358 static gchar *
359 log_manager_get_dir (McAccount   *account,
360                      const gchar *chat_id)
361 {
362         const gchar *account_id;
363         gchar       *basedir;
364
365         account_id = mc_account_get_unique_name (account);
366         basedir = g_build_path (G_DIR_SEPARATOR_S,
367                                 g_get_home_dir (),
368                                 ".gnome2",
369                                 PACKAGE_NAME,
370                                 "logs",
371                                 account_id,
372                                 chat_id,
373                                 NULL);
374
375         if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
376                 gossip_debug (DEBUG_DOMAIN, "Creating directory:'%s'", basedir);
377
378                 g_mkdir_with_parents (basedir, LOG_DIR_CREATE_MODE);
379         }
380
381         return basedir;
382 }
383
384 static gchar *
385 log_manager_get_filename (McAccount   *account,
386                           const gchar *chat_id)
387 {
388         gchar *basedir;
389         gchar *timestamp;
390         gchar *filename;
391
392         basedir = log_manager_get_dir (account, chat_id);
393         timestamp = log_manager_get_timestamp_filename ();
394         filename = g_build_filename (basedir, timestamp, NULL);
395
396         g_free (basedir);
397         g_free (timestamp);
398
399         return filename;
400 }
401
402 static gchar *
403 log_manager_get_filename_for_date (McAccount   *account,
404                                    const gchar *chat_id,
405                                    const gchar *date)
406 {
407         gchar *basedir;
408         gchar *timestamp;
409         gchar *filename;
410
411         basedir = log_manager_get_dir (account, chat_id);
412         timestamp = g_strconcat (date, LOG_FILENAME_SUFFIX, NULL);
413         filename = g_build_filename (basedir, timestamp, NULL);
414
415         g_free (basedir);
416         g_free (timestamp);
417
418         return filename;
419 }
420
421 static gchar *
422 log_manager_get_timestamp_filename (void)
423 {
424         GossipTime  t;
425         gchar      *time_str;
426         gchar      *filename;
427
428         t = gossip_time_get_current ();
429         time_str = gossip_time_to_string_local (t, LOG_TIME_FORMAT);
430         filename = g_strconcat (time_str, LOG_FILENAME_SUFFIX, NULL);
431
432         g_free (time_str);
433
434         return filename;
435 }
436
437 static gchar *
438 log_manager_get_timestamp_from_message (GossipMessage *message)
439 {
440         GossipTime t;
441
442         t = gossip_message_get_timestamp (message);
443
444         /* We keep the timestamps in the messages as UTC. */
445         return gossip_time_to_string_utc (t, LOG_TIME_FORMAT_FULL);
446 }
447