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-device-transfer"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <math.h>
9 : :
10 : : #include <libvalent-core.h>
11 : :
12 : : #include "valent-channel.h"
13 : : #include "valent-device.h"
14 : : #include "valent-device-transfer.h"
15 : : #include "valent-packet.h"
16 : :
17 : :
18 : : /**
19 : : * ValentDeviceTransfer:
20 : : *
21 : : * A class for device file transfers.
22 : : *
23 : : * `ValentDeviceTransfer` is an implementation of [class@Valent.Transfer] for the
24 : : * common case of transferring a file between devices.
25 : : *
26 : : * The direction of the transfer is automatically detected from the content of
27 : : * [property@Valent.DeviceTransfer:packet]. If the KDE Connect packet holds
28 : : * payload information the transfer is assumed to be a download, otherwise it is
29 : : * assumed to be an upload.
30 : : *
31 : : * Since: 1.0
32 : : */
33 : :
34 : : struct _ValentDeviceTransfer
35 : : {
36 : : ValentObject parent_instance;
37 : :
38 : : ValentDevice *device;
39 : : GFile *file;
40 : : JsonNode *packet;
41 : : };
42 : :
43 [ + + + - ]: 228 : G_DEFINE_FINAL_TYPE (ValentDeviceTransfer, valent_device_transfer, VALENT_TYPE_TRANSFER)
44 : :
45 : :
46 : : typedef enum {
47 : : PROP_DEVICE = 1,
48 : : PROP_FILE,
49 : : PROP_PACKET,
50 : : } ValentDeviceTransferProperty;
51 : :
52 : : static GParamSpec *properties[PROP_PACKET + 1] = { NULL, };
53 : :
54 : :
55 : : static void
56 : 12 : valent_device_transfer_update_packet (JsonNode *packet,
57 : : GFileInfo *info)
58 : : {
59 : 12 : JsonObject *body;
60 : 12 : uint64_t btime_s, mtime_s;
61 : 12 : uint32_t btime_us, mtime_us;
62 : 12 : int64_t creation_time, last_modified;
63 : :
64 [ + - ]: 12 : g_assert (VALENT_IS_PACKET (packet));
65 : :
66 : 12 : btime_s = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED);
67 : 12 : btime_us = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CREATED_USEC);
68 : 12 : creation_time = (int64_t)((btime_s * 1000) + floor (btime_us / 1000));
69 : :
70 : 12 : mtime_s = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
71 : 12 : mtime_us = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC);
72 : 12 : last_modified = (int64_t)((mtime_s * 1000) + floor (mtime_us / 1000));
73 : :
74 : 12 : body = valent_packet_get_body (packet);
75 : 12 : json_object_set_int_member (body, "creationTime", creation_time);
76 : 12 : json_object_set_int_member (body, "lastModified", last_modified);
77 : 12 : valent_packet_set_payload_size (packet, g_file_info_get_size (info));
78 : 12 : }
79 : :
80 : : /*
81 : : * ValentDeviceTransfer
82 : : */
83 : : static void
84 : 22 : valent_device_transfer_execute_task (GTask *task,
85 : : gpointer source_object,
86 : : gpointer task_data,
87 : : GCancellable *cancellable)
88 : : {
89 : 22 : ValentDeviceTransfer *self = VALENT_DEVICE_TRANSFER (source_object);
90 : 22 : g_autoptr (ValentChannel) channel = NULL;
91 [ - - ]: 22 : g_autoptr (GFile) file = NULL;
92 [ + - - - ]: 22 : g_autoptr (JsonNode) packet = NULL;
93 [ + - - - ]: 22 : g_autoptr (GIOStream) stream = NULL;
94 [ - - ]: 22 : g_autoptr (GInputStream) source = NULL;
95 [ + - ]: 22 : g_autoptr (GOutputStream) target = NULL;
96 : 22 : gboolean is_download = FALSE;
97 : 22 : gssize transferred;
98 : 22 : int64_t last_modified = 0;
99 : 22 : int64_t creation_time = 0;
100 : 22 : goffset payload_size;
101 : 22 : GError *error = NULL;
102 : :
103 [ + - ]: 22 : if (g_task_return_error_if_cancelled (task))
104 : : return;
105 : :
106 : 22 : valent_object_lock (VALENT_OBJECT (self));
107 : 22 : channel = valent_device_ref_channel (self->device);
108 : 22 : file = g_object_ref (self->file);
109 : 22 : packet = json_node_ref (self->packet);
110 : 22 : valent_object_unlock (VALENT_OBJECT (self));
111 : :
112 [ - + ]: 22 : if (channel == NULL)
113 : : {
114 : 0 : g_task_return_new_error (task,
115 : : G_IO_ERROR,
116 : : G_IO_ERROR_NOT_CONNECTED,
117 : : "Device is disconnected");
118 : 0 : return;
119 : : }
120 : :
121 : : /* Determine if this is a download or an upload. This should be reliable,
122 : : * given that the channel service must set the `payloadTransferInfo` field in
123 : : * its valent_channel_upload() implementation. */
124 : 22 : is_download = valent_packet_has_payload (packet);
125 : :
126 [ + + ]: 22 : if (is_download)
127 : : {
128 : 10 : target = (GOutputStream *)g_file_replace (file,
129 : : NULL,
130 : : FALSE,
131 : : G_FILE_CREATE_REPLACE_DESTINATION,
132 : : cancellable,
133 : : &error);
134 : :
135 [ - + ]: 10 : if (target == NULL)
136 : 0 : return g_task_return_error (task, error);
137 : :
138 : 10 : stream = valent_channel_download (channel, packet, cancellable, &error);
139 : :
140 [ - + ]: 10 : if (stream == NULL)
141 : 0 : return g_task_return_error (task, error);
142 : :
143 : 10 : source = g_object_ref (g_io_stream_get_input_stream (stream));
144 : : }
145 : : else
146 : : {
147 [ - - ]: 12 : g_autoptr (GFileInfo) info = NULL;
148 : :
149 : 12 : info = g_file_query_info (file,
150 : : G_FILE_ATTRIBUTE_TIME_CREATED","
151 : : G_FILE_ATTRIBUTE_TIME_CREATED_USEC","
152 : : G_FILE_ATTRIBUTE_TIME_MODIFIED","
153 : : G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC","
154 : : G_FILE_ATTRIBUTE_STANDARD_SIZE,
155 : : G_FILE_QUERY_INFO_NONE,
156 : : cancellable,
157 : : &error);
158 : :
159 [ - + ]: 12 : if (info == NULL)
160 : 0 : return g_task_return_error (task, error);
161 : :
162 : 12 : source = (GInputStream *)g_file_read (file, cancellable, &error);
163 : :
164 [ - + ]: 12 : if (source == NULL)
165 : 0 : return g_task_return_error (task, error);
166 : :
167 : 12 : valent_device_transfer_update_packet (packet, info);
168 : 12 : stream = valent_channel_upload (channel, packet, cancellable, &error);
169 : :
170 [ - + ]: 12 : if (stream == NULL)
171 : 0 : return g_task_return_error (task, error);
172 : :
173 : 12 : target = g_object_ref (g_io_stream_get_output_stream (stream));
174 : : }
175 : :
176 : : /* Transfer the payload */
177 : 22 : transferred = g_output_stream_splice (target,
178 : : source,
179 : : (G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
180 : : G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET),
181 : : cancellable,
182 : : &error);
183 : :
184 [ - + ]: 22 : if (error != NULL)
185 : : {
186 [ # # ]: 0 : if (is_download)
187 : 0 : g_file_delete (file, NULL, NULL);
188 : :
189 : 0 : return g_task_return_error (task, error);
190 : : }
191 : :
192 : : /* If possible, confirm the transferred size with the payload size */
193 : 22 : payload_size = valent_packet_get_payload_size (packet);
194 : :
195 : 22 : if G_UNLIKELY (payload_size > G_MAXSSIZE)
196 : : {
197 : : g_warning ("%s(): Payload size greater than %"G_GSSIZE_FORMAT";"
198 : : "unable to confirm transfer completion",
199 : : G_STRFUNC, G_MAXSSIZE);
200 : : }
201 [ - + ]: 22 : else if (transferred < payload_size)
202 : : {
203 : 0 : g_debug ("%s(): Transfer incomplete (%"G_GSSIZE_FORMAT"/%"G_GOFFSET_FORMAT" bytes)",
204 : : G_STRFUNC, transferred, payload_size);
205 : :
206 [ # # ]: 0 : if (is_download)
207 : 0 : g_file_delete (file, NULL, NULL);
208 : :
209 : 0 : g_task_return_new_error (task,
210 : : G_IO_ERROR,
211 : : G_IO_ERROR_PARTIAL_INPUT,
212 : : "Transfer incomplete");
213 : 0 : return;
214 : : }
215 : :
216 : : /* Attempt to set file attributes for downloaded files. */
217 [ + + ]: 22 : if (is_download)
218 : : {
219 : : /* NOTE: this is not supported by the Linux kernel... */
220 [ + + ]: 10 : if (valent_packet_get_int (packet, "creationTime", &creation_time))
221 : : {
222 : 1 : gboolean success;
223 : 1 : g_autoptr (GError) warn = NULL;
224 : :
225 : 2 : success = g_file_set_attribute_uint64 (file,
226 : : G_FILE_ATTRIBUTE_TIME_CREATED,
227 : 1 : (uint64_t)floor (creation_time / 1000),
228 : : G_FILE_QUERY_INFO_NONE,
229 : : cancellable,
230 : : &warn);
231 : :
232 [ - + ]: 1 : if (success)
233 : : {
234 : 0 : g_file_set_attribute_uint32 (file,
235 : : G_FILE_ATTRIBUTE_TIME_CREATED_USEC,
236 : 0 : (uint32_t)((creation_time % 1000) * 1000),
237 : : G_FILE_QUERY_INFO_NONE,
238 : : cancellable,
239 : : &warn);
240 : : }
241 : :
242 [ + - ]: 1 : if (warn != NULL)
243 : 1 : g_debug ("%s: %s", G_OBJECT_TYPE_NAME (self), warn->message);
244 : : }
245 : :
246 [ + + ]: 10 : if (valent_packet_get_int (packet, "lastModified", &last_modified))
247 : : {
248 : 1 : gboolean success;
249 : 1 : g_autoptr (GError) warn = NULL;
250 : :
251 : 2 : success = g_file_set_attribute_uint64 (file,
252 : : G_FILE_ATTRIBUTE_TIME_MODIFIED,
253 : 1 : (uint64_t)floor (last_modified / 1000),
254 : : G_FILE_QUERY_INFO_NONE,
255 : : cancellable,
256 : : &warn);
257 : :
258 [ + - ]: 1 : if (success)
259 : : {
260 : 1 : g_file_set_attribute_uint32 (file,
261 : : G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
262 : 1 : (uint32_t)((last_modified % 1000) * 1000),
263 : : G_FILE_QUERY_INFO_NONE,
264 : : cancellable,
265 : : &warn);
266 : : }
267 : :
268 [ - + ]: 1 : if (warn != NULL)
269 : 0 : g_debug ("%s: %s", G_OBJECT_TYPE_NAME (self), warn->message);
270 : : }
271 : : }
272 : :
273 [ + - ]: 22 : g_task_return_boolean (task, TRUE);
274 : : }
275 : :
276 : : static void
277 : 22 : valent_device_transfer_execute (ValentTransfer *transfer,
278 : : GCancellable *cancellable,
279 : : GAsyncReadyCallback callback,
280 : : gpointer user_data)
281 : : {
282 : 44 : g_autoptr (GTask) task = NULL;
283 : :
284 : 22 : VALENT_ENTRY;
285 : :
286 [ + - ]: 22 : g_assert (VALENT_IS_DEVICE_TRANSFER (transfer));
287 [ + - + - : 22 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
288 : :
289 : 22 : task = g_task_new (transfer, cancellable, callback, user_data);
290 [ + - ]: 22 : g_task_set_source_tag (task, valent_device_transfer_execute);
291 : 22 : g_task_run_in_thread (task, valent_device_transfer_execute_task);
292 : :
293 [ + - ]: 22 : VALENT_EXIT;
294 : : }
295 : :
296 : : /*
297 : : * GObject
298 : : */
299 : : static void
300 : 19 : valent_device_transfer_finalize (GObject *object)
301 : : {
302 : 19 : ValentDeviceTransfer *self = VALENT_DEVICE_TRANSFER (object);
303 : :
304 : 19 : valent_object_lock (VALENT_OBJECT (self));
305 [ + - ]: 19 : g_clear_object (&self->device);
306 [ + - ]: 19 : g_clear_object (&self->file);
307 [ + - ]: 19 : g_clear_pointer (&self->packet, json_node_unref);
308 : 19 : valent_object_unlock (VALENT_OBJECT (self));
309 : :
310 : 19 : G_OBJECT_CLASS (valent_device_transfer_parent_class)->finalize (object);
311 : 19 : }
312 : :
313 : : static void
314 : 11 : valent_device_transfer_get_property (GObject *object,
315 : : guint prop_id,
316 : : GValue *value,
317 : : GParamSpec *pspec)
318 : : {
319 : 11 : ValentDeviceTransfer *self = VALENT_DEVICE_TRANSFER (object);
320 : :
321 [ + + + - ]: 11 : switch ((ValentDeviceTransferProperty)prop_id)
322 : : {
323 : 4 : case PROP_DEVICE:
324 : 4 : g_value_take_object (value, valent_device_transfer_ref_device (self));
325 : 4 : break;
326 : :
327 : 5 : case PROP_FILE:
328 : 5 : g_value_take_object (value, valent_device_transfer_ref_file (self));
329 : 5 : break;
330 : :
331 : 2 : case PROP_PACKET:
332 : 2 : g_value_take_boxed (value, valent_device_transfer_ref_packet (self));
333 : 2 : break;
334 : :
335 : 0 : default:
336 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
337 : : }
338 : 11 : }
339 : :
340 : : static void
341 : 66 : valent_device_transfer_set_property (GObject *object,
342 : : guint prop_id,
343 : : const GValue *value,
344 : : GParamSpec *pspec)
345 : : {
346 : 66 : ValentDeviceTransfer *self = VALENT_DEVICE_TRANSFER (object);
347 : :
348 [ + + + - ]: 66 : switch ((ValentDeviceTransferProperty)prop_id)
349 : : {
350 : : case PROP_DEVICE:
351 : 22 : valent_object_lock (VALENT_OBJECT (self));
352 : 22 : self->device = g_value_dup_object (value);
353 : 22 : valent_object_unlock (VALENT_OBJECT (self));
354 : 22 : break;
355 : :
356 : : case PROP_FILE:
357 : 22 : valent_object_lock (VALENT_OBJECT (self));
358 : 22 : self->file = g_value_dup_object (value);
359 : 22 : valent_object_unlock (VALENT_OBJECT (self));
360 : 22 : break;
361 : :
362 : : case PROP_PACKET:
363 : 22 : valent_object_lock (VALENT_OBJECT (self));
364 : 22 : self->packet = g_value_dup_boxed (value);
365 : 22 : valent_object_unlock (VALENT_OBJECT (self));
366 : 22 : break;
367 : :
368 : 0 : default:
369 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
370 : : }
371 : 66 : }
372 : :
373 : : static void
374 : 7 : valent_device_transfer_class_init (ValentDeviceTransferClass *klass)
375 : : {
376 : 7 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
377 : 7 : ValentTransferClass *transfer_class = VALENT_TRANSFER_CLASS (klass);
378 : :
379 : 7 : object_class->finalize = valent_device_transfer_finalize;
380 : 7 : object_class->get_property = valent_device_transfer_get_property;
381 : 7 : object_class->set_property = valent_device_transfer_set_property;
382 : :
383 : 7 : transfer_class->execute = valent_device_transfer_execute;
384 : :
385 : : /**
386 : : * ValentDeviceTransfer:device: (getter ref_device)
387 : : *
388 : : * The [class@Valent.Device] this transfer is for.
389 : : *
390 : : * Since: 1.0
391 : : */
392 : 14 : properties [PROP_DEVICE] =
393 : 7 : g_param_spec_object ("device", NULL, NULL,
394 : : VALENT_TYPE_DEVICE,
395 : : (G_PARAM_READWRITE |
396 : : G_PARAM_CONSTRUCT_ONLY |
397 : : G_PARAM_EXPLICIT_NOTIFY |
398 : : G_PARAM_STATIC_STRINGS));
399 : :
400 : : /**
401 : : * ValentDeviceTransfer:file: (getter ref_file)
402 : : *
403 : : * The local [iface@Gio.File].
404 : : *
405 : : * If this a download, then this is the destination. If it's upload, this is
406 : : * the source.
407 : : *
408 : : * Since: 1.0
409 : : */
410 : 14 : properties [PROP_FILE] =
411 : 7 : g_param_spec_object ("file", NULL, NULL,
412 : : G_TYPE_FILE,
413 : : (G_PARAM_READWRITE |
414 : : G_PARAM_CONSTRUCT_ONLY |
415 : : G_PARAM_EXPLICIT_NOTIFY |
416 : : G_PARAM_STATIC_STRINGS));
417 : :
418 : : /**
419 : : * ValentDeviceTransfer:packet: (getter ref_packet)
420 : : *
421 : : * The KDE Connect packet describing the payload.
422 : : *
423 : : * Since: 1.0
424 : : */
425 : 14 : properties [PROP_PACKET] =
426 : 7 : g_param_spec_boxed ("packet", NULL, NULL,
427 : : JSON_TYPE_NODE,
428 : : (G_PARAM_READWRITE |
429 : : G_PARAM_CONSTRUCT_ONLY |
430 : : G_PARAM_EXPLICIT_NOTIFY |
431 : : G_PARAM_STATIC_STRINGS));
432 : :
433 : 7 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
434 : 7 : }
435 : :
436 : : static void
437 : 22 : valent_device_transfer_init (ValentDeviceTransfer *self)
438 : : {
439 : 22 : }
440 : :
441 : : /**
442 : : * valent_device_transfer_new:
443 : : * @device: a `ValentDevice`
444 : : * @packet: a KDE Connect packet
445 : : * @file: a `GFile`
446 : : *
447 : : * A convenience for creating a simple file transfer.
448 : : *
449 : : * Returns: (transfer full) (nullable): a `ValentDeviceTransfer`
450 : : *
451 : : * Since: 1.0
452 : : */
453 : : ValentTransfer *
454 : 22 : valent_device_transfer_new (ValentDevice *device,
455 : : JsonNode *packet,
456 : : GFile *file)
457 : : {
458 [ + - ]: 22 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
459 [ - + ]: 22 : g_return_val_if_fail (VALENT_IS_PACKET (packet), NULL);
460 [ + - + - : 22 : g_return_val_if_fail (G_IS_FILE (file), NULL);
+ - - + ]
461 : :
462 : 22 : return g_object_new (VALENT_TYPE_DEVICE_TRANSFER,
463 : : "device", device,
464 : : "packet", packet,
465 : : "file", file,
466 : : NULL);
467 : : }
468 : :
469 : : /**
470 : : * valent_device_transfer_ref_device: (get-property device)
471 : : * @transfer: a `ValentDeviceTransfer`
472 : : *
473 : : * Get the [class@Valent.Device].
474 : : *
475 : : * Returns: (transfer full) (nullable): a `ValentDevice`
476 : : *
477 : : * Since: 1.0
478 : : */
479 : : ValentDevice *
480 : 4 : valent_device_transfer_ref_device (ValentDeviceTransfer *transfer)
481 : : {
482 : 8 : g_autoptr (ValentDevice) ret = NULL;
483 : :
484 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_DEVICE_TRANSFER (transfer), NULL);
485 : :
486 : 4 : valent_object_lock (VALENT_OBJECT (transfer));
487 [ + - ]: 4 : if (transfer->device != NULL)
488 : 4 : ret = g_object_ref (transfer->device);
489 : 4 : valent_object_unlock (VALENT_OBJECT (transfer));
490 : :
491 : 4 : return g_steal_pointer (&ret);
492 : : }
493 : :
494 : : /**
495 : : * valent_device_transfer_ref_file: (get-property file)
496 : : * @transfer: a `ValentDeviceTransfer`
497 : : *
498 : : * Get the local [iface@Gio.File].
499 : : *
500 : : * Returns: (transfer full) (nullable): a `GFile`
501 : : *
502 : : * Since: 1.0
503 : : */
504 : : GFile *
505 : 11 : valent_device_transfer_ref_file (ValentDeviceTransfer *transfer)
506 : : {
507 : 22 : g_autoptr (GFile) ret = NULL;
508 : :
509 [ + - ]: 11 : g_return_val_if_fail (VALENT_IS_DEVICE_TRANSFER (transfer), NULL);
510 : :
511 : 11 : valent_object_lock (VALENT_OBJECT (transfer));
512 [ + - ]: 11 : if (transfer->file != NULL)
513 : 11 : ret = g_object_ref (transfer->file);
514 : 11 : valent_object_unlock (VALENT_OBJECT (transfer));
515 : :
516 : 11 : return g_steal_pointer (&ret);
517 : : }
518 : :
519 : : /**
520 : : * valent_device_transfer_ref_packet: (get-property packet)
521 : : * @transfer: a `ValentDeviceTransfer`
522 : : *
523 : : * Get the KDE Connect packet.
524 : : *
525 : : * Returns: (transfer full) (nullable): a KDE Connect packet
526 : : *
527 : : * Since: 1.0
528 : : */
529 : : JsonNode *
530 : 22 : valent_device_transfer_ref_packet (ValentDeviceTransfer *transfer)
531 : : {
532 : 22 : g_autoptr (JsonNode) ret = NULL;
533 : :
534 [ + - ]: 22 : g_return_val_if_fail (VALENT_IS_DEVICE_TRANSFER (transfer), NULL);
535 : :
536 : 22 : valent_object_lock (VALENT_OBJECT (transfer));
537 [ + - ]: 22 : if (transfer->packet != NULL)
538 : 22 : ret = json_node_ref (transfer->packet);
539 : 22 : valent_object_unlock (VALENT_OBJECT (transfer));
540 : :
541 : 22 : return g_steal_pointer (&ret);
542 : : }
543 : :
|