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