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