]> git.0d.be Git - empathy.git/blob - libempathy/empathy-message.c
Merge branch 'remember-my-message-yo'
[empathy.git] / libempathy / empathy-message.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2004-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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28
29 #include <telepathy-glib/util.h>
30 #include <telepathy-glib/account.h>
31 #include <telepathy-glib/account-manager.h>
32
33 #include <telepathy-logger/entity.h>
34 #include <telepathy-logger/event.h>
35 #include <telepathy-logger/text-event.h>
36
37 #include "empathy-message.h"
38 #include "empathy-utils.h"
39 #include "empathy-enum-types.h"
40
41 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyMessage)
42 typedef struct {
43         TpMessage *tp_message;
44         TpChannelTextMessageType  type;
45         EmpathyContact           *sender;
46         EmpathyContact           *receiver;
47         gchar                    *body;
48         gint64                    timestamp;
49         gboolean                  is_backlog;
50         guint                     id;
51         gboolean                  incoming;
52         TpChannelTextMessageFlags flags;
53 } EmpathyMessagePriv;
54
55 static void empathy_message_finalize   (GObject            *object);
56 static void message_get_property      (GObject            *object,
57                                        guint               param_id,
58                                        GValue             *value,
59                                        GParamSpec         *pspec);
60 static void message_set_property      (GObject            *object,
61                                        guint               param_id,
62                                        const GValue       *value,
63                                        GParamSpec         *pspec);
64
65 G_DEFINE_TYPE (EmpathyMessage, empathy_message, G_TYPE_OBJECT);
66
67 enum {
68         PROP_0,
69         PROP_TYPE,
70         PROP_SENDER,
71         PROP_RECEIVER,
72         PROP_BODY,
73         PROP_TIMESTAMP,
74         PROP_IS_BACKLOG,
75         PROP_INCOMING,
76         PROP_FLAGS,
77         PROP_TP_MESSAGE,
78 };
79
80 static void
81 empathy_message_class_init (EmpathyMessageClass *class)
82 {
83         GObjectClass *object_class;
84
85         object_class = G_OBJECT_CLASS (class);
86         object_class->finalize     = empathy_message_finalize;
87         object_class->get_property = message_get_property;
88         object_class->set_property = message_set_property;
89
90         g_object_class_install_property (object_class,
91                                          PROP_TYPE,
92                                          g_param_spec_uint ("type",
93                                                             "Message Type",
94                                                             "The type of message",
95                                                             TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
96                                                             TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY,
97                                                             TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
98                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
99                                                             G_PARAM_CONSTRUCT_ONLY));
100         g_object_class_install_property (object_class,
101                                          PROP_SENDER,
102                                          g_param_spec_object ("sender",
103                                                               "Message Sender",
104                                                               "The sender of the message",
105                                                               EMPATHY_TYPE_CONTACT,
106                                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
107         g_object_class_install_property (object_class,
108                                          PROP_RECEIVER,
109                                          g_param_spec_object ("receiver",
110                                                               "Message Receiver",
111                                                               "The receiver of the message",
112                                                               EMPATHY_TYPE_CONTACT,
113                                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
114         g_object_class_install_property (object_class,
115                                          PROP_BODY,
116                                          g_param_spec_string ("body",
117                                                               "Message Body",
118                                                               "The content of the message",
119                                                               NULL,
120                                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
121                                                               G_PARAM_CONSTRUCT_ONLY));
122         g_object_class_install_property (object_class,
123                                          PROP_TIMESTAMP,
124                                          g_param_spec_int64 ("timestamp",
125                                                             "timestamp",
126                                                             "timestamp",
127                                                             G_MININT64, G_MAXINT64, 0,
128                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
129                                                             G_PARAM_CONSTRUCT_ONLY));
130         g_object_class_install_property (object_class,
131                                          PROP_IS_BACKLOG,
132                                          g_param_spec_boolean ("is-backlog",
133                                                                "History message",
134                                                                "If the message belongs to history",
135                                                                FALSE,
136                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
137                                                                G_PARAM_CONSTRUCT_ONLY));
138
139
140         g_object_class_install_property (object_class,
141                                          PROP_INCOMING,
142                                          g_param_spec_boolean ("incoming",
143                                                                "Incoming",
144                                                                "If this is an incoming (as opposed to sent) message",
145                                                                FALSE,
146                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
147                                                                G_PARAM_CONSTRUCT_ONLY));
148
149         g_object_class_install_property (object_class,
150                                          PROP_FLAGS,
151                                          g_param_spec_uint ("flags",
152                                                                "Flags",
153                                                                "The TpChannelTextMessageFlags of this message",
154                                                                0, G_MAXUINT, 0,
155                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
156                                                                G_PARAM_CONSTRUCT_ONLY));
157
158         g_object_class_install_property (object_class,
159                                          PROP_TP_MESSAGE,
160                                          g_param_spec_object ("tp-message",
161                                                                "TpMessage",
162                                                                "The TpMessage of this message",
163                                                                TP_TYPE_MESSAGE,
164                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
165                                                                G_PARAM_CONSTRUCT_ONLY));
166
167         g_type_class_add_private (object_class, sizeof (EmpathyMessagePriv));
168
169 }
170
171 static void
172 empathy_message_init (EmpathyMessage *message)
173 {
174         EmpathyMessagePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (message,
175                 EMPATHY_TYPE_MESSAGE, EmpathyMessagePriv);
176
177         message->priv = priv;
178         priv->timestamp = empathy_time_get_current ();
179 }
180
181 static void
182 empathy_message_finalize (GObject *object)
183 {
184         EmpathyMessagePriv *priv;
185
186         priv = GET_PRIV (object);
187
188         if (priv->sender) {
189                 g_object_unref (priv->sender);
190         }
191         if (priv->receiver) {
192                 g_object_unref (priv->receiver);
193         }
194
195         if (priv->tp_message) {
196                 g_object_unref (priv->tp_message);
197         }
198
199         g_free (priv->body);
200
201         G_OBJECT_CLASS (empathy_message_parent_class)->finalize (object);
202 }
203
204 static void
205 message_get_property (GObject    *object,
206                       guint       param_id,
207                       GValue     *value,
208                       GParamSpec *pspec)
209 {
210         EmpathyMessagePriv *priv;
211
212         priv = GET_PRIV (object);
213
214         switch (param_id) {
215         case PROP_TYPE:
216                 g_value_set_uint (value, priv->type);
217                 break;
218         case PROP_SENDER:
219                 g_value_set_object (value, priv->sender);
220                 break;
221         case PROP_RECEIVER:
222                 g_value_set_object (value, priv->receiver);
223                 break;
224         case PROP_BODY:
225                 g_value_set_string (value, priv->body);
226                 break;
227         case PROP_TIMESTAMP:
228                 g_value_set_int64 (value, priv->timestamp);
229                 break;
230         case PROP_IS_BACKLOG:
231                 g_value_set_boolean (value, priv->is_backlog);
232                 break;
233         case PROP_INCOMING:
234                 g_value_set_boolean (value, priv->incoming);
235                 break;
236         case PROP_FLAGS:
237                 g_value_set_uint (value, priv->flags);
238                 break;
239         case PROP_TP_MESSAGE:
240                 g_value_set_object (value, priv->tp_message);
241                 break;
242         default:
243                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
244                 break;
245         };
246 }
247
248 static void
249 message_set_property (GObject      *object,
250                       guint         param_id,
251                       const GValue *value,
252                       GParamSpec   *pspec)
253 {
254         EmpathyMessagePriv *priv;
255
256         priv = GET_PRIV (object);
257
258         switch (param_id) {
259         case PROP_TYPE:
260                 priv->type = g_value_get_uint (value);
261                 break;
262         case PROP_SENDER:
263                 empathy_message_set_sender (EMPATHY_MESSAGE (object),
264                                            EMPATHY_CONTACT (g_value_get_object (value)));
265                 break;
266         case PROP_RECEIVER:
267                 empathy_message_set_receiver (EMPATHY_MESSAGE (object),
268                                              EMPATHY_CONTACT (g_value_get_object (value)));
269                 break;
270         case PROP_BODY:
271                 g_assert (priv->body == NULL); /* construct only */
272                 priv->body = g_value_dup_string (value);
273                 break;
274         case PROP_TIMESTAMP:
275                 priv->timestamp = g_value_get_int64 (value);
276                 if (priv->timestamp <= 0)
277                         priv->timestamp = empathy_time_get_current ();
278                 break;
279         case PROP_IS_BACKLOG:
280                 priv->is_backlog = g_value_get_boolean (value);
281                 break;
282         case PROP_INCOMING:
283                 priv->incoming = g_value_get_boolean (value);
284                 break;
285         case PROP_FLAGS:
286                 priv->flags = g_value_get_uint (value);
287                 break;
288         case PROP_TP_MESSAGE:
289                 priv->tp_message = g_value_dup_object (value);
290                 break;
291         default:
292                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
293                 break;
294         };
295 }
296
297 EmpathyMessage *
298 empathy_message_from_tpl_log_event (TplEvent *logevent)
299 {
300         EmpathyMessage *retval = NULL;
301         TpAccountManager *acc_man = NULL;
302         TpAccount *account = NULL;
303         TplEntity *receiver = NULL;
304         TplEntity *sender = NULL;
305         gchar *body= NULL;
306         EmpathyContact *contact;
307
308         g_return_val_if_fail (TPL_IS_EVENT (logevent), NULL);
309
310         acc_man = tp_account_manager_dup ();
311         /* FIXME Currently Empathy shows in the log viewer only valid accounts, so it
312          * won't be selected any non-existing (ie removed) account.
313          * When #610455 will be fixed, calling tp_account_manager_ensure_account ()
314          * might add a not existing account to the AM. tp_account_new () probably
315          * will be the best way to handle it.
316          * Note: When creating an EmpathyContact from a TplEntity instance, the
317          * TpAccount is passed *only* to let EmpathyContact be able to retrieve the
318          * avatar (contact_get_avatar_filename () need a TpAccount).
319          * If the way EmpathyContact stores the avatar is changes, it might not be
320          * needed anymore any TpAccount passing and the following call will be
321          * useless */
322         account = tp_account_manager_ensure_account (acc_man,
323                         tpl_event_get_account_path (logevent));
324         g_object_unref (acc_man);
325
326         /* TODO Currently only TplTextEvent exists as subclass of TplEvent, in
327          * future more TplEvent will exist and EmpathyMessage should probably
328          * be enhanced to support other types of log entries (ie TplCallEvent).
329          *
330          * For now we just check (simply) that we are dealing with the only supported type,
331          * then there will be a if/then/else or switch handling all the supported
332          * cases.
333          */
334         if (!TPL_IS_TEXT_EVENT (logevent))
335                 return NULL;
336
337         body = g_strdup (tpl_text_event_get_message (
338                                 TPL_TEXT_EVENT (logevent)));
339         receiver = tpl_event_get_receiver (logevent);
340         sender = tpl_event_get_sender (logevent);
341
342         retval = g_object_new (EMPATHY_TYPE_MESSAGE,
343                 "type", tpl_text_event_get_message_type (TPL_TEXT_EVENT (logevent)),
344                 "body", body,
345                 "is-backlog", TRUE,
346                 "timestamp", tpl_event_get_timestamp (logevent),
347                 NULL);
348
349         if (receiver != NULL) {
350                 contact = empathy_contact_from_tpl_contact (account, receiver);
351                 empathy_message_set_receiver (retval, contact);
352                 g_object_unref (contact);
353         }
354
355         if (sender != NULL) {
356                 contact = empathy_contact_from_tpl_contact (account, sender);
357                 empathy_message_set_sender (retval, contact);
358                 g_object_unref (contact);
359         }
360
361         g_free (body);
362
363         return retval;
364 }
365
366 TpMessage *
367 empathy_message_get_tp_message (EmpathyMessage *message)
368 {
369         EmpathyMessagePriv *priv;
370
371         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), NULL);
372
373         priv = GET_PRIV (message);
374
375         return priv->tp_message;
376 }
377
378 TpChannelTextMessageType
379 empathy_message_get_tptype (EmpathyMessage *message)
380 {
381         EmpathyMessagePriv *priv;
382
383         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message),
384                               TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL);
385
386         priv = GET_PRIV (message);
387
388         return priv->type;
389 }
390
391 EmpathyContact *
392 empathy_message_get_sender (EmpathyMessage *message)
393 {
394         EmpathyMessagePriv *priv;
395
396         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), NULL);
397
398         priv = GET_PRIV (message);
399
400         return priv->sender;
401 }
402
403 void
404 empathy_message_set_sender (EmpathyMessage *message, EmpathyContact *contact)
405 {
406         EmpathyMessagePriv *priv;
407         EmpathyContact     *old_sender;
408
409         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
410         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
411
412         priv = GET_PRIV (message);
413
414         old_sender = priv->sender;
415         priv->sender = g_object_ref (contact);
416
417         if (old_sender) {
418                 g_object_unref (old_sender);
419         }
420
421         g_object_notify (G_OBJECT (message), "sender");
422 }
423
424 EmpathyContact *
425 empathy_message_get_receiver (EmpathyMessage *message)
426 {
427         EmpathyMessagePriv *priv;
428
429         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), NULL);
430
431         priv = GET_PRIV (message);
432
433         return priv->receiver;
434 }
435
436 void
437 empathy_message_set_receiver (EmpathyMessage *message, EmpathyContact *contact)
438 {
439         EmpathyMessagePriv *priv;
440         EmpathyContact     *old_receiver;
441
442         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
443         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
444
445         priv = GET_PRIV (message);
446
447         old_receiver = priv->receiver;
448         priv->receiver = g_object_ref (contact);
449
450         if (old_receiver) {
451                 g_object_unref (old_receiver);
452         }
453
454         g_object_notify (G_OBJECT (message), "receiver");
455 }
456
457 const gchar *
458 empathy_message_get_body (EmpathyMessage *message)
459 {
460         EmpathyMessagePriv *priv;
461
462         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), NULL);
463
464         priv = GET_PRIV (message);
465
466         return priv->body;
467 }
468
469 gint64
470 empathy_message_get_timestamp (EmpathyMessage *message)
471 {
472         EmpathyMessagePriv *priv;
473
474         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), -1);
475
476         priv = GET_PRIV (message);
477
478         return priv->timestamp;
479 }
480
481 gboolean
482 empathy_message_is_backlog (EmpathyMessage *message)
483 {
484         EmpathyMessagePriv *priv;
485
486         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
487
488         priv = GET_PRIV (message);
489
490         return priv->is_backlog;
491 }
492
493 #define IS_SEPARATOR(ch) (ch == ' ' || ch == ',' || ch == '.' || ch == ':')
494 gboolean
495 empathy_message_should_highlight (EmpathyMessage *message)
496 {
497         EmpathyContact *contact;
498         const gchar   *msg, *to;
499         gchar         *cf_msg, *cf_to;
500         gchar         *ch;
501         gboolean       ret_val;
502         TpChannelTextMessageFlags flags;
503
504         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
505
506         ret_val = FALSE;
507
508         msg = empathy_message_get_body (message);
509         if (!msg) {
510                 return FALSE;
511         }
512
513         contact = empathy_message_get_receiver (message);
514         if (!contact || !empathy_contact_is_user (contact)) {
515                 return FALSE;
516         }
517
518         to = empathy_contact_get_alias (contact);
519         if (!to) {
520                 return FALSE;
521         }
522
523         flags = empathy_message_get_flags (message);
524         if (flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK) {
525                 /* FIXME: Ideally we shouldn't highlight scrollback messages only if they
526                  * have already been received by the user before (and so are in the logs) */
527                 return FALSE;
528         }
529
530         cf_msg = g_utf8_casefold (msg, -1);
531         cf_to = g_utf8_casefold (to, -1);
532
533         ch = strstr (cf_msg, cf_to);
534         if (ch == NULL) {
535                 goto finished;
536         }
537         if (ch != cf_msg) {
538                 /* Not first in the message */
539                 if (!IS_SEPARATOR (*(ch - 1))) {
540                         goto finished;
541                 }
542         }
543
544         ch = ch + strlen (cf_to);
545         if (ch >= cf_msg + strlen (cf_msg)) {
546                 ret_val = TRUE;
547                 goto finished;
548         }
549
550         if (IS_SEPARATOR (*ch)) {
551                 ret_val = TRUE;
552                 goto finished;
553         }
554
555 finished:
556         g_free (cf_msg);
557         g_free (cf_to);
558
559         return ret_val;
560 }
561
562 TpChannelTextMessageType
563 empathy_message_type_from_str (const gchar *type_str)
564 {
565         if (strcmp (type_str, "normal") == 0) {
566                 return TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
567         }
568         if (strcmp (type_str, "action") == 0) {
569                 return TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION;
570         }
571         else if (strcmp (type_str, "notice") == 0) {
572                 return TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE;
573         }
574         else if (strcmp (type_str, "auto-reply") == 0) {
575                 return TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY;
576         }
577
578         return TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
579 }
580
581 const gchar *
582 empathy_message_type_to_str (TpChannelTextMessageType type)
583 {
584         switch (type) {
585         case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
586                 return "action";
587         case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
588                 return "notice";
589         case TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY:
590                 return "auto-reply";
591         case TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT:
592                 return "delivery-report";
593         case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
594         default:
595                 return "normal";
596         }
597 }
598
599 gboolean
600 empathy_message_is_incoming (EmpathyMessage *message)
601 {
602         EmpathyMessagePriv *priv = GET_PRIV (message);
603
604         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
605
606         return priv->incoming;
607 }
608
609 gboolean
610 empathy_message_equal (EmpathyMessage *message1, EmpathyMessage *message2)
611 {
612         EmpathyMessagePriv *priv1;
613         EmpathyMessagePriv *priv2;
614
615         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message1), FALSE);
616         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message2), FALSE);
617
618         priv1 = GET_PRIV (message1);
619         priv2 = GET_PRIV (message2);
620
621         if (priv1->timestamp == priv2->timestamp &&
622                         !tp_strdiff (priv1->body, priv2->body)) {
623                 return TRUE;
624         }
625
626         return FALSE;
627 }
628
629 TpChannelTextMessageFlags
630 empathy_message_get_flags (EmpathyMessage *self)
631 {
632         EmpathyMessagePriv *priv = GET_PRIV (self);
633
634         g_return_val_if_fail (EMPATHY_IS_MESSAGE (self), 0);
635
636         return priv->flags;
637 }
638
639 EmpathyMessage *
640 empathy_message_new_from_tp_message (TpMessage *tp_msg,
641                                      gboolean incoming)
642 {
643         EmpathyMessage *message;
644         gchar *body;
645         TpChannelTextMessageFlags flags;
646
647         g_return_val_if_fail (TP_IS_MESSAGE (tp_msg), NULL);
648
649         body = tp_message_to_text (tp_msg, &flags);
650
651         message = g_object_new (EMPATHY_TYPE_MESSAGE,
652                 "body", body,
653                 "type", tp_message_get_message_type (tp_msg),
654                 "timestamp", tp_message_get_received_timestamp (tp_msg),
655                 "flags", flags,
656                 "is-backlog", flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK,
657                 "incoming", incoming,
658                 "tp-message", tp_msg,
659                 NULL);
660
661         g_free (body);
662         return message;
663 }