1 /*
2  * Copyright (C) 2009-2011 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
19  *          Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
20  */
21 
22 #include "config.h"
23 #include "text-channel-internal.h"
24 
25 #include <glib.h>
26 #include <telepathy-glib/telepathy-glib.h>
27 
28 #include "action-chain-internal.h"
29 #include "entity-internal.h"
30 #include "event-internal.h"
31 #include "log-manager-internal.h"
32 #include "log-store-sqlite-internal.h"
33 #include "observer-internal.h"
34 #include "text-event.h"
35 #include "text-event-internal.h"
36 #include "util-internal.h"
37 
38 #define DEBUG_FLAG TPL_DEBUG_CHANNEL
39 #include "debug-internal.h"
40 
41 struct _TplTextChannelPriv
42 {
43   TpAccount *account;
44   TplEntity *self;
45   gboolean is_chatroom;
46   TplEntity *remote;
47 };
48 
G_DEFINE_TYPE(TplTextChannel,_tpl_text_channel,TP_TYPE_TEXT_CHANNEL)49 G_DEFINE_TYPE (TplTextChannel, _tpl_text_channel, TP_TYPE_TEXT_CHANNEL)
50 
51 
52 static void
53 get_my_contact (TplTextChannel *self)
54 {
55   TpChannel *chan = TP_CHANNEL (self);
56   TpConnection *tp_conn = tp_channel_borrow_connection (chan);
57   TpContact *my_contact;
58 
59   my_contact = tp_channel_group_get_self_contact (chan);
60   if (my_contact == 0)
61     my_contact = tp_connection_get_self_contact (tp_conn);
62 
63   self->priv->self = tpl_entity_new_from_tp_contact (my_contact,
64       TPL_ENTITY_SELF);
65 }
66 
67 
68 static void
get_remote_contact(TplTextChannel * self)69 get_remote_contact (TplTextChannel *self)
70 {
71   TpChannel *chan = TP_CHANNEL (self);
72   TpContact *contact;
73 
74   contact = tp_channel_get_target_contact (chan);
75 
76   if (contact == NULL)
77     {
78       self->priv->is_chatroom = TRUE;
79       self->priv->remote =
80         tpl_entity_new_from_room_id (tp_channel_get_identifier (chan));
81 
82       PATH_DEBUG (self, "Chatroom id: %s",
83           tpl_entity_get_identifier (self->priv->remote));
84     }
85   else
86     {
87       self->priv->remote =
88         tpl_entity_new_from_tp_contact (contact, TPL_ENTITY_CONTACT);
89     }
90 }
91 
92 
93 static void
on_channel_invalidated_cb(TpProxy * proxy,guint domain,gint code,gchar * message,gpointer user_data)94 on_channel_invalidated_cb (TpProxy *proxy,
95     guint domain,
96     gint code,
97     gchar *message,
98     gpointer user_data)
99 {
100   TpChannel *chan = TP_CHANNEL (user_data);
101   TplObserver *observer = _tpl_observer_dup (NULL);
102 
103   g_return_if_fail (observer);
104 
105   PATH_DEBUG (chan, "%s #%d %s",
106       g_quark_to_string (domain), code, message);
107 
108   if (!_tpl_observer_unregister_channel (observer, chan))
109     PATH_DEBUG (chan, "Channel couldn't be unregistered correctly (BUG?)");
110 
111   g_object_unref (observer);
112 }
113 
114 
115 static guint
get_message_pending_id(TpMessage * m)116 get_message_pending_id (TpMessage *m)
117 {
118   return tp_asv_get_uint32 (tp_message_peek (TP_MESSAGE (m), 0),
119       "pending-message-id", NULL);
120 }
121 
122 
123 static gint64
get_original_message_timestamp(TpMessage * message)124 get_original_message_timestamp (TpMessage *message)
125 {
126   gint64 timestamp;
127 
128   timestamp = tp_asv_get_int64 (tp_message_peek (message, 0),
129       "original-message-sent", NULL);
130 
131   if (timestamp == 0)
132     timestamp = tp_asv_get_int64 (tp_message_peek (message, 0),
133         "original-message-received", NULL);
134 
135   return timestamp;
136 }
137 
138 
139 static gint64
get_network_timestamp(TpMessage * message)140 get_network_timestamp (TpMessage *message)
141 {
142   GDateTime *datetime = g_date_time_new_now_utc ();
143   gint64 now = g_date_time_to_unix (datetime);
144   gint64 timestamp;
145 
146   timestamp = tp_message_get_sent_timestamp (message);
147 
148   if (timestamp == 0)
149     timestamp = tp_message_get_received_timestamp (message);
150 
151   if (timestamp == 0)
152     {
153       DEBUG ("TpMessage is not timestamped. Using current time instead.");
154       timestamp = now;
155     }
156 
157   if (timestamp - now > 60 * 60)
158     DEBUG ("timestamp is more than an hour in the future.");
159   else  if (now - timestamp > 60 * 60)
160     DEBUG ("timestamp is more than an hour in the past.");
161 
162   g_date_time_unref (datetime);
163 
164   return timestamp;
165 }
166 
167 
168 static gint64
get_message_edit_timestamp(TpMessage * message)169 get_message_edit_timestamp (TpMessage *message)
170 {
171   if (tp_message_get_supersedes (message) != NULL)
172     return get_network_timestamp (message);
173   else
174     return 0;
175 }
176 
177 
178 static gint64
get_message_timestamp(TpMessage * message)179 get_message_timestamp (TpMessage *message)
180 {
181   gint64 timestamp;
182 
183   timestamp = get_original_message_timestamp (message);
184 
185   if (timestamp == 0)
186     timestamp = get_network_timestamp (message);
187 
188   return timestamp;
189 }
190 
191 
192 static void
tpl_text_channel_store_message(TplTextChannel * self,TpMessage * message,TplEntity * sender,TplEntity * receiver)193 tpl_text_channel_store_message (TplTextChannel *self,
194     TpMessage *message,
195     TplEntity *sender,
196     TplEntity *receiver)
197 {
198   TplTextChannelPriv *priv = self->priv;
199   const gchar *direction;
200   TpChannelTextMessageType type;
201   gint64 timestamp;
202   gchar *text;
203   TplTextEvent *event;
204   TplLogManager *logmanager;
205   GError *error = NULL;
206 
207   if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
208     direction = "sent";
209   else
210     direction = "received";
211 
212   if (tp_message_is_scrollback (message))
213     {
214       DEBUG ("Ignoring %s scrollback message.", direction);
215       return;
216     }
217 
218   if (tp_message_is_rescued (message))
219     {
220       DEBUG ("Ignoring %s rescued message.", direction);
221       return;
222     }
223 
224   type = tp_message_get_message_type (message);
225 
226   if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT)
227     {
228       DEBUG ("Ignoring %s delivery report message.", direction);
229       return;
230     }
231 
232   /* Ensure timestamp */
233   timestamp = get_message_timestamp (message);
234 
235   text = tp_message_to_text (message, NULL);
236 
237   if (text == NULL)
238     {
239       DEBUG ("Ignoring %s message with no supported content", direction);
240       return;
241     }
242 
243   if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
244     DEBUG ("Logging message sent to %s (%s)",
245         tpl_entity_get_alias (receiver),
246         tpl_entity_get_identifier (receiver));
247   else
248     DEBUG ("Logging message received from %s (%s)",
249         tpl_entity_get_alias (sender),
250         tpl_entity_get_identifier (sender));
251 
252 
253   /* Initialise TplTextEvent */
254   event = g_object_new (TPL_TYPE_TEXT_EVENT,
255       /* TplEvent */
256       "account", priv->account,
257       "channel-path", tp_proxy_get_object_path (TP_PROXY (self)),
258       "receiver", receiver,
259       "sender", sender,
260       "timestamp", timestamp,
261       "message-token", tp_message_get_token (message),
262       "supersedes-token", tp_message_get_supersedes (message),
263       "edit-timestamp", get_message_edit_timestamp (message),
264       /* TplTextEvent */
265       "message-type", type,
266       "message", text,
267       NULL);
268 
269   /* Store sent event */
270   logmanager = tpl_log_manager_dup_singleton ();
271   _tpl_log_manager_add_event (logmanager, TPL_EVENT (event), &error);
272 
273   if (error != NULL)
274     {
275       PATH_DEBUG (self, "LogStore: %s", error->message);
276       g_error_free (error);
277     }
278   else if (tpl_entity_get_entity_type (sender) != TPL_ENTITY_SELF)
279     {
280       TplLogStore *cache = _tpl_log_store_sqlite_dup ();
281       _tpl_log_store_sqlite_add_pending_message (cache,
282           TP_CHANNEL (self),
283           get_message_pending_id (message),
284           timestamp,
285           &error);
286 
287       if (error != NULL)
288         {
289           PATH_DEBUG (self, "Failed to cache pending message: %s",
290               error->message);
291           g_error_free (error);
292         }
293     }
294 
295   g_object_unref (logmanager);
296   g_object_unref (event);
297   g_free (text);
298 }
299 
300 
301 static void
on_message_received_cb(TpTextChannel * text_chan,TpSignalledMessage * message,gpointer user_data)302 on_message_received_cb (TpTextChannel *text_chan,
303     TpSignalledMessage *message,
304     gpointer user_data)
305 {
306   TplTextChannel *self = TPL_TEXT_CHANNEL (text_chan);
307   TplTextChannelPriv *priv = self->priv;
308   TplEntity *receiver;
309   TplEntity *sender;
310 
311   if (priv->is_chatroom)
312     receiver = priv->remote;
313   else
314     receiver = priv->self;
315 
316   sender = tpl_entity_new_from_tp_contact (
317       tp_signalled_message_get_sender (TP_MESSAGE (message)),
318       TPL_ENTITY_CONTACT);
319 
320   tpl_text_channel_store_message (self, TP_MESSAGE (message),
321       sender, receiver);
322 
323   g_object_unref (sender);
324 }
325 
326 
327 static void
on_message_sent_cb(TpChannel * proxy,TpSignalledMessage * message,guint flags,const gchar * token,gpointer user_data,GObject * weak_object)328 on_message_sent_cb (TpChannel *proxy,
329     TpSignalledMessage *message,
330     guint flags,
331     const gchar *token,
332     gpointer user_data,
333     GObject *weak_object)
334 {
335   TplTextChannel *self = TPL_TEXT_CHANNEL (proxy);
336   TplTextChannelPriv *priv = self->priv;
337   TplEntity *sender;
338   TplEntity *receiver = priv->remote;
339 
340   if (tp_signalled_message_get_sender (TP_MESSAGE (message)) != NULL)
341     sender = tpl_entity_new_from_tp_contact (
342         tp_signalled_message_get_sender (TP_MESSAGE (message)),
343         TPL_ENTITY_SELF);
344   else
345     sender = g_object_ref (priv->self);
346 
347   tpl_text_channel_store_message (self, TP_MESSAGE (message),
348       sender, receiver);
349 
350   g_object_unref (sender);
351 }
352 
353 
354 static void
on_pending_message_removed_cb(TpTextChannel * self,TpSignalledMessage * message,gpointer user_data)355 on_pending_message_removed_cb (TpTextChannel *self,
356     TpSignalledMessage *message,
357     gpointer user_data)
358 {
359   TplLogStore *cache;
360   GList *ids = NULL;
361   GError *error = NULL;
362 
363   ids = g_list_prepend (ids,
364       GUINT_TO_POINTER (get_message_pending_id (TP_MESSAGE (message))));
365 
366   cache = _tpl_log_store_sqlite_dup ();
367   _tpl_log_store_sqlite_remove_pending_messages (cache, TP_CHANNEL (self),
368       ids, &error);
369 
370   if (error != NULL)
371     {
372       PATH_DEBUG (self, "Failed to remove pending message from cache: %s",
373           error->message);
374       g_error_free (error);
375     }
376 
377   g_object_unref (cache);
378 }
379 
380 
381 static gint
pending_message_compare_id(TpSignalledMessage * m1,TpSignalledMessage * m2)382 pending_message_compare_id (TpSignalledMessage *m1,
383     TpSignalledMessage *m2)
384 {
385   guint id1, id2;
386 
387   id1 = get_message_pending_id (TP_MESSAGE (m1));
388   id2 = get_message_pending_id (TP_MESSAGE (m2));
389 
390   if (id1 > id2)
391     return 1;
392   else if (id1 < id2)
393     return -1;
394   else
395     return 0;
396 }
397 
398 
399 static gint
pending_message_compare_timestamp(TpSignalledMessage * m1,TpSignalledMessage * m2)400 pending_message_compare_timestamp (TpSignalledMessage *m1,
401     TpSignalledMessage *m2)
402 {
403   gint64 ts1, ts2;
404 
405   ts1 = get_message_timestamp (TP_MESSAGE (m1));
406   ts2 = get_message_timestamp (TP_MESSAGE (m2));
407 
408   if (ts1 > ts2)
409     return 1;
410   else if (ts1 < ts2)
411     return -1;
412   else
413     return 0;
414 }
415 
416 
417 static void
store_pending_messages(TplTextChannel * self)418 store_pending_messages (TplTextChannel *self)
419 {
420   TplLogStore *cache;
421   GError *error = NULL;
422   GList *cached_messages;
423   GList *pending_messages;
424   GList *cached_it, *pending_it;
425   GList *to_remove = NULL;
426   GList *to_log = NULL;
427 
428   cache = _tpl_log_store_sqlite_dup ();
429   cached_messages = _tpl_log_store_sqlite_get_pending_messages (cache,
430       TP_CHANNEL (self), &error);
431 
432   if (error != NULL)
433     {
434       DEBUG ("Failed to read pending_message cache: %s.", error->message);
435       g_error_free (error);
436       /* We simply ignore this error, as if the list was empty */
437     }
438 
439   pending_messages =
440     tp_text_channel_get_pending_messages (TP_TEXT_CHANNEL (self));
441 
442   pending_messages = g_list_sort (pending_messages,
443       (GCompareFunc) pending_message_compare_id);
444 
445   cached_it = cached_messages;
446   pending_it = pending_messages;
447 
448   while (cached_it != NULL || pending_it != NULL)
449     {
450       TplPendingMessage *cached;
451       TpSignalledMessage *pending;
452       guint pending_id;
453       gint64 pending_ts;
454 
455       if (cached_it == NULL)
456         {
457           /* No more cached pending, just log the pending messages */
458           to_log = g_list_prepend (to_log, pending_it->data);
459           pending_it = g_list_next (pending_it);
460           continue;
461         }
462 
463       cached = cached_it->data;
464 
465       if (pending_it == NULL)
466         {
467           /* No more pending, just remove the cached messages */
468           to_remove = g_list_prepend (to_remove, GUINT_TO_POINTER (cached->id));
469           cached_it = g_list_next (cached_it);
470           continue;
471         }
472 
473       pending = pending_it->data;
474       pending_id = get_message_pending_id (TP_MESSAGE (pending));
475       pending_ts = get_message_timestamp (TP_MESSAGE (pending));
476 
477       if (cached->id == pending_id)
478         {
479           if (cached->timestamp != pending_ts)
480             {
481               /* The cache messaged is invalid, remove it */
482               to_remove = g_list_prepend (to_remove,
483                   GUINT_TO_POINTER (cached->id));
484               cached_it = g_list_next (cached_it);
485             }
486           else
487             {
488               /* The message is already logged */
489               cached_it = g_list_next (cached_it);
490               pending_it = g_list_next (pending_it);
491             }
492         }
493       else if (cached->id < pending_id)
494         {
495           /* The cached ID is not valid anymore, remove it */
496           to_remove = g_list_prepend (to_remove, GUINT_TO_POINTER (cached->id));
497           cached_it = g_list_next (cached_it);
498         }
499       else
500         {
501           /* The pending message has not been logged */
502           to_log = g_list_prepend (to_log, pending);
503           pending_it = g_list_next (pending_it);
504         }
505     }
506 
507   g_list_foreach (cached_messages, (GFunc) g_free, NULL);
508   g_list_free (cached_messages);
509   g_list_free (pending_messages);
510 
511 
512   /* We need to remove before we log to avoid collisions */
513   if (to_remove != NULL)
514     {
515       if (!_tpl_log_store_sqlite_remove_pending_messages (cache,
516             TP_CHANNEL (self), to_remove, &error))
517         {
518           DEBUG ("Failed remove old pending messages from cache: %s", error->message);
519           g_error_free (error);
520         }
521       g_list_free (to_remove);
522     }
523 
524   if (to_log != NULL)
525     {
526       GList *it;
527 
528       to_log = g_list_sort (to_log,
529           (GCompareFunc) pending_message_compare_timestamp);
530 
531       for (it = to_log; it != NULL; it = g_list_next (it))
532         on_message_received_cb (TP_TEXT_CHANNEL (self),
533             TP_SIGNALLED_MESSAGE (it->data), self);
534 
535       g_list_free (to_log);
536     }
537 
538   g_object_unref (cache);
539 }
540 
541 
542 static void
connect_message_signals(TplTextChannel * self)543 connect_message_signals (TplTextChannel *self)
544 {
545   tp_g_signal_connect_object (self, "invalidated",
546       G_CALLBACK (on_channel_invalidated_cb), self, 0);
547 
548   tp_g_signal_connect_object (self, "message-received",
549       G_CALLBACK (on_message_received_cb), self, 0);
550 
551   tp_g_signal_connect_object (self, "message-sent",
552       G_CALLBACK (on_message_sent_cb), self, 0);
553 
554   tp_g_signal_connect_object (self, "pending-message-removed",
555       G_CALLBACK (on_pending_message_removed_cb), self, 0);
556 }
557 
558 
559 static void
_tpl_text_channel_prepare_core_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)560 _tpl_text_channel_prepare_core_async (TpProxy *proxy,
561     const TpProxyFeature *feature,
562     GAsyncReadyCallback callback,
563     gpointer user_data)
564 {
565   TplTextChannel *self = (TplTextChannel *) proxy;
566 
567   if (!tp_proxy_has_interface_by_id (self,
568           TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES))
569     {
570       g_simple_async_report_error_in_idle ((GObject *) self,
571           callback, user_data, TPL_TEXT_CHANNEL_ERROR,
572           TPL_TEXT_CHANNEL_ERROR_NEED_MESSAGE_INTERFACE,
573           "The text channel does not implement Message interface.");
574       return;
575     }
576 
577   get_my_contact (self);
578   get_remote_contact (self);
579   store_pending_messages (self);
580   connect_message_signals (self);
581 
582   tp_simple_async_report_success_in_idle ((GObject *) self, callback, user_data,
583       _tpl_text_channel_prepare_core_async);
584 }
585 
586 
587 GQuark
_tpl_text_channel_get_feature_quark_core(void)588 _tpl_text_channel_get_feature_quark_core (void)
589 {
590   return g_quark_from_static_string ("tpl-text-channel-feature-core");
591 }
592 
593 enum {
594     FEAT_CORE,
595     N_FEAT
596 };
597 
598 static const TpProxyFeature *
tpl_text_channel_list_features(TpProxyClass * cls G_GNUC_UNUSED)599 tpl_text_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
600 {
601   static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
602   static GQuark depends_on[3] = { 0, 0, 0 };
603 
604   if (G_LIKELY (features[0].name != 0))
605     return features;
606 
607   features[FEAT_CORE].name = TPL_TEXT_CHANNEL_FEATURE_CORE;
608   features[FEAT_CORE].prepare_async = _tpl_text_channel_prepare_core_async;
609   depends_on[0] = TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES;
610   depends_on[1] = TP_CHANNEL_FEATURE_CONTACTS;
611   features[FEAT_CORE].depends_on = depends_on;
612 
613   /* assert that the terminator at the end is there */
614   g_assert (features[N_FEAT].name == 0);
615 
616   return features;
617 }
618 
619 static void
tpl_text_channel_dispose(GObject * obj)620 tpl_text_channel_dispose (GObject *obj)
621 {
622   TplTextChannelPriv *priv = TPL_TEXT_CHANNEL (obj)->priv;
623 
624   tp_clear_object (&priv->remote);
625   tp_clear_object (&priv->self);
626 
627   G_OBJECT_CLASS (_tpl_text_channel_parent_class)->dispose (obj);
628 }
629 
630 
631 static void
tpl_text_channel_finalize(GObject * obj)632 tpl_text_channel_finalize (GObject *obj)
633 {
634   PATH_DEBUG (obj, "finalizing channel %p", obj);
635 
636   G_OBJECT_CLASS (_tpl_text_channel_parent_class)->finalize (obj);
637 }
638 
639 
640 static void
_tpl_text_channel_class_init(TplTextChannelClass * klass)641 _tpl_text_channel_class_init (TplTextChannelClass *klass)
642 {
643   GObjectClass *object_class = G_OBJECT_CLASS (klass);
644   TpProxyClass *proxy_class = (TpProxyClass *) klass;
645 
646   object_class->dispose = tpl_text_channel_dispose;
647   object_class->finalize = tpl_text_channel_finalize;
648 
649   proxy_class->list_features = tpl_text_channel_list_features;
650 
651   g_type_class_add_private (object_class, sizeof (TplTextChannelPriv));
652 }
653 
654 
655 static void
_tpl_text_channel_init(TplTextChannel * self)656 _tpl_text_channel_init (TplTextChannel *self)
657 {
658   TplTextChannelPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
659       TPL_TYPE_TEXT_CHANNEL, TplTextChannelPriv);
660 
661   self->priv = priv;
662 }
663 
664 
665 /**
666  * _tpl_text_channel_new:
667  * @conn: TpConnection instance owning the channel
668  * @object_path: the channel's DBus path
669  * @tp_chan_props: channel's immutable properties, obtained for example by
670  * %tp_channel_borrow_immutable_properties()
671  * @error: location of the GError, used in case a problem is raised while
672  * creating the channel
673  *
674  * Convenience function to create a new TPL Channel Text proxy.
675  * The returned #TplTextChannel is not guaranteed to be ready at the point of
676  * return.
677  *
678  * TplTextChannel is actually a subclass of the abstract TplChannel which is a
679  * subclass of TpChannel.
680  * Use #TpChannel methods, casting the #TplTextChannel instance to a
681  * TpChannel, to access TpChannel data/methods from it.
682  *
683  * TplTextChannel is usually created using #tpl_channel_factory_build, from
684  * within a #TplObserver singleton, when its Observer_Channel method is called
685  * by the Channel Dispatcher.
686  *
687  * Returns: the TplTextChannel instance or %NULL if @object_path is not valid
688  */
689 TplTextChannel *
_tpl_text_channel_new(TpConnection * conn,const gchar * object_path,GHashTable * tp_chan_props,GError ** error)690 _tpl_text_channel_new (TpConnection *conn,
691     const gchar *object_path,
692     GHashTable *tp_chan_props,
693     GError **error)
694 {
695   return _tpl_text_channel_new_with_factory (NULL, conn, object_path,
696       tp_chan_props, error);
697 }
698 
699 TplTextChannel *
_tpl_text_channel_new_with_factory(TpSimpleClientFactory * factory,TpConnection * conn,const gchar * object_path,const GHashTable * tp_chan_props,GError ** error)700 _tpl_text_channel_new_with_factory (TpSimpleClientFactory *factory,
701     TpConnection *conn,
702     const gchar *object_path,
703     const GHashTable *tp_chan_props,
704     GError **error)
705 {
706   TpProxy *conn_proxy = TP_PROXY (conn);
707   TplTextChannel *self;
708 
709   /* Do what tpl_channel_new does + set TplTextChannel specific */
710 
711   g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
712   g_return_val_if_fail (!TPL_STR_EMPTY (object_path), NULL);
713   g_return_val_if_fail (tp_chan_props != NULL, NULL);
714 
715   if (!tp_dbus_check_valid_object_path (object_path, error))
716     return NULL;
717 
718   self = g_object_new (TPL_TYPE_TEXT_CHANNEL,
719       /* TpChannel properties */
720       "factory", factory,
721       "connection", conn,
722       "dbus-daemon", conn_proxy->dbus_daemon,
723       "bus-name", conn_proxy->bus_name,
724       "object-path", object_path,
725       "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
726       "channel-properties", tp_chan_props,
727       NULL);
728 
729   self->priv->account = g_object_ref (tp_connection_get_account (conn));
730 
731   return self;
732 }
733