* Bug 665039 — Crash in folks_backends_sw_backend_add_service
* Bug 665728 — TpfPersonaStore: prepare() isn't mutually exclusive inside a
single thread
+* Bug 665692 — Use constructors correctly
+
+API changes:
+* Add Edsf.PersonaStore.source
+* Make Edsf.Persona.contact writeable on construct (previously private setter)
+* Make Edsf.Persona.contact_id writeable on construct (previously private
+ setter)
+* Add Swf.PersonaStore.service
+* Make Swf.Persona.lsw_contact writeable on construct (previously private
+ setter)
+* Add Trf.Persona.tracker_id
+* Add Trf.Persona.cursor
+* Make AbstractFieldDetails.value writeable on construct (previously just a
+ normal setter)
+* Make AbstractFieldDetails.parameters writeable on construct (previously just a
+ normal setter)
+* Add ObjectCache.type_id
+* Add ObjectCache.id
Overview of changes from libfolks 0.6.4.1 to libfolks 0.6.5
=============================================================
*/
public Backend ()
{
+ Object ();
+ }
+
+ construct
+ {
this._persona_stores = new HashMap<string, PersonaStore> ();
this._persona_stores_ro = this._persona_stores.read_only_view;
}
private E.BookClient _addressbook;
private E.BookClientView _ebookview;
private E.SourceList? _source_list = null;
- private E.Source _source;
private string _query_str;
/* The timeout after which we consider a property change to have failed if we
}
/**
+ * The EDS {@link E.Source} associated with this persona store.
+ *
+ * @since UNRELEASED
+ */
+ public E.Source source
+ {
+ get; construct;
+ }
+
+ /**
* Create a new PersonaStore.
*
* Create a new persona store to store the {@link Persona}s for the contacts
public PersonaStore (E.Source s)
{
string eds_uid = s.peek_uid ();
- Object (id: eds_uid, display_name: eds_uid);
- this._source = s;
+ Object (id: eds_uid,
+ display_name: eds_uid,
+ source: s);
+ }
+
+ construct
+ {
this._personas = new HashMap<string, Persona> ();
this._personas_ro = this._personas.read_only_view;
this._query_str = "(contains \"x-evolution-any-field\" \"\")";
- this._source.changed.connect (this._source_changed_cb);
+ this.source.changed.connect (this._source_changed_cb);
this._notify_if_default ();
}
this._source_list.changed.connect (this._source_list_changed_cb);
/* Connect to the address book. */
- this._addressbook = new E.BookClient (this._source);
+ this._addressbook = new E.BookClient (this.source);
this._addressbook.notify["readonly"].connect (
this._address_book_notify_read_only_cb);
*/
private void _update_trust_level ()
{
- unowned SourceGroup? group = (SourceGroup?) this._source.peek_group ();
+ unowned SourceGroup? group = (SourceGroup?) this.source.peek_group ();
if (group != null)
{
var base_uri = group.peek_base_uri ();
E.BookClient.get_sources (out sources);
var default_source = sources.peek_default_source ();
if (default_source != null &&
- this._source.peek_uid () == default_source.peek_uid ())
+ this.source.peek_uid () == default_source.peek_uid ())
{
is_default = true;
}
private bool _is_favourite;
+ private E.Contact _contact; /* should be set on construct */
+
/**
* The e-d-s contact represented by this Persona
*/
public E.Contact contact
{
- get;
- private set;
+ get { return this._contact; }
+ construct { this._contact = value; }
}
/**
*
* @since 0.6.0
*/
- public string contact_id { get; private set; }
+ public string contact_id { get; construct; }
private string _full_name = "";
/**
if (full_name == null)
full_name = "";
- debug ("Creating new Edsf.Persona with IID '%s'", iid);
-
Object (display_id: full_name,
uid: uid,
iid: iid,
store: store,
- is_user: is_user);
+ is_user: is_user,
+ contact_id: contact_id,
+ contact: contact);
+ }
+
+ construct
+ {
+ debug ("Creating new Edsf.Persona with IID '%s'", this.iid);
this._gender = Gender.UNSPECIFIED;
- this.contact_id = contact_id;
this._phone_numbers = new HashSet<PhoneFieldDetails> (
(GLib.HashFunc) PhoneFieldDetails.hash,
(GLib.EqualFunc) PhoneFieldDetails.equal);
(GLib.EqualFunc) RoleFieldDetails.equal);
this._roles_ro = this._roles.read_only_view;
- this._update (contact);
+ this._update (this._contact);
}
/**
/**
* Update attribs of the persona.
*/
- internal void _update (E.Contact contact)
+ internal void _update (E.Contact updated_contact)
{
- this.contact = contact;
-
this.freeze_notify ();
+ /* We get a new E.Contact instance from EDS containing all the updates,
+ * so replace our existing contact with it. */
+ this._contact = updated_contact;
+ this.notify_property ("contact");
+
this._update_names ();
this._update_avatar ();
this._update_urls ();
*/
public MemoryIcon (string? image_type, uint8[] image_data)
{
+ /* Note: To be correct, these should both be properties of the object
+ * and this constructor should just call Object(…). However, this is an
+ * internal class, so we can skip all the pain of making a uint8[] object
+ * for this class only. */
this._image_data = image_data;
this._image_type = image_type;
}
*/
public Backend ()
{
+ Object ();
+ }
+
+ construct
+ {
this._persona_stores = new HashMap<string, PersonaStore> ();
this._persona_stores_ro = this._persona_stores.read_only_view;
}
{
private HashMap<string, Persona> _personas;
private Map<string, Persona> _personas_ro;
- private File _file;
private GLib.KeyFile _key_file;
private unowned Cancellable _save_key_file_cancellable = null;
private bool _is_prepared = false;
}
/**
+ * File containing the persona store data.
+ *
+ * This must be in GLib key file format.
+ *
+ * @since UNRELEASED
+ */
+ public File file { get; construct; }
+
+ /**
* Create a new PersonaStore.
*
* Create a new persona store to expose the {@link Persona}s provided by the
var id = key_file.get_basename ();
Object (id: id,
- display_name: id);
+ display_name: id,
+ file: key_file);
+ }
+ construct
+ {
this.trust_level = PersonaStoreTrust.FULL;
- this._file = key_file;
this._personas = new HashMap<string, Persona> ();
this._personas_ro = this._personas.read_only_view;
}
{
this._prepare_pending = true;
- var filename = this._file.get_path ();
+ var filename = this.file.get_path ();
this._key_file = new GLib.KeyFile ();
/* Load or create the file */
{
uint8 *contents = null;
- yield this._file.load_contents_async (null, out contents,
+ yield this.file.load_contents_async (null, out contents,
null);
var contents_s = (string) contents;
}
/* Ensure the parent directory tree exists for the new file */
- File parent_dir = this._file.get_parent ();
+ File parent_dir = this.file.get_parent ();
try
{
try
{
/* Create the file */
- FileOutputStream stream = yield this._file.create_async (
+ FileOutputStream stream = yield this.file.create_async (
FileCreateFlags.PRIVATE, Priority.DEFAULT);
yield stream.close_async (Priority.DEFAULT);
}
var added_personas = new HashSet<Persona> ();
foreach (var persona_id in groups)
{
- Persona persona = new Kf.Persona (this._key_file, persona_id,
- this);
+ Persona persona = new Kf.Persona (persona_id, this);
this._personas.set (persona.iid, persona);
added_personas.add (persona);
}
/* Create a new persona and set its addresses property to update the
* key file */
- Persona persona = new Kf.Persona (this._key_file, persona_id, this);
+ Persona persona = new Kf.Persona (persona_id, this);
this._personas.set (persona.iid, persona);
try
return persona;
}
+ internal unowned KeyFile get_key_file ()
+ {
+ return this._key_file;
+ }
+
internal async void save_key_file ()
{
var key_file_data = this._key_file.to_data ();
var cancellable = new Cancellable ();
- debug ("Saving key file '%s'.", this._file.get_path ());
+ debug ("Saving key file '%s'.", this.file.get_path ());
/* There's no point in having two competing file write operations.
* We can ensure that only one is running by just checking if a
* Vala <= 0.10, it returned the character length). FIXME: We need to
* take this into account until we depend explicitly on
* Vala >= 0.11. */
- yield this._file.replace_contents_async (key_file_data,
+ yield this.file.replace_contents_async (key_file_data,
key_file_data.length, null, false, FileCreateFlags.PRIVATE,
cancellable);
}
/* Translators: the first parameter is a filename, the second is
* an error message. */
warning (_("Could not write updated key file '%s': %s"),
- this._file.get_path (), e.message);
+ this.file.get_path (), e.message);
}
}
ImDetails,
WebServiceDetails
{
- private unowned GLib.KeyFile _key_file;
private HashMultiMap<string, ImFieldDetails> _im_addresses;
private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
private string _alias = ""; /* must not be null */
debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
- this._key_file.set_string (this.display_id, "__alias", alias);
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+ key_file.set_string (this.display_id, "__alias", alias);
yield ((Kf.PersonaStore) this.store).save_key_file ();
this._alias = alias;
public async void change_im_addresses (
MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
{
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
/* Remove the current IM addresses from the key file */
foreach (var protocol1 in this._im_addresses.get_keys ())
{
try
{
- this._key_file.remove_key (this.display_id, protocol1);
+ key_file.remove_key (this.display_id, protocol1);
}
catch (KeyFileError e1)
{
string[] addrs = (string[]) normalised_addresses.to_array ();
addrs.length = normalised_addresses.size;
- this._key_file.set_string_list (this.display_id, protocol2, addrs);
+ key_file.set_string_list (this.display_id, protocol2, addrs);
}
/* Get the PersonaStore to save the key file */
MultiMap<string, WebServiceFieldDetails> web_service_addresses)
throws PropertyError
{
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
/* Remove the current web service addresses from the key file */
foreach (var web_service1 in this._web_service_addresses.get_keys ())
{
try
{
- this._key_file.remove_key (this.display_id,
+ key_file.remove_key (this.display_id,
"web-service." + web_service1);
}
catch (KeyFileError e)
foreach (var ws_fd1 in ws_fds)
addrs += ws_fd1.value;
- this._key_file.set_string_list (this.display_id,
+ key_file.set_string_list (this.display_id,
"web-service." + web_service2, addrs);
foreach (var ws_fd2 in ws_fds)
* Create a new persona for the {@link PersonaStore} `store`, representing
* the Persona given by the group `uid` in the key file `key_file`.
*/
- public Persona (KeyFile key_file, string id, Folks.PersonaStore store)
+ public Persona (string id, Folks.PersonaStore store)
{
var iid = store.id + ":" + id;
var uid = this.build_uid ("key-file", store.id, id);
uid: uid,
store: store,
is_user: false);
+ }
- debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", uid, iid,
- id);
+ construct
+ {
+ debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
+ this.iid, this.display_id);
- this._key_file = key_file;
this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
this._web_service_addresses =
(GLib.EqualFunc) WebServiceFieldDetails.equal);
/* Load the IM addresses from the key file */
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
try
{
- var keys = this._key_file.get_keys (this.display_id);
+ var keys = key_file.get_keys (this.display_id);
foreach (unowned string key in keys)
{
/* Alias */
if (key == "__alias")
{
- this._alias = this._key_file.get_string (this.display_id,
- key);
+ this._alias = key_file.get_string (this.display_id, key);
if (this._alias == null)
{
decomposed_key[0] == "web-service")
{
unowned string web_service = decomposed_key[1];
- var web_service_addresses = this._key_file.get_string_list (
+ var web_service_addresses = key_file.get_string_list (
this.display_id, web_service);
foreach (var web_service_address in web_service_addresses)
/* IM addresses */
unowned string protocol = key;
- var im_addresses = this._key_file.get_string_list (
+ var im_addresses = key_file.get_string_list (
this.display_id, protocol);
foreach (var im_address in im_addresses)
private bool _is_prepared = false;
private bool _prepare_pending = false;
private bool _is_quiescent = false;
- private ClientService _service;
private ClientContactView _contact_view;
/* No writeable properties
}
/**
+ * The libsocialweb {@link SocialWebClient.ClientService} associated with the
+ * persona store.
+ *
+ * @since UNRELEASED
+ */
+ public ClientService service { get; construct; }
+
+ /**
* Create a new PersonaStore.
*
* Create a new persona store to store the {@link Persona}s for the contacts
*/
public PersonaStore (ClientService service)
{
- Object (display_name: service.get_display_name(),
- id: service.get_name ());
+ Object (display_name: service.get_display_name (),
+ id: service.get_name (),
+ service: service);
+ }
+ construct
+ {
this.trust_level = PersonaStoreTrust.PARTIAL;
- this._service = service;
this._personas = new HashMap<string, Persona> ();
this._personas_ro = this._personas.read_only_view;
}
* async call to return. See: bgo#665039. */
this.ref ();
- this._service.get_static_capabilities (
+ this.service.get_static_capabilities (
(service, caps, error) =>
{
if (caps == null)
/* Take another ref for this async call. */
this.ref ();
- this._service.contacts_query_open_view
+ this.service.contacts_query_open_view
("people", parameters, (query, contact_view) =>
{
/* The D-Bus call could return an error. In this
{
foreach (var contact in contacts)
{
- if (this._service.get_name () != contact.service)
+ if (this.service.get_name () != contact.service)
{
continue;
}
var removed_personas = new HashSet<Persona> ();
foreach (var contact in contacts)
{
- if (this._service.get_name () != contact.service)
+ if (this.service.get_name () != contact.service)
{
continue;
}
public Contact lsw_contact
{
get { return this._lsw_contact; }
- private set
+ construct
{
if (_lsw_contact != null && _lsw_contact != value)
{
public Persona (PersonaStore store, Contact contact)
{
var id = get_contact_id (contact);
- var service = contact.service.dup();
var uid = this.build_uid (BACKEND_NAME, store.id, id);
var iid = this._build_iid (store.id, id);
uid: uid,
iid: iid,
store: store,
- is_user: false);
- this.lsw_contact = contact;
+ is_user: false,
+ lsw_contact: contact);
+ }
+ construct
+ {
debug ("Creating new Sw.Persona '%s' for %s UID '%s': %p",
- uid, store.display_name, id, this);
+ this.uid, this.store.display_name, this.display_id, this);
- var facebook_jid = this._build_facebook_jid (store.id, id);
+ var facebook_jid =
+ this._build_facebook_jid (this.store.id, this.display_id);
if (facebook_jid != null)
{
try
}
}
+ var service = this.lsw_contact.service.dup ();
this._web_service_addresses.set (service,
- new WebServiceFieldDetails (id));
+ new WebServiceFieldDetails (this.display_id));
- update (contact);
+ this.update (this.lsw_contact);
}
~Persona ()
*/
public Backend ()
{
+ Object ();
+ }
+
+ construct
+ {
this._persona_stores = new HashMap<string, PersonaStore> ();
this._persona_stores_ro = this._persona_stores.read_only_view;
}
internal class Logger : GLib.Object
{
private static LoggerIface _logger;
- private string _account_path;
private uint _logger_watch_id;
public signal void invalidated ();
public signal void favourite_contacts_changed (string[] added,
string[] removed);
+ /**
+ * D-Bus object path of the {@link TelepathyGLib.Account} to watch for
+ * favourite contacts.
+ *
+ * @since UNRELEASED
+ */
+ public string account_path { get; construct; }
+
public Logger (string account_path)
{
- this._account_path = account_path;
+ Object (account_path: account_path);
}
~Logger ()
*/
internal class Tpf.PersonaStoreCache : Folks.ObjectCache<Tpf.Persona>
{
- private weak PersonaStore _store;
+ /**
+ * The {@link Tpf.PersonaStore} associated with this cache.
+ *
+ * @since UNRELEASED
+ */
+ public weak PersonaStore store { get; construct; }
/* Version number of the variant type returned by
* get_serialised_object_type(). This must be modified whenever that variant
internal PersonaStoreCache (PersonaStore store)
{
- base ("tpf-persona-stores", store.id);
-
- this._store = store;
+ Object (type_id: "tpf-persona-stores",
+ id: store.id,
+ store: store);
}
protected override VariantType? get_serialised_object_type (
Object (account: account,
display_name: account.display_name,
id: account.get_object_path ());
+ }
+ construct
+ {
debug ("Creating new Tpf.PersonaStore %p ('%s') for TpAccount %p.",
- this, this.id, account);
+ this, this.id, this.account);
this._debug = Debug.dup ();
this._debug.print_status.connect (this._debug_print_status);
PresenceDetails,
UrlDetails
{
- private HashSet<string> _groups;
- private Set<string> _groups_ro;
- private bool _is_favourite;
- private string _alias; /* must never be null */
- private string _full_name; /* must never be null */
- private HashMultiMap<string, ImFieldDetails> _im_addresses;
private const string[] _linkable_properties = { "im-addresses" };
private const string[] _always_writeable_properties =
{
set { this.change_structured_name.begin (value); } /* not writeable */
}
+ private string _full_name = ""; /* must never be null */
+
/**
* {@inheritDoc}
*
}
}
+ private string _alias = ""; /* must never be null */
+
/**
* An alias for the Persona.
*
this.notify_property ("alias");
}
+ private bool _is_favourite = false;
+
/**
* Whether this Persona is a user-defined favourite.
*
this.notify_property ("is-favourite");
}
- private HashSet<EmailFieldDetails> _email_addresses;
+ private HashSet<EmailFieldDetails> _email_addresses =
+ new HashSet<EmailFieldDetails> (
+ (GLib.HashFunc) EmailFieldDetails.hash,
+ (GLib.EqualFunc) EmailFieldDetails.equal);
private Set<EmailFieldDetails> _email_addresses_ro;
/**
this._email_addresses, "email");
}
+ private HashMultiMap<string, ImFieldDetails> _im_addresses =
+ new HashMultiMap<string, ImFieldDetails> (null, null,
+ (GLib.HashFunc) ImFieldDetails.hash,
+ (GLib.EqualFunc) ImFieldDetails.equal);
+
/**
* A mapping of IM protocol to an (unordered) set of IM addresses.
*
set { this.change_im_addresses.begin (value); }
}
+ private HashSet<string> _groups = new HashSet<string> ();
+ private Set<string> _groups_ro;
+
/**
* A mapping of group ID to whether the contact is a member.
*
*/
public Contact? contact { get; construct; }
- private HashSet<PhoneFieldDetails> _phone_numbers;
+ private HashSet<PhoneFieldDetails> _phone_numbers =
+ new HashSet<PhoneFieldDetails> (
+ (GLib.HashFunc) PhoneFieldDetails.hash,
+ (GLib.EqualFunc) PhoneFieldDetails.equal);
private Set<PhoneFieldDetails> _phone_numbers_ro;
/**
this._phone_numbers, "tel");
}
- private HashSet<UrlFieldDetails> _urls;
+ private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> (
+ (GLib.HashFunc) UrlFieldDetails.hash,
+ (GLib.EqualFunc) UrlFieldDetails.equal);
private Set<UrlFieldDetails> _urls_ro;
/**
var account = this._account_for_connection (connection);
var uid = this.build_uid (store.type_id, store.id, id);
- Object (alias: contact.get_alias (),
- contact: contact,
+ Object (contact: contact,
display_id: id,
/* FIXME: This IID format should be moved out to the ImDetails
* interface along with the code in
store: store,
is_user: contact.handle == connection.self_handle);
+ debug ("Created new Tpf.Persona '%s' for service-specific UID '%s': %p",
+ uid, id, this);
+ }
+
+ construct
+ {
+ this._groups_ro = this._groups.read_only_view;
+ this._email_addresses_ro = this._email_addresses.read_only_view;
+ this._phone_numbers_ro = this._phone_numbers.read_only_view;
+ this._urls_ro = this._urls.read_only_view;
+
+ /* Contact can be null if we've been created from the cache. All the code
+ * below this point is for non-cached personas. */
+ if (this.contact == null)
+ {
+ return;
+ }
- this._full_name = "";
+ /* Set our alias. */
+ this._alias = this.contact.get_alias ();
- contact.notify["alias"].connect ((s, p) =>
+ this.contact.notify["alias"].connect ((s, p) =>
{
/* Tp guarantees that aliases are always non-null. */
assert (this.contact.alias != null);
}
});
- debug ("Creating new Tpf.Persona '%s' for service-specific UID '%s': %p",
- uid, id, this);
- this._is_constructed = true;
-
/* Set our single IM address */
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null,
- (GLib.HashFunc) ImFieldDetails.hash,
- (GLib.EqualFunc) ImFieldDetails.equal);
+ var connection = this.contact.connection;
+ var account = this._account_for_connection (connection);
try
{
- var im_addr = ImDetails.normalise_im_address (id,
+ var im_addr = ImDetails.normalise_im_address (this.display_id,
account.get_protocol ());
var im_fd = new ImFieldDetails (im_addr);
this._im_addresses.set (account.get_protocol (), im_fd);
warning (e.message);
}
- /* Groups */
- this._groups = new HashSet<string> ();
- this._groups_ro = this._groups.read_only_view;
-
- this._email_addresses = new HashSet<EmailFieldDetails> (
- (GLib.HashFunc) EmailFieldDetails.hash,
- (GLib.EqualFunc) EmailFieldDetails.equal);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- this._phone_numbers = new HashSet<PhoneFieldDetails> (
- (GLib.HashFunc) PhoneFieldDetails.hash,
- (GLib.EqualFunc) PhoneFieldDetails.equal);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this._urls = new HashSet<UrlFieldDetails> (
- (GLib.HashFunc) UrlFieldDetails.hash,
- (GLib.EqualFunc) UrlFieldDetails.equal);
- this._urls_ro = this._urls.read_only_view;
-
- contact.notify["avatar-file"].connect ((s, p) =>
+ this.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"].connect ((s, p) =>
{
this._contact_notify_presence_message ();
});
- contact.notify["presence-type"].connect ((s, p) =>
+ this.contact.notify["presence-type"].connect ((s, p) =>
{
this._contact_notify_presence_type ();
});
- contact.notify["presence-status"].connect ((s, p) =>
+ this.contact.notify["presence-status"].connect ((s, p) =>
{
this._contact_notify_presence_status ();
});
this._contact_notify_presence_type ();
this._contact_notify_presence_status ();
- contact.notify["contact-info"].connect ((s, p) =>
+ this.contact.notify["contact-info"].connect ((s, p) =>
{
this._contact_notify_contact_info ();
});
}
}
+ /* Called after all construction-time properties have been set. */
+ public override void constructed ()
+ {
+ this._is_constructed = true;
+ }
+
private void _store_notify_supported_fields ()
{
var tpf_store = this.store as Tpf.PersonaStore;
store: store,
is_user: is_user);
- debug ("Creating new Tpf.Persona '%s' from cache: %p", uid, this);
+ debug ("Created new Tpf.Persona '%s' from cache: %p", uid, this);
// IM addresses
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (null, null,
- (GLib.HashFunc) ImFieldDetails.hash,
- (GLib.EqualFunc) ImFieldDetails.equal);
-
var im_fd = new ImFieldDetails (im_address);
this._im_addresses.set (protocol, im_fd);
*/
public Backend ()
{
+ Object ();
+ }
+
+ construct
+ {
this._persona_stores = new HashMap<string, PersonaStore> ();
this._persona_stores_ro = this._persona_stores.read_only_view;
}
*/
public PersonaStore ()
{
- Object (id: BACKEND_NAME, display_name: BACKEND_NAME);
+ Object (id: BACKEND_NAME,
+ display_name: BACKEND_NAME);
+ }
+
+ construct
+ {
this._personas = new HashMap<string, Persona> ();
this._personas_ro = this._personas.read_only_view;
debug ("Initial query : \n%s\n", this._INITIAL_QUERY);
"FILTER (tracker:id(?p) = %s) " +
"} ";
- string query = query_t.printf (persona.tracker_id (), nickname,
- persona.tracker_id ());
+ string query = query_t.printf (persona.tracker_id, nickname,
+ persona.tracker_id);
yield this._tracker_update (query, "change_nickname");
}
" ?p a nco:PersonContact . " +
"FILTER (tracker:id(?p) = %s) } ";
- string query = query_t.printf (persona.tracker_id (), prop_name,
- prop_name, prop_value, persona.tracker_id ());
+ string query = query_t.printf (persona.tracker_id, prop_name,
+ prop_name, prop_value, persona.tracker_id);
yield this._tracker_update (query, callers_name);
}
if (is_favourite)
{
- query = ins_q.printf (((Trf.Persona) persona).tracker_id ());
+ query = ins_q.printf (((Trf.Persona) persona).tracker_id);
}
else
{
- query = del_q.printf (((Trf.Persona) persona).tracker_id ());
+ query = del_q.printf (((Trf.Persona) persona).tracker_id);
}
yield this._tracker_update (query, "change_is_favourite");
Set<AbstractFieldDetails<string>> properties, Trf.Attrib attrib)
{
string? query_name = null;
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
var builder = new Tracker.Sparql.Builder.update ();
builder.insert_open (null);
" FILTER(tracker:id(?p) = %s) " +
"} ";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
string del_q = del_t.printf (p_id);
var builder = new Tracker.Sparql.Builder.update ();
" FILTER(tracker:id(?p) = %s)" +
"}";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
string del_q = del_t.printf (p_id);
var builder = new Tracker.Sparql.Builder.update ();
" FILTER (tracker:id(?p) = %s) " +
"} ";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
TimeVal tv;
bday.to_timeval (out tv);
string query = q_t.printf (p_id, tv.to_iso8601 (), p_id);
" FILTER (tracker:id(?p) = %s) " +
"} ";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
string query;
if (gender == Gender.UNSPECIFIED)
" FILTER(tracker:id(?c) = %s) " +
"}";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
var image_urn = yield this._get_property (int.parse (p_id),
Trf.OntologyDefs.NCO_PHOTO);
" FILTER (tracker:id(?p) = %s) " +
"} ";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
string query = query_d.printf (p_id);
if (sname != null)
" FILTER (tracker:id(?p) = %s) " +
"} ";
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
string query = query_t.printf (p_id, full_name, p_id);
yield this._tracker_update (query, "_set_full_name");
}
private async void _set_attrib_set (Folks.Persona persona,
Set<Object> attribs, Trf.Attrib what)
{
- var p_id = ((Trf.Persona) persona).tracker_id ();
+ var p_id = ((Trf.Persona) persona).tracker_id;
unowned string? related_attrib = null;
unowned string? related_prop = null;
private async string _urn_from_persona (Folks.Persona persona)
{
- var id = ((Trf.Persona) persona).tracker_id ();
+ var id = ((Trf.Persona) persona).tracker_id;
return yield this._urn_from_tracker_id (id);
}
}
}
- debug ("Creating new Trf.Persona with iid '%s'", iid);
-
Object (display_id: fullname,
uid: uid,
iid: iid,
store: store,
- is_user: is_user);
+ is_user: is_user,
+ tracker_id: tracker_id,
+ /* Ideally we wouldn't have to do this, since passing iterators
+ * around is ugly. However, we can't fix the Tracker backend to
+ * not pass Cursors from PersonaStore to Personas without breaking
+ * API. */
+ cursor: cursor);
+ }
+
+ construct
+ {
+ debug ("Creating new Trf.Persona with iid '%s'", this.iid);
this._gender = Gender.UNSPECIFIED;
- this._full_name = fullname;
- this._tracker_id = tracker_id;
+ this._full_name = "";
this._structured_name = null;
this._phone_numbers = new HashSet<PhoneFieldDetails> (
(GLib.HashFunc) PhoneFieldDetails.hash,
this._local_ids_ro = this._local_ids.read_only_view;
/* Set the initial property values if we have a results cursor. */
- if (cursor != null)
+ if (this._cursor != null)
{
- this._cursor = cursor;
-
this._update_names ();
this._update_avatar ();
this._update_im_addresses ();
}
}
- internal string tracker_id ()
+ /**
+ * ID of the {@link Trf.Persona} in Tracker.
+ *
+ * @since UNRELEASED
+ */
+ public string tracker_id
+ {
+ get { return this._tracker_id; }
+ construct { this._tracker_id = value; }
+ }
+
+ /**
+ * A {@link Sparql.Cursor} representing the persona in a set of query results.
+ *
+ * This is an internal (read: horrible) API which shouldn't be used by client
+ * code. It's only exposed publicly due to the design of libfolks’ Tracker
+ * backend.
+ *
+ * @since UNRELEASED
+ */
+ public Sparql.Cursor? cursor
{
- return this._tracker_id;
+ get { return this._cursor; }
+ construct { this._cursor = value; }
}
/**
*/
public Backend ()
{
+ Object ();
+ }
+
+ construct
+ {
this._persona_stores = new HashMap<string, PersonaStore> ();
this._persona_stores_ro = this._persona_stores.read_only_view;
}
public virtual T @value
{
get { return this._value; }
- set { this._value = value; }
+ construct set { this._value = value; }
}
/**
public virtual MultiMap<string, string> parameters
{
get { return this._parameters; }
- set
+ construct set
{
if (value == null)
this._parameters.clear ();
*/
private AvatarCache ()
{
+ Object ();
+ }
+
+ construct
+ {
this._cache_directory =
File.new_for_path (Environment.get_user_cache_dir ())
.get_child ("folks")
private BackendStore ()
{
+ Object ();
+ }
+
+ construct
+ {
/* Treat this as a library init function */
var debug_no_colour = Environment.get_variable ("FOLKS_DEBUG_NO_COLOUR");
this._debug =
private Debug ()
{
/* Private constructor for singleton */
+ Object ();
+ }
+ construct
+ {
this._domains_handled = new HashSet<string> ();
/* Install a log handler for log messages emitted as a result of
*/
public IndividualAggregator ()
{
+ Object ();
+ }
+
+ construct
+ {
this._stores = new HashMap<string, PersonaStore> ();
this._individuals = new HashMap<string, Individual> ();
this._individuals_ro = this._individuals.read_only_view;
UrlDetails,
WebServiceDetails
{
- private bool _is_favourite;
- private string _alias;
- private HashSet<string> _groups;
- private Set<string> _groups_ro;
/* Stores the Personas contained in this Individual. */
- private HashSet<Persona> _persona_set;
+ private HashSet<Persona> _persona_set =
+ new HashSet<Persona> (direct_hash, direct_equal);
/* Read-only view of the above set */
private Set<Persona> _persona_set_ro;
/* Mapping from PersonaStore -> number of Personas from that store contained
* in this Individual. There shouldn't be any entries with a number < 1.
* This is used for working out when to disconnect from store signals. */
- private HashMap<PersonaStore, uint> _stores;
+ private HashMap<PersonaStore, uint> _stores =
+ new HashMap<PersonaStore, uint> (null, null);
/* The number of Personas in this Individual which have
* Persona.is_user == true. Iff this is > 0, Individual.is_user == true. */
private uint _persona_user_count = 0;
- private HashMultiMap<string, ImFieldDetails> _im_addresses;
- private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
- private string _nickname = "";
/**
* The trust level of the Individual.
*/
public signal void removed (Individual? replacement_individual);
+ private string _alias = "";
+
/**
* {@inheritDoc}
*/
set { this.change_full_name.begin (value); } /* not writeable */
}
+ private string _nickname = "";
+
/**
* {@inheritDoc}
*/
set { this.change_gender.begin (value); } /* not writeable */
}
- private HashSet<UrlFieldDetails> _urls;
+ private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> (
+ (GLib.HashFunc) UrlFieldDetails.hash,
+ (GLib.EqualFunc) UrlFieldDetails.equal);
private Set<UrlFieldDetails> _urls_ro;
/**
set { this.change_urls.begin (value); } /* not writeable */
}
- private HashSet<PhoneFieldDetails> _phone_numbers;
+ private HashSet<PhoneFieldDetails> _phone_numbers =
+ new HashSet<PhoneFieldDetails> (
+ (GLib.HashFunc) PhoneFieldDetails.hash,
+ (GLib.EqualFunc) PhoneFieldDetails.equal);
private Set<PhoneFieldDetails> _phone_numbers_ro;
/**
set { this.change_phone_numbers.begin (value); } /* not writeable */
}
- private HashSet<EmailFieldDetails> _email_addresses;
+ private HashSet<EmailFieldDetails> _email_addresses =
+ new HashSet<EmailFieldDetails> (
+ (GLib.HashFunc) EmailFieldDetails.hash,
+ (GLib.EqualFunc) EmailFieldDetails.equal);
private Set<EmailFieldDetails> _email_addresses_ro;
/**
set { this.change_email_addresses.begin (value); } /* not writeable */
}
- private HashSet<RoleFieldDetails> _roles;
+ private HashSet<RoleFieldDetails> _roles = new HashSet<RoleFieldDetails> (
+ (GLib.HashFunc) RoleFieldDetails.hash,
+ (GLib.EqualFunc) RoleFieldDetails.equal);
private Set<RoleFieldDetails> _roles_ro;
/**
set { this.change_roles.begin (value); } /* not writeable */
}
- private HashSet<string> _local_ids;
+ private HashSet<string> _local_ids = new HashSet<string> ();
private Set<string> _local_ids_ro;
/**
set { this.change_calendar_event_id.begin (value); } /* not writeable */
}
- private HashSet<NoteFieldDetails> _notes;
+ private HashSet<NoteFieldDetails> _notes = new HashSet<NoteFieldDetails> (
+ (GLib.HashFunc) NoteFieldDetails.hash,
+ (GLib.EqualFunc) NoteFieldDetails.equal);
private Set<NoteFieldDetails> _notes_ro;
/**
set { this.change_notes.begin (value); } /* not writeable */
}
- private HashSet<PostalAddressFieldDetails> _postal_addresses;
+ private HashSet<PostalAddressFieldDetails> _postal_addresses =
+ new HashSet<PostalAddressFieldDetails> (
+ (GLib.HashFunc) PostalAddressFieldDetails.hash,
+ (GLib.EqualFunc) PostalAddressFieldDetails.equal);
private Set<PostalAddressFieldDetails> _postal_addresses_ro;
/**
set { this.change_postal_addresses.begin (value); } /* not writeable */
}
+ private bool _is_favourite = false;
+
/**
* Whether this Individual is a user-defined favourite.
*
this.notify_property ("is-favourite");
}
+ private HashSet<string> _groups = new HashSet<string> ();
+ private Set<string> _groups_ro;
+
/**
* {@inheritDoc}
*/
this._update_groups ();
}
+ private HashMultiMap<string, ImFieldDetails> _im_addresses =
+ new HashMultiMap<string, ImFieldDetails> (
+ null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
+
/**
* {@inheritDoc}
*/
set { this.change_im_addresses.begin (value); } /* not writeable */
}
+ private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (null, null,
+ (GLib.HashFunc) WebServiceFieldDetails.hash,
+ (GLib.EqualFunc) WebServiceFieldDetails.equal);
+
/**
* {@inheritDoc}
*/
*/
public Individual (Set<Persona>? personas)
{
+ Object (personas: personas);
+ }
+
+ construct
+ {
debug ("Creating new Individual with %u Personas: %p",
- (personas != null ? personas.size : 0), this);
+ (this.personas != null ? this.personas.size : 0), this);
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
- this._web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null,
- (GLib.HashFunc) WebServiceFieldDetails.hash,
- (GLib.EqualFunc) WebServiceFieldDetails.equal);
- this._persona_set =
- new HashSet<Persona> (direct_hash, direct_equal);
this._persona_set_ro = this._persona_set.read_only_view;
- this._stores = new HashMap<PersonaStore, uint> (null, null);
- this._gender = Gender.UNSPECIFIED;
- this._urls = new HashSet<UrlFieldDetails> (
- (GLib.HashFunc) UrlFieldDetails.hash,
- (GLib.EqualFunc) UrlFieldDetails.equal);
this._urls_ro = this._urls.read_only_view;
- this._phone_numbers = new HashSet<PhoneFieldDetails> (
- (GLib.HashFunc) PhoneFieldDetails.hash,
- (GLib.EqualFunc) PhoneFieldDetails.equal);
this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this._email_addresses = new HashSet<EmailFieldDetails> (
- (GLib.HashFunc) EmailFieldDetails.hash,
- (GLib.EqualFunc) EmailFieldDetails.equal);
this._email_addresses_ro = this._email_addresses.read_only_view;
- this._roles = new HashSet<RoleFieldDetails> (
- (GLib.HashFunc) RoleFieldDetails.hash,
- (GLib.EqualFunc) RoleFieldDetails.equal);
this._roles_ro = this._roles.read_only_view;
- this._local_ids = new HashSet<string> ();
this._local_ids_ro = this._local_ids.read_only_view;
- this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
- (GLib.HashFunc) PostalAddressFieldDetails.hash,
- (GLib.EqualFunc) PostalAddressFieldDetails.equal);
this._postal_addresses_ro = this._postal_addresses.read_only_view;
- this._notes = new HashSet<NoteFieldDetails> (
- (GLib.HashFunc) NoteFieldDetails.hash,
- (GLib.EqualFunc) NoteFieldDetails.equal);
this._notes_ro = this._notes.read_only_view;
-
- this.personas = personas;
+ this._groups_ro = this._groups.read_only_view;
}
~Individual ()
{
var new_groups = new HashSet<string> ();
- /* this._groups is null during initial construction */
- if (this._groups == null)
- {
- this._groups = new HashSet<string> ();
- this._groups_ro = this._groups.read_only_view;
- }
-
/* FIXME: this should partition the personas by store (maybe we should
* keep that mapping in general in this class), and execute
* "groups-changed" on the store (with the set of personas), to allow the
MultiMap<string, string>? parameters = null,
string? uid = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
-
- /* These are kept the same value now */
- this.id = uid;
+ Object (value: value,
+ id: uid,
+ parameters: parameters);
}
/**
* are used (for version numbers). */
private static const size_t _HEADER_WIDTH = 8; /* bytes */
- private string _type_id;
- private string _id;
private File _cache_directory;
private File _cache_file;
uint8 object_version);
/**
+ * A string identifying the type of object being cached.
+ *
+ * This has to be suitable for use as a directory name; i.e. lower case,
+ * hyphen-separated tokens.
+ *
+ * @since UNRELEASED
+ */
+ public string type_id { get; construct; }
+
+ /**
+ * A string identifying the particular cache instance.
+ *
+ * This will form the file name of the cache file, but will be escaped
+ * beforehand, so can be an arbitrary non-empty string.
+ *
+ * @since UNRELEASED
+ */
+ public string id
+ {
+ get { return this._id; }
+ construct { assert (value != ""); this._id = value; }
+ }
+ private string _id;
+
+ /**
* Create a new cache instance using the given type ID and ID. This is
* protected as the `type_id` will typically be set statically by subclasses.
*
*/
protected ObjectCache (string type_id, string id)
{
- assert (id != "");
-
- this._type_id = type_id;
- this._id = id;
+ Object (type_id: type_id,
+ id: id);
+ }
+ construct
+ {
debug ("Creating object cache for type ID '%s' with ID '%s'.",
- type_id, id);
+ this.type_id, this.id);
this._cache_directory =
File.new_for_path (Environment.get_user_cache_dir ())
.get_child ("folks")
- .get_child (type_id);
+ .get_child (this.type_id);
this._cache_file =
- this._cache_directory.get_child (Uri.escape_string (id, "", false));
+ this._cache_directory.get_child (Uri.escape_string (this.id,
+ "", false));
}
/**
public async Set<T>? load_objects (Cancellable? cancellable = null)
{
debug ("Loading cache (type ID '%s', ID '%s') from file '%s'.",
- this._type_id, this._id, this._cache_file.get_path ());
+ this.type_id, this._id, this._cache_file.get_path ());
// Read in the file
uint8[] data;
// Unpack the stored data
var type_id = variant.get_child_value (0).get_string ();
- if (type_id != this._type_id)
+ if (type_id != this.type_id)
{
warning ("Cache file '%s' had type ID '%s', but '%s' was expected." +
"The file was deleted.", this._cache_file.get_path (), type_id,
- this._type_id);
+ this.type_id);
yield this.clear_cache ();
return null;
Cancellable? cancellable = null)
{
debug ("Storing cache (type ID '%s', ID '%s') to file '%s'.",
- this._type_id, this._id, this._cache_file.get_path ());
+ this.type_id, this._id, this._cache_file.get_path ());
var child_type = this.get_serialised_object_type (uint8.MAX);
assert (child_type != null); // uint8.MAX should always be supported
var object_version = this.get_serialised_object_version ();
var variant = new Variant.tuple ({
- new Variant.string (this._type_id), // Type ID
+ new Variant.string (this.type_id), // Type ID
new Variant.string (this._id), // ID
new Variant.array (child_type, children) // Array of objects
});
public async void clear_cache ()
{
debug ("Clearing cache (type ID '%s', ID '%s'); deleting file '%s'.",
- this._type_id, this._id, this._cache_file.get_path ());
+ this.type_id, this._id, this._cache_file.get_path ());
try
{
public PhoneFieldDetails (string value,
MultiMap<string, string>? parameters = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
+ Object (value: value,
+ parameters: parameters);
}
/**
public PostalAddressFieldDetails (PostalAddress value,
MultiMap<string, string>? parameters = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
-
- /* We keep these sync'd both directions */
- this.id = this.value.uid;
+ /* We keep id and value.uid synchronised in both directions. */
+ Object (value: value,
+ parameters: parameters,
+ id: value.uid);
+ }
+ construct
+ {
/* Keep the PostalAddress.uid sync'd to our id */
this.value.notify["uid"].connect ((s, p) =>
{
*
* @since 0.5.1
*/
- public static Set<string> known_email_aliases = null;
+ public static Set<string> known_email_aliases =
+ new Gee.HashSet<string> (str_hash, str_equal);
private static double _DIST_THRESHOLD = 0.70;
private const string _SEPARATORS = "._-+";
- public PotentialMatch ()
+ static construct
{
- if (this.known_email_aliases == null)
- {
- this.known_email_aliases = new Gee.HashSet<string> (str_hash,
- str_equal);
- this.known_email_aliases.add ("admin");
- this.known_email_aliases.add ("abuse");
- this.known_email_aliases.add ("webmaster");
- }
+ PotentialMatch.known_email_aliases.add ("admin");
+ PotentialMatch.known_email_aliases.add ("abuse");
+ PotentialMatch.known_email_aliases.add ("webmaster");
}
/**
if (fd_a.value == fd_b.value)
{
- if (this.known_email_aliases.contains
+ if (PotentialMatch.known_email_aliases.contains
(email_split_a[0]) == true)
{
if (this._result < MatchResult.HIGH)
public RoleFieldDetails (Role value,
MultiMap<string, string>? parameters = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
-
- /* We keep these sync'd both directions */
- this.id = this.value.uid;
+ /* We keep id and value.uid synchronised in both directions. */
+ Object (value: value,
+ parameters: parameters,
+ id: value.uid);
+ }
+ construct
+ {
/* Keep the Role.uid sync'd to our id */
this.value.notify["uid"].connect ((s, p) =>
{
public UrlFieldDetails (string value,
MultiMap<string, string>? parameters = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
+ Object (value: value,
+ parameters: parameters);
}
/**
public WebServiceFieldDetails (string value,
MultiMap<string, string>? parameters = null)
{
- this.value = value;
- if (parameters != null)
- this.parameters = parameters;
+ Object (value: value,
+ parameters: parameters);
}
/**