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-contacts-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <json-glib/json-glib.h>
10 : : #include <valent.h>
11 : :
12 : : #include "valent-contacts-device.h"
13 : : #include "valent-contacts-plugin.h"
14 : :
15 : :
16 : : struct _ValentContactsPlugin
17 : : {
18 : : ValentDevicePlugin parent_instance;
19 : :
20 : : GCancellable *cancellable;
21 : :
22 : : ValentContactsAdapter *adapter;
23 : : GListModel *local_contacts;
24 : : };
25 : :
26 [ + + + - ]: 40 : G_DEFINE_FINAL_TYPE (ValentContactsPlugin, valent_contacts_plugin, VALENT_TYPE_DEVICE_PLUGIN)
27 : :
28 : :
29 : : /*
30 : : * Local Contacts
31 : : */
32 : : static void
33 : 9 : on_local_uid_changed (GSettings *settings,
34 : : const char *key,
35 : : ValentContactsPlugin *self)
36 : : {
37 : 9 : ValentContacts *contacts = valent_contacts_get_default ();
38 : 18 : g_autofree char *local_iri = NULL;
39 : :
40 [ - + ]: 9 : g_clear_object (&self->local_contacts);
41 : :
42 : 9 : local_iri = g_settings_get_string (settings, "local-uid");
43 [ + - + + ]: 9 : if (local_iri != NULL && *local_iri != '\0')
44 : : {
45 : 3 : unsigned int n_adapters = g_list_model_get_n_items (G_LIST_MODEL (contacts));
46 : :
47 [ + - ]: 3 : for (unsigned int i = 0; i < n_adapters; i++)
48 : : {
49 : 9 : g_autoptr (GListModel) adapter = NULL;
50 : 3 : const char *iri = NULL;
51 : :
52 : 3 : adapter = g_list_model_get_item (G_LIST_MODEL (contacts), i);
53 : 3 : iri = valent_resource_get_iri (VALENT_RESOURCE (adapter));
54 [ + - ]: 3 : if (g_strcmp0 (local_iri, iri) == 0)
55 : : {
56 : 3 : self->local_contacts = g_list_model_get_item (adapter, i);
57 [ + - ]: 3 : break;
58 : : }
59 : : }
60 : : }
61 : 9 : }
62 : :
63 : : static void
64 : 1 : valent_contact_plugin_handle_request_vcards_by_uid (ValentContactsPlugin *self,
65 : : JsonNode *packet)
66 : : {
67 : 1 : g_autoptr (JsonBuilder) builder = NULL;
68 [ - + ]: 1 : g_autoptr (JsonNode) response = NULL;
69 [ + - - - ]: 1 : g_autoptr (JsonNode) uids_node = NULL;
70 : 1 : JsonArray *uids_response = NULL;
71 : 1 : JsonArray *uids_request = NULL;
72 : 1 : GSettings *settings;
73 [ + - - - ]: 1 : g_autoptr (GHashTable) uidx = NULL;
74 : 1 : unsigned int n_uids = 0;
75 : 1 : unsigned int n_contacts = 0;
76 : :
77 [ - + ]: 1 : g_assert (VALENT_IS_CONTACTS_PLUGIN (self));
78 : :
79 : : #ifndef __clang_analyzer__
80 : : /* Bail if exporting is disabled */
81 : 1 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
82 : :
83 [ + - - + ]: 1 : if (self->local_contacts == NULL || !g_settings_get_boolean (settings, "local-sync"))
84 : 0 : return;
85 : :
86 [ - + ]: 1 : if (!valent_packet_get_array (packet, "uids", &uids_request))
87 : : {
88 : 0 : g_debug ("%s(): expected \"uids\" field holding an array",
89 : : G_STRFUNC);
90 : 0 : return;
91 : : }
92 : :
93 : 1 : uidx = g_hash_table_new (g_str_hash, g_str_equal);
94 : 1 : n_uids = json_array_get_length (uids_request);
95 [ + + ]: 4 : for (unsigned int i = 0; i < n_uids; i++)
96 : : {
97 : 3 : const char *uid = json_array_get_string_element (uids_request, i);
98 [ + - + - ]: 3 : if (uid != NULL && *uid != '\0')
99 : 3 : g_hash_table_add (uidx, (char *)uid);
100 : : }
101 : :
102 [ - + ]: 1 : if (g_hash_table_size (uidx) == 0)
103 [ # # ]: 0 : return;
104 : :
105 : 1 : valent_packet_init (&builder, "kdeconnect.contacts.response_vcards");
106 : 1 : uids_response = json_array_new ();
107 : :
108 : 1 : n_contacts = g_list_model_get_n_items (self->local_contacts);
109 [ + + ]: 4 : for (unsigned int i = 0; i < n_contacts; i++)
110 : : {
111 : 3 : g_autoptr (EContact) contact = NULL;
112 : 3 : const char *uid = NULL;
113 : :
114 : 3 : contact = g_list_model_get_item (self->local_contacts, i);
115 : 3 : uid = e_contact_get_const (contact, E_CONTACT_UID);
116 [ + - ]: 3 : if (g_hash_table_contains (uidx, uid))
117 : : {
118 : 3 : g_autofree char *vcard_data = NULL;
119 : :
120 : : #if EDS_CHECK_VERSION (3, 59, 0)
121 : : vcard_data = e_vcard_to_string (E_VCARD (contact));
122 : : #else
123 : 3 : vcard_data = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_21);
124 : : #endif
125 : :
126 : 3 : json_builder_set_member_name (builder, uid);
127 : 3 : json_builder_add_string_value (builder, vcard_data);
128 : :
129 : 3 : json_array_add_string_element (uids_response, uid);
130 : : }
131 : : }
132 : :
133 : 1 : uids_node = json_node_new (JSON_NODE_ARRAY);
134 : 1 : json_node_take_array (uids_node, g_steal_pointer (&uids_response));
135 : 1 : json_builder_set_member_name (builder, "uids");
136 : 1 : json_builder_add_value (builder, g_steal_pointer (&uids_node));
137 : :
138 : 1 : response = valent_packet_end (&builder);
139 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
140 : : #endif /* __clang_analyzer__ */
141 : : }
142 : :
143 : : static void
144 : 1 : valent_contact_plugin_handle_request_all_uids_timestamps (ValentContactsPlugin *self,
145 : : JsonNode *packet)
146 : : {
147 : 1 : GSettings *settings;
148 : 1 : g_autoptr (JsonBuilder) builder = NULL;
149 [ - + ]: 1 : g_autoptr (JsonNode) response = NULL;
150 [ - - + - ]: 1 : g_autoptr (JsonNode) uids_node = NULL;
151 : 1 : JsonArray *uids_response = NULL;
152 : 1 : unsigned int n_items = 0;
153 : :
154 [ - + ]: 1 : g_assert (VALENT_IS_CONTACTS_PLUGIN (self));
155 : :
156 : 1 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
157 [ + - - + ]: 1 : if (self->local_contacts == NULL || !g_settings_get_boolean (settings, "local-sync"))
158 [ # # ]: 0 : return;
159 : :
160 : 1 : valent_packet_init (&builder, "kdeconnect.contacts.response_uids_timestamps");
161 : 1 : uids_response = json_array_new ();
162 : :
163 : 1 : n_items = g_list_model_get_n_items (self->local_contacts);
164 [ + + ]: 4 : for (unsigned int i = 0; i < n_items; i++)
165 : : {
166 : 3 : g_autoptr (EContact) contact = NULL;
167 : 3 : const char *uid;
168 : 3 : int64_t timestamp = 0;
169 : :
170 : 3 : contact = g_list_model_get_item (self->local_contacts, i);
171 : 3 : uid = e_contact_get_const (contact, E_CONTACT_UID);
172 : 3 : json_builder_set_member_name (builder, uid);
173 : :
174 : : // TODO: We probably need to convert between the custom field
175 : : // `X-KDECONNECT-TIMESTAMP` and `E_CONTACT_REV` to set a proper timestamp
176 : 3 : timestamp = 0;
177 : 3 : json_builder_add_int_value (builder, timestamp);
178 [ + - ]: 3 : json_array_add_string_element (uids_response, uid);
179 : : }
180 : :
181 : 1 : uids_node = json_node_new (JSON_NODE_ARRAY);
182 : 1 : json_node_take_array (uids_node, g_steal_pointer (&uids_response));
183 : 1 : json_builder_set_member_name (builder, "uids");
184 : 1 : json_builder_add_value (builder, g_steal_pointer (&uids_node));
185 : :
186 : 1 : response = valent_packet_end (&builder);
187 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
188 : : }
189 : :
190 : : /*
191 : : * GActions
192 : : */
193 : : static void
194 : 1 : contacts_fetch_action (GSimpleAction *action,
195 : : GVariant *parameter,
196 : : gpointer user_data)
197 : : {
198 : 1 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (user_data);
199 : 2 : g_autoptr (JsonNode) packet = NULL;
200 : :
201 [ - + ]: 1 : g_assert (VALENT_IS_CONTACTS_PLUGIN (self));
202 : :
203 : 1 : packet = valent_packet_new ("kdeconnect.contacts.request_all_uids_timestamps");
204 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
205 : 1 : }
206 : :
207 : : static const GActionEntry actions[] = {
208 : : {"fetch", contacts_fetch_action, NULL, NULL, NULL}
209 : : };
210 : :
211 : : /*
212 : : * ValentDevicePlugin
213 : : */
214 : : static void
215 : 12 : valent_contacts_plugin_update_state (ValentDevicePlugin *plugin,
216 : : ValentDeviceState state)
217 : : {
218 : 12 : gboolean available;
219 : :
220 [ + + ]: 12 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
221 [ + + ]: 6 : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
222 : :
223 : 12 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
224 : 12 : }
225 : :
226 : : static void
227 : 4 : valent_contacts_plugin_handle_packet (ValentDevicePlugin *plugin,
228 : : const char *type,
229 : : JsonNode *packet)
230 : : {
231 : 4 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (plugin);
232 : :
233 [ - + ]: 4 : g_assert (VALENT_IS_CONTACTS_PLUGIN (plugin));
234 [ + - ]: 4 : g_assert (type != NULL);
235 [ + - ]: 4 : g_assert (VALENT_IS_PACKET (packet));
236 : :
237 : : /* A response to a request for a listing of contacts or vCards
238 : : */
239 [ + + ]: 4 : if (g_str_equal (type, "kdeconnect.contacts.response_uids_timestamps") ||
240 [ + + ]: 3 : g_str_equal (type, "kdeconnect.contacts.response_vcards"))
241 : 2 : valent_contacts_device_handle_packet (self->adapter, type, packet);
242 : :
243 : : /* A request for a listing of contacts
244 : : */
245 [ + + ]: 2 : else if (g_str_equal (type, "kdeconnect.contacts.request_all_uids_timestamps"))
246 : 1 : valent_contact_plugin_handle_request_all_uids_timestamps (self, packet);
247 : :
248 : : /* A request for contacts
249 : : */
250 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.contacts.request_vcards_by_uid"))
251 : 1 : valent_contact_plugin_handle_request_vcards_by_uid (self, packet);
252 : :
253 : : else
254 : 0 : g_assert_not_reached ();
255 : 4 : }
256 : :
257 : : /*
258 : : * ValentObject
259 : : */
260 : : static void
261 : 12 : valent_contacts_plugin_destroy (ValentObject *object)
262 : : {
263 : 12 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (object);
264 : 12 : ValentComponent *component = NULL;
265 : :
266 : 12 : g_cancellable_cancel (self->cancellable);
267 [ + + ]: 12 : g_clear_object (&self->cancellable);
268 : :
269 [ + + ]: 12 : if (self->adapter != NULL)
270 : : {
271 : 6 : component = VALENT_COMPONENT (valent_contacts_get_default ());
272 : 6 : valent_component_unexport_adapter (component, VALENT_EXTENSION (self->adapter));
273 : 6 : valent_object_destroy (VALENT_OBJECT (self->adapter));
274 [ + - ]: 6 : g_clear_object (&self->adapter);
275 : : }
276 : :
277 [ + + ]: 12 : g_clear_object (&self->local_contacts);
278 : :
279 : 12 : VALENT_OBJECT_CLASS (valent_contacts_plugin_parent_class)->destroy (object);
280 : 12 : }
281 : :
282 : : /*
283 : : * GObject
284 : : */
285 : : static void
286 : 6 : valent_contacts_plugin_constructed (GObject *object)
287 : : {
288 : 6 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (object);
289 : 6 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
290 : 6 : ValentComponent *component = NULL;
291 : 6 : ValentDevice *device = NULL;
292 : 6 : GSettings *settings;
293 : :
294 : 6 : G_OBJECT_CLASS (valent_contacts_plugin_parent_class)->constructed (object);
295 : :
296 : 6 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
297 : : actions,
298 : : G_N_ELEMENTS (actions),
299 : : plugin);
300 : 6 : self->cancellable = g_cancellable_new ();
301 : :
302 : 6 : device = valent_resource_get_source (VALENT_RESOURCE (self));
303 : 6 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
304 : :
305 : : /* Remote Adapter
306 : : */
307 : 6 : self->adapter = valent_contacts_device_new (device);
308 : 6 : component = VALENT_COMPONENT (valent_contacts_get_default ());
309 : 6 : valent_component_export_adapter (component, VALENT_EXTENSION (self->adapter));
310 : :
311 : : /* Local address book, shared with remote device
312 : : */
313 : 6 : g_signal_connect_object (settings,
314 : : "changed::local-uid",
315 : : G_CALLBACK (on_local_uid_changed),
316 : : self,
317 : : G_CONNECT_DEFAULT);
318 : 6 : on_local_uid_changed (settings, "local-uid", self);
319 : 6 : }
320 : :
321 : : static void
322 : 11 : valent_contacts_plugin_class_init (ValentContactsPluginClass *klass)
323 : : {
324 : 11 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
325 : 11 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
326 : 11 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
327 : :
328 : 11 : object_class->constructed = valent_contacts_plugin_constructed;
329 : :
330 : 11 : vobject_class->destroy = valent_contacts_plugin_destroy;
331 : :
332 : 11 : plugin_class->handle_packet = valent_contacts_plugin_handle_packet;
333 : 11 : plugin_class->update_state = valent_contacts_plugin_update_state;
334 : : }
335 : :
336 : : static void
337 : 6 : valent_contacts_plugin_init (ValentContactsPlugin *self)
338 : : {
339 : 6 : }
340 : :
|