927
1177
GroupDetails.ChangeReason.NONE);
932
private bool _add_persona (Persona p)
934
if (this._persona_set.add (p))
936
debug ("Add persona %p with uid %s", p, p.uid);
937
this._personas.set (p.iid, p);
944
private bool _remove_persona (Persona p)
946
if (this._persona_set.remove (p))
948
debug ("Remove persona %p with uid %s", p, p.uid);
949
this._personas.unset (p.iid);
950
if (this._self_persona == p)
952
this._self_persona = null;
961
private void _contact_weak_notify_cb (Object obj)
963
if (this._contact_persona_map == null)
968
Contact contact = obj as Contact;
969
debug ("Weak notify for TpContact %s", contact.get_identifier ());
971
Persona? persona = null;
972
this._contact_persona_map.unset (contact, out persona);
1180
this._cached = false;
1183
private void _self_handle_changed_cb (Object s, ParamSpec? p)
1185
var c = (Connection) s;
1187
/* Remove the old self persona */
1188
if (this._self_contact != null)
1189
this._ignore_by_handle (this._self_contact.handle, null, null, 0);
1191
if (c.self_handle == 0)
1193
/* We can only claim to have reached a quiescent state once we've
1194
* got the stored contact list and the self handle. */
1195
this._got_self_handle = true;
1196
this._notify_if_is_quiescent ();
1201
uint[] contact_handles = { c.self_handle };
1203
/* We have to do it this way instead of using
1204
* TpLowleve.get_contacts_by_handle_async() as we're in a notification
1206
c.get_contacts_by_handle (contact_handles,
1207
(uint[]) this._contact_features,
1208
(conn, contacts, failed, error, weak_object) =>
1213
/* Translators: the first parameter is a Telepathy handle,
1214
* and the second is an error message. */
1215
_("Failed to create contact for self handle '%u': %s"),
1216
conn.self_handle, error.message);
1220
debug ("Creating persona from self-handle");
1222
/* Add the local user */
1223
Contact contact = contacts[0];
1225
Persona persona = this._add_persona_from_contact (contact, false, out added);
1227
var personas = new HashSet<Persona> ();
1229
personas.add (persona);
1231
this._self_contact = contact;
1232
this._emit_personas_changed (personas, null);
1234
this._got_self_handle = true;
1235
this._notify_if_is_quiescent ();
1240
private void _new_group_channels_cb (TelepathyGLib.Channel? channel,
1241
GLib.AsyncResult? result)
1243
if (channel == null)
1245
/* Translators: do not translate "NewChannels", as it's a D-Bus
1247
warning (_("Error creating channel for NewChannels signal."));
1251
this._set_up_new_group_channel (channel);
1252
this._channel_group_changes_resolve (channel);
1255
private void _channel_group_changes_resolve (Channel channel)
1257
unowned string group = channel.get_identifier ();
1259
var change_maps = new HashMap<HashSet<Tpf.Persona>, bool> ();
1260
if (this._group_outgoing_adds[group] != null)
1261
change_maps.set (this._group_outgoing_adds[group], true);
1263
if (this._group_outgoing_removes[group] != null)
1264
change_maps.set (this._group_outgoing_removes[group], false);
1266
if (change_maps.size < 1)
1269
foreach (var entry in change_maps.entries)
1271
var changes = entry.key;
1273
foreach (var persona in changes)
1277
FolksTpLowlevel.channel_group_change_membership (channel,
1278
(Handle) persona.contact.handle, entry.value, null);
1280
catch (GLib.Error e)
1282
if (entry.value == true)
1284
/* Translators: the parameter is a persona identifier and
1285
* the second parameter is a group name. */
1286
warning (_("Failed to add Telepathy contact ‘%s’ to group ‘%s’."),
1287
persona.contact != null ?
1288
persona.contact.identifier :
1295
/* Translators: the parameter is a persona identifier
1296
* and the second parameter is a group name. */
1297
_("Failed to remove Telepathy contact ‘%s’ from group ‘%s’."),
1299
persona.contact != null ?
1300
persona.contact.identifier :
1311
private void _set_up_new_standard_channel (Channel channel)
1313
debug ("Setting up new standard channel '%s' for Tpf.PersonaStore " +
1314
"%p ('%s').", this, this.id, channel.get_identifier ());
1316
/* hold a ref to the channel here until it's ready, so it doesn't
1318
this._standard_channels_unready[channel.get_identifier ()] = channel;
1320
channel.notify["channel-ready"].connect ((s, p) =>
1322
var c = (Channel) s;
1323
unowned string name = c.get_identifier ();
1325
debug ("Channel '%s' is ready.", name);
1327
if (name == "publish")
1331
c.group_members_changed_detailed.connect (
1332
this._publish_channel_group_members_changed_detailed_cb);
1334
else if (name == "stored")
1338
c.group_members_changed_detailed.connect (
1339
this._stored_channel_group_members_changed_detailed_cb);
1341
else if (name == "subscribe")
1343
this._subscribe = c;
1345
c.group_members_changed_detailed.connect (
1346
this._subscribe_channel_group_members_changed_detailed_cb);
1348
c.group_flags_changed.connect (
1349
this._subscribe_channel_group_flags_changed_cb);
1351
this._subscribe_channel_group_flags_changed_cb (c,
1352
c.group_get_flags (), 0);
1355
this._standard_channels_unready.unset (name);
1357
c.invalidated.connect (this._channel_invalidated_cb);
1359
unowned Intset? members = c.group_get_members ();
1360
if (members != null && name == "stored")
1362
this._channel_group_pend_incoming_adds.begin (c,
1363
members.to_array (), true, (obj, res) =>
1365
this._channel_group_pend_incoming_adds.end (res);
1367
/* We've got some members for the stored channel group. */
1368
this._got_stored_channel_members = true;
1369
this._notify_if_is_quiescent ();
1372
else if (members != null)
1374
this._channel_group_pend_incoming_adds.begin (c,
1375
members.to_array (), true);
1380
private void _disconnect_from_standard_channel (Channel channel)
1382
var name = channel.get_identifier ();
1383
debug ("Disconnecting from channel '%s' for Tpf.PersonaStore %p ('%s').",
1384
name, this, this.id);
1386
channel.invalidated.disconnect (this._channel_invalidated_cb);
1388
if (name == "publish")
1390
channel.group_members_changed_detailed.disconnect (
1391
this._publish_channel_group_members_changed_detailed_cb);
1393
else if (name == "stored")
1395
channel.group_members_changed_detailed.disconnect (
1396
this._stored_channel_group_members_changed_detailed_cb);
1398
else if (name == "subscribe")
1400
channel.group_members_changed_detailed.disconnect (
1401
this._subscribe_channel_group_members_changed_detailed_cb);
1402
channel.group_flags_changed.disconnect (
1403
this._subscribe_channel_group_flags_changed_cb);
1407
private void _publish_channel_group_members_changed_detailed_cb (
1409
/* FIXME: Array<uint> => Array<Handle>; parser bug */
1411
Array<uint> removed,
1412
Array<uint> local_pending,
1413
Array<uint> remote_pending,
1416
if (added.length > 0)
1417
this._channel_group_pend_incoming_adds.begin (channel, added, true);
1419
/* we refuse to send these contacts our presence, so remove them */
1420
for (var i = 0; i < removed.length; i++)
1422
var handle = removed.index (i);
1423
this._ignore_by_handle_if_needed (handle, details);
1426
/* FIXME: continue for the other arrays */
1429
private void _stored_channel_group_members_changed_detailed_cb (
1431
/* FIXME: Array<uint> => Array<Handle>; parser bug */
1433
Array<uint> removed,
1434
Array<uint> local_pending,
1435
Array<uint> remote_pending,
1438
if (added.length > 0)
1440
this._channel_group_pend_incoming_adds.begin (channel, added, true,
1443
this._channel_group_pend_incoming_adds.end (res);
1445
/* We can only claim to have reached a quiescent state once we've
1446
* got the stored contact list and the self handle. */
1447
this._got_stored_channel_members = true;
1448
this._notify_if_is_quiescent ();
1452
for (var i = 0; i < removed.length; i++)
1454
var handle = removed.index (i);
1455
this._ignore_by_handle_if_needed (handle, details);
1459
private void _subscribe_channel_group_flags_changed_cb (
1464
this._update_capability ((ChannelGroupFlags) added,
1465
(ChannelGroupFlags) removed, ChannelGroupFlags.CAN_ADD,
1466
ref this._can_add_personas, "can-add-personas");
1468
this._update_capability ((ChannelGroupFlags) added,
1469
(ChannelGroupFlags) removed, ChannelGroupFlags.CAN_REMOVE,
1470
ref this._can_remove_personas, "can-remove-personas");
1473
private void _update_capability (
1474
ChannelGroupFlags added,
1475
ChannelGroupFlags removed,
1476
ChannelGroupFlags tp_flag,
1477
ref MaybeBool private_member,
1480
var new_value = private_member;
1482
if ((added & tp_flag) != 0)
1483
new_value = MaybeBool.TRUE;
1485
if ((removed & tp_flag) != 0)
1486
new_value = MaybeBool.FALSE;
1488
if (new_value != private_member)
1490
private_member = new_value;
1491
this.notify_property (prop_name);
1495
private void _subscribe_channel_group_members_changed_detailed_cb (
1497
/* FIXME: Array<uint> => Array<Handle>; parser bug */
1499
Array<uint> removed,
1500
Array<uint> local_pending,
1501
Array<uint> remote_pending,
1504
if (added.length > 0)
1506
this._channel_group_pend_incoming_adds.begin (channel, added, true);
1508
/* expose ourselves to anyone we can see */
1509
if (this._publish != null)
1511
this._channel_group_pend_incoming_adds.begin (this._publish,
1516
/* these contacts refused to send us their presence, so remove them */
1517
for (var i = 0; i < removed.length; i++)
1519
var handle = removed.index (i);
1520
this._ignore_by_handle_if_needed (handle, details);
1523
/* FIXME: continue for the other arrays */
1526
private void _channel_invalidated_cb (TelepathyGLib.Proxy proxy, uint domain,
1527
int code, string message)
1529
var channel = (Channel) proxy;
1531
this._channel_group_personas_map.unset (channel);
1532
this._channel_group_incoming_adds.unset (channel);
1534
if (proxy == this._publish)
1535
this._publish = null;
1536
else if (proxy == this._stored)
1537
this._stored = null;
1538
else if (proxy == this._subscribe)
1539
this._subscribe = null;
1542
var error = new GLib.Error ((Quark) domain, code, "%s", message);
1543
var name = channel.get_identifier ();
1544
this.group_removed (name, error);
1545
this._groups.unset (name);
1549
private void _ignore_by_handle_if_needed (uint handle,
1550
HashTable<string, HashTable<string, Value?>> details)
1552
unowned TelepathyGLib.Intset members;
1554
if (this._subscribe != null)
1556
members = this._subscribe.group_get_members ();
1557
if (members.is_member (handle))
1560
members = this._subscribe.group_get_remote_pending ();
1561
if (members.is_member (handle))
1565
if (this._publish != null)
1567
members = this._publish.group_get_members ();
1568
if (members.is_member (handle))
1572
unowned string message = TelepathyGLib.asv_get_string (details,
1575
Persona? actor = null;
1576
var actor_handle = TelepathyGLib.asv_get_uint32 (details, "actor",
1578
if (actor_handle > 0 && valid)
1579
actor = this._handle_persona_map[actor_handle];
1581
GroupDetails.ChangeReason reason = GroupDetails.ChangeReason.NONE;
1582
var tp_reason = TelepathyGLib.asv_get_uint32 (details, "change-reason",
1585
reason = Tpf.PersonaStore._change_reason_from_tp_reason (tp_reason);
1587
this._ignore_by_handle (handle, message, actor, reason);
1590
private static GroupDetails.ChangeReason _change_reason_from_tp_reason (
1593
return (GroupDetails.ChangeReason) reason;
1596
private void _ignore_by_handle (uint handle, string? message, Persona? actor,
1597
GroupDetails.ChangeReason reason)
1599
var persona = this._handle_persona_map[handle];
1601
debug ("Ignoring handle %u (persona: %p)", handle, persona);
1603
if (this._self_contact != null && this._self_contact.handle == handle)
1604
this._self_contact = null;
1607
* remove all handle-keyed entries
1609
this._handle_persona_map.unset (handle);
1611
/* skip _channel_group_incoming_adds because they occurred after removal
973
1614
if (persona == null)
978
if (this._remove_persona (persona))
980
/* This should never happen because TpConnection keeps a ref on
981
* self and roster TpContacts, so they should have been removed
982
* already. But deal with it just in case... */
983
warning ("A TpContact part of the ContactList is disposed");
984
var personas = new HashSet<Persona> ();
985
personas.add (persona);
986
this._emit_personas_changed (null, personas);
990
/* Ensure that we have a Persona wrapping this TpContact. This will keep the
991
* Persona internally only (won't emit personas_changed) and until the
992
* TpContact is destroyed (we keep only weak ref). */
993
internal Tpf.Persona _ensure_persona_for_contact (Contact contact)
995
Persona? persona = this._contact_persona_map[contact];
999
persona = new Tpf.Persona (contact, this);
1000
this._contact_persona_map[contact] = persona;
1001
contact.weak_ref (this._contact_weak_notify_cb);
1003
var is_favourite = this._favourite_ids.contains (contact.get_identifier ());
1004
persona._set_is_favourite (is_favourite);
1006
debug ("Persona %p with uid %s created for TpContact %s, favourite: %s",
1007
persona, persona.uid, contact.get_identifier (),
1008
is_favourite ? "yes" : "no");
1013
private void _self_contact_changed_cb (Object s, ParamSpec? p)
1015
var contact = this._conn.self_contact;
1017
var personas_added = new HashSet<Persona> ();
1018
var personas_removed = new HashSet<Persona> ();
1020
/* Remove old self persona if not also part of roster */
1021
if (this._self_persona != null &&
1022
!this._self_persona.is_in_contact_list &&
1023
this._remove_persona (this._self_persona))
1025
personas_removed.add (this._self_persona);
1027
this._self_persona = null;
1029
if (contact != null)
1031
/* Add the local user to roster */
1032
this._self_persona = this._ensure_persona_for_contact (contact);
1033
if (this._add_persona (this._self_persona))
1034
personas_added.add (this._self_persona);
1037
this._emit_personas_changed (personas_added, personas_removed);
1039
this._got_initial_self_contact = true;
1040
this._notify_if_is_quiescent ();
1043
private void _contact_list_state_changed_cb (Object s, ParamSpec? p)
1045
/* Once the contact list is downloaded from server, state moves to
1046
* SUCCESS and won't change anymore */
1047
if (this._conn.contact_list_state != ContactListState.SUCCESS)
1050
this._conn.contact_list_changed.connect (this._contact_list_changed_cb);
1051
this._contact_list_changed_cb (this._conn.dup_contact_list (),
1052
new GLib.GenericArray<TelepathyGLib.Contact> ());
1054
this._got_initial_members = true;
1055
this._populate_counters ();
1056
this._notify_if_is_quiescent ();
1059
private void _contact_list_changed_cb (GLib.GenericArray<TelepathyGLib.Contact> added,
1060
GLib.GenericArray<TelepathyGLib.Contact> removed)
1062
var personas_added = new HashSet<Persona> ();
1063
var personas_removed = new HashSet<Persona> ();
1065
debug ("contact list changed: %d added, %d removed",
1066
added.length, removed.length);
1068
foreach (Contact contact in added.data)
1070
var persona = this._ensure_persona_for_contact (contact);
1072
if (!persona.is_in_contact_list)
1074
persona.is_in_contact_list = true;
1077
if (this._add_persona (persona))
1079
personas_added.add (persona);
1083
foreach (Contact contact in removed.data)
1085
var persona = this._contact_persona_map[contact];
1087
if (persona == null)
1089
warning ("Unknown TpContact removed from ContactList: %s",
1090
contact.get_identifier ());
1094
/* If self contact was also part of the roster but got removed,
1095
* we keep it in our persona store, but with is_in_contact_list=false.
1096
* This matches behaviour of _self_contact_changed_cb() where we add
1097
* the self persona into the user-visible set even if it is not part
1099
if (persona == this._self_persona)
1101
persona.is_in_contact_list = false;
1105
if (this._remove_persona (persona))
1107
personas_removed.add (persona);
1111
this._emit_personas_changed (personas_added, personas_removed);
1617
/* If we hold a weak ref. on the persona's TpContact, release that. */
1618
if (persona.contact != null &&
1619
this._weakly_referenced_contacts.unset (persona.contact) == true)
1621
persona.contact.weak_unref (this._contact_weak_notify_cb);
1625
* remove all persona-keyed entries
1627
foreach (var channel in this._channel_group_personas_map.keys)
1629
var members = this._channel_group_personas_map[channel];
1630
if (members != null)
1631
members.remove (persona);
1634
foreach (var name in this._group_outgoing_adds.keys)
1636
var members = this._group_outgoing_adds[name];
1637
if (members != null)
1638
members.remove (persona);
1641
var personas = new HashSet<Persona> ();
1642
personas.add (persona);
1644
this._emit_personas_changed (null, personas, message, actor, reason);
1645
this._personas.unset (persona.iid);
1646
this._persona_set.remove (persona);
1115
1650
* Remove a {@link Persona} from the PersonaStore.
1117
1652
* See {@link Folks.PersonaStore.remove_persona}.
1119
* @throws Folks.PersonaStoreError.UNSUPPORTED_ON_USER if `persona` is the
1120
* local user — removing the local user isn’t supported
1121
* @throws Folks.PersonaStoreError.REMOVE_FAILED if removing the contact
1124
1654
public override async void remove_persona (Folks.Persona persona)
1125
1655
throws Folks.PersonaStoreError
1127
1657
var tp_persona = (Tpf.Persona) persona;
1129
if (tp_persona.contact == null)
1131
warning ("Skipping server-side removal of Tpf.Persona %p because " +
1132
"it has no attached TpContact", tp_persona);
1136
if (persona == this._self_persona &&
1659
if (tp_persona.contact == this._self_contact &&
1137
1660
tp_persona.is_in_contact_list == false)
1139
1662
throw new PersonaStoreError.UNSUPPORTED_ON_USER (
1140
1663
_("Telepathy contacts representing the local user may not be removed."));
1145
yield tp_persona.contact.remove_async ();
1147
catch (GLib.Error e)
1149
/* Translators: the parameter is an error message. */
1150
throw new PersonaStoreError.REMOVE_FAILED (
1151
_("Failed to remove a persona from store: %s"), e.message);
1155
private async Persona _ensure_persona_for_id (string contact_id)
1158
var contact = yield this._conn.dup_contact_by_id_async (contact_id, {});
1159
return this._ensure_persona_for_contact (contact);
1666
if (tp_persona.contact == null)
1668
warning ("Skipping server-side removal of Tpf.Persona %p because " +
1669
"it has no attached TpContact", tp_persona);
1675
FolksTpLowlevel.channel_group_change_membership (this._stored,
1676
(Handle) tp_persona.contact.handle, false, null);
1678
catch (GLib.Error e1)
1681
/* Translators: The first parameter is a contact identifier, the
1682
* second is a contact list identifier and the third is an error
1684
_("Failed to remove Telepathy contact ‘%s’ from ‘%s’ list: %s"),
1685
tp_persona.contact.identifier, "stored", e1.message);
1690
FolksTpLowlevel.channel_group_change_membership (this._subscribe,
1691
(Handle) tp_persona.contact.handle, false, null);
1693
catch (GLib.Error e2)
1696
/* Translators: The first parameter is a contact identifier, the
1697
* second is a contact list identifier and the third is an error
1699
_("Failed to remove Telepathy contact ‘%s’ from ‘%s’ list: %s"),
1700
tp_persona.contact.identifier, "subscribe", e2.message);
1705
FolksTpLowlevel.channel_group_change_membership (this._publish,
1706
(Handle) tp_persona.contact.handle, false, null);
1708
catch (GLib.Error e3)
1711
/* Translators: The first parameter is a contact identifier, the
1712
* second is a contact list identifier and the third is an error
1714
_("Failed to remove Telepathy contact ‘%s’ from ‘%s’ list: %s"),
1715
tp_persona.contact.identifier, "publish", e3.message);
1718
/* the contact will be actually removed (and signaled) when we hear back
1719
* from the server */
1722
/* Only non-group contact list channels should use create_personas == true,
1723
* since the exposed set of Personas are meant to be filtered by them */
1724
private async void _channel_group_pend_incoming_adds (Channel channel,
1726
bool create_personas)
1728
var adds_length = adds != null ? adds.length : 0;
1729
if (adds_length >= 1)
1731
if (create_personas)
1733
yield this._create_personas_from_channel_handles_async (channel,
1737
for (var i = 0; i < adds.length; i++)
1739
var channel_handle = (Handle) adds.index (i);
1740
var contact_handle = channel.group_get_handle_owner (
1743
HashSet<uint>? contact_handles =
1744
this._channel_group_incoming_adds[channel];
1745
if (contact_handles == null)
1747
contact_handles = new HashSet<uint> ();
1748
this._channel_group_incoming_adds[channel] =
1751
contact_handles.add (contact_handle);
1755
this._channel_groups_add_new_personas ();
1758
private void _channel_group_handle_incoming_removes (Channel channel,
1759
Array<uint> removes)
1761
var removes_length = removes != null ? removes.length : 0;
1762
if (removes_length >= 1)
1764
var members_removed = new GLib.List<Persona> ();
1766
HashSet<Persona> members = this._channel_group_personas_map[channel];
1767
if (members == null)
1768
members = new HashSet<Persona> ();
1770
for (var i = 0; i < removes.length; i++)
1772
var channel_handle = (Handle) removes.index (i);
1773
var contact_handle = channel.group_get_handle_owner (
1775
var persona = this._handle_persona_map[contact_handle];
1778
if (persona != null)
1780
members.remove (persona);
1781
members_removed.prepend (persona);
1785
this._channel_group_personas_map[channel] = members;
1787
var name = channel.get_identifier ();
1788
if (this._group_is_display_group (name) &&
1789
members_removed.length () > 0)
1791
members_removed.reverse ();
1792
this.group_members_changed (name, null, members_removed);
1797
private void _set_up_new_group_channel (Channel channel)
1799
/* hold a ref to the channel here until it's ready, so it doesn't
1801
this._group_channels_unready[channel.get_identifier ()] = channel;
1803
channel.notify["channel-ready"].connect ((s, p) =>
1805
var c = (Channel) s;
1806
var name = c.get_identifier ();
1808
var existing_channel = this._groups[name];
1809
if (existing_channel != null)
1811
/* Somehow, this group channel has already been set up. We have to
1812
* hold a reference to the existing group while unsetting it in
1813
* the group map so that unsetting it doesn't cause it to be
1814
* destroyed. If that were to happen, channel_invalidated_cb()
1815
* would remove it from the group map a second time, causing a
1817
existing_channel.ref ();
1818
this._groups.unset (name);
1819
existing_channel.unref ();
1822
/* Drop all references before we set the new channel */
1823
existing_channel = null;
1825
this._groups[name] = c;
1826
this._group_channels_unready.unset (name);
1828
c.invalidated.connect (this._channel_invalidated_cb);
1829
c.group_members_changed_detailed.connect (
1830
this._channel_group_members_changed_detailed_cb);
1832
unowned Intset members = c.group_get_members ();
1833
if (members != null)
1835
this._channel_group_pend_incoming_adds.begin (c,
1836
members.to_array (), false);
1841
private void _disconnect_from_group_channel (Channel channel)
1843
var name = channel.get_identifier ();
1844
debug ("Disconnecting from group channel '%s'.", name);
1846
channel.group_members_changed_detailed.disconnect (
1847
this._channel_group_members_changed_detailed_cb);
1848
channel.invalidated.disconnect (this._channel_invalidated_cb);
1851
private void _channel_group_members_changed_detailed_cb (Channel channel,
1852
/* FIXME: Array<uint> => Array<Handle>; parser bug */
1854
Array<uint> removed,
1855
Array<uint> local_pending,
1856
Array<uint> remote_pending,
1860
this._channel_group_pend_incoming_adds.begin (channel, added, false);
1862
if (removed != null)
1864
this._channel_group_handle_incoming_removes (channel, removed);
1867
/* FIXME: continue for the other arrays */
1870
internal async void _change_group_membership (Folks.Persona persona,
1871
string group, bool is_member)
1873
var tp_persona = (Tpf.Persona) persona;
1874
var channel = this._groups[group];
1875
var change_map = is_member ? this._group_outgoing_adds :
1876
this._group_outgoing_removes;
1877
var change_set = change_map[group];
1879
if (change_set == null)
1881
change_set = new HashSet<Tpf.Persona> ();
1882
change_map[group] = change_set;
1884
change_set.add (tp_persona);
1886
if (channel == null)
1888
/* the changes queued above will be resolve in the NewChannels handler
1890
FolksTpLowlevel.connection_create_group_async (this.account.connection,
1895
/* the channel is already ready, so resolve immediately */
1896
this._channel_group_changes_resolve (channel);
1900
private void _change_standard_contact_list_membership (
1901
TelepathyGLib.Channel channel, Folks.Persona persona, bool is_member,
1904
var tp_persona = (Tpf.Persona) persona;
1906
if (tp_persona.contact == null)
1908
warning ("Skipping Tpf.Persona %p contact list change because it " +
1909
"has no attached TpContact", tp_persona);
1915
FolksTpLowlevel.channel_group_change_membership (channel,
1916
(Handle) tp_persona.contact.handle, is_member, message);
1918
catch (GLib.Error e)
1920
if (is_member == true)
1923
/* Translators: The first parameter is a contact identifier,
1924
* the second is a contact list identifier and the third is an
1926
_("Failed to add Telepathy contact ‘%s’ to ‘%s’ list: %s"),
1927
tp_persona.contact.identifier, channel.get_identifier (),
1933
/* Translators: The first parameter is a contact identifier,
1934
* the second is a contact list identifier and the third is an
1936
_("Failed to remove Telepathy contact ‘%s’ from ‘%s’ list: %s"),
1937
tp_persona.contact.identifier, channel.get_identifier (),
1943
private async Channel? _add_standard_channel (Connection conn, string name)
1945
Channel? channel = null;
1947
debug ("Adding standard channel '%s' to connection %p for " +
1948
"Tpf.PersonaStore %p ('%s').", name, conn, this, this.id);
1950
/* FIXME: handle the error GLib.Error from this function */
1954
yield FolksTpLowlevel.connection_open_contact_list_channel_async (
1957
catch (GLib.Error e)
1959
debug ("Failed to add channel '%s': %s", name, e.message);
1961
/* If the Connection doesn't support 'stored' channels we
1962
* pretend we've received the stored channel members.
1964
* When this happens it probably means the ConnectionManager doesn't
1965
* implement the Channel.Type.ContactList interface.
1967
* See: https://bugzilla.gnome.org/show_bug.cgi?id=656184 */
1968
this._got_stored_channel_members = true;
1969
this._notify_if_is_quiescent ();
1971
/* XXX: assuming there's no decent way to recover from this */
1976
this._set_up_new_standard_channel (channel);
1981
/* FIXME: Array<uint> => Array<Handle>; parser bug */
1982
private async void _create_personas_from_channel_handles_async (
1984
Array<uint> channel_handles)
1986
uint[] contact_handles = {};
1987
for (var i = 0; i < channel_handles.length; i++)
1989
var channel_handle = (Handle) channel_handles.index (i);
1990
var contact_handle = channel.group_get_handle_owner (channel_handle);
1991
Persona? persona = this._handle_persona_map[contact_handle];
1993
if (persona == null)
1995
contact_handles += contact_handle;
1999
/* Mark the persona as having been seen in the contact list.
2000
* The persona might have originally been discovered by querying
2001
* the Telepathy connection's self-handle; in this case, its
2002
* is-in-contact-list property will originally be false, as a
2003
* contact could be exposed as the self-handle, but not actually
2004
* be in the user's contact list. */
2005
debug ("Setting is-in-contact-list for '%s' to true",
2007
persona.is_in_contact_list = true;
2013
if (contact_handles.length < 1)
2016
GLib.List<TelepathyGLib.Contact> contacts =
2017
yield FolksTpLowlevel.connection_get_contacts_by_handle_async (
2018
this._conn, contact_handles, (uint[]) _contact_features);
2020
if (contacts == null || contacts.length () < 1)
2023
var contacts_array = new TelepathyGLib.Contact[contacts.length ()];
2025
unowned GLib.List<TelepathyGLib.Contact> l = contacts;
2026
for (; l != null; l = l.next)
2028
contacts_array[j] = l.data;
2032
this._add_new_personas_from_contacts (contacts_array);
2034
catch (GLib.Error e)
2037
/* Translators: the first parameter is a channel identifier and
2038
* the second is an error message.. */
2039
_("Failed to create incoming Telepathy contacts from channel ‘%s’: %s"),
2040
channel.get_identifier (), e.message);
2044
private async HashSet<Persona> _ensure_personas_from_contact_ids (
2045
string[] contact_ids) throws GLib.Error
2047
var personas = new HashSet<Persona> ();
2048
var personas_added = new HashSet<Persona> ();
2050
if (contact_ids.length == 0)
2053
GLib.List<TelepathyGLib.Contact> contacts =
2054
yield FolksTpLowlevel.connection_get_contacts_by_id_async (
2055
this._conn, contact_ids, (uint[]) _contact_features);
2057
unowned GLib.List<TelepathyGLib.Contact> l;
2058
for (l = contacts; l != null; l = l.next)
2060
var contact = l.data;
2062
debug ("Creating persona from contact '%s'", contact.identifier);
2065
var persona = this._add_persona_from_contact (contact, true, out added);
2067
personas_added.add (persona);
2069
personas.add (persona);
2072
if (personas_added.size > 0)
2074
this._emit_personas_changed (personas_added, null);
2080
private void _contact_weak_notify_cb (Object obj)
2082
var c = obj as Contact;
2083
if (this._weakly_referenced_contacts != null)
2085
Handle handle = this._weakly_referenced_contacts.get (c);
2086
this._weakly_referenced_contacts.unset (c);
2090
this._ignore_by_handle ((!) handle, null, null,
2091
GroupDetails.ChangeReason.NONE);
2096
internal Tpf.Persona? _ensure_persona_from_contact (Contact contact)
2098
uint handle = contact.get_handle ();
2100
debug ("Ensuring contact %p (handle: %u) exists in Tpf.PersonaStore " +
2101
"%p ('%s').", contact, handle, this, this.id);
2108
/* If the persona already exists, return them. */
2109
var persona = this._handle_persona_map[handle];
2111
if (persona != null)
2116
/* Otherwise, add the persona to the store. See bgo#665376 for details of
2117
* why this is necessary. Since the TpContact is coming from a source
2118
* other than the TpChannels which are associated with this store, we only
2119
* hold a weak reference to it and remove it from the store as soon as
2120
* it's destroyed. */
2122
persona = this._add_persona_from_contact (contact, false, out added);
2129
/* Weak ref. on the contact. */
2130
contact.weak_ref (this._contact_weak_notify_cb);
2131
this._weakly_referenced_contacts.set (contact, handle);
2133
/* Signal the addition of the new persona. */
2134
var personas = new HashSet<Persona> ();
2135
this._emit_personas_changed (personas, null);
2140
private Tpf.Persona? _add_persona_from_contact (Contact contact,
2141
bool from_contact_list,
2144
var h = contact.get_handle ();
2145
Persona? persona = null;
2147
debug ("Adding persona from contact '%s'", contact.identifier);
2149
persona = this._handle_persona_map[h];
2150
if (persona == null)
2152
persona = new Tpf.Persona (contact, this);
2154
this._personas.set (persona.iid, persona);
2155
this._persona_set.add (persona);
2156
this._handle_persona_map[h] = persona;
2158
/* If the handle is a favourite, ensure the persona's marked
2159
* as such. This deals with the case where we receive a
2160
* contact _after_ we've discovered that they're a
2162
persona.is_favourite = this._favourite_handles.contains (h);
2164
/* Only emit this debug message in the false case to reduce debug
2165
* spam (see https://bugzilla.gnome.org/show_bug.cgi?id=640901#c2). */
2166
if (from_contact_list == false)
2168
debug (" Setting is-in-contact-list to false");
2171
persona.is_in_contact_list = from_contact_list;
2177
debug (" ...already exists.");
2179
/* Mark the persona as having been seen in the contact list.
2180
* The persona might have originally been discovered by querying
2181
* the Telepathy connection's self-handle; in this case, its
2182
* is-in-contact-list property will originally be false, as a
2183
* contact could be exposed as the self-handle, but not actually
2184
* be in the user's contact list. */
2185
if (persona.is_in_contact_list == false && from_contact_list == true)
2187
debug (" Setting is-in-contact-list to true");
2188
persona.is_in_contact_list = true;
2196
private void _add_new_personas_from_contacts (Contact[] contacts)
2198
var personas = new HashSet<Persona> ();
2200
foreach (Contact contact in contacts)
2203
var persona = this._add_persona_from_contact (contact, true, out added);
2205
personas.add (persona);
2208
this._channel_groups_add_new_personas ();
2210
if (personas.size > 0)
2212
this._emit_personas_changed (personas, null);
2216
private void _channel_groups_add_new_personas ()
2218
foreach (var entry in this._channel_group_incoming_adds.entries)
2220
var channel = (Channel) entry.key;
2221
var members_added = new GLib.List<Persona> ();
2223
HashSet<Persona> members = this._channel_group_personas_map[channel];
2224
if (members == null)
2225
members = new HashSet<Persona> ();
2227
debug ("Adding members to channel '%s':", channel.get_identifier ());
2229
var contact_handles = entry.value;
2230
if (contact_handles != null && contact_handles.size > 0)
2232
var contact_handles_added = new HashSet<uint> ();
2233
foreach (var contact_handle in contact_handles)
2235
var persona = this._handle_persona_map[contact_handle];
2236
if (persona != null)
2238
debug (" %s", persona.uid);
2239
members.add (persona);
2240
members_added.prepend (persona);
2241
contact_handles_added.add (contact_handle);
2245
foreach (var handle in contact_handles_added)
2246
contact_handles.remove (handle);
2249
if (members.size > 0)
2250
this._channel_group_personas_map[channel] = members;
2252
var name = channel.get_identifier ();
2253
if (this._group_is_display_group (name) &&
2254
members_added.length () > 0)
2256
members_added.reverse ();
2257
this.group_members_changed (name, members_added, null);
2262
private bool _group_is_display_group (string group)
2264
for (var i = 0; i < this._undisplayed_groups.length; i++)
2266
if (this._undisplayed_groups[i] == group)
1163
2274
* Add a new {@link Persona} to the PersonaStore.
1165
2276
* See {@link Folks.PersonaStore.add_persona_from_details}.
1167
* @throws Folks.PersonaStoreError.INVALID_ARGUMENT if the `contact` key was
1168
* not provided in `details`
1169
* @throws Folks.PersonaStoreError.STORE_OFFLINE if the CM is offline
1170
* @throws Folks.PersonaStoreError.CREATE_FAILED if adding the contact failed
1172
2278
public override async Folks.Persona? add_persona_from_details (
1173
2279
HashTable<string, Value?> details) throws Folks.PersonaStoreError