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-share-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <gio/gio.h>
10 : : #include <json-glib/json-glib.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-share-plugin.h"
14 : : #include "valent-share-download.h"
15 : : #include "valent-share-upload.h"
16 : :
17 : :
18 : : struct _ValentSharePlugin
19 : : {
20 : : ValentDevicePlugin parent_instance;
21 : :
22 : : GHashTable *transfers;
23 : : ValentTransfer *upload;
24 : : ValentTransfer *download;
25 : : };
26 : :
27 [ + + + - ]: 118 : G_DEFINE_FINAL_TYPE (ValentSharePlugin, valent_share_plugin, VALENT_TYPE_DEVICE_PLUGIN)
28 : :
29 : :
30 : : static GFile *
31 : 7 : valent_share_plugin_create_download_file (ValentSharePlugin *self,
32 : : const char *filename,
33 : : gboolean unique)
34 : : {
35 : 7 : GSettings *settings;
36 : 14 : g_autofree char *download_folder = NULL;
37 : :
38 [ - + ]: 7 : g_return_val_if_fail (VALENT_IS_SHARE_PLUGIN (self), NULL);
39 [ + - ]: 7 : g_return_val_if_fail (filename != NULL, NULL);
40 : :
41 : : /* Check for a configured download directory, returning a fallback if
42 : : * necessary, but don't save the fallback as though it were configured. */
43 : 7 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
44 : 7 : download_folder = g_settings_get_string (settings, "download-folder");
45 : :
46 [ + - + - ]: 7 : if (download_folder == NULL || *download_folder == '\0')
47 : : {
48 : 7 : const char *user_download = NULL;
49 : :
50 : 7 : user_download = valent_get_user_directory (G_USER_DIRECTORY_DOWNLOAD);
51 : 7 : g_set_str (&download_folder, user_download);
52 : : }
53 : :
54 [ - + ]: 7 : if (g_mkdir_with_parents (download_folder, 0700) == -1)
55 : : {
56 : 0 : int error = errno;
57 : :
58 : 0 : g_critical ("%s(): creating \"%s\": %s",
59 : : G_STRFUNC,
60 : : download_folder,
61 : : g_strerror (error));
62 : : }
63 : :
64 : 7 : return valent_get_user_file (download_folder, filename, unique);
65 : : }
66 : :
67 : : /*
68 : : * File Downloads
69 : : */
70 : : static void
71 : 11 : valent_share_download_file_notification (ValentSharePlugin *self,
72 : : ValentTransfer *transfer)
73 : : {
74 : 11 : g_autoptr (ValentDevice) device = NULL;
75 [ + - - - ]: 11 : g_autoptr (GNotification) notification = NULL;
76 [ + - - - ]: 11 : g_autoptr (GIcon) icon = NULL;
77 : 11 : const char *title = NULL;
78 [ + - - - ]: 11 : g_autofree char *body = NULL;
79 : 11 : g_autofree char *id = NULL;
80 : 11 : unsigned int n_files = 0;
81 : 11 : const char *device_name;
82 : 11 : ValentTransferState state = VALENT_TRANSFER_STATE_PENDING;
83 : :
84 [ - + ]: 11 : g_return_if_fail (VALENT_IS_TRANSFER (transfer));
85 [ + - ]: 11 : g_return_if_fail (VALENT_IS_SHARE_DOWNLOAD (transfer));
86 : :
87 [ + - ]: 11 : if ((n_files = g_list_model_get_n_items (G_LIST_MODEL (transfer))) == 0)
88 : : return;
89 : :
90 : 11 : g_object_get (transfer,
91 : : "device", &device,
92 : : "id", &id,
93 : : "state", &state,
94 : : NULL);
95 : 11 : device_name = valent_device_get_name (device);
96 : :
97 [ + + - - ]: 11 : switch (state)
98 : : {
99 : 7 : case VALENT_TRANSFER_STATE_PENDING:
100 : : case VALENT_TRANSFER_STATE_ACTIVE:
101 : 7 : icon = g_themed_icon_new ("document-save-symbolic");
102 : 7 : title = _("Transferring Files");
103 : 7 : body = g_strdup_printf (ngettext ("Receiving one file from %1$s",
104 : : "Receiving %2$d files from %1$s",
105 : : n_files),
106 : : device_name, n_files);
107 : 7 : break;
108 : :
109 : 4 : case VALENT_TRANSFER_STATE_COMPLETE:
110 : 4 : icon = g_themed_icon_new ("document-save-symbolic");
111 : 4 : title = _("Transfer Complete");
112 : 4 : body = g_strdup_printf (ngettext ("Received one file from %1$s",
113 : : "Received %2$d files from %1$s",
114 : : n_files),
115 : : device_name, n_files);
116 : 4 : break;
117 : :
118 : 0 : case VALENT_TRANSFER_STATE_FAILED:
119 : 0 : icon = g_themed_icon_new ("dialog-warning-symbolic");
120 : 0 : title = _("Transfer Failed");
121 : 0 : body = g_strdup_printf (ngettext ("Receiving one file from %1$s",
122 : : "Receiving %2$d files from %1$s",
123 : : n_files),
124 : : device_name, n_files);
125 : 0 : break;
126 : : }
127 : :
128 : 11 : notification = g_notification_new (title);
129 : 11 : g_notification_set_body (notification, body);
130 : 11 : g_notification_set_icon (notification, icon);
131 : :
132 [ + + ]: 11 : if (state == VALENT_TRANSFER_STATE_ACTIVE)
133 : : {
134 : 7 : valent_notification_add_device_button (notification,
135 : : device,
136 : 7 : _("Cancel"),
137 : : "share.cancel",
138 : : g_variant_new_string (id));
139 : : }
140 [ + - ]: 4 : else if (state == VALENT_TRANSFER_STATE_COMPLETE)
141 : : {
142 : 15 : g_autoptr (ValentTransfer) item = NULL;
143 [ + - ]: 4 : g_autoptr (GFile) file = NULL;
144 [ + - ]: 4 : g_autoptr (GFile) dir = NULL;
145 [ + - ]: 4 : g_autofree char *dirname = NULL;
146 : :
147 : 4 : item = g_list_model_get_item (G_LIST_MODEL (transfer), 0);
148 : 4 : file = valent_device_transfer_ref_file (VALENT_DEVICE_TRANSFER (item));
149 : 4 : dir = g_file_get_parent (file);
150 : 4 : dirname = g_file_get_uri (dir);
151 : :
152 : 4 : valent_notification_add_device_button (notification,
153 : : device,
154 : 4 : _("Open Folder"),
155 : : "share.view",
156 : : g_variant_new_string (dirname));
157 : :
158 [ + + ]: 4 : if (n_files == 1)
159 : : {
160 : 3 : g_autofree char *uri = NULL;
161 : :
162 : 3 : uri = g_file_get_uri (file);
163 : 6 : valent_notification_add_device_button (notification,
164 : : device,
165 : 3 : _("Open File"),
166 : : "share.view",
167 : : g_variant_new_string (uri));
168 : : }
169 : : }
170 : :
171 : 11 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
172 : : id,
173 : : notification);
174 : : }
175 : :
176 : : static void
177 : 4 : valent_share_download_file_cb (ValentTransfer *transfer,
178 : : GAsyncResult *result,
179 : : gpointer user_data)
180 : : {
181 : 8 : g_autoptr (ValentSharePlugin) self = VALENT_SHARE_PLUGIN (user_data);
182 : 4 : g_autofree char *id = NULL;
183 : 4 : g_autoptr (GError) error = NULL;
184 : :
185 [ - + ]: 4 : g_assert (VALENT_IS_TRANSFER (transfer));
186 : :
187 : 4 : id = valent_transfer_dup_id (transfer);
188 [ + - ]: 4 : if (valent_transfer_execute_finish (transfer, result, &error))
189 : : {
190 : 4 : valent_share_download_file_notification (self, transfer);
191 : : }
192 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
193 : : {
194 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
195 : 0 : valent_share_download_file_notification (self, transfer);
196 : : }
197 : : else
198 : : {
199 : 0 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
200 : : }
201 : :
202 [ + + ]: 4 : if (self->download == transfer)
203 [ + - ]: 3 : g_clear_object (&self->download);
204 : :
205 [ - + ]: 4 : g_hash_table_remove (self->transfers, id);
206 : 4 : }
207 : :
208 : : /*
209 : : * File Download (Open)
210 : : */
211 : : static void
212 : 1 : valent_share_download_open_notification (ValentSharePlugin *self,
213 : : ValentTransfer *transfer)
214 : : {
215 : 1 : g_autoptr (ValentDevice) device = NULL;
216 [ + - - - ]: 1 : g_autoptr (GFile) file = NULL;
217 [ + - - - ]: 1 : g_autoptr (GNotification) notification = NULL;
218 [ + - - - ]: 1 : g_autoptr (GIcon) icon = NULL;
219 : 1 : const char *title = NULL;
220 [ + - - - ]: 1 : g_autofree char *body = NULL;
221 : 1 : g_autofree char *id = NULL;
222 : 1 : g_autofree char *filename = NULL;
223 : 1 : const char *device_name;
224 : 1 : ValentTransferState state = VALENT_TRANSFER_STATE_PENDING;
225 : :
226 [ - + ]: 1 : g_return_if_fail (VALENT_IS_TRANSFER (transfer));
227 [ + - ]: 1 : g_return_if_fail (VALENT_IS_DEVICE_TRANSFER (transfer));
228 : :
229 : 1 : g_object_get (transfer,
230 : : "device", &device,
231 : : "file", &file,
232 : : "id", &id,
233 : : "state", &state,
234 : : NULL);
235 : 1 : device_name = valent_device_get_name (device);
236 : 1 : filename = g_file_get_basename (file);
237 : :
238 [ + - - - ]: 1 : switch (state)
239 : : {
240 : 1 : case VALENT_TRANSFER_STATE_PENDING:
241 : : case VALENT_TRANSFER_STATE_ACTIVE:
242 : 1 : icon = g_themed_icon_new ("document-save-symbolic");
243 : 1 : title = _("Transferring File");
244 : 1 : body = g_strdup_printf (_("Opening “%s” from “%s”"), filename, device_name);
245 : 1 : break;
246 : :
247 : 0 : case VALENT_TRANSFER_STATE_COMPLETE:
248 : 0 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
249 : 0 : return;
250 : :
251 : 0 : case VALENT_TRANSFER_STATE_FAILED:
252 : 0 : icon = g_themed_icon_new ("dialog-warning-symbolic");
253 : 0 : title = _("Transfer Failed");
254 : 0 : body = g_strdup_printf (_("Opening “%s” from “%s”"), filename, device_name);
255 : 0 : break;
256 : : }
257 : :
258 : 1 : notification = g_notification_new (title);
259 : 1 : g_notification_set_body (notification, body);
260 : 1 : g_notification_set_icon (notification, icon);
261 : :
262 [ + - ]: 1 : if (state == VALENT_TRANSFER_STATE_ACTIVE)
263 : : {
264 : 1 : valent_notification_add_device_button (notification,
265 : : device,
266 : 1 : _("Cancel"),
267 : : "share.cancel",
268 : : g_variant_new_string (id));
269 : : }
270 : :
271 : 1 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
272 : : id,
273 : : notification);
274 : : }
275 : :
276 : : static void
277 : 1 : valent_share_download_open_cb (ValentTransfer *transfer,
278 : : GAsyncResult *result,
279 : : gpointer user_data)
280 : : {
281 : 2 : g_autoptr (ValentSharePlugin) self = VALENT_SHARE_PLUGIN (user_data);
282 : 1 : g_autofree char *id = NULL;
283 : 1 : g_autoptr (GError) error = NULL;
284 : :
285 [ - + ]: 1 : g_assert (VALENT_IS_TRANSFER (transfer));
286 : :
287 : 1 : id = valent_transfer_dup_id (transfer);
288 : :
289 [ + - ]: 1 : if (valent_transfer_execute_finish (transfer, result, &error))
290 : : {
291 : 2 : g_autoptr (GFile) file = NULL;
292 [ + - ]: 1 : g_autofree char *uri = NULL;
293 : :
294 : 1 : file = valent_device_transfer_ref_file (VALENT_DEVICE_TRANSFER (transfer));
295 : 1 : uri = g_file_get_uri (file);
296 : :
297 : 1 : g_app_info_launch_default_for_uri_async (uri, NULL, NULL, NULL, NULL);
298 : 1 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
299 : : }
300 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
301 : : {
302 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
303 : 0 : valent_share_download_open_notification (self, transfer);
304 : : }
305 : :
306 [ - + ]: 1 : g_hash_table_remove (self->transfers, id);
307 : 1 : }
308 : :
309 : : /*
310 : : * File Upload (Open)
311 : : */
312 : : static void
313 : 2 : valent_share_upload_open_notification (ValentSharePlugin *self,
314 : : ValentTransfer *transfer)
315 : : {
316 : 2 : g_autoptr (ValentDevice) device = NULL;
317 [ + - - - ]: 2 : g_autoptr (GFile) file = NULL;
318 [ + - - - ]: 2 : g_autoptr (GNotification) notification = NULL;
319 [ + - - - ]: 2 : g_autoptr (GIcon) icon = NULL;
320 : 2 : const char *title = NULL;
321 [ + - - - ]: 2 : g_autofree char *body = NULL;
322 : 2 : g_autofree char *id = NULL;
323 : 2 : g_autofree char *filename = NULL;
324 : 2 : const char *device_name;
325 : 2 : ValentTransferState state = VALENT_TRANSFER_STATE_PENDING;
326 : :
327 [ - + ]: 2 : g_return_if_fail (VALENT_IS_TRANSFER (transfer));
328 [ + - ]: 2 : g_return_if_fail (VALENT_IS_DEVICE_TRANSFER (transfer));
329 : :
330 : 2 : g_object_get (transfer,
331 : : "device", &device,
332 : : "file", &file,
333 : : "id", &id,
334 : : "state", &state,
335 : : NULL);
336 : 2 : device_name = valent_device_get_name (device);
337 : 2 : filename = g_file_get_basename (file);
338 : :
339 [ + + - - ]: 2 : switch (state)
340 : : {
341 : 1 : case VALENT_TRANSFER_STATE_PENDING:
342 : : case VALENT_TRANSFER_STATE_ACTIVE:
343 : 1 : icon = g_themed_icon_new ("document-send-symbolic");
344 : 1 : title = _("Transferring File");
345 : 1 : body = g_strdup_printf (_("Opening “%s” on “%s”"), filename, device_name);
346 : 1 : break;
347 : :
348 : 1 : case VALENT_TRANSFER_STATE_COMPLETE:
349 : 1 : icon = g_themed_icon_new ("document-send-symbolic");
350 : 1 : title = _("Transfer Complete");
351 : 1 : body = g_strdup_printf (_("Opened “%s” on “%s”"), filename, device_name);
352 : 1 : break;
353 : :
354 : 0 : case VALENT_TRANSFER_STATE_FAILED:
355 : 0 : icon = g_themed_icon_new ("dialog-warning-symbolic");
356 : 0 : title = _("Transfer Failed");
357 : 0 : body = g_strdup_printf (_("Opening “%s” on “%s”"), filename, device_name);
358 : 0 : break;
359 : : }
360 : :
361 : 2 : notification = g_notification_new (title);
362 : 2 : g_notification_set_body (notification, body);
363 : 2 : g_notification_set_icon (notification, icon);
364 : :
365 [ + + ]: 2 : if (state == VALENT_TRANSFER_STATE_ACTIVE)
366 : : {
367 : 1 : valent_notification_add_device_button (notification,
368 : : device,
369 : 1 : _("Cancel"),
370 : : "share.cancel",
371 : : g_variant_new_string (id));
372 : : }
373 : :
374 : 2 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
375 : : id,
376 : : notification);
377 : : }
378 : :
379 : : static void
380 : 1 : valent_share_upload_open_cb (ValentTransfer *transfer,
381 : : GAsyncResult *result,
382 : : gpointer user_data)
383 : : {
384 : 2 : g_autoptr (ValentSharePlugin) self = VALENT_SHARE_PLUGIN (user_data);
385 : 1 : g_autofree char *id = NULL;
386 : 1 : g_autoptr (GError) error = NULL;
387 : :
388 [ - + ]: 1 : g_assert (VALENT_IS_DEVICE_TRANSFER (transfer));
389 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
390 : :
391 : 1 : id = valent_transfer_dup_id (transfer);
392 [ + - ]: 1 : if (valent_transfer_execute_finish (transfer, result, &error))
393 : : {
394 : 1 : valent_share_upload_open_notification (self, transfer);
395 : : }
396 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
397 : : {
398 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
399 : 0 : valent_share_upload_open_notification (self, transfer);
400 : : }
401 : : else
402 : : {
403 : 0 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
404 : : }
405 : :
406 [ - + ]: 1 : g_hash_table_remove (self->transfers, id);
407 : 1 : }
408 : :
409 : : static void
410 : 1 : valent_share_plugin_open_file (ValentSharePlugin *self,
411 : : GFile *file)
412 : : {
413 : 1 : ValentDevice *device = NULL;
414 : 2 : g_autoptr (ValentTransfer) transfer = NULL;
415 [ + - ]: 1 : g_autofree char *filename = NULL;
416 : 1 : g_autoptr (JsonBuilder) builder = NULL;
417 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
418 : :
419 : 1 : filename = g_file_get_basename (file);
420 : :
421 : 1 : valent_packet_init (&builder, "kdeconnect.share.request");
422 : 1 : json_builder_set_member_name (builder, "filename");
423 : 1 : json_builder_add_string_value (builder, filename);
424 : 1 : json_builder_set_member_name (builder, "open");
425 : 1 : json_builder_add_boolean_value (builder, TRUE);
426 : 1 : packet = valent_packet_end (&builder);
427 : :
428 : : /* File uploads that request to be opened are sent as discrete transfers
429 : : * because the remote client (i.e. kdeconnect-android) may download them
430 : : * discretely. Otherwise the remote client may get confused by the
431 : : * `numberOfFiles` field and consider a concurrent multi-file transfer as
432 : : * incomplete.
433 : : */
434 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
435 : 1 : transfer = valent_device_transfer_new (device, packet, file);
436 : 2 : g_hash_table_insert (self->transfers,
437 : 1 : valent_transfer_dup_id (transfer),
438 : : g_object_ref (transfer));
439 : :
440 : 1 : valent_transfer_execute (transfer,
441 : : NULL,
442 : : (GAsyncReadyCallback)valent_share_upload_open_cb,
443 : : g_object_ref (self));
444 [ + - ]: 1 : valent_share_upload_open_notification (self, transfer);
445 : 1 : }
446 : :
447 : : /*
448 : : * File Uploads
449 : : */
450 : : static void
451 : 7 : valent_share_upload_file_notification (ValentSharePlugin *self,
452 : : ValentTransfer *transfer)
453 : : {
454 : 7 : g_autoptr (ValentDevice) device = NULL;
455 [ + - - - ]: 7 : g_autoptr (GNotification) notification = NULL;
456 [ + - - - ]: 7 : g_autoptr (GIcon) icon = NULL;
457 : 7 : const char *title = NULL;
458 [ + - - - ]: 7 : g_autofree char *body = NULL;
459 : 7 : g_autofree char *id = NULL;
460 : 7 : unsigned int n_files = 0;
461 : 7 : const char *device_name;
462 : 7 : ValentTransferState state = VALENT_TRANSFER_STATE_PENDING;
463 : :
464 [ - + ]: 7 : g_return_if_fail (VALENT_IS_TRANSFER (transfer));
465 [ + - ]: 7 : g_return_if_fail (VALENT_IS_SHARE_UPLOAD (transfer));
466 : :
467 [ + - ]: 7 : if ((n_files = g_list_model_get_n_items (G_LIST_MODEL (transfer))) == 0)
468 : : return;
469 : :
470 : 7 : g_object_get (transfer,
471 : : "device", &device,
472 : : "id", &id,
473 : : "state", &state,
474 : : NULL);
475 : 7 : device_name = valent_device_get_name (device);
476 : :
477 [ + + - - ]: 7 : switch (state)
478 : : {
479 : 5 : case VALENT_TRANSFER_STATE_PENDING:
480 : : case VALENT_TRANSFER_STATE_ACTIVE:
481 : 5 : icon = g_themed_icon_new ("document-send-symbolic");
482 : 5 : title = _("Transferring Files");
483 : 5 : body = g_strdup_printf (ngettext ("Sending one file to %1$s",
484 : : "Sending %2$d files to %1$s",
485 : : n_files),
486 : : device_name, n_files);
487 : 5 : break;
488 : :
489 : 2 : case VALENT_TRANSFER_STATE_COMPLETE:
490 : 2 : icon = g_themed_icon_new ("document-send-symbolic");
491 : 2 : title = _("Transfer Complete");
492 : 2 : body = g_strdup_printf (ngettext ("Sent one file to %1$s",
493 : : "Sent %2$d files to %1$s",
494 : : n_files),
495 : : device_name, n_files);
496 : 2 : break;
497 : :
498 : 0 : case VALENT_TRANSFER_STATE_FAILED:
499 : 0 : icon = g_themed_icon_new ("dialog-warning-symbolic");
500 : 0 : title = _("Transfer Failed");
501 : 0 : body = g_strdup_printf (ngettext ("Sending one file to %1$s",
502 : : "Sending %2$d files to %1$s",
503 : : n_files),
504 : : device_name, n_files);
505 : 0 : break;
506 : : }
507 : :
508 : 7 : notification = g_notification_new (title);
509 : 7 : g_notification_set_body (notification, body);
510 : 7 : g_notification_set_icon (notification, icon);
511 : :
512 [ + + ]: 7 : if (state == VALENT_TRANSFER_STATE_ACTIVE)
513 : : {
514 : 5 : valent_notification_add_device_button (notification,
515 : : device,
516 : 5 : _("Cancel"),
517 : : "share.cancel",
518 : : g_variant_new_string (id));
519 : : }
520 : :
521 : 7 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
522 : : id,
523 : : notification);
524 : : }
525 : :
526 : : static void
527 : 2 : valent_share_upload_file_cb (ValentTransfer *transfer,
528 : : GAsyncResult *result,
529 : : gpointer user_data)
530 : : {
531 : 4 : g_autoptr (ValentSharePlugin) self = VALENT_SHARE_PLUGIN (user_data);
532 : 2 : g_autofree char *id = NULL;
533 : 2 : g_autoptr (GError) error = NULL;
534 : :
535 [ - + ]: 2 : g_assert (VALENT_IS_SHARE_UPLOAD (transfer));
536 [ + - ]: 2 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
537 : :
538 : 2 : id = valent_transfer_dup_id (transfer);
539 [ + - ]: 2 : if (valent_transfer_execute_finish (transfer, result, &error))
540 : : {
541 : 2 : valent_share_upload_file_notification (self, transfer);
542 : : }
543 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
544 : : {
545 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
546 : 0 : valent_share_upload_file_notification (self, transfer);
547 : : }
548 : : else
549 : : {
550 : 0 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
551 : : }
552 : :
553 [ + - ]: 2 : if (self->upload == transfer)
554 [ + - ]: 2 : g_clear_object (&self->upload);
555 : :
556 [ - + ]: 2 : g_hash_table_remove (self->transfers, id);
557 : 2 : }
558 : :
559 : : static void
560 : 5 : valent_share_upload_files_added (ValentTransfer *transfer,
561 : : unsigned int position,
562 : : unsigned int removed,
563 : : unsigned int added,
564 : : ValentSharePlugin *self)
565 : : {
566 [ - + ]: 5 : g_assert (VALENT_IS_SHARE_UPLOAD (transfer));
567 [ + - ]: 5 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
568 : :
569 : : /* If no files were added, something went wrong */
570 [ + - ]: 5 : g_return_if_fail (added > 0);
571 : :
572 : : /* Start the transfer, if necessary */
573 [ + + ]: 5 : if (valent_transfer_get_state (transfer) == VALENT_TRANSFER_STATE_PENDING)
574 : : {
575 : 2 : valent_transfer_execute (transfer,
576 : : NULL,
577 : : (GAsyncReadyCallback)valent_share_upload_file_cb,
578 : : g_object_ref (self));
579 : : }
580 : :
581 : 5 : valent_share_upload_file_notification (self, transfer);
582 : : }
583 : :
584 : : static void
585 : 5 : valent_share_plugin_upload_file (ValentSharePlugin *self,
586 : : GFile *file)
587 : : {
588 [ - + ]: 5 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
589 [ + - + - : 5 : g_assert (G_IS_FILE (file));
+ - + - ]
590 : :
591 : : /* Create a new transfer, if necessary */
592 [ + + ]: 5 : if (self->upload == NULL)
593 : : {
594 : 2 : ValentDevice *device;
595 : :
596 : 2 : device = valent_resource_get_source (VALENT_RESOURCE (self));
597 : :
598 : 2 : self->upload = valent_share_upload_new (device);
599 : 2 : g_signal_connect_object (self->upload,
600 : : "items-changed",
601 : : G_CALLBACK (valent_share_upload_files_added),
602 : : self,
603 : : G_CONNECT_DEFAULT);
604 : 2 : g_hash_table_replace (self->transfers,
605 : 2 : valent_transfer_dup_id (self->upload),
606 : 2 : g_object_ref (self->upload));
607 : : }
608 : :
609 : 5 : valent_share_upload_add_file (VALENT_SHARE_UPLOAD (self->upload), file);
610 : 5 : }
611 : :
612 : : /*
613 : : * GActions
614 : : */
615 : : /**
616 : : * ValentSharePlugin|share.cancel:
617 : : * @parameter: "s"
618 : : * @id: The transfer ID
619 : : *
620 : : * Each transfer is given a UUID for the purposes of cancelling it. Usually this
621 : : * action will only be activated from the transfer notification as sent by
622 : : * upload_operation() or the incoming file handler.
623 : : */
624 : : static void
625 : 0 : share_cancel_action (GSimpleAction *action,
626 : : GVariant *parameter,
627 : : gpointer user_data)
628 : : {
629 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
630 : 0 : ValentTransfer *transfer;
631 : 0 : const char *id;
632 : :
633 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
634 : :
635 : 0 : id = g_variant_get_string (parameter, NULL);
636 : 0 : transfer = g_hash_table_lookup (self->transfers, id);
637 : :
638 [ # # ]: 0 : if (transfer != NULL)
639 : 0 : valent_transfer_cancel (transfer);
640 : 0 : }
641 : :
642 : : /**
643 : : * ValentSharePlugin|share.copy:
644 : : * @parameter: "s"
645 : : * @text: The text content
646 : : *
647 : : * This action allows copying shared text to the clipboard from a notification.
648 : : */
649 : : static void
650 : 0 : share_copy_action (GSimpleAction *action,
651 : : GVariant *parameter,
652 : : gpointer user_data)
653 : : {
654 : 0 : const char *text;
655 : :
656 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
657 : :
658 : 0 : text = g_variant_get_string (parameter, NULL);
659 : 0 : valent_clipboard_write_text (valent_clipboard_get_default (),
660 : : text,
661 : : NULL,
662 : : NULL,
663 : : NULL);
664 : 0 : }
665 : :
666 : : /**
667 : : * ValentSharePlugin|share.open:
668 : : * @parameter: "s"
669 : : * @uri: File URI to open
670 : : *
671 : : * This action is used to open a URI.
672 : : *
673 : : * By convention, the remote device will open the URI with the default handler
674 : : * for that type.
675 : : *
676 : : * If the URI scheme is `file://`, it will be converted to a file upload,
677 : : * requesting it be opened after transfer.
678 : : */
679 : : static void
680 : 2 : share_open_action (GSimpleAction *action,
681 : : GVariant *parameter,
682 : : gpointer user_data)
683 : : {
684 : 2 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
685 : 2 : const char *uri_string = NULL;
686 : 2 : g_autoptr (GUri) uri = NULL;
687 : 2 : g_autoptr (GError) error = NULL;
688 : :
689 [ - + ]: 2 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
690 [ + - ]: 2 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
691 : :
692 : 2 : uri_string = g_variant_get_string (parameter, NULL);
693 : :
694 [ - + ]: 2 : if ((uri = g_uri_parse (uri_string, G_URI_FLAGS_NONE, &error)) == NULL)
695 : : {
696 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
697 [ # # ]: 0 : return;
698 : : }
699 : :
700 [ + - ]: 2 : if (g_str_equal ("file", g_uri_get_scheme (uri)) ||
701 [ + + ]: 2 : g_str_equal ("resource", g_uri_get_scheme (uri)))
702 : : {
703 [ - + ]: 2 : g_autoptr (GFile) file = NULL;
704 : :
705 : 1 : file = g_file_new_for_uri (uri_string);
706 [ + - ]: 1 : valent_share_plugin_open_file (self, file);
707 : : }
708 : : else
709 : : {
710 : 1 : g_autoptr (JsonBuilder) builder = NULL;
711 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
712 : :
713 : 1 : valent_packet_init (&builder, "kdeconnect.share.request");
714 : 1 : json_builder_set_member_name (builder, "url");
715 : 1 : json_builder_add_string_value (builder, uri_string);
716 : 1 : packet = valent_packet_end (&builder);
717 : :
718 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
719 : : }
720 : : }
721 : :
722 : : static void
723 : 0 : share_save_action_cb (GFile *file,
724 : : GAsyncResult *result,
725 : : gpointer user_data)
726 : : {
727 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
728 : 0 : g_autoptr (GNotification) notification = NULL;
729 [ # # ]: 0 : g_autoptr (GIcon) icon = NULL;
730 [ # # ]: 0 : g_autoptr (GFile) parent = NULL;
731 : 0 : ValentDevice *device = NULL;
732 [ # # ]: 0 : g_autofree char *title = NULL;
733 : 0 : g_autofree char *dir_uri = NULL;
734 : 0 : g_autofree char *file_uri = NULL;
735 : 0 : g_autofree char *basename = NULL;
736 : 0 : const char *name = NULL;
737 : 0 : g_autoptr (GError) error = NULL;
738 : :
739 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
740 : :
741 [ # # ]: 0 : if (!g_file_replace_contents_finish (file, result, NULL, &error))
742 : : {
743 : 0 : g_warning ("Saving \"%s\": %s", g_file_peek_path (file), error->message);
744 [ # # ]: 0 : return;
745 : : }
746 : :
747 : 0 : device = valent_resource_get_source (VALENT_RESOURCE (self));
748 : 0 : name = valent_device_get_name (device);
749 : 0 : parent = g_file_get_parent (file);
750 : 0 : dir_uri = g_file_get_uri (parent);
751 : 0 : file_uri = g_file_get_uri (file);
752 : 0 : basename = g_file_get_basename (file);
753 : :
754 : 0 : title = g_strdup_printf (_("Text from “%s” saved to “%s”"), name, basename);
755 : 0 : icon = g_themed_icon_new ("document-save-symbolic");
756 : :
757 : 0 : notification = g_notification_new (title);
758 : 0 : g_notification_set_icon (notification, icon);
759 : 0 : valent_notification_add_device_button (notification,
760 : : device,
761 : 0 : _("Open Folder"),
762 : : "share.view",
763 : : g_variant_new_string (dir_uri));
764 : 0 : valent_notification_add_device_button (notification,
765 : : device,
766 : 0 : _("Open File"),
767 : : "share.view",
768 : : g_variant_new_string (file_uri));
769 [ # # ]: 0 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
770 : : file_uri,
771 : : notification);
772 : : }
773 : :
774 : : /**
775 : : * ValentSharePlugin|share.save:
776 : : * @parameter: "s"
777 : : * @text: The text content
778 : : *
779 : : * This action allows saving shared text to file from a notification.
780 : : */
781 : : static void
782 : 0 : share_save_action (GSimpleAction *action,
783 : : GVariant *parameter,
784 : : gpointer user_data)
785 : : {
786 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
787 : 0 : ValentDevice *device = NULL;
788 : 0 : g_autoptr (GBytes) bytes = NULL;
789 [ # # ]: 0 : g_autoptr (GDateTime) date = NULL;
790 [ # # ]: 0 : g_autofree char *date_str = NULL;
791 : 0 : g_autoptr (GFile) file = NULL;
792 [ # # ]: 0 : g_autofree char *filename = NULL;
793 : 0 : const char *name;
794 : 0 : const char *text;
795 : :
796 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
797 : :
798 : 0 : device = valent_resource_get_source (VALENT_RESOURCE (self));
799 : 0 : name = valent_device_get_name (device);
800 : 0 : text = g_variant_get_string (parameter, NULL);
801 : :
802 : 0 : bytes = g_bytes_new (text, strlen (text));
803 : 0 : date = g_date_time_new_now_local ();
804 : 0 : date_str = g_date_time_format (date, "%F %T");
805 : : /* TRANSLATORS: this is a filename used for text shared by a device, where
806 : : * the first "%s" is the date and the second "%s" is the device name, e.g.
807 : : * "Text from 07-12-2024 10:00:46 PM (OnePlus 6)"
808 : : * */
809 : 0 : filename = g_strdup_printf (_("Text from %s (%s).txt"), date_str, name);
810 : 0 : file = valent_share_plugin_create_download_file (self, filename, TRUE);
811 : :
812 : 0 : g_file_replace_contents_bytes_async (file,
813 : : bytes,
814 : : NULL,
815 : : FALSE,
816 : : G_FILE_CREATE_REPLACE_DESTINATION,
817 : : NULL,
818 : : (GAsyncReadyCallback)share_save_action_cb,
819 : : NULL);
820 : 0 : }
821 : :
822 : : /**
823 : : * ValentSharePlugin|share.text:
824 : : * @parameter: "s"
825 : : * @text: text to share
826 : : *
827 : : * This action simply sends a chunk of text to the remote device. Ultimately,
828 : : * how the remote device handles the text is undefined. It may be copied to the
829 : : * clipboard, stored as a temporary file or just displayed.
830 : : */
831 : : static void
832 : 1 : share_text_action (GSimpleAction *action,
833 : : GVariant *parameter,
834 : : gpointer user_data)
835 : : {
836 : 1 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
837 : 1 : const char *text;
838 : 2 : g_autoptr (JsonBuilder) builder = NULL;
839 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
840 : :
841 [ - + ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
842 [ + - ]: 1 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
843 : :
844 : 1 : text = g_variant_get_string (parameter, NULL);
845 : :
846 : 1 : valent_packet_init (&builder, "kdeconnect.share.request");
847 : 1 : json_builder_set_member_name (builder, "text");
848 : 1 : json_builder_add_string_value (builder, text);
849 : 1 : packet = valent_packet_end (&builder);
850 : :
851 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
852 : 1 : }
853 : :
854 : : /**
855 : : * ValentSharePlugin|share.uri:
856 : : * @parameter: "s"
857 : : * @uri: URI to share
858 : : *
859 : : * This action is used to share a URI.
860 : : *
861 : : * By convention, the remote device will open the URI with the default handler
862 : : * for that type.
863 : : *
864 : : * If the URI scheme is `file://`, it will be converted to a file upload.
865 : : */
866 : : static void
867 : 9 : share_uri_action (GSimpleAction *action,
868 : : GVariant *parameter,
869 : : gpointer user_data)
870 : : {
871 : 9 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
872 : 9 : const char *uri_string = NULL;
873 : 9 : g_autoptr (GUri) uri = NULL;
874 : 9 : g_autoptr (GError) error = NULL;
875 : :
876 [ - + ]: 9 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
877 [ + - ]: 9 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
878 : :
879 : 9 : uri_string = g_variant_get_string (parameter, NULL);
880 : :
881 [ - + ]: 9 : if ((uri = g_uri_parse (uri_string, G_URI_FLAGS_NONE, &error)) == NULL)
882 : : {
883 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
884 [ # # ]: 0 : return;
885 : : }
886 : :
887 [ + - ]: 9 : if (g_str_equal ("file", g_uri_get_scheme (uri)) ||
888 [ + + ]: 9 : g_str_equal ("resource", g_uri_get_scheme (uri)))
889 : : {
890 [ - + ]: 9 : g_autoptr (GFile) file = NULL;
891 : :
892 : 5 : file = g_file_new_for_uri (uri_string);
893 [ + - ]: 5 : valent_share_plugin_upload_file (self, file);
894 : : }
895 : : else
896 : : {
897 : 4 : g_autoptr (JsonBuilder) builder = NULL;
898 [ - + ]: 4 : g_autoptr (JsonNode) packet = NULL;
899 : :
900 : 4 : valent_packet_init (&builder, "kdeconnect.share.request");
901 : 4 : json_builder_set_member_name (builder, "url");
902 : 4 : json_builder_add_string_value (builder, uri_string);
903 : 4 : packet = valent_packet_end (&builder);
904 : :
905 [ + - ]: 4 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
906 : : }
907 : : }
908 : :
909 : : /**
910 : : * ValentSharePlugin|share.uris:
911 : : * @parameter: "as"
912 : : * @uris: a list of URIs
913 : : *
914 : : * This action is a convenience for sending multiple URIs, as with the
915 : : * `ValentSharePlugin|share.uri` `GAction`.
916 : : */
917 : : static void
918 : 1 : share_uris_action (GSimpleAction *action,
919 : : GVariant *parameter,
920 : : gpointer user_data)
921 : : {
922 : 1 : GVariantIter iter;
923 : 1 : GVariant *child;
924 : :
925 [ - + ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
926 [ + - ]: 1 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING_ARRAY));
927 : :
928 : 1 : g_variant_iter_init (&iter, parameter);
929 : :
930 [ + + ]: 9 : while ((child = g_variant_iter_next_value (&iter)) != NULL)
931 : : {
932 : 7 : share_uri_action (action, child, user_data);
933 : 8 : g_clear_pointer (&child, g_variant_unref);
934 : : }
935 : 1 : }
936 : :
937 : : /**
938 : : * ValentSharePlugin|share.view:
939 : : * @parameter: "s"
940 : : * @uri: File or directory URI to view
941 : : *
942 : : * This action opens a file or directory.
943 : : */
944 : : static void
945 : 0 : share_view_action (GSimpleAction *action,
946 : : GVariant *parameter,
947 : : gpointer user_data)
948 : : {
949 : 0 : const char *uri;
950 : :
951 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
952 [ # # ]: 0 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
953 : :
954 : 0 : uri = g_variant_get_string (parameter, NULL);
955 : 0 : g_app_info_launch_default_for_uri_async (uri, NULL, NULL, NULL, NULL);
956 : 0 : }
957 : :
958 : : static GActionEntry actions[] = {
959 : : {"cancel", share_cancel_action, "s", NULL, NULL},
960 : : {"copy", share_copy_action, "s", NULL, NULL},
961 : : {"open", share_open_action, "s", NULL, NULL},
962 : : {"save", share_save_action, "s", NULL, NULL},
963 : : {"text", share_text_action, "s", NULL, NULL},
964 : : {"uri", share_uri_action, "s", NULL, NULL},
965 : : {"uris", share_uris_action, "as", NULL, NULL},
966 : : {"view", share_view_action, "s", NULL, NULL},
967 : : };
968 : :
969 : : /*
970 : : * Packet Handlers
971 : : */
972 : : static void
973 : 7 : valent_share_plugin_handle_file (ValentSharePlugin *self,
974 : : JsonNode *packet)
975 : : {
976 : 7 : g_autoptr (ValentTransfer) transfer = NULL;
977 : 7 : ValentDevice *device;
978 : 7 : const char *filename;
979 [ + - + - ]: 7 : g_autoptr (GFile) file = NULL;
980 : 7 : int64_t number_of_files = 0;
981 : :
982 [ - + ]: 7 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
983 [ + - ]: 7 : g_assert (VALENT_IS_PACKET (packet));
984 : :
985 : : /* Common packet fields */
986 [ - + ]: 7 : if (!valent_packet_has_payload (packet))
987 : : {
988 : 0 : g_warning ("%s(): missing payload info", G_STRFUNC);
989 : 0 : return;
990 : : }
991 : :
992 [ - + ]: 7 : if (!valent_packet_get_string (packet, "filename", &filename))
993 : : {
994 : 0 : g_debug ("%s(): expected \"filename\" field holding a string",
995 : : G_STRFUNC);
996 : 0 : return;
997 : : }
998 : :
999 : : /* Newer implementations support sequential multi-file transfers */
1000 [ + + ]: 7 : if (!valent_packet_get_int (packet, "numberOfFiles", &number_of_files))
1001 : : {
1002 : 2 : json_object_set_int_member (valent_packet_get_body (packet),
1003 : : "numberOfFiles",
1004 : : 1);
1005 : : }
1006 : :
1007 [ + + ]: 7 : if (!valent_packet_get_int (packet, "totalPayloadSize", NULL))
1008 : : {
1009 : 2 : json_object_set_int_member (valent_packet_get_body (packet),
1010 : : "totalPayloadSize",
1011 : 2 : valent_packet_get_payload_size (packet));
1012 : : }
1013 : :
1014 : 7 : file = valent_share_plugin_create_download_file (self, filename, TRUE);
1015 : 7 : device = valent_resource_get_source (VALENT_RESOURCE (self));
1016 : :
1017 : : /* If the packet includes a request to open the file when the transfer
1018 : : * completes, use a separate routine for success/failure. */
1019 [ + + ]: 7 : if (valent_packet_check_field (packet, "open"))
1020 : : {
1021 : 1 : transfer = valent_device_transfer_new (device, packet, file);
1022 : 2 : g_hash_table_replace (self->transfers,
1023 : 1 : valent_transfer_dup_id (transfer),
1024 : : g_object_ref (transfer));
1025 : :
1026 : 1 : valent_transfer_execute (transfer,
1027 : : NULL,
1028 : : (GAsyncReadyCallback)valent_share_download_open_cb,
1029 : : g_object_ref (self));
1030 : 1 : valent_share_download_open_notification (self, transfer);
1031 : 1 : return;
1032 : : }
1033 : :
1034 : : /* If the packet is missing the `numberOfFiles` field it is a legacy transfer
1035 : : * transfer; use a discrete transfer with standard success/failure handling. */
1036 [ + + ]: 6 : if (!number_of_files)
1037 : : {
1038 : 1 : transfer = valent_share_download_new (device);
1039 : 2 : g_hash_table_replace (self->transfers,
1040 : 1 : valent_transfer_dup_id (transfer),
1041 : : g_object_ref (transfer));
1042 : :
1043 : 1 : valent_share_download_add_file (VALENT_SHARE_DOWNLOAD (transfer),
1044 : : file,
1045 : : packet);
1046 : :
1047 : 1 : valent_transfer_execute (transfer,
1048 : : NULL,
1049 : : (GAsyncReadyCallback)valent_share_download_file_cb,
1050 : : g_object_ref (self));
1051 : 1 : valent_share_download_file_notification (self, transfer);
1052 : 1 : return;
1053 : : }
1054 : :
1055 : : /* Otherwise the file will appended to a multi-file transfer */
1056 [ + + ]: 5 : if (self->download != NULL)
1057 : : {
1058 : 2 : transfer = g_object_ref (self->download);
1059 : : }
1060 : : else
1061 : : {
1062 : 3 : transfer = valent_share_download_new (device);
1063 : 6 : g_hash_table_replace (self->transfers,
1064 : 3 : valent_transfer_dup_id (transfer),
1065 : : g_object_ref (transfer));
1066 : : }
1067 : :
1068 : 5 : valent_share_download_add_file (VALENT_SHARE_DOWNLOAD (transfer),
1069 : : file,
1070 : : packet);
1071 : :
1072 [ + + ]: 5 : if (self->download != transfer)
1073 : 3 : g_set_object (&self->download, transfer);
1074 : :
1075 [ + + ]: 5 : if (valent_transfer_get_state (transfer) == VALENT_TRANSFER_STATE_PENDING)
1076 : : {
1077 : 3 : valent_transfer_execute (transfer,
1078 : : NULL,
1079 : : (GAsyncReadyCallback)valent_share_download_file_cb,
1080 : : g_object_ref (self));
1081 : : }
1082 : :
1083 [ + - ]: 5 : valent_share_download_file_notification (self, transfer);
1084 : : }
1085 : :
1086 : : static void
1087 : 1 : valent_share_plugin_handle_file_update (ValentSharePlugin *self,
1088 : : JsonNode *packet)
1089 : : {
1090 [ - + ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1091 [ + - ]: 1 : g_assert (VALENT_IS_PACKET (packet));
1092 : :
1093 [ + - ]: 1 : if (self->download == NULL)
1094 : : return;
1095 : :
1096 [ - + ]: 1 : if (!valent_packet_check_field (packet, "numberOfFiles"))
1097 : : {
1098 : 0 : g_debug ("%s(): expected \"numberOfFiles\" field holding an integer",
1099 : : G_STRFUNC);
1100 : 0 : return;
1101 : : }
1102 : :
1103 [ - + ]: 1 : if (!valent_packet_check_field (packet, "totalPayloadSize"))
1104 : : {
1105 : 0 : g_debug ("%s(): expected \"totalPayloadSize\" field holding an integer",
1106 : : G_STRFUNC);
1107 : 0 : return;
1108 : : }
1109 : :
1110 : 1 : valent_share_download_update (VALENT_SHARE_DOWNLOAD (self->download), packet);
1111 : 1 : valent_share_download_file_notification (self, self->download);
1112 : : }
1113 : :
1114 : : static void
1115 : 1 : valent_share_plugin_handle_text (ValentSharePlugin *self,
1116 : : const char *text)
1117 : : {
1118 : 1 : ValentResource *resource = VALENT_RESOURCE (self);
1119 : 1 : ValentDevice *device = NULL;
1120 : 2 : g_autoptr (GNotification) notification = NULL;
1121 [ + - ]: 1 : g_autofree char *id = NULL;
1122 : 1 : g_autofree char *title = NULL;
1123 : 1 : const char *name = NULL;
1124 : :
1125 [ - + ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1126 [ + - ]: 1 : g_assert (text != NULL);
1127 : :
1128 : 1 : device = valent_resource_get_source (resource);
1129 : 1 : name = valent_device_get_name (device);
1130 : 1 : id = g_compute_checksum_for_string (G_CHECKSUM_MD5, text, -1);
1131 : 1 : title = g_strdup_printf (_("Text from “%s”"), name);
1132 : :
1133 : 1 : notification = g_notification_new (title);
1134 : 1 : g_notification_set_body (notification, text);
1135 : 1 : valent_notification_add_device_button (notification,
1136 : : device,
1137 : 1 : _("Save"),
1138 : : "share.save",
1139 : : g_variant_new_string (text));
1140 : 1 : valent_notification_add_device_button (notification,
1141 : : device,
1142 : 1 : _("Copy"),
1143 : : "share.copy",
1144 : : g_variant_new_string (text));
1145 : :
1146 : 1 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
1147 : : id,
1148 : : notification);
1149 : 1 : }
1150 : :
1151 : : static void
1152 : 1 : valent_share_plugin_handle_url (ValentSharePlugin *self,
1153 : : const char *url)
1154 : : {
1155 [ - + ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1156 [ + - ]: 1 : g_assert (url != NULL);
1157 : :
1158 : 1 : g_app_info_launch_default_for_uri_async (url, NULL, NULL, NULL, NULL);
1159 : 1 : }
1160 : :
1161 : : /*
1162 : : * ValentDevicePlugin
1163 : : */
1164 : : static void
1165 : 26 : valent_share_plugin_update_state (ValentDevicePlugin *plugin,
1166 : : ValentDeviceState state)
1167 : : {
1168 : 26 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (plugin);
1169 : 26 : gboolean available;
1170 : :
1171 [ - + ]: 26 : g_assert (VALENT_IS_SHARE_PLUGIN (plugin));
1172 : :
1173 [ + + ]: 26 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
1174 [ + + ]: 13 : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
1175 : :
1176 : : /* If the device has been unpaired it should be considered untrusted, so
1177 : : * cancel any ongoing transfers. */
1178 [ + + ]: 13 : if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
1179 : : {
1180 : 4 : GHashTableIter iter;
1181 : 4 : ValentTransfer *transfer;
1182 : :
1183 : 4 : g_hash_table_iter_init (&iter, self->transfers);
1184 : :
1185 [ - + ]: 4 : while (g_hash_table_iter_next (&iter, NULL, (void **)&transfer))
1186 : : {
1187 : 0 : valent_transfer_cancel (transfer);
1188 : 0 : g_hash_table_iter_remove (&iter);
1189 : : }
1190 : :
1191 [ - + ]: 4 : g_clear_object (&self->download);
1192 [ - + ]: 4 : g_clear_object (&self->upload);
1193 : : }
1194 : :
1195 : 26 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
1196 : 26 : }
1197 : :
1198 : : static void
1199 : 10 : valent_share_plugin_handle_packet (ValentDevicePlugin *plugin,
1200 : : const char *type,
1201 : : JsonNode *packet)
1202 : : {
1203 : 10 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (plugin);
1204 : 10 : const char *text;
1205 : 10 : const char *url;
1206 : :
1207 [ - + ]: 10 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1208 [ + - ]: 10 : g_assert (type != NULL);
1209 [ + - ]: 10 : g_assert (VALENT_IS_PACKET (packet));
1210 : :
1211 [ + + ]: 10 : if (g_str_equal (type, "kdeconnect.share.request"))
1212 : : {
1213 [ + + ]: 9 : if (valent_packet_check_field (packet, "filename"))
1214 : 7 : valent_share_plugin_handle_file (self, packet);
1215 : :
1216 [ + + ]: 2 : else if (valent_packet_get_string (packet, "text", &text))
1217 : 1 : valent_share_plugin_handle_text (self, text);
1218 : :
1219 [ + - ]: 1 : else if (valent_packet_get_string (packet, "url", &url))
1220 : 1 : valent_share_plugin_handle_url (self, url);
1221 : :
1222 : : else
1223 : 0 : g_warning ("%s(): unsupported share request", G_STRFUNC);
1224 : : }
1225 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.share.request.update"))
1226 : : {
1227 : 1 : valent_share_plugin_handle_file_update (self, packet);
1228 : : }
1229 : : else
1230 : 0 : g_assert_not_reached ();
1231 : 10 : }
1232 : :
1233 : : /*
1234 : : * ValentObject
1235 : : */
1236 : : static void
1237 : 26 : valent_share_plugin_destroy (ValentObject *object)
1238 : : {
1239 : 26 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (object);
1240 : 26 : GHashTableIter iter;
1241 : 26 : ValentTransfer *transfer;
1242 : :
1243 : : /* Cancel active transfers */
1244 : 26 : g_hash_table_iter_init (&iter, self->transfers);
1245 : :
1246 [ - + ]: 26 : while (g_hash_table_iter_next (&iter, NULL, (void **)&transfer))
1247 : : {
1248 : 0 : valent_transfer_cancel (transfer);
1249 : 0 : g_hash_table_iter_remove (&iter);
1250 : : }
1251 : :
1252 [ - + ]: 26 : g_clear_object (&self->download);
1253 [ - + ]: 26 : g_clear_object (&self->upload);
1254 : :
1255 : 26 : VALENT_OBJECT_CLASS (valent_share_plugin_parent_class)->destroy (object);
1256 : 26 : }
1257 : :
1258 : : /*
1259 : : * GObject
1260 : : */
1261 : : static void
1262 : 13 : valent_share_plugin_constructed (GObject *object)
1263 : : {
1264 : 13 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
1265 : :
1266 : 13 : G_OBJECT_CLASS (valent_share_plugin_parent_class)->constructed (object);
1267 : :
1268 : 13 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
1269 : : actions,
1270 : : G_N_ELEMENTS (actions),
1271 : : plugin);
1272 : 13 : }
1273 : :
1274 : : static void
1275 : 13 : valent_share_plugin_finalize (GObject *object)
1276 : : {
1277 : 13 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (object);
1278 : :
1279 [ + - ]: 13 : g_clear_pointer (&self->transfers, g_hash_table_unref);
1280 : :
1281 : 13 : G_OBJECT_CLASS (valent_share_plugin_parent_class)->finalize (object);
1282 : 13 : }
1283 : :
1284 : : static void
1285 : 13 : valent_share_plugin_class_init (ValentSharePluginClass *klass)
1286 : : {
1287 : 13 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
1288 : 13 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
1289 : 13 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
1290 : :
1291 : 13 : object_class->constructed = valent_share_plugin_constructed;
1292 : 13 : object_class->finalize = valent_share_plugin_finalize;
1293 : :
1294 : 13 : vobject_class->destroy = valent_share_plugin_destroy;
1295 : :
1296 : 13 : plugin_class->handle_packet = valent_share_plugin_handle_packet;
1297 : 13 : plugin_class->update_state = valent_share_plugin_update_state;
1298 : : }
1299 : :
1300 : : static void
1301 : 13 : valent_share_plugin_init (ValentSharePlugin *self)
1302 : : {
1303 : 13 : self->transfers = g_hash_table_new_full (g_str_hash,
1304 : : g_str_equal,
1305 : : g_free,
1306 : : g_object_unref);
1307 : 13 : }
1308 : :
|