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