Clarify immutability of Persona.uid
[platform/upstream/folks.git] / folks / persona.vala
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  *
4  * This library is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 2.1 of the License, or
7  * (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
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors:
18  *       Travis Reitter <travis.reitter@collabora.co.uk>
19  */
20
21 using GLib;
22
23 /**
24  * Represents a "shard" of a person from a single source (a single
25  * {@link Backend}), such as an XMPP contact from Telepathy or a vCard contact
26  * from evolution-data-server.
27  *
28  * All the personas belonging to one physical person are aggregated to form a
29  * single {@link Individual} representing that person.
30  */
31 public abstract class Folks.Persona : Object
32 {
33   /**
34    * The internal ID used to represent the Persona for linking.
35    *
36    * This is opaque, and shouldn't be parsed or considered meaningful by
37    * clients.
38    *
39    * The internal ID should be unique within a backend, but may not be unique
40    * across backends, so that links can be made between Personas with similar
41    * internal IDs.
42    */
43   /* For example: jabber:foo@xmpp.example.org or joe@example.org */
44   public string iid { get; construct; }
45
46   /**
47    * The universal ID used to represent the Persona outside its {@link Backend}.
48    *
49    * This is opaque, and should only be parsed by clients using
50    * {@link Persona.split_uid}.
51    *
52    * This is the canonical way to refer to any Persona. It is guaranteed to be
53    * unique within the Persona's {@link PersonaStore}.
54    *
55    * A Persona's UID is immutable over the life of the Persona in the backing
56    * store, so a given UID is guaranteed to refer to the same Persona each time
57    * libfolks is used, until the Persona is permanently removed from its backing
58    * store.
59    *
60    * @see Persona.build_uid
61    * @see Persona.split_uid
62    */
63   /* For example: telepathy:jabber:foo@xmpp.example.org or
64    * key-file:relationships.ini:joe@example.org
65    *
66    * It comprises three components, separated by colons:
67    * # {@link Backend.name}
68    * # {@link PersonaStore.id}
69    * # Persona identifier
70    * Each component is escaped by replacing all colons with double underscores
71    * before building the UID.*/
72   public string uid { get; construct; }
73
74   /**
75    * The human-readable, service-specific universal ID used to represent the
76    * Persona.
77    *
78    * For example: `foo@@xmpp.example.org`.
79    *
80    * This should be used whenever the user needs to be presented with a
81    * familiar, service-specific ID. For instance, in a prompt for the user to
82    * select a specific IM contact within an {@link Individual} to begin a chat
83    * with.
84    *
85    * This is not guaranteed to be unique outside of the Persona's
86    * {@link PersonaStore}.
87    *
88    * @since 0.1.13
89    */
90   public string display_id { get; construct; }
91
92   /**
93    * Whether the Persona is the user.
94    *
95    * Iff the Persona represents the user (the person who owns the account in
96    * the respective backend) this is `true`.
97    *
98    * @since 0.3.0
99    */
100   public bool is_user { get; construct; }
101
102   /**
103    * The {@link PersonaStore} which contains this Persona.
104    */
105   public weak PersonaStore store { get; construct; }
106
107   /**
108    * The names of the properties of this Persona which are linkable.
109    *
110    * If a property name is in this list, and the Persona is from a
111    * {@link PersonaStore} whose trust level is {@link PersonaStoreTrust.FULL},
112    * the {@link IndividualAggregator} should be able to reliably use the value
113    * of the property from a given Persona instance to link the Persona with
114    * other Personas and form {@link Individual}s.
115    *
116    * Note that {@link Persona.uid} is always implicitly a member of this list,
117    * and doesn't need to be added explicitly.
118    *
119    * This list will have no effect if the Persona's {@link PersonaStore} trust
120    * level is not {@link PersonaStoreTrust.FULL}.
121    *
122    * @since 0.1.13
123    */
124   public abstract string[] linkable_properties { get; }
125
126   /**
127    * Callback into the aggregator to manipulate a link mapping.
128    *
129    * This is a callback provided by the {@link IndividualAggregator} whenever
130    * a {@link Persona.linkable_property_to_links} method is called, which should
131    * be called by the `linkable_property_to_links` implementation for each
132    * linkable-property-to-individual mapping it wants to add or remove in the
133    * aggregator.
134    *
135    * @param link the mapping string to be added to the
136    * {@link IndividualAggregator}
137    * @since 0.1.13
138    */
139   public delegate void LinkablePropertyCallback (string link);
140
141   /* FIXME: This code should move to the ImDetails interface as a concrete
142    * method of the interface. However, that depends on bgo#624842 */
143   /**
144    * Produce one or more mapping strings for the given property's value.
145    *
146    * This is a virtual method, to be overridden by subclasses of {@link Persona}
147    * who have linkable properties. Each of their linkable properties should be
148    * handled by their implementation of this function, examining the current
149    * value of the property and calling `callback` with one or more mapping
150    * strings for the property's value. Each of these mapping strings will be
151    * added to the {@link IndividualAggregator}'s link map, related to the
152    * {@link Individual} instance which contains this {@link Persona}.
153    *
154    * @param prop_name the name of the linkable property to use, which must be
155    * listed in {@link Persona.linkable_properties}
156    * @param callback a callback to execute for each of the mapping strings
157    * generated by this property
158    * @see Persona.linkable_properties
159    * @since 0.1.13
160    */
161   public virtual void linkable_property_to_links (string prop_name,
162       LinkablePropertyCallback callback)
163     {
164       /* Backend-specific Persona subclasses should override this if they have
165        * any linkable properties */
166       assert_not_reached ();
167     }
168
169   private static string _escape_uid_component (string component)
170     {
171       /* Escape colons with backslashes */
172       string escaped = component.replace ("\\", "\\\\");
173       return escaped.replace (":", "\\:");
174     }
175
176   private static string _unescape_uid_component (string component)
177     {
178       /* Unescape colons and backslashes */
179       string unescaped = component.replace ("\\:", ":");
180       return unescaped.replace ("\\", "\\\\");
181     }
182
183   /**
184    * Build a UID from the given components.
185    *
186    * Each component is escaped before the UID is built.
187    *
188    * @param backend_name the {@link Backend.name}
189    * @param persona_store_id the {@link PersonaStore.id}
190    * @param persona_id the Persona identifier (backend-specific)
191    * @return a valid UID
192    * @see Persona.split_uid
193    * @since 0.1.13
194    */
195   public static string build_uid (string backend_name,
196       string persona_store_id, string persona_id)
197     {
198       return "%s:%s:%s".printf (Persona._escape_uid_component (backend_name),
199           Persona._escape_uid_component (persona_store_id),
200           Persona._escape_uid_component (persona_id));
201     }
202
203   /**
204    * Split a UID into its component parts.
205    *
206    * Each component is unescaped before being returned. The UID //must// be
207    * correctly formed.
208    *
209    * @param uid a valid UID
210    * @param backend_name the {@link Backend.name}
211    * @param persona_store_id the {@link PersonaStore.id}
212    * @param persona_id the Persona identifier (backend-specific)
213    * @see Persona.build_uid
214    * @since 0.1.13
215    */
216   public static void split_uid (string uid, out string backend_name,
217       out string persona_store_id, out string persona_id)
218     {
219       assert (uid.validate ());
220
221       size_t backend_name_length = 0, persona_store_id_length = 0;
222       var escaped = false;
223       for (unowned string i = uid; i.get_char () != '\0'; i = i.next_char ())
224         {
225           if (i.get_char () == '\\')
226             escaped = !escaped;
227           else if (escaped == false && i.get_char () == ':')
228             {
229               if (backend_name_length == 0)
230                 backend_name_length = ((char*) i) - ((char*) uid);
231               else
232                 persona_store_id_length =
233                   (((char*) i) - ((char*) uid)) - backend_name_length - 1;
234             }
235         }
236
237       assert (backend_name_length != 0 && persona_store_id_length != 0);
238
239       backend_name = Persona._unescape_uid_component (
240           uid.substring (0, (long) backend_name_length));
241       persona_store_id = Persona._unescape_uid_component (
242           ((string) ((char*) uid + backend_name_length + 1)).substring (0,
243               (long) persona_store_id_length));
244       persona_id = Persona._unescape_uid_component (
245           ((string) ((char*) uid + backend_name_length +
246               persona_store_id_length + 2)));
247     }
248 }