/*
* Copyright (C) 2010 Collabora Ltd.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors:
* Travis Reitter
*/
using Gee;
using GLib;
using TelepathyGLib;
using Folks;
public errordomain Tpf.PersonaError
{
INVALID_ARGUMENT
}
/**
* A persona subclass which represents a single instant messaging contact from
* Telepathy.
*/
public class Tpf.Persona : Folks.Persona,
Alias,
Avatar,
Folks.Capabilities,
Favourite,
Groups,
Presence
{
private HashTable _groups;
private bool _is_favourite;
private string _alias;
/* Whether we've finished being constructed; this is used to prevent
* unnecessary trips to the Telepathy service to tell it about properties
* being set which are actually just being set from data it's just given us.
*/
private bool is_constructed = false;
/**
* {@inheritDoc}
*/
public File avatar { get; set; }
/**
* {@inheritDoc}
*/
public CapabilitiesFlags capabilities { get; private set; }
/**
* {@inheritDoc}
*/
public Folks.PresenceType presence_type { get; private set; }
/**
* {@inheritDoc}
*/
public string presence_message { get; private set; }
/**
* {@inheritDoc}
*/
public string alias
{
get { return this._alias; }
set
{
if (this._alias == value)
return;
if (this.is_constructed)
((Tpf.PersonaStore) this.store).change_alias (this, value);
this._alias = value;
}
}
/**
* {@inheritDoc}
*/
public bool is_favourite
{
get { return this._is_favourite; }
set
{
if (this._is_favourite == value)
return;
if (this.is_constructed)
((Tpf.PersonaStore) this.store).change_is_favourite (this, value);
this._is_favourite = value;
}
}
/**
* {@inheritDoc}
*/
public HashTable groups
{
get { return this._groups; }
set
{
value.foreach ((k, v) =>
{
var group = (string) k;
if (this._groups.lookup (group) == false)
this._change_group (group, true);
});
this._groups.foreach ((k, v) =>
{
var group = (string) k;
if (value.lookup (group) == false)
this._change_group (group, true);
});
}
}
/**
* {@inheritDoc}
*/
public void change_group (string group, bool is_member)
{
if (_change_group (group, is_member))
{
((Tpf.PersonaStore) this.store).change_group_membership (this, group,
is_member);
this.group_changed (group, is_member);
}
}
private bool _change_group (string group, bool is_member)
{
bool changed = false;
if (is_member)
{
if (this._groups.lookup (group) != true)
{
this._groups.insert (group, true);
changed = true;
}
}
else
changed = this._groups.remove (group);
return changed;
}
/**
* The Telepathy contact represented by this persona.
*/
public Contact contact { get; construct; }
/**
* Create a new persona.
*
* Create a new persona for the {@link PersonaStore} `store`, representing
* the Telepathy contact given by `contact`.
*/
public Persona (Contact contact, PersonaStore store) throws Tpf.PersonaError
{
/* FIXME: There is the possibility of a crash in the error condition below
* due to bgo#604299, where the C self variable isn't initialised until we
* chain up to the Object constructor, below. */
var uid = contact.get_identifier ();
if (uid == null || uid == "")
throw new Tpf.PersonaError.INVALID_ARGUMENT ("contact has an " +
"invalid UID");
var account = account_for_connection (contact.get_connection ());
var account_id = ((Proxy) account).object_path;
/* this isn't meant to convey any real information, so no need to escape
* existing delimiters */
var iid = "telepathy:" + account_id + ":" + uid;
var alias = contact.get_alias ();
if (alias == null || alias.strip () == "")
alias = uid;
Object (alias: alias,
contact: contact,
iid: iid,
uid: uid,
store: store);
this.is_constructed = true;
this._groups = new HashTable (str_hash, str_equal);
contact.notify["avatar-file"].connect ((s, p) =>
{
this.contact_notify_avatar ();
});
this.contact_notify_avatar ();
contact.notify["presence-message"].connect ((s, p) =>
{
this.contact_notify_presence_message ();
});
contact.notify["presence-type"].connect ((s, p) =>
{
this.contact_notify_presence_type ();
});
this.contact_notify_presence_message ();
this.contact_notify_presence_type ();
contact.notify["capabilities"].connect ((s, p) =>
{
this.contact_notify_capabilities ();
});
this.contact_notify_capabilities ();
((Tpf.PersonaStore) this.store).group_members_changed.connect (
(s, group, added, removed) =>
{
if (added.find (this) != null)
this._change_group (group, true);
if (removed.find (this) != null)
this._change_group (group, false);
});
((Tpf.PersonaStore) this.store).group_removed.connect (
(s, group, error) =>
{
if (error != null)
warning ("group invalidated: %s", error.message);
this._change_group (group, false);
});
}
private static Account? account_for_connection (Connection conn)
{
var manager = AccountManager.dup ();
var accounts = manager.get_valid_accounts ();
Account account_found = null;
accounts.foreach ((l) =>
{
var account = (Account) l;
if (account.get_connection () == conn)
{
account_found = account;
return;
}
});
return account_found;
}
private void contact_notify_presence_message ()
{
this.presence_message = this.contact.get_presence_message ();
}
private void contact_notify_presence_type ()
{
this.presence_type = folks_presence_type_from_tp (
this.contact.get_presence_type ());
}
private static PresenceType folks_presence_type_from_tp (
TelepathyGLib.ConnectionPresenceType type)
{
switch (type)
{
case TelepathyGLib.ConnectionPresenceType.AVAILABLE:
return PresenceType.AVAILABLE;
case TelepathyGLib.ConnectionPresenceType.AWAY:
return PresenceType.AWAY;
case TelepathyGLib.ConnectionPresenceType.BUSY:
return PresenceType.BUSY;
case TelepathyGLib.ConnectionPresenceType.ERROR:
return PresenceType.ERROR;
case TelepathyGLib.ConnectionPresenceType.EXTENDED_AWAY:
return PresenceType.EXTENDED_AWAY;
case TelepathyGLib.ConnectionPresenceType.HIDDEN:
return PresenceType.HIDDEN;
case TelepathyGLib.ConnectionPresenceType.OFFLINE:
return PresenceType.OFFLINE;
case TelepathyGLib.ConnectionPresenceType.UNKNOWN:
return PresenceType.UNKNOWN;
case TelepathyGLib.ConnectionPresenceType.UNSET:
return PresenceType.UNSET;
default:
return PresenceType.UNKNOWN;
}
}
private void contact_notify_avatar ()
{
var file = this.contact.get_avatar_file ();
if (this.avatar != file)
this.avatar = file;
}
private void contact_notify_capabilities ()
{
var caps = this.contact.get_capabilities ();
if (caps != null)
this.capabilities = folks_capabilities_flags_from_tp (caps);
}
/* Based off tp_caps_to_capabilities() in empathy-contact.c */
private static CapabilitiesFlags folks_capabilities_flags_from_tp (
TelepathyGLib.Capabilities caps)
{
CapabilitiesFlags capabilities = 0;
var classes = caps.get_channel_classes ();
classes.foreach ((m) =>
{
unowned ValueArray class_struct = (ValueArray) m;
unowned Value val = class_struct.get_nth (0);
unowned HashTable fixed_prop = (HashTable) val.get_boxed ();
TelepathyGLib.HandleType handle_type =
(TelepathyGLib.HandleType) TelepathyGLib.asv_get_uint32 (
fixed_prop, TelepathyGLib.PROP_CHANNEL_TARGET_HANDLE_TYPE,
null);
if (handle_type != HandleType.CONTACT)
return; /* i.e. continue the loop */
unowned string chan_type = TelepathyGLib.asv_get_string (fixed_prop,
TelepathyGLib.PROP_CHANNEL_CHANNEL_TYPE);
if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_FILE_TRANSFER)
{
capabilities |= CapabilitiesFlags.FILE_TRANSFER;
}
else if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_STREAM_TUBE)
{
var service = TelepathyGLib.asv_get_string (fixed_prop,
TelepathyGLib.PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
if (service == "rfb")
capabilities |= CapabilitiesFlags.STREAM_TUBE;
}
else if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_STREAMED_MEDIA)
{
val = class_struct.get_nth (1);
unowned string[] allowed_prop = (string[]) val.get_boxed ();
if (allowed_prop != null)
{
for (int i = 0; allowed_prop[i] != null; i++)
{
unowned string prop = allowed_prop[i];
if (prop ==
TelepathyGLib.PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO)
capabilities |= CapabilitiesFlags.AUDIO;
else if (prop ==
TelepathyGLib.PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO)
capabilities |= CapabilitiesFlags.VIDEO;
}
}
}
});
return capabilities;
}
}