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 [ + + + - ]: 61 : G_DEFINE_FINAL_TYPE (ValentContactsPlugin, valent_contacts_plugin, VALENT_TYPE_DEVICE_PLUGIN)
27 : :
28 : :
29 : : /*
30 : : * Local Contacts
31 : : */
32 : : static void
33 : 8 : on_local_uid_changed (GSettings *settings,
34 : : const char *key,
35 : : ValentContactsPlugin *self)
36 : : {
37 : 8 : ValentContacts *contacts = valent_contacts_get_default ();
38 : 16 : g_autofree char *local_iri = NULL;
39 : :
40 [ - + ]: 8 : g_clear_object (&self->local_contacts);
41 : :
42 : 8 : local_iri = g_settings_get_string (settings, "local-uid");
43 [ + - + + ]: 8 : 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 : 8 : 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 : 8 : }
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 : 3 : vcard_data = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_21);
121 : :
122 : 3 : json_builder_set_member_name (builder, uid);
123 : 3 : json_builder_add_string_value (builder, vcard_data);
124 : :
125 : 3 : json_array_add_string_element (uids_response, uid);
126 : : }
127 : : }
128 : :
129 : 1 : uids_node = json_node_new (JSON_NODE_ARRAY);
130 : 1 : json_node_take_array (uids_node, g_steal_pointer (&uids_response));
131 : 1 : json_builder_set_member_name (builder, "uids");
132 : 1 : json_builder_add_value (builder, g_steal_pointer (&uids_node));
133 : :
134 : 1 : response = valent_packet_end (&builder);
135 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
136 : : #endif /* __clang_analyzer__ */
137 : : }
138 : :
139 : : static void
140 : 1 : valent_contact_plugin_handle_request_all_uids_timestamps (ValentContactsPlugin *self,
141 : : JsonNode *packet)
142 : : {
143 : 1 : GSettings *settings;
144 : 1 : g_autoptr (JsonBuilder) builder = NULL;
145 [ - + ]: 1 : g_autoptr (JsonNode) response = NULL;
146 [ - - + - ]: 1 : g_autoptr (JsonNode) uids_node = NULL;
147 : 1 : JsonArray *uids_response = NULL;
148 : 1 : unsigned int n_items = 0;
149 : :
150 [ + - ]: 1 : g_assert (VALENT_IS_CONTACTS_PLUGIN (self));
151 : :
152 : 1 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
153 [ + - - + ]: 1 : if (self->local_contacts == NULL || !g_settings_get_boolean (settings, "local-sync"))
154 [ # # ]: 0 : return;
155 : :
156 : 1 : valent_packet_init (&builder, "kdeconnect.contacts.response_uids_timestamps");
157 : 1 : uids_response = json_array_new ();
158 : :
159 : 1 : n_items = g_list_model_get_n_items (self->local_contacts);
160 [ + + ]: 4 : for (unsigned int i = 0; i < n_items; i++)
161 : : {
162 : 3 : g_autoptr (EContact) contact = NULL;
163 : 3 : const char *uid;
164 : 3 : int64_t timestamp = 0;
165 : :
166 : 3 : contact = g_list_model_get_item (self->local_contacts, i);
167 : 3 : uid = e_contact_get_const (contact, E_CONTACT_UID);
168 : 3 : json_builder_set_member_name (builder, uid);
169 : :
170 : : // TODO: We probably need to convert between the custom field
171 : : // `X-KDECONNECT-TIMESTAMP` and `E_CONTACT_REV` to set a proper timestamp
172 : 3 : timestamp = 0;
173 : 3 : json_builder_add_int_value (builder, timestamp);
174 [ + - ]: 3 : json_array_add_string_element (uids_response, uid);
175 : : }
176 : :
177 : 1 : uids_node = json_node_new (JSON_NODE_ARRAY);
178 : 1 : json_node_take_array (uids_node, g_steal_pointer (&uids_response));
179 : 1 : json_builder_set_member_name (builder, "uids");
180 : 1 : json_builder_add_value (builder, g_steal_pointer (&uids_node));
181 : :
182 : 1 : response = valent_packet_end (&builder);
183 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
184 : : }
185 : :
186 : : /*
187 : : * GActions
188 : : */
189 : : static void
190 : 1 : contacts_fetch_action (GSimpleAction *action,
191 : : GVariant *parameter,
192 : : gpointer user_data)
193 : : {
194 : 1 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (user_data);
195 : 2 : g_autoptr (JsonNode) packet = NULL;
196 : :
197 [ + - ]: 1 : g_assert (VALENT_IS_CONTACTS_PLUGIN (self));
198 : :
199 : 1 : packet = valent_packet_new ("kdeconnect.contacts.request_all_uids_timestamps");
200 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
201 : 1 : }
202 : :
203 : : static const GActionEntry actions[] = {
204 : : {"fetch", contacts_fetch_action, NULL, NULL, NULL}
205 : : };
206 : :
207 : : /*
208 : : * ValentDevicePlugin
209 : : */
210 : : static void
211 : 18 : valent_contacts_plugin_update_state (ValentDevicePlugin *plugin,
212 : : ValentDeviceState state)
213 : : {
214 : 18 : gboolean available;
215 : :
216 : 18 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
217 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
218 : :
219 : 18 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
220 : 18 : }
221 : :
222 : : static void
223 : 4 : valent_contacts_plugin_handle_packet (ValentDevicePlugin *plugin,
224 : : const char *type,
225 : : JsonNode *packet)
226 : : {
227 : 4 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (plugin);
228 : :
229 [ + - ]: 4 : g_assert (VALENT_IS_CONTACTS_PLUGIN (plugin));
230 [ - + ]: 4 : g_assert (type != NULL);
231 [ - + ]: 4 : g_assert (VALENT_IS_PACKET (packet));
232 : :
233 : : /* A response to a request for a listing of contacts or vCards
234 : : */
235 [ + + ]: 4 : if (g_str_equal (type, "kdeconnect.contacts.response_uids_timestamps") ||
236 [ + + ]: 3 : g_str_equal (type, "kdeconnect.contacts.response_vcards"))
237 : 2 : valent_contacts_device_handle_packet (self->adapter, type, packet);
238 : :
239 : : /* A request for a listing of contacts
240 : : */
241 [ + + ]: 2 : else if (g_str_equal (type, "kdeconnect.contacts.request_all_uids_timestamps"))
242 : 1 : valent_contact_plugin_handle_request_all_uids_timestamps (self, packet);
243 : :
244 : : /* A request for contacts
245 : : */
246 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.contacts.request_vcards_by_uid"))
247 : 1 : valent_contact_plugin_handle_request_vcards_by_uid (self, packet);
248 : :
249 : : else
250 : 0 : g_assert_not_reached ();
251 : 4 : }
252 : :
253 : : /*
254 : : * ValentObject
255 : : */
256 : : static void
257 : 10 : valent_contacts_plugin_destroy (ValentObject *object)
258 : : {
259 : 10 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (object);
260 : 10 : ValentComponent *component = NULL;
261 : :
262 : 10 : g_cancellable_cancel (self->cancellable);
263 [ + + ]: 10 : g_clear_object (&self->cancellable);
264 : :
265 [ + + ]: 10 : if (self->adapter != NULL)
266 : : {
267 : 5 : component = VALENT_COMPONENT (valent_contacts_get_default ());
268 : 5 : valent_component_unexport_adapter (component, VALENT_EXTENSION (self->adapter));
269 : 5 : valent_object_destroy (VALENT_OBJECT (self->adapter));
270 [ + - ]: 5 : g_clear_object (&self->adapter);
271 : : }
272 : :
273 [ + + ]: 10 : g_clear_object (&self->local_contacts);
274 : :
275 : 10 : VALENT_OBJECT_CLASS (valent_contacts_plugin_parent_class)->destroy (object);
276 : 10 : }
277 : :
278 : : /*
279 : : * GObject
280 : : */
281 : : static void
282 : 5 : valent_contacts_plugin_constructed (GObject *object)
283 : : {
284 : 5 : ValentContactsPlugin *self = VALENT_CONTACTS_PLUGIN (object);
285 : 5 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
286 : 5 : ValentComponent *component = NULL;
287 : 5 : ValentDevice *device = NULL;
288 : 5 : GSettings *settings;
289 : :
290 : 5 : G_OBJECT_CLASS (valent_contacts_plugin_parent_class)->constructed (object);
291 : :
292 : 5 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
293 : : actions,
294 : : G_N_ELEMENTS (actions),
295 : : plugin);
296 : 5 : self->cancellable = g_cancellable_new ();
297 : :
298 : 5 : device = valent_resource_get_source (VALENT_RESOURCE (self));
299 : 5 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
300 : :
301 : : /* Remote Adapter
302 : : */
303 : 5 : self->adapter = valent_contacts_device_new (device);
304 : 5 : component = VALENT_COMPONENT (valent_contacts_get_default ());
305 : 5 : valent_component_export_adapter (component, VALENT_EXTENSION (self->adapter));
306 : :
307 : : /* Local address book, shared with remote device
308 : : */
309 : 5 : g_signal_connect_object (settings,
310 : : "changed::local-uid",
311 : : G_CALLBACK (on_local_uid_changed),
312 : : self,
313 : : G_CONNECT_DEFAULT);
314 : 5 : on_local_uid_changed (settings, "local-uid", self);
315 : 5 : }
316 : :
317 : : static void
318 : 18 : valent_contacts_plugin_class_init (ValentContactsPluginClass *klass)
319 : : {
320 : 18 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
321 : 18 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
322 : 18 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
323 : :
324 : 18 : object_class->constructed = valent_contacts_plugin_constructed;
325 : :
326 : 18 : vobject_class->destroy = valent_contacts_plugin_destroy;
327 : :
328 : 18 : plugin_class->handle_packet = valent_contacts_plugin_handle_packet;
329 : 18 : plugin_class->update_state = valent_contacts_plugin_update_state;
330 : : }
331 : :
332 : : static void
333 : 5 : valent_contacts_plugin_init (ValentContactsPlugin *self)
334 : : {
335 : 5 : }
336 : :
|