Post-release version bump
[platform/upstream/folks.git] / folks / phone-details.vala
1 /*
2  * Copyright (C) 2011 Collabora Ltd.
3  * Copyright (C) 2011 Philip Withnall
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *       Marco Barisione <marco.barisione@collabora.co.uk>
20  *       Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
21  *       Philip Withnall <philip@tecnocode.co.uk>
22  */
23
24 using GLib;
25 using Gee;
26
27 /**
28  * Object representing a phone number that can have some parameters associated
29  * with it.
30  *
31  * See {@link Folks.AbstractFieldDetails} for details on common parameter names
32  * and values.
33  *
34  * @since 0.6.0
35  */
36 public class Folks.PhoneFieldDetails : AbstractFieldDetails<string>
37 {
38   private const string[] _extension_chars = { "p", "P", "w", "W", "x", "X" };
39   private const string[] _common_delimiters = { ",", ".", "(", ")", "-", " ",
40       "\t", "/" };
41   private const string[] _valid_digits = { "#", "*", "0", "1", "2", "3", "4",
42       "5", "6", "7", "8", "9" };
43
44   private string _id;
45   /**
46    * {@inheritDoc}
47    */
48   public override string id
49     {
50       get { return this._id; }
51       set { this._id = (value != null ? value : ""); }
52     }
53
54   /**
55    * Create a new PhoneFieldDetails.
56    *
57    * @param value the value of the field, which should be a non-empty phone
58    * number (no particular format is mandated)
59    * @param parameters initial parameters. See
60    * {@link AbstractFieldDetails.parameters}. A `null` value is equivalent to a
61    * empty map of parameters.
62    *
63    * @return a new PhoneFieldDetails
64    *
65    * @since 0.6.0
66    */
67   public PhoneFieldDetails (string value,
68       MultiMap<string, string>? parameters = null)
69     {
70       if (value == "")
71         {
72           warning ("Empty phone number passed to PhoneFieldDetails.");
73         }
74
75       Object (value: value,
76               parameters: parameters);
77     }
78
79   /**
80    * {@inheritDoc}
81    *
82    * @since 0.6.0
83    */
84   public override bool equal (AbstractFieldDetails<string> that)
85     {
86       return base.equal (that);
87     }
88
89   /**
90    * {@inheritDoc}
91    */
92   public override bool values_equal (AbstractFieldDetails<string> that)
93     {
94       var _that_fd = that as PhoneFieldDetails;
95       if (_that_fd == null)
96         return false;
97       PhoneFieldDetails that_fd = (!) _that_fd;
98
99       var n1 = this._drop_extension (this.get_normalised ());
100       var n2 = this._drop_extension (that_fd.get_normalised ());
101
102       /* Based on http://blog.barisione.org/2010-06/handling-phone-numbers/ */
103       if (n1.length >= 7 && n2.length >= 7)
104         {
105           var n1_reduced = n1.slice (-7, n1.length);
106           var n2_reduced = n2.slice (-7, n2.length);
107
108           debug ("[PhoneDetails.equal] Comparing %s with %s",
109               n1_reduced, n2_reduced);
110
111           return  n1_reduced == n2_reduced;
112         }
113
114       return n1 == n2;
115     }
116
117   /**
118    * {@inheritDoc}
119    *
120    * @since 0.6.0
121    */
122   public override uint hash ()
123     {
124       return base.hash ();
125     }
126
127   /**
128    * Return this object's normalised phone number.
129    *
130    * Typical normalisations:
131    *
132    *  - `1-800-123-4567` → `18001234567`
133    *  - `+1-800-123-4567` → `18001234567`
134    *  - `+1-800-123-4567P123` → `18001234567P123`
135    *
136    * @return the normalised form of `number`
137    *
138    * @since 0.6.0
139    */
140   public string get_normalised ()
141     {
142       string normalised_number = "";
143
144       for (int i = 0; i < this.value.length; i++)
145         {
146           var digit = this.value.slice (i, i + 1);
147
148           if (i == 0 && digit == "+")
149             {
150               /* we drop the initial + */
151               continue;
152             }
153           else if (digit in this._extension_chars ||
154               digit in this._valid_digits)
155             {
156               /* lets keep valid digits */
157               normalised_number += digit;
158             }
159           else if (digit in this._common_delimiters)
160             {
161               continue;
162             }
163           else
164             {
165               debug ("[PhoneDetails.get_normalised] unknown digit: %s", digit);
166             }
167        }
168
169       return normalised_number.up ();
170     }
171
172   /**
173    * Returns the given number without its extension (if any).
174    *
175    * @param number the phone number to process
176    * @return the number without its extension; if the number didn't have an
177    * extension in the first place, the number is returned unmodified
178    *
179    * @since 0.6.0
180    */
181   internal static string _drop_extension (string number)
182     {
183       for (var i = 0; i < PhoneFieldDetails._extension_chars.length; i++)
184         {
185           if (number.index_of (PhoneFieldDetails._extension_chars[i]) >= 0)
186             {
187               return number.split (PhoneFieldDetails._extension_chars[i])[0];
188             }
189         }
190
191       return number;
192     }
193 }
194
195 /**
196  * Interface for classes that can provide a phone number, such as
197  * {@link Persona} and {@link Individual}.
198  *
199  * @since 0.3.5
200  */
201 public interface Folks.PhoneDetails : Object
202 {
203   /**
204    * The phone numbers of the contact.
205    *
206    * A list of phone numbers associated to the contact.
207    *
208    * @since 0.6.0
209    */
210   public abstract Set<PhoneFieldDetails> phone_numbers { get; set; }
211
212   /**
213    * Change the contact's phone numbers.
214    *
215    * It's preferred to call this rather than setting
216    * {@link PhoneDetails.phone_numbers} directly, as this method gives error
217    * notification and will only return once the phone numbers have been written
218    * to the relevant backing store (or the operation's failed).
219    *
220    * @param phone_numbers the set of phone numbers
221    * @throws PropertyError if setting the phone numbers failed
222    * @since 0.6.2
223    */
224   public virtual async void change_phone_numbers (
225       Set<PhoneFieldDetails> phone_numbers) throws PropertyError
226     {
227       /* Default implementation. */
228       throw new PropertyError.NOT_WRITEABLE (
229           _("Phone numbers are not writeable on this contact."));
230     }
231 }