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