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 [ + + + - ]: 125 : 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 : 10 : valent_share_download_file_notification (ValentSharePlugin *self,
72 : : ValentTransfer *transfer)
73 : : {
74 : 10 : g_autoptr (ValentDevice) device = NULL;
75 [ + - - - ]: 10 : g_autoptr (GNotification) notification = NULL;
76 [ + - - - ]: 10 : g_autoptr (GIcon) icon = NULL;
77 : 10 : const char *title = NULL;
78 [ + - - - ]: 10 : g_autofree char *body = NULL;
79 : 10 : g_autofree char *id = NULL;
80 : 10 : unsigned int n_files = 0;
81 : 10 : const char *device_name;
82 : 10 : ValentTransferState state = VALENT_TRANSFER_STATE_PENDING;
83 : :
84 [ + - ]: 10 : g_return_if_fail (VALENT_IS_TRANSFER (transfer));
85 [ - + ]: 10 : g_return_if_fail (VALENT_IS_SHARE_DOWNLOAD (transfer));
86 : :
87 [ + - ]: 10 : if ((n_files = g_list_model_get_n_items (G_LIST_MODEL (transfer))) == 0)
88 : : return;
89 : :
90 : 10 : g_object_get (transfer,
91 : : "device", &device,
92 : : "id", &id,
93 : : "state", &state,
94 : : NULL);
95 : 10 : device_name = valent_device_get_name (device);
96 : :
97 [ + + - - ]: 10 : switch (state)
98 : : {
99 : 6 : case VALENT_TRANSFER_STATE_PENDING:
100 : : case VALENT_TRANSFER_STATE_ACTIVE:
101 : 6 : icon = g_themed_icon_new ("document-save-symbolic");
102 : 6 : title = _("Transferring Files");
103 : 6 : 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 : 6 : 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 : 10 : notification = g_notification_new (title);
129 : 10 : g_notification_set_body (notification, body);
130 : 10 : g_notification_set_icon (notification, icon);
131 : :
132 [ + + ]: 10 : if (state == VALENT_TRANSFER_STATE_ACTIVE)
133 : : {
134 : 6 : valent_notification_add_device_button (notification,
135 : : device,
136 : 6 : _("Cancel"),
137 : : "share.cancel",
138 : : g_variant_new_string (id));
139 : : }
140 [ + - ]: 4 : else if (state == VALENT_TRANSFER_STATE_COMPLETE)
141 : : {
142 : 14 : 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 : 4 : g_autofree char *uri = NULL;
161 : :
162 : 4 : uri = g_file_get_uri (file);
163 : 8 : valent_notification_add_device_button (notification,
164 : : device,
165 : 4 : _("Open File"),
166 : : "share.view",
167 : : g_variant_new_string (uri));
168 : : }
169 : : }
170 : :
171 : 10 : 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, 0);
603 : 2 : g_hash_table_replace (self->transfers,
604 : 2 : valent_transfer_dup_id (self->upload),
605 : 2 : g_object_ref (self->upload));
606 : : }
607 : :
608 : 5 : valent_share_upload_add_file (VALENT_SHARE_UPLOAD (self->upload), file);
609 : 5 : }
610 : :
611 : : /*
612 : : * GActions
613 : : */
614 : : /**
615 : : * ValentSharePlugin|share.cancel:
616 : : * @parameter: "s"
617 : : * @id: The transfer ID
618 : : *
619 : : * Each transfer is given a UUID for the purposes of cancelling it. Usually this
620 : : * action will only be activated from the transfer notification as sent by
621 : : * upload_operation() or the incoming file handler.
622 : : */
623 : : static void
624 : 0 : share_cancel_action (GSimpleAction *action,
625 : : GVariant *parameter,
626 : : gpointer user_data)
627 : : {
628 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
629 : 0 : ValentTransfer *transfer;
630 : 0 : const char *id;
631 : :
632 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
633 : :
634 : 0 : id = g_variant_get_string (parameter, NULL);
635 : 0 : transfer = g_hash_table_lookup (self->transfers, id);
636 : :
637 [ # # ]: 0 : if (transfer != NULL)
638 : 0 : valent_transfer_cancel (transfer);
639 : 0 : }
640 : :
641 : : /**
642 : : * ValentSharePlugin|share.copy:
643 : : * @parameter: "s"
644 : : * @text: The text content
645 : : *
646 : : * This action allows copying shared text to the clipboard from a notification.
647 : : */
648 : : static void
649 : 0 : share_copy_action (GSimpleAction *action,
650 : : GVariant *parameter,
651 : : gpointer user_data)
652 : : {
653 : 0 : const char *text;
654 : :
655 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
656 : :
657 : 0 : text = g_variant_get_string (parameter, NULL);
658 : 0 : valent_clipboard_write_text (valent_clipboard_get_default (),
659 : : text,
660 : : NULL,
661 : : NULL,
662 : : NULL);
663 : 0 : }
664 : :
665 : : /**
666 : : * ValentSharePlugin|share.open:
667 : : * @parameter: "s"
668 : : * @uri: File URI to open
669 : : *
670 : : * This action is used to open a URI.
671 : : *
672 : : * By convention, the remote device will open the URI with the default handler
673 : : * for that type.
674 : : *
675 : : * If the URI scheme is `file://`, it will be converted to a file upload,
676 : : * requesting it be opened after transfer.
677 : : */
678 : : static void
679 : 2 : share_open_action (GSimpleAction *action,
680 : : GVariant *parameter,
681 : : gpointer user_data)
682 : : {
683 : 2 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
684 : 2 : const char *uri_string = NULL;
685 : 2 : g_autoptr (GUri) uri = NULL;
686 : 2 : g_autoptr (GError) error = NULL;
687 : :
688 [ + - ]: 2 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
689 [ - + ]: 2 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
690 : :
691 : 2 : uri_string = g_variant_get_string (parameter, NULL);
692 : :
693 [ - + ]: 2 : if ((uri = g_uri_parse (uri_string, G_URI_FLAGS_NONE, &error)) == NULL)
694 : : {
695 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
696 [ # # ]: 0 : return;
697 : : }
698 : :
699 [ + - ]: 2 : if (g_str_equal ("file", g_uri_get_scheme (uri)) ||
700 [ + + ]: 2 : g_str_equal ("resource", g_uri_get_scheme (uri)))
701 : : {
702 [ - + ]: 2 : g_autoptr (GFile) file = NULL;
703 : :
704 : 1 : file = g_file_new_for_uri (uri_string);
705 [ + - ]: 1 : valent_share_plugin_open_file (self, file);
706 : : }
707 : : else
708 : : {
709 : 1 : g_autoptr (JsonBuilder) builder = NULL;
710 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
711 : :
712 : 1 : valent_packet_init (&builder, "kdeconnect.share.request");
713 : 1 : json_builder_set_member_name (builder, "url");
714 : 1 : json_builder_add_string_value (builder, uri_string);
715 : 1 : packet = valent_packet_end (&builder);
716 : :
717 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
718 : : }
719 : : }
720 : :
721 : : static void
722 : 0 : share_save_action_cb (GFile *file,
723 : : GAsyncResult *result,
724 : : gpointer user_data)
725 : : {
726 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
727 : 0 : g_autoptr (GNotification) notification = NULL;
728 [ # # ]: 0 : g_autoptr (GIcon) icon = NULL;
729 [ # # ]: 0 : g_autoptr (GFile) parent = NULL;
730 : 0 : ValentDevice *device = NULL;
731 [ # # ]: 0 : g_autofree char *title = NULL;
732 : 0 : g_autofree char *dir_uri = NULL;
733 : 0 : g_autofree char *file_uri = NULL;
734 : 0 : g_autofree char *basename = NULL;
735 : 0 : const char *name = NULL;
736 : 0 : g_autoptr (GError) error = NULL;
737 : :
738 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
739 : :
740 [ # # ]: 0 : if (!g_file_replace_contents_finish (file, result, NULL, &error))
741 : : {
742 : 0 : g_warning ("Saving \"%s\": %s", g_file_peek_path (file), error->message);
743 [ # # ]: 0 : return;
744 : : }
745 : :
746 : 0 : device = valent_resource_get_source (VALENT_RESOURCE (self));
747 : 0 : name = valent_device_get_name (device);
748 : 0 : parent = g_file_get_parent (file);
749 : 0 : dir_uri = g_file_get_uri (parent);
750 : 0 : file_uri = g_file_get_uri (file);
751 : 0 : basename = g_file_get_basename (file);
752 : :
753 : 0 : title = g_strdup_printf (_("Text from “%s” saved to “%s”"), name, basename);
754 : 0 : icon = g_themed_icon_new ("document-save-symbolic");
755 : :
756 : 0 : notification = g_notification_new (title);
757 : 0 : g_notification_set_icon (notification, icon);
758 : 0 : valent_notification_add_device_button (notification,
759 : : device,
760 : 0 : _("Open Folder"),
761 : : "share.view",
762 : : g_variant_new_string (dir_uri));
763 : 0 : valent_notification_add_device_button (notification,
764 : : device,
765 : 0 : _("Open File"),
766 : : "share.view",
767 : : g_variant_new_string (file_uri));
768 [ # # ]: 0 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
769 : : file_uri,
770 : : notification);
771 : : }
772 : :
773 : : /**
774 : : * ValentSharePlugin|share.save:
775 : : * @parameter: "s"
776 : : * @text: The text content
777 : : *
778 : : * This action allows saving shared text to file from a notification.
779 : : */
780 : : static void
781 : 0 : share_save_action (GSimpleAction *action,
782 : : GVariant *parameter,
783 : : gpointer user_data)
784 : : {
785 : 0 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
786 : 0 : ValentDevice *device = NULL;
787 : 0 : g_autoptr (GBytes) bytes = NULL;
788 [ # # ]: 0 : g_autoptr (GDateTime) date = NULL;
789 [ # # ]: 0 : g_autofree char *date_str = NULL;
790 : 0 : g_autoptr (GFile) file = NULL;
791 [ # # ]: 0 : g_autofree char *filename = NULL;
792 : 0 : const char *name;
793 : 0 : const char *text;
794 : :
795 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
796 : :
797 : 0 : device = valent_resource_get_source (VALENT_RESOURCE (self));
798 : 0 : name = valent_device_get_name (device);
799 : 0 : text = g_variant_get_string (parameter, NULL);
800 : :
801 : 0 : bytes = g_bytes_new (text, strlen (text));
802 : 0 : date = g_date_time_new_now_local ();
803 : 0 : date_str = g_date_time_format (date, "%F %T");
804 : : /* TRANSLATORS: this is a filename used for text shared by a device, where
805 : : * the first "%s" is the date and the second "%s" is the device name, e.g.
806 : : * "Text from 07-12-2024 10:00:46 PM (OnePlus 6)"
807 : : * */
808 : 0 : filename = g_strdup_printf (_("Text from %s (%s).txt"), date_str, name);
809 : 0 : file = valent_share_plugin_create_download_file (self, filename, TRUE);
810 : :
811 : 0 : g_file_replace_contents_bytes_async (file,
812 : : bytes,
813 : : NULL,
814 : : FALSE,
815 : : G_FILE_CREATE_REPLACE_DESTINATION,
816 : : NULL,
817 : : (GAsyncReadyCallback)share_save_action_cb,
818 : : NULL);
819 : 0 : }
820 : :
821 : : /**
822 : : * ValentSharePlugin|share.text:
823 : : * @parameter: "s"
824 : : * @text: text to share
825 : : *
826 : : * This action simply sends a chunk of text to the remote device. Ultimately,
827 : : * how the remote device handles the text is undefined. It may be copied to the
828 : : * clipboard, stored as a temporary file or just displayed.
829 : : */
830 : : static void
831 : 1 : share_text_action (GSimpleAction *action,
832 : : GVariant *parameter,
833 : : gpointer user_data)
834 : : {
835 : 1 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
836 : 1 : const char *text;
837 : 2 : g_autoptr (JsonBuilder) builder = NULL;
838 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
839 : :
840 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
841 [ - + ]: 1 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
842 : :
843 : 1 : text = g_variant_get_string (parameter, NULL);
844 : :
845 : 1 : valent_packet_init (&builder, "kdeconnect.share.request");
846 : 1 : json_builder_set_member_name (builder, "text");
847 : 1 : json_builder_add_string_value (builder, text);
848 : 1 : packet = valent_packet_end (&builder);
849 : :
850 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
851 : 1 : }
852 : :
853 : : /**
854 : : * ValentSharePlugin|share.uri:
855 : : * @parameter: "s"
856 : : * @uri: URI to share
857 : : *
858 : : * This action is used to share a URI.
859 : : *
860 : : * By convention, the remote device will open the URI with the default handler
861 : : * for that type.
862 : : *
863 : : * If the URI scheme is `file://`, it will be converted to a file upload.
864 : : */
865 : : static void
866 : 9 : share_uri_action (GSimpleAction *action,
867 : : GVariant *parameter,
868 : : gpointer user_data)
869 : : {
870 : 9 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (user_data);
871 : 9 : const char *uri_string = NULL;
872 : 9 : g_autoptr (GUri) uri = NULL;
873 : 9 : g_autoptr (GError) error = NULL;
874 : :
875 [ + - ]: 9 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
876 [ - + ]: 9 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
877 : :
878 : 9 : uri_string = g_variant_get_string (parameter, NULL);
879 : :
880 [ - + ]: 9 : if ((uri = g_uri_parse (uri_string, G_URI_FLAGS_NONE, &error)) == NULL)
881 : : {
882 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
883 [ # # ]: 0 : return;
884 : : }
885 : :
886 [ + - ]: 9 : if (g_str_equal ("file", g_uri_get_scheme (uri)) ||
887 [ + + ]: 9 : g_str_equal ("resource", g_uri_get_scheme (uri)))
888 : : {
889 [ - + ]: 9 : g_autoptr (GFile) file = NULL;
890 : :
891 : 5 : file = g_file_new_for_uri (uri_string);
892 [ + - ]: 5 : valent_share_plugin_upload_file (self, file);
893 : : }
894 : : else
895 : : {
896 : 4 : g_autoptr (JsonBuilder) builder = NULL;
897 [ - + ]: 4 : g_autoptr (JsonNode) packet = NULL;
898 : :
899 : 4 : valent_packet_init (&builder, "kdeconnect.share.request");
900 : 4 : json_builder_set_member_name (builder, "url");
901 : 4 : json_builder_add_string_value (builder, uri_string);
902 : 4 : packet = valent_packet_end (&builder);
903 : :
904 [ + - ]: 4 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
905 : : }
906 : : }
907 : :
908 : : /**
909 : : * ValentSharePlugin|share.uris:
910 : : * @parameter: "as"
911 : : * @uris: a list of URIs
912 : : *
913 : : * This action is a convenience for sending multiple URIs, as with the
914 : : * `ValentSharePlugin|share.uri` `GAction`.
915 : : */
916 : : static void
917 : 1 : share_uris_action (GSimpleAction *action,
918 : : GVariant *parameter,
919 : : gpointer user_data)
920 : : {
921 : 1 : GVariantIter iter;
922 : 1 : GVariant *child;
923 : :
924 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
925 [ - + ]: 1 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING_ARRAY));
926 : :
927 : 1 : g_variant_iter_init (&iter, parameter);
928 : :
929 [ + + ]: 9 : while ((child = g_variant_iter_next_value (&iter)) != NULL)
930 : : {
931 : 7 : share_uri_action (action, child, user_data);
932 : 8 : g_clear_pointer (&child, g_variant_unref);
933 : : }
934 : 1 : }
935 : :
936 : : /**
937 : : * ValentSharePlugin|share.view:
938 : : * @parameter: "s"
939 : : * @uri: File or directory URI to view
940 : : *
941 : : * This action opens a file or directory.
942 : : */
943 : : static void
944 : 0 : share_view_action (GSimpleAction *action,
945 : : GVariant *parameter,
946 : : gpointer user_data)
947 : : {
948 : 0 : const char *uri;
949 : :
950 [ # # ]: 0 : g_assert (VALENT_IS_SHARE_PLUGIN (user_data));
951 [ # # ]: 0 : g_assert (g_variant_is_of_type (parameter, G_VARIANT_TYPE_STRING));
952 : :
953 : 0 : uri = g_variant_get_string (parameter, NULL);
954 : 0 : g_app_info_launch_default_for_uri_async (uri, NULL, NULL, NULL, NULL);
955 : 0 : }
956 : :
957 : : static GActionEntry actions[] = {
958 : : {"cancel", share_cancel_action, "s", NULL, NULL},
959 : : {"copy", share_copy_action, "s", NULL, NULL},
960 : : {"open", share_open_action, "s", NULL, NULL},
961 : : {"save", share_save_action, "s", NULL, NULL},
962 : : {"text", share_text_action, "s", NULL, NULL},
963 : : {"uri", share_uri_action, "s", NULL, NULL},
964 : : {"uris", share_uris_action, "as", NULL, NULL},
965 : : {"view", share_view_action, "s", NULL, NULL},
966 : : };
967 : :
968 : : /*
969 : : * Packet Handlers
970 : : */
971 : : static void
972 : 7 : valent_share_plugin_handle_file (ValentSharePlugin *self,
973 : : JsonNode *packet)
974 : : {
975 : 7 : g_autoptr (ValentTransfer) transfer = NULL;
976 : 7 : ValentDevice *device;
977 : 7 : const char *filename;
978 [ + - + - ]: 7 : g_autoptr (GFile) file = NULL;
979 : 7 : int64_t number_of_files = 0;
980 : :
981 [ + - ]: 7 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
982 [ - + ]: 7 : g_assert (VALENT_IS_PACKET (packet));
983 : :
984 : : /* Common packet fields */
985 [ - + ]: 7 : if (!valent_packet_has_payload (packet))
986 : : {
987 : 0 : g_warning ("%s(): missing payload info", G_STRFUNC);
988 : 0 : return;
989 : : }
990 : :
991 [ - + ]: 7 : if (!valent_packet_get_string (packet, "filename", &filename))
992 : : {
993 : 0 : g_debug ("%s(): expected \"filename\" field holding a string",
994 : : G_STRFUNC);
995 : 0 : return;
996 : : }
997 : :
998 : : /* Newer implementations support sequential multi-file transfers */
999 [ + + ]: 7 : if (!valent_packet_get_int (packet, "numberOfFiles", &number_of_files))
1000 : : {
1001 : 2 : json_object_set_int_member (valent_packet_get_body (packet),
1002 : : "numberOfFiles",
1003 : : 1);
1004 : : }
1005 : :
1006 [ + + ]: 7 : if (!valent_packet_get_int (packet, "totalPayloadSize", NULL))
1007 : : {
1008 : 2 : json_object_set_int_member (valent_packet_get_body (packet),
1009 : : "totalPayloadSize",
1010 : : valent_packet_get_payload_size (packet));
1011 : : }
1012 : :
1013 : 7 : file = valent_share_plugin_create_download_file (self, filename, TRUE);
1014 : 7 : device = valent_resource_get_source (VALENT_RESOURCE (self));
1015 : :
1016 : : /* If the packet includes a request to open the file when the transfer
1017 : : * completes, use a separate routine for success/failure. */
1018 [ + + ]: 7 : if (valent_packet_check_field (packet, "open"))
1019 : : {
1020 : 1 : transfer = valent_device_transfer_new (device, packet, file);
1021 : 2 : g_hash_table_replace (self->transfers,
1022 : 1 : valent_transfer_dup_id (transfer),
1023 : : g_object_ref (transfer));
1024 : :
1025 : 1 : valent_transfer_execute (transfer,
1026 : : NULL,
1027 : : (GAsyncReadyCallback)valent_share_download_open_cb,
1028 : : g_object_ref (self));
1029 : 1 : valent_share_download_open_notification (self, transfer);
1030 : 1 : return;
1031 : : }
1032 : :
1033 : : /* If the packet is missing the `numberOfFiles` field it is a legacy transfer
1034 : : * transfer; use a discrete transfer with standard success/failure handling. */
1035 [ + + ]: 6 : if (!number_of_files)
1036 : : {
1037 : 1 : transfer = valent_share_download_new (device);
1038 : 2 : g_hash_table_replace (self->transfers,
1039 : 1 : valent_transfer_dup_id (transfer),
1040 : : g_object_ref (transfer));
1041 : :
1042 : 1 : valent_share_download_add_file (VALENT_SHARE_DOWNLOAD (transfer),
1043 : : file,
1044 : : packet);
1045 : :
1046 : 1 : valent_transfer_execute (transfer,
1047 : : NULL,
1048 : : (GAsyncReadyCallback)valent_share_download_file_cb,
1049 : : g_object_ref (self));
1050 : 1 : valent_share_download_file_notification (self, transfer);
1051 : 1 : return;
1052 : : }
1053 : :
1054 : : /* Otherwise the file will appended to a multi-file transfer */
1055 [ + + ]: 5 : if (self->download != NULL)
1056 : : {
1057 : 1 : transfer = g_object_ref (self->download);
1058 : : }
1059 : : else
1060 : : {
1061 : 4 : transfer = valent_share_download_new (device);
1062 : 8 : g_hash_table_replace (self->transfers,
1063 : 4 : valent_transfer_dup_id (transfer),
1064 : : g_object_ref (transfer));
1065 : : }
1066 : :
1067 : 5 : valent_share_download_add_file (VALENT_SHARE_DOWNLOAD (transfer),
1068 : : file,
1069 : : packet);
1070 : :
1071 [ + + ]: 5 : if (self->download != transfer)
1072 : 4 : g_set_object (&self->download, transfer);
1073 : :
1074 [ + + ]: 5 : if (valent_transfer_get_state (transfer) == VALENT_TRANSFER_STATE_PENDING)
1075 : : {
1076 : 4 : valent_transfer_execute (transfer,
1077 : : NULL,
1078 : : (GAsyncReadyCallback)valent_share_download_file_cb,
1079 : : g_object_ref (self));
1080 : : }
1081 : :
1082 [ + - ]: 5 : valent_share_download_file_notification (self, transfer);
1083 : : }
1084 : :
1085 : : static void
1086 : 1 : valent_share_plugin_handle_file_update (ValentSharePlugin *self,
1087 : : JsonNode *packet)
1088 : : {
1089 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1090 [ - + ]: 1 : g_assert (VALENT_IS_PACKET (packet));
1091 : :
1092 [ - + ]: 1 : if (self->download == NULL)
1093 : : return;
1094 : :
1095 [ # # ]: 0 : if (!valent_packet_check_field (packet, "numberOfFiles"))
1096 : : {
1097 : 0 : g_debug ("%s(): expected \"numberOfFiles\" field holding an integer",
1098 : : G_STRFUNC);
1099 : 0 : return;
1100 : : }
1101 : :
1102 [ # # ]: 0 : if (!valent_packet_check_field (packet, "totalPayloadSize"))
1103 : : {
1104 : 0 : g_debug ("%s(): expected \"totalPayloadSize\" field holding an integer",
1105 : : G_STRFUNC);
1106 : 0 : return;
1107 : : }
1108 : :
1109 : 0 : valent_share_download_update (VALENT_SHARE_DOWNLOAD (self->download), packet);
1110 : 0 : valent_share_download_file_notification (self, self->download);
1111 : : }
1112 : :
1113 : : static void
1114 : 1 : valent_share_plugin_handle_text (ValentSharePlugin *self,
1115 : : const char *text)
1116 : : {
1117 : 1 : ValentResource *resource = VALENT_RESOURCE (self);
1118 : 1 : ValentDevice *device = NULL;
1119 : 2 : g_autoptr (GNotification) notification = NULL;
1120 [ + - ]: 1 : g_autofree char *id = NULL;
1121 : 1 : g_autofree char *title = NULL;
1122 : 1 : const char *name = NULL;
1123 : :
1124 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1125 [ - + ]: 1 : g_assert (text != NULL);
1126 : :
1127 : 1 : device = valent_resource_get_source (resource);
1128 : 1 : name = valent_device_get_name (device);
1129 : 1 : id = g_compute_checksum_for_string (G_CHECKSUM_MD5, text, -1);
1130 : 1 : title = g_strdup_printf (_("Text from “%s”"), name);
1131 : :
1132 : 1 : notification = g_notification_new (title);
1133 : 1 : g_notification_set_body (notification, text);
1134 : 1 : valent_notification_add_device_button (notification,
1135 : : device,
1136 : 1 : _("Save"),
1137 : : "share.save",
1138 : : g_variant_new_string (text));
1139 : 1 : valent_notification_add_device_button (notification,
1140 : : device,
1141 : 1 : _("Copy"),
1142 : : "share.copy",
1143 : : g_variant_new_string (text));
1144 : :
1145 : 1 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
1146 : : id,
1147 : : notification);
1148 : 1 : }
1149 : :
1150 : : static void
1151 : 1 : valent_share_plugin_handle_url (ValentSharePlugin *self,
1152 : : const char *url)
1153 : : {
1154 [ + - ]: 1 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1155 [ - + ]: 1 : g_assert (url != NULL);
1156 : :
1157 : 1 : g_app_info_launch_default_for_uri_async (url, NULL, NULL, NULL, NULL);
1158 : 1 : }
1159 : :
1160 : : /*
1161 : : * ValentDevicePlugin
1162 : : */
1163 : : static void
1164 : 33 : valent_share_plugin_update_state (ValentDevicePlugin *plugin,
1165 : : ValentDeviceState state)
1166 : : {
1167 : 33 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (plugin);
1168 : 33 : gboolean available;
1169 : :
1170 [ + - ]: 33 : g_assert (VALENT_IS_SHARE_PLUGIN (plugin));
1171 : :
1172 : 33 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
1173 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
1174 : :
1175 : : /* If the device has been unpaired it should be considered untrusted, so
1176 : : * cancel any ongoing transfers. */
1177 [ - + ]: 33 : if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
1178 : : {
1179 : 0 : GHashTableIter iter;
1180 : 0 : ValentTransfer *transfer;
1181 : :
1182 : 0 : g_hash_table_iter_init (&iter, self->transfers);
1183 : :
1184 [ # # ]: 0 : while (g_hash_table_iter_next (&iter, NULL, (void **)&transfer))
1185 : : {
1186 : 0 : valent_transfer_cancel (transfer);
1187 : 0 : g_hash_table_iter_remove (&iter);
1188 : : }
1189 : :
1190 [ # # ]: 0 : g_clear_object (&self->download);
1191 [ # # ]: 0 : g_clear_object (&self->upload);
1192 : : }
1193 : :
1194 : 33 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
1195 : 33 : }
1196 : :
1197 : : static void
1198 : 10 : valent_share_plugin_handle_packet (ValentDevicePlugin *plugin,
1199 : : const char *type,
1200 : : JsonNode *packet)
1201 : : {
1202 : 10 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (plugin);
1203 : 10 : const char *text;
1204 : 10 : const char *url;
1205 : :
1206 [ + - ]: 10 : g_assert (VALENT_IS_SHARE_PLUGIN (self));
1207 [ - + ]: 10 : g_assert (type != NULL);
1208 [ - + ]: 10 : g_assert (VALENT_IS_PACKET (packet));
1209 : :
1210 [ + + ]: 10 : if (g_str_equal (type, "kdeconnect.share.request"))
1211 : : {
1212 [ + + ]: 9 : if (valent_packet_check_field (packet, "filename"))
1213 : 7 : valent_share_plugin_handle_file (self, packet);
1214 : :
1215 [ + + ]: 2 : else if (valent_packet_get_string (packet, "text", &text))
1216 : 1 : valent_share_plugin_handle_text (self, text);
1217 : :
1218 [ + - ]: 1 : else if (valent_packet_get_string (packet, "url", &url))
1219 : 1 : valent_share_plugin_handle_url (self, url);
1220 : :
1221 : : else
1222 : 0 : g_warning ("%s(): unsupported share request", G_STRFUNC);
1223 : : }
1224 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.share.request.update"))
1225 : : {
1226 : 1 : valent_share_plugin_handle_file_update (self, packet);
1227 : : }
1228 : : else
1229 : 0 : g_assert_not_reached ();
1230 : 10 : }
1231 : :
1232 : : /*
1233 : : * ValentObject
1234 : : */
1235 : : static void
1236 : 20 : valent_share_plugin_destroy (ValentObject *object)
1237 : : {
1238 : 20 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (object);
1239 : 20 : GHashTableIter iter;
1240 : 20 : ValentTransfer *transfer;
1241 : :
1242 : : /* Cancel active transfers */
1243 : 20 : g_hash_table_iter_init (&iter, self->transfers);
1244 : :
1245 [ - + ]: 20 : while (g_hash_table_iter_next (&iter, NULL, (void **)&transfer))
1246 : : {
1247 : 0 : valent_transfer_cancel (transfer);
1248 : 0 : g_hash_table_iter_remove (&iter);
1249 : : }
1250 : :
1251 [ - + ]: 20 : g_clear_object (&self->download);
1252 [ - + ]: 20 : g_clear_object (&self->upload);
1253 : :
1254 : 20 : VALENT_OBJECT_CLASS (valent_share_plugin_parent_class)->destroy (object);
1255 : 20 : }
1256 : :
1257 : : /*
1258 : : * GObject
1259 : : */
1260 : : static void
1261 : 11 : valent_share_plugin_constructed (GObject *object)
1262 : : {
1263 : 11 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
1264 : :
1265 : 11 : G_OBJECT_CLASS (valent_share_plugin_parent_class)->constructed (object);
1266 : :
1267 : 11 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
1268 : : actions,
1269 : : G_N_ELEMENTS (actions),
1270 : : plugin);
1271 : 11 : }
1272 : :
1273 : : static void
1274 : 10 : valent_share_plugin_finalize (GObject *object)
1275 : : {
1276 : 10 : ValentSharePlugin *self = VALENT_SHARE_PLUGIN (object);
1277 : :
1278 [ + - ]: 10 : g_clear_pointer (&self->transfers, g_hash_table_unref);
1279 : :
1280 : 10 : G_OBJECT_CLASS (valent_share_plugin_parent_class)->finalize (object);
1281 : 10 : }
1282 : :
1283 : : static void
1284 : 13 : valent_share_plugin_class_init (ValentSharePluginClass *klass)
1285 : : {
1286 : 13 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
1287 : 13 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
1288 : 13 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
1289 : :
1290 : 13 : object_class->constructed = valent_share_plugin_constructed;
1291 : 13 : object_class->finalize = valent_share_plugin_finalize;
1292 : :
1293 : 13 : vobject_class->destroy = valent_share_plugin_destroy;
1294 : :
1295 : 13 : plugin_class->handle_packet = valent_share_plugin_handle_packet;
1296 : 13 : plugin_class->update_state = valent_share_plugin_update_state;
1297 : : }
1298 : :
1299 : : static void
1300 : 11 : valent_share_plugin_init (ValentSharePlugin *self)
1301 : : {
1302 : 11 : self->transfers = g_hash_table_new_full (g_str_hash,
1303 : : g_str_equal,
1304 : : g_free,
1305 : : g_object_unref);
1306 : 11 : }
1307 : :
|