Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-sms-utils"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <gdk/gdk.h>
10 : : #include <glib/gi18n.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-sms-utils.h"
14 : :
15 : :
16 [ + + ]: 22 : G_DEFINE_QUARK (VALENT_CONTACT_ICON, valent_contact_icon)
17 [ + + ]: 26 : G_DEFINE_QUARK (VALENT_CONTACT_PAINTABLE, valent_contact_paintable)
18 : :
19 : :
20 : : static GLoadableIcon *
21 : 11 : _e_contact_get_icon (EContact *contact)
22 : : {
23 : 11 : GLoadableIcon *icon = NULL;
24 : 22 : g_autoptr (EContactPhoto) photo = NULL;
25 : 11 : const unsigned char *data;
26 : 11 : size_t len;
27 : 11 : const char *uri;
28 : :
29 [ + - + - : 11 : g_assert (E_IS_CONTACT (contact));
- + - - ]
30 : :
31 : 11 : icon = g_object_get_qdata (G_OBJECT (contact), valent_contact_icon_quark ());
32 : :
33 [ - + - - : 11 : if (G_IS_LOADABLE_ICON (icon))
- - - - ]
34 : : return icon;
35 : :
36 [ + - ]: 11 : if ((photo = e_contact_get (contact, E_CONTACT_PHOTO)) == NULL)
37 : : return NULL;
38 : :
39 [ + - ]: 11 : if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED &&
40 [ + - ]: 11 : (data = e_contact_photo_get_inlined (photo, &len)))
41 : : {
42 [ + - ]: 11 : g_autoptr (GBytes) bytes = NULL;
43 : :
44 : 11 : bytes = g_bytes_new (data, len);
45 [ + - ]: 11 : icon = G_LOADABLE_ICON (g_bytes_icon_new (bytes));
46 : : }
47 [ # # ]: 0 : else if (photo->type == E_CONTACT_PHOTO_TYPE_URI &&
48 [ # # ]: 0 : (uri = e_contact_photo_get_uri (photo)))
49 : : {
50 : 11 : g_autoptr (GFile) file = NULL;
51 : :
52 : 0 : file = g_file_new_for_uri (uri);
53 [ # # ]: 0 : icon = G_LOADABLE_ICON (g_file_icon_new (file));
54 : : }
55 : :
56 [ + - + - : 11 : if (G_IS_LOADABLE_ICON (icon))
+ - + - ]
57 : : {
58 : 11 : g_object_set_qdata_full (G_OBJECT (contact),
59 : : valent_contact_icon_quark (),
60 : : icon,
61 : : g_object_unref);
62 : : }
63 : :
64 : 11 : return icon;
65 : : }
66 : :
67 : : static GdkPaintable *
68 : 15 : _e_contact_get_paintable (EContact *contact,
69 : : int size,
70 : : int scale,
71 : : GError **error)
72 : : {
73 : 15 : GdkPaintable *paintable = NULL;
74 : 15 : GLoadableIcon *icon = NULL;
75 : 30 : g_autoptr (GInputStream) stream = NULL;
76 [ + + ]: 15 : g_autoptr (GdkPixbuf) pixbuf = NULL;
77 : :
78 [ + - + - : 15 : g_assert (E_IS_CONTACT (contact));
- + - - ]
79 [ - + ]: 15 : g_assert (size > 0);
80 [ - + ]: 15 : g_assert (scale > 0);
81 : :
82 : 15 : paintable = g_object_get_qdata (G_OBJECT (contact),
83 : : valent_contact_paintable_quark ());
84 : :
85 [ + + ]: 15 : if (GDK_IS_PAINTABLE (paintable))
86 : : return paintable;
87 : :
88 [ + - ]: 11 : if ((icon = _e_contact_get_icon (contact)) == NULL)
89 : : return NULL;
90 : :
91 [ - + ]: 11 : if ((stream = g_loadable_icon_load (icon, -1, NULL, NULL, error)) == NULL)
92 : : return NULL;
93 : :
94 : 11 : pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
95 : : size,
96 : : size,
97 : : TRUE,
98 : : NULL,
99 : : error);
100 : :
101 [ + - ]: 11 : if (pixbuf == NULL)
102 : : return NULL;
103 : :
104 : 11 : paintable = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf));
105 : 11 : g_object_set_qdata_full (G_OBJECT (contact),
106 : : valent_contact_paintable_quark (),
107 : : paintable,
108 : : g_object_unref);
109 : :
110 : 11 : return paintable;
111 : : }
112 : :
113 : : /**
114 : : * valent_sms_avatar_from_contact:
115 : : * @avatar: a `AdwAvatar`
116 : : * @contact: a `EContact`
117 : : *
118 : : * Set the `GdkPaintable` for @avatar from @contact.
119 : : */
120 : : void
121 : 15 : valent_sms_avatar_from_contact (AdwAvatar *avatar,
122 : : EContact *contact)
123 : : {
124 : 15 : GdkPaintable *paintable;
125 : 15 : const char *name;
126 : 15 : int size, scale;
127 : :
128 [ + - ]: 15 : g_return_if_fail (ADW_IS_AVATAR (avatar));
129 [ + - + - : 15 : g_return_if_fail (E_IS_CONTACT (contact));
- + - - ]
130 : :
131 : 15 : size = adw_avatar_get_size (avatar);
132 : 15 : scale = gtk_widget_get_scale_factor (GTK_WIDGET (avatar));
133 : 15 : paintable = _e_contact_get_paintable (contact, size, scale, NULL);
134 : :
135 : 15 : name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
136 : 15 : adw_avatar_set_text (avatar, name);
137 : :
138 : 15 : adw_avatar_set_custom_image (avatar, paintable);
139 : 15 : adw_avatar_set_show_initials (avatar, paintable != NULL);
140 : : }
141 : :
142 : : static void
143 : 4 : valent_sms_contact_from_phone_cb (ValentContactStore *store,
144 : : GAsyncResult *result,
145 : : gpointer user_data)
146 : : {
147 : 4 : g_autoptr (GTask) task = user_data;
148 : 4 : const char *number = g_task_get_task_data (task);
149 [ - - + - ]: 4 : g_autoslist (GObject) contacts = NULL;
150 : 4 : EContact *contact = NULL;
151 : 4 : GError *error = NULL;
152 : :
153 : 4 : contacts = valent_contact_store_query_finish (store, result, &error);
154 : :
155 [ - + ]: 4 : if (error != NULL)
156 : 0 : return g_task_return_error (task, error);
157 : :
158 : : /* Prefer using libphonenumber */
159 [ + - ]: 4 : if (e_phone_number_is_supported ())
160 : : {
161 [ + - ]: 4 : if (contacts != NULL)
162 : 4 : contact = g_object_ref (contacts->data);
163 : : }
164 : : else
165 : : {
166 : 0 : g_autofree char *normalized = NULL;
167 : :
168 : 0 : normalized = valent_phone_number_normalize (number);
169 : :
170 [ # # ]: 0 : for (const GSList *iter = contacts; iter; iter = iter->next)
171 : : {
172 [ # # ]: 0 : if (valent_phone_number_of_contact (iter->data, normalized))
173 : : {
174 : 0 : contact = g_object_ref (iter->data);
175 : 0 : break;
176 : : }
177 : : }
178 : : }
179 : :
180 [ - + ]: 4 : if (contact == NULL)
181 : : {
182 : 0 : contact = e_contact_new ();
183 : 0 : e_contact_set (contact, E_CONTACT_FULL_NAME, number);
184 : 0 : e_contact_set (contact, E_CONTACT_PHONE_OTHER, number);
185 : : }
186 : :
187 : 4 : g_task_return_pointer (task, contact, g_object_unref);
188 : : }
189 : :
190 : : /**
191 : : * valent_sms_contact_from_phone:
192 : : * @store: a `ValentContactStore`
193 : : * @phone: a phone number
194 : : * @cancellable: (nullable): `GCancellable`
195 : : * @callback: (scope async): a `GAsyncReadyCallback`
196 : : * @user_data: (closure): user supplied data
197 : : *
198 : : * A convenience wrapper around [method@Valent.ContactStore.query] for finding a
199 : : * contact by phone number.
200 : : *
201 : : * Call valent_sms_contact_from_phone_finish() to get the result.
202 : : */
203 : : void
204 : 5 : valent_sms_contact_from_phone (ValentContactStore *store,
205 : : const char *number,
206 : : GCancellable *cancellable,
207 : : GAsyncReadyCallback callback,
208 : : gpointer user_data)
209 : : {
210 : 0 : g_autoptr (GTask) task = NULL;
211 : 0 : g_autoptr (EBookQuery) query = NULL;
212 [ + - ]: 5 : g_autofree char *sexp = NULL;
213 : :
214 [ + - ]: 5 : g_return_if_fail (VALENT_IS_CONTACT_STORE (store));
215 [ + - - + ]: 5 : g_return_if_fail (number != NULL && *number != '\0');
216 [ - + - - : 5 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
217 : :
218 : 5 : task = g_task_new (store, cancellable, callback, user_data);
219 [ + - ]: 5 : g_task_set_source_tag (task, valent_sms_contact_from_phone);
220 [ - + ]: 10 : g_task_set_task_data (task, g_strdup (number), g_free);
221 : :
222 : : /* Prefer using libphonenumber */
223 [ + - ]: 5 : if (e_phone_number_is_supported ())
224 : : {
225 : 5 : query = e_book_query_field_test (E_CONTACT_TEL,
226 : : E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER,
227 : : number);
228 : 5 : sexp = e_book_query_to_string (query);
229 : : }
230 : : else
231 : : {
232 : 0 : query = e_book_query_field_exists (E_CONTACT_TEL);
233 : 0 : sexp = e_book_query_to_string (query);
234 : : }
235 : :
236 : 5 : valent_contact_store_query (store,
237 : : sexp,
238 : : cancellable,
239 : : (GAsyncReadyCallback)valent_sms_contact_from_phone_cb,
240 : : g_steal_pointer (&task));
241 : : }
242 : :
243 : : /**
244 : : * valent_sms_contact_from_phone_finish:
245 : : * @store: a `ValentContactStore`
246 : : * @result: a `GAsyncResult`
247 : : * @error: (nullable): a `GError`
248 : : *
249 : : * Finish an operation started by valent_sms_contact_from_phone().
250 : : *
251 : : * Returns: (transfer full): an `EContact`
252 : : */
253 : : EContact *
254 : 4 : valent_sms_contact_from_phone_finish (ValentContactStore *store,
255 : : GAsyncResult *result,
256 : : GError **error)
257 : : {
258 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_CONTACT_STORE (store), NULL);
259 [ - + ]: 4 : g_return_val_if_fail (g_task_is_valid (result, store), NULL);
260 [ + - - + ]: 4 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
261 : :
262 : 4 : return g_task_propagate_pointer (G_TASK (result), error);
263 : : }
264 : :
265 : : /**
266 : : * valent_phone_number_normalize:
267 : : * @number: a phone number string
268 : : *
269 : : * Return a normalized version of @number.
270 : : *
271 : : * Returns: (transfer full): a normalized phone number string
272 : : *
273 : : * Since: 1.0
274 : : */
275 : : char *
276 : 15 : valent_phone_number_normalize (const char *number)
277 : : {
278 : 30 : g_autofree char *normalized = NULL;
279 : 15 : size_t i, len;
280 : 15 : const char *s = number;
281 : :
282 [ + - ]: 15 : g_return_val_if_fail (number != NULL, NULL);
283 : :
284 : : #ifndef __clang_analyzer__
285 : 15 : i = 0;
286 : 15 : len = strlen (number);
287 : 15 : normalized = g_new (char, len + 1);
288 : :
289 [ + + ]: 19 : while (*s == '0')
290 : 4 : s++;
291 : :
292 [ + + ]: 212 : while (*s != '\0')
293 : : {
294 [ + + ]: 197 : if G_LIKELY (g_ascii_isdigit (*s))
295 : 152 : normalized[i++] = *s;
296 : :
297 : 197 : s++;
298 : : }
299 : 15 : normalized[i++] = '\0';
300 : 15 : normalized = g_realloc (normalized, i * sizeof (char));
301 : :
302 : : /* If we fail or the number is stripped completely, return the original */
303 [ - + ]: 15 : if G_UNLIKELY (*normalized == '\0')
304 [ # # ]: 0 : return g_strdup (number);
305 : : #endif /* __clang_analyzer__ */
306 : :
307 : : return g_steal_pointer (&normalized);
308 : : }
309 : :
310 : : static inline gboolean
311 : 1 : valent_phone_number_compare_normalized (const char *number1,
312 : : const char *number2)
313 : : {
314 : 1 : size_t number1_len, number2_len;
315 : :
316 [ + - ]: 1 : g_assert (number1 != NULL);
317 [ - + ]: 1 : g_assert (number2 != NULL);
318 : :
319 : 1 : number1_len = strlen (number1);
320 : 1 : number2_len = strlen (number2);
321 : :
322 [ - + ]: 1 : if (number1_len > number2_len)
323 : 0 : return g_str_equal (number1 + number1_len - number2_len, number2);
324 : : else
325 : 1 : return g_str_equal (number2 + number2_len - number1_len, number1);
326 : : }
327 : :
328 : : /**
329 : : * valent_phone_number_equal:
330 : : * @number1: a phone number string
331 : : * @number2: a phone number string
332 : : *
333 : : * Normalize and compare @number1 with @number2 and return %TRUE if they match
334 : : * or %FALSE if they don't.
335 : : *
336 : : * Returns: %TRUE or %FALSE indicating equality
337 : : *
338 : : * Since: 1.0
339 : : */
340 : : gboolean
341 : 4 : valent_phone_number_equal (const char *number1,
342 : : const char *number2)
343 : : {
344 : 8 : g_autofree char *normalized1 = NULL;
345 : 4 : g_autofree char *normalized2 = NULL;
346 : 4 : size_t num1_len, num2_len;
347 : :
348 [ + - ]: 4 : g_return_val_if_fail (number1 != NULL, FALSE);
349 [ - + ]: 4 : g_return_val_if_fail (number2 != NULL, FALSE);
350 : :
351 : 4 : normalized1 = valent_phone_number_normalize (number1);
352 : 4 : normalized2 = valent_phone_number_normalize (number2);
353 : :
354 : 4 : num1_len = strlen (normalized1);
355 : 4 : num2_len = strlen (normalized2);
356 : :
357 [ - + ]: 4 : if (num1_len > num2_len)
358 : 0 : return g_str_equal (normalized1 + num1_len - num2_len, normalized2);
359 : : else
360 : 4 : return g_str_equal (normalized2 + num2_len - num1_len, normalized1);
361 : : }
362 : :
363 : : /**
364 : : * valent_phone_number_of_contact:
365 : : * @contact: an `EContact`
366 : : * @number: a normalized phone number
367 : : *
368 : : * Check if @contact has @number as one of it's phone numbers.
369 : : *
370 : : * Since this function is typically used to test against a series of contacts, it is expected that
371 : : * @number has already been normalized with valent_phone_number_normalize().
372 : : *
373 : : * Returns: %TRUE if @number belongs to the contact
374 : : */
375 : : gboolean
376 : 1 : valent_phone_number_of_contact (EContact *contact,
377 : : const char *number)
378 : : {
379 : 1 : GList *numbers = NULL;
380 : 1 : gboolean ret = FALSE;
381 : :
382 [ + - + - : 1 : g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
- + - - ]
383 [ - + ]: 1 : g_return_val_if_fail (number != NULL, FALSE);
384 : :
385 : 1 : numbers = e_contact_get (contact, E_CONTACT_TEL);
386 : :
387 [ + - ]: 1 : for (const GList *iter = numbers; iter; iter = iter->next)
388 : : {
389 : 1 : g_autofree char *normalized = NULL;
390 : :
391 : 1 : normalized = valent_phone_number_normalize (iter->data);
392 : :
393 [ - + ]: 1 : if ((ret = valent_phone_number_compare_normalized (number, normalized)))
394 : : break;
395 : : }
396 : 1 : g_list_free_full (numbers, g_free);
397 : :
398 : 1 : return ret;
399 : : }
400 : :
|