kf-persona.vala 14.2 KB
Newer Older
Philip Withnall's avatar
Philip Withnall committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
 * 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 <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Philip Withnall <philip.withnall@collabora.co.uk>
 */

using GLib;
22
using Gee;
Philip Withnall's avatar
Philip Withnall committed
23
24
25
26
27
using Folks;
using Folks.Backends.Kf;

/**
 * A persona subclass which represents a single persona from a simple key file.
28
29
 *
 * @since 0.1.13
Philip Withnall's avatar
Philip Withnall committed
30
31
 */
public class Folks.Backends.Kf.Persona : Folks.Persona,
32
    AliasDetails,
33
    AntiLinkable,
34
35
    ImDetails,
    WebServiceDetails
Philip Withnall's avatar
Philip Withnall committed
36
{
37
  private HashMultiMap<string, ImFieldDetails> _im_addresses;
38
  private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
39
  private string _alias = ""; /* must not be null */
40
41
42
43
44
  private const string[] _linkable_properties =
    {
      "im-addresses",
      "web-service-addresses"
    };
45
46
47
48
  private const string[] _writeable_properties =
    {
      "alias",
      "im-addresses",
49
50
      "web-service-addresses",
      "anti-links"
51
    };
52
53
54
55
56
57
58
59

  /**
   * {@inheritDoc}
   */
  public override string[] linkable_properties
    {
      get { return this._linkable_properties; }
    }
60

61
62
63
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
64
   * @since 0.6.0
65
66
67
68
69
70
   */
  public override string[] writeable_properties
    {
      get { return this._writeable_properties; }
    }

71
72
73
  /**
   * {@inheritDoc}
   *
Philip Withnall's avatar
Philip Withnall committed
74
   * @since 0.1.15
75
   */
76
  [CCode (notify = false)]
77
78
79
  public string alias
    {
      get { return this._alias; }
80
81
      set { this.change_alias.begin (value); }
    }
82

83
84
85
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
86
   * @since 0.6.2
87
88
89
   */
  public async void change_alias (string alias) throws PropertyError
    {
90
91
92
93
94
95
      /* Deal with badly-behaved callers. */
      if (alias == null)
        {
          alias = "";
        }

96
      if (this._alias == alias)
97
        {
98
99
          return;
        }
100

101
      debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
102

103
104
      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
      key_file.set_string (this.display_id, "__alias", alias);
105
      yield ((Kf.PersonaStore) this.store).save_key_file ();
106

107
108
      this._alias = alias;
      this.notify_property ("alias");
109
    }
Philip Withnall's avatar
Philip Withnall committed
110
111
112
113

  /**
   * {@inheritDoc}
   */
114
  [CCode (notify = false)]
115
  public MultiMap<string, ImFieldDetails> im_addresses
Philip Withnall's avatar
Philip Withnall committed
116
    {
117
118
119
      get { return this._im_addresses; }
      set { this.change_im_addresses.begin (value); }
    }
Philip Withnall's avatar
Philip Withnall committed
120

121
122
123
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
124
   * @since 0.6.2
125
126
127
128
   */
  public async void change_im_addresses (
      MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
    {
129
130
      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();

131
132
      /* Remove the current IM addresses from the key file */
      foreach (var protocol1 in this._im_addresses.get_keys ())
Philip Withnall's avatar
Philip Withnall committed
133
        {
134
          try
Philip Withnall's avatar
Philip Withnall committed
135
            {
136
              key_file.remove_key (this.display_id, protocol1);
137
138
139
140
141
            }
          catch (KeyFileError e1)
            {
              /* Ignore the error, since it's just a group or key not found
               * error. */
142
            }
143
        }
Philip Withnall's avatar
Philip Withnall committed
144

145
146
147
148
      /* Add the new IM addresses to the key file and build a normalised
       * table of them to set as the new property value */
      var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
          null, null,
149
150
          (GLib.HashFunc) ImFieldDetails.hash,
          (GLib.EqualFunc) ImFieldDetails.equal);
Philip Withnall's avatar
Philip Withnall committed
151

152
153
154
155
      foreach (var protocol2 in im_addresses.get_keys ())
        {
          var addresses = im_addresses.get (protocol2);
          var normalised_addresses = new HashSet<string> ();
156

157
158
159
160
          foreach (var im_fd in addresses)
            {
              string normalised_address;
              try
161
                {
162
163
164
165
166
167
168
169
170
171
172
173
174
                  normalised_address = ImDetails.normalise_im_address (
                      im_fd.value, protocol2);
                }
               catch (ImDetailsError e2)
                {
                  throw new PropertyError.INVALID_VALUE (
                      /* Translators: this is an error message for if the user
                       * provides an invalid IM address. The first parameter is
                       * an IM address (e.g. “foo@jabber.org”), the second is
                       * the name of a protocol (e.g. “jabber”) and the third is
                       * an error message. */
                      _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
                      im_fd.value, protocol2, e2.message);
175
                }
176

177
178
179
              normalised_addresses.add (normalised_address);
              var new_im_fd = new ImFieldDetails (normalised_address);
              new_im_addresses.set (protocol2, new_im_fd);
180
            }
Philip Withnall's avatar
Philip Withnall committed
181

182
183
          string[] addrs = (string[]) normalised_addresses.to_array ();
          addrs.length = normalised_addresses.size;
184

185
          key_file.set_string_list (this.display_id, protocol2, addrs);
Philip Withnall's avatar
Philip Withnall committed
186
        }
187
188
189
190
191
192

      /* Get the PersonaStore to save the key file */
      yield ((Kf.PersonaStore) this.store).save_key_file ();

      this._im_addresses = new_im_addresses;
      this.notify_property ("im-addresses");
Philip Withnall's avatar
Philip Withnall committed
193
194
    }

195
196
197
  /**
   * {@inheritDoc}
   */
198
  [CCode (notify = false)]
199
  public MultiMap<string, WebServiceFieldDetails> web_service_addresses
200
    {
201
202
203
      get { return this._web_service_addresses; }
      set { this.change_web_service_addresses.begin (value); }
    }
204

205
206
207
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
208
   * @since 0.6.2
209
210
211
212
213
   */
  public async void change_web_service_addresses (
      MultiMap<string, WebServiceFieldDetails> web_service_addresses)
          throws PropertyError
    {
214
215
      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();

216
217
      /* Remove the current web service addresses from the key file */
      foreach (var web_service1 in this._web_service_addresses.get_keys ())
218
        {
219
          try
220
            {
221
              key_file.remove_key (this.display_id,
222
                  "web-service." + web_service1);
223
            }
224
          catch (KeyFileError e)
225
            {
226
227
228
229
              /* Ignore the error, since it's just a group or key not found
               * error. */
            }
        }
230

231
232
233
234
235
      /* Add the new web service addresses to the key file and build a
       * table of them to set as the new property value */
      var new_web_service_addresses =
        new HashMultiMap<string, WebServiceFieldDetails> (
            null, null,
236
237
            (GLib.HashFunc) WebServiceFieldDetails.hash,
            (GLib.EqualFunc) WebServiceFieldDetails.equal);
238

239
240
241
      foreach (var web_service2 in web_service_addresses.get_keys ())
        {
          var ws_fds = web_service_addresses.get (web_service2);
242

243
244
245
          string[] addrs = new string[0];
          foreach (var ws_fd1 in ws_fds)
            addrs += ws_fd1.value;
246

247
          key_file.set_string_list (this.display_id,
248
              "web-service." + web_service2, addrs);
249

250
251
          foreach (var ws_fd2 in ws_fds)
            new_web_service_addresses.set (web_service2, ws_fd2);
252
        }
253
254
255
256
257
258

      /* Get the PersonaStore to save the key file */
      yield ((Kf.PersonaStore) this.store).save_key_file ();

      this._web_service_addresses = new_web_service_addresses;
      this.notify_property ("web-service-addresses");
259
260
    }

261
262
263
264
265
266
  private HashSet<string> _anti_links;
  private Set<string> _anti_links_ro;

  /**
   * {@inheritDoc}
   *
Philip Withnall's avatar
Philip Withnall committed
267
   * @since 0.7.3
268
269
270
271
272
273
274
275
276
277
278
   */
  [CCode (notify = false)]
  public Set<string> anti_links
    {
      get { return this._anti_links_ro; }
      set { this.change_anti_links.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Philip Withnall's avatar
Philip Withnall committed
279
   * @since 0.7.3
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
   */
  public async void change_anti_links (Set<string> anti_links)
      throws PropertyError
    {
      if (Folks.Internal.equal_sets<string> (anti_links, this.anti_links))
        {
          return;
        }

      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();

      /* Skip the persona's UID; don't allow reflexive anti-links. */
      anti_links.remove (this.uid);

      key_file.set_string_list (this.display_id,
          Kf.PersonaStore.anti_links_key_name, anti_links.to_array ());

      /* Get the PersonaStore to save the key file */
      yield ((Kf.PersonaStore) this.store).save_key_file ();

      /* Update the stored anti-links. */
      this._anti_links.clear ();
      this._anti_links.add_all (anti_links);
      this.notify_property ("anti-links");
    }

Philip Withnall's avatar
Philip Withnall committed
306
307
308
309
310
311
  /**
   * Create a new persona.
   *
   * Create a new persona for the {@link PersonaStore} `store`, representing
   * the Persona given by the group `uid` in the key file `key_file`.
   */
312
  public Persona (string id, Folks.PersonaStore store)
Philip Withnall's avatar
Philip Withnall committed
313
    {
314
315
      var iid = store.id + ":" + id;
      var uid = this.build_uid ("key-file", store.id, id);
Philip Withnall's avatar
Philip Withnall committed
316

317
      Object (display_id: id,
318
319
              iid: iid,
              uid: uid,
Philip Withnall's avatar
Philip Withnall committed
320
              store: store,
321
              is_user: false);
322
    }
Philip Withnall's avatar
Philip Withnall committed
323

324
325
326
327
  construct
    {
      debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
          this.iid, this.display_id);
328

329
      this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
330
          null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
331
332
333
      this._web_service_addresses =
        new HashMultiMap<string, WebServiceFieldDetails> (
            null, null,
334
335
            (GLib.HashFunc) WebServiceFieldDetails.hash,
            (GLib.EqualFunc) WebServiceFieldDetails.equal);
336
337
      this._anti_links = new HashSet<string> ();
      this._anti_links_ro = this._anti_links.read_only_view;
Philip Withnall's avatar
Philip Withnall committed
338
339

      /* Load the IM addresses from the key file */
340
341
      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();

Philip Withnall's avatar
Philip Withnall committed
342
343
      try
        {
344
          var keys = key_file.get_keys (this.display_id);
345
          foreach (unowned string key in keys)
Philip Withnall's avatar
Philip Withnall committed
346
            {
347
348
349
              /* Alias */
              if (key == "__alias")
                {
350
                  this._alias = key_file.get_string (this.display_id, key);
351
352
353
354
355
356

                  if (this._alias == null)
                    {
                      this._alias = "";
                    }

357
                  debug ("    Loaded alias '%s'.", this._alias);
358
359
360
                  continue;
                }

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
              /* Anti-links. */
              if (key == Kf.PersonaStore.anti_links_key_name)
                {
                  var anti_link_array =
                      key_file.get_string_list (this.display_id, key);

                  if (anti_link_array != null)
                    {
                      foreach (var anti_link in anti_link_array)
                        {
                          this._anti_links.add (anti_link);
                        }

                      debug ("    Loaded %u anti-links.",
                          anti_link_array.length);
                      continue;
                    }
                }

380
381
382
383
384
385
              /* Web service addresses */
              var decomposed_key = key.split(".", 2);
              if (decomposed_key.length == 2 &&
                  decomposed_key[0] == "web-service")
                {
                  unowned string web_service = decomposed_key[1];
386
                  var web_service_addresses = key_file.get_string_list (
387
                      this.display_id, web_service);
388

389
390
                  foreach (var web_service_address in web_service_addresses)
                    {
391
                      this._web_service_addresses.set (web_service,
392
                          new WebServiceFieldDetails (web_service_address));
393
                    }
394

395
396
397
                  continue;
                }

398
              /* IM addresses */
399
              unowned string protocol = key;
400
              var im_addresses = key_file.get_string_list (
401
                  this.display_id, protocol);
Philip Withnall's avatar
Philip Withnall committed
402

403
              foreach (var im_address in im_addresses)
404
                {
405
                  string address;
406
407
                  try
                    {
Travis Reitter's avatar
Travis Reitter committed
408
                      address = ImDetails.normalise_im_address (im_address,
409
                          protocol);
410
                    }
Travis Reitter's avatar
Travis Reitter committed
411
                  catch (ImDetailsError e)
412
                    {
413
414
                      /* Warn of and ignore any invalid IM addresses */
                      warning (e.message);
415
                      continue;
416
                    }
Philip Withnall's avatar
Philip Withnall committed
417

418
419
                  var im_fd = new ImFieldDetails (address);
                  this._im_addresses.set (protocol, im_fd);
420
                }
Philip Withnall's avatar
Philip Withnall committed
421
422
423
424
            }
        }
      catch (KeyFileError e)
        {
425
426
427
428
429
          /* We get a GROUP_NOT_FOUND exception if we're creating a new
           * Persona, since it doesn't yet exist in the key file. We shouldn't
           * get any other exceptions, since we're iterating through a list of
           * keys we've just retrieved. */
          if (!(e is KeyFileError.GROUP_NOT_FOUND))
430
431
432
433
            {
              /* Translators: the parameter is an error message. */
              warning (_("Couldn't load data from key file: %s"), e.message);
            }
Philip Withnall's avatar
Philip Withnall committed
434
435
        }
    }
436

437
438
439
  /**
   * {@inheritDoc}
   */
440
441
442
443
444
  public override void linkable_property_to_links (string prop_name,
      Folks.Persona.LinkablePropertyCallback callback)
    {
      if (prop_name == "im-addresses")
        {
445
          foreach (var protocol in this._im_addresses.get_keys ())
446
            {
447
              var im_addresses = this._im_addresses.get (protocol);
448

449
450
              foreach (var im_fd in im_addresses)
                  callback (protocol + ":" + im_fd.value);
451
            }
452
        }
453
454
      else if (prop_name == "web-service-addresses")
        {
455
          foreach (var web_service in this.web_service_addresses.get_keys ())
456
            {
457
458
              var web_service_addresses =
                  this._web_service_addresses.get (web_service);
459

460
461
              foreach (var ws_fd in web_service_addresses)
                  callback (web_service + ":" + ws_fd.value);
462
463
            }
        }
464
465
466
467
468
469
      else
        {
          /* Chain up */
          base.linkable_property_to_links (prop_name, callback);
        }
    }
Philip Withnall's avatar
Philip Withnall committed
470
}