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-download"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <valent.h>
10 : :
11 : : #include "valent-share-download.h"
12 : :
13 : : /* The maximum time in milliseconds to wait for the next expected transfer item,
14 : : * allowing for the gap between one file completing and the packet for the next.
15 : : *
16 : : * The current timeout matches kdeconnect-android which waits 1000ms before
17 : : * reporting an error, while kdeconnect-kde has no wait period. */
18 : : #define OPERATION_TIMEOUT_MS 1000
19 : :
20 : :
21 : : /**
22 : : * ValentShareDownload:
23 : : *
24 : : * A class for multi-file downloads.
25 : : *
26 : : * `ValentShareDownload` is a class that supports multi-file downloads for
27 : : * `ValentSharePlugin`.
28 : : */
29 : :
30 : : struct _ValentShareDownload
31 : : {
32 : : ValentTransfer parent_instance;
33 : :
34 : : ValentDevice *device;
35 : : GPtrArray *items;
36 : :
37 : : unsigned int position;
38 : : int64_t number_of_files;
39 : : goffset payload_size;
40 : : };
41 : :
42 : : static void g_list_model_iface_init (GListModelInterface *iface);
43 : : static gboolean valent_share_download_timeout (gpointer data);
44 : :
45 [ + + + - ]: 30 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentShareDownload, valent_share_download, VALENT_TYPE_TRANSFER,
46 : : G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
47 : :
48 : : typedef enum {
49 : : PROP_DEVICE = 1,
50 : : } ValentShareDownloadProperty;
51 : :
52 : : static GParamSpec *properties[PROP_DEVICE + 1] = { NULL, };
53 : :
54 : :
55 : : /*
56 : : * ValentTransfer
57 : : */
58 : : static void
59 : 6 : valent_transfer_execute_cb (GObject *object,
60 : : GAsyncResult *result,
61 : : gpointer user_data)
62 : : {
63 : 6 : ValentTransfer *transfer = VALENT_TRANSFER (object);
64 : 6 : ValentShareDownload *self = g_task_get_source_object (G_TASK (user_data));
65 : 6 : g_autoptr (GTask) task = G_TASK (user_data);
66 [ - - + - ]: 6 : g_autoptr (GError) error = NULL;
67 : :
68 [ - + ]: 6 : if (!valent_transfer_execute_finish (transfer, result, &error))
69 : : {
70 : 0 : g_task_return_error (task, g_steal_pointer (&error));
71 [ # # ]: 0 : return;
72 : : }
73 : :
74 [ + + ]: 6 : if (self->position < self->items->len)
75 : : {
76 : 1 : ValentTransfer *item = g_ptr_array_index (self->items, self->position++);
77 : :
78 : 1 : valent_transfer_execute (item,
79 : : g_task_get_cancellable (task),
80 : : valent_transfer_execute_cb,
81 : : g_object_ref (task));
82 : : }
83 [ + + ]: 5 : else if (self->position < self->number_of_files)
84 : : {
85 [ - + ]: 6 : g_autoptr (GSource) source = NULL;
86 : :
87 : 1 : source = g_timeout_source_new (OPERATION_TIMEOUT_MS);
88 [ + - ]: 1 : g_task_attach_source (task, source, valent_share_download_timeout);
89 : : }
90 : : else
91 : : {
92 : 4 : g_task_return_boolean (task, TRUE);
93 : : }
94 : : }
95 : :
96 : : static gboolean
97 : 1 : valent_share_download_timeout (gpointer data)
98 : : {
99 : 1 : ValentShareDownload *self = g_task_get_source_object (G_TASK (data));
100 : 1 : GTask *task = G_TASK (data);
101 : :
102 [ + - ]: 1 : if (g_task_return_error_if_cancelled (task))
103 : : return G_SOURCE_REMOVE;
104 : :
105 [ + - ]: 1 : if (self->position < self->items->len)
106 : : {
107 : 1 : ValentTransfer *item = g_ptr_array_index (self->items, self->position++);
108 : :
109 : 1 : valent_transfer_execute (item,
110 : : g_task_get_cancellable (task),
111 : : valent_transfer_execute_cb,
112 : : g_object_ref (task));
113 : : }
114 [ # # ]: 0 : else if (self->position < self->number_of_files)
115 : : {
116 : 0 : g_task_return_new_error (task,
117 : : G_IO_ERROR,
118 : : G_IO_ERROR_PARTIAL_INPUT,
119 : : "Failed to receive %u of %u files",
120 : : (unsigned int)self->number_of_files - self->position,
121 : : (unsigned int)self->number_of_files);
122 : : }
123 : :
124 : : return G_SOURCE_REMOVE;
125 : : }
126 : :
127 : : static void
128 : 4 : valent_share_download_execute (ValentTransfer *transfer,
129 : : GCancellable *cancellable,
130 : : GAsyncReadyCallback callback,
131 : : gpointer user_data)
132 : : {
133 : 4 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (transfer);
134 : 8 : g_autoptr (GTask) task = NULL;
135 : :
136 [ - + ]: 4 : g_assert (VALENT_IS_SHARE_DOWNLOAD (self));
137 [ + - + - : 4 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
138 : :
139 : 4 : task = g_task_new (self, cancellable, callback, user_data);
140 [ + - ]: 4 : g_task_set_source_tag (task, valent_share_download_execute);
141 : :
142 [ + - ]: 4 : if (self->position < self->items->len)
143 : : {
144 : 4 : ValentTransfer *item = g_ptr_array_index (self->items, self->position++);
145 : :
146 : 4 : valent_transfer_execute (item,
147 : : g_task_get_cancellable (task),
148 : : valent_transfer_execute_cb,
149 : : g_object_ref (task));
150 : : }
151 [ # # ]: 0 : else if (self->position < self->number_of_files)
152 : : {
153 [ + - ]: 4 : g_autoptr (GSource) source = NULL;
154 : :
155 : 0 : source = g_timeout_source_new (OPERATION_TIMEOUT_MS);
156 [ # # ]: 0 : g_task_attach_source (task, source, valent_share_download_timeout);
157 : : }
158 : : else
159 : : {
160 : 0 : g_task_return_boolean (task, TRUE);
161 : : }
162 : 4 : }
163 : :
164 : : /*
165 : : * GListModel
166 : : */
167 : : static gpointer
168 : 4 : valent_share_download_get_item (GListModel *model,
169 : : unsigned int position)
170 : : {
171 : 4 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (model);
172 : :
173 [ - + ]: 4 : g_assert (VALENT_SHARE_DOWNLOAD (self));
174 : :
175 [ + - ]: 4 : if G_UNLIKELY (position >= self->items->len)
176 : : return NULL;
177 : :
178 : 4 : return g_object_ref (g_ptr_array_index (self->items, position));
179 : : }
180 : :
181 : : static GType
182 : 0 : valent_share_download_get_item_type (GListModel *model)
183 : : {
184 [ # # ]: 0 : g_assert (VALENT_SHARE_DOWNLOAD (model));
185 : :
186 : 0 : return VALENT_TYPE_TRANSFER;
187 : : }
188 : :
189 : : static unsigned int
190 : 11 : valent_share_download_get_n_items (GListModel *model)
191 : : {
192 : 11 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (model);
193 : :
194 [ - + ]: 11 : g_assert (VALENT_SHARE_DOWNLOAD (self));
195 : :
196 : : /* FIXME: this indicates the number of total transfers, not the number of
197 : : * items currently available in the list model. */
198 : 11 : return self->number_of_files;
199 : : }
200 : :
201 : : static void
202 : 2 : g_list_model_iface_init (GListModelInterface *iface)
203 : : {
204 : 2 : iface->get_item = valent_share_download_get_item;
205 : 2 : iface->get_item_type = valent_share_download_get_item_type;
206 : 2 : iface->get_n_items = valent_share_download_get_n_items;
207 : 2 : }
208 : :
209 : : /*
210 : : * GObject
211 : : */
212 : : static void
213 : 4 : valent_share_download_finalize (GObject *object)
214 : : {
215 : 4 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (object);
216 : :
217 [ + - ]: 4 : g_clear_object (&self->device);
218 [ + - ]: 4 : g_clear_pointer (&self->items, g_ptr_array_unref);
219 : :
220 : 4 : G_OBJECT_CLASS (valent_share_download_parent_class)->finalize (object);
221 : 4 : }
222 : :
223 : : static void
224 : 11 : valent_share_download_get_property (GObject *object,
225 : : guint prop_id,
226 : : GValue *value,
227 : : GParamSpec *pspec)
228 : : {
229 : 11 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (object);
230 : :
231 [ + - ]: 11 : switch ((ValentShareDownloadProperty)prop_id)
232 : : {
233 : 11 : case PROP_DEVICE:
234 : 11 : g_value_set_object (value, self->device);
235 : 11 : break;
236 : :
237 : 0 : default:
238 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
239 : : }
240 : 11 : }
241 : :
242 : : static void
243 : 4 : valent_share_download_set_property (GObject *object,
244 : : guint prop_id,
245 : : const GValue *value,
246 : : GParamSpec *pspec)
247 : : {
248 : 4 : ValentShareDownload *self = VALENT_SHARE_DOWNLOAD (object);
249 : :
250 [ + - ]: 4 : switch ((ValentShareDownloadProperty)prop_id)
251 : : {
252 : 4 : case PROP_DEVICE:
253 : 4 : self->device = g_value_dup_object (value);
254 : 4 : break;
255 : :
256 : 0 : default:
257 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
258 : : }
259 : 4 : }
260 : :
261 : : static void
262 : 2 : valent_share_download_class_init (ValentShareDownloadClass *klass)
263 : : {
264 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
265 : 2 : ValentTransferClass *transfer_class = VALENT_TRANSFER_CLASS (klass);
266 : :
267 : 2 : object_class->finalize = valent_share_download_finalize;
268 : 2 : object_class->get_property = valent_share_download_get_property;
269 : 2 : object_class->set_property = valent_share_download_set_property;
270 : :
271 : 2 : transfer_class->execute = valent_share_download_execute;
272 : :
273 : : /**
274 : : * ValentShareDownload:device:
275 : : *
276 : : * The [class@Valent.Device] this transfer is for.
277 : : */
278 : 4 : properties [PROP_DEVICE] =
279 : 2 : g_param_spec_object ("device", NULL, NULL,
280 : : VALENT_TYPE_DEVICE,
281 : : (G_PARAM_READWRITE |
282 : : G_PARAM_CONSTRUCT_ONLY |
283 : : G_PARAM_EXPLICIT_NOTIFY |
284 : : G_PARAM_STATIC_STRINGS));
285 : :
286 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
287 : 2 : }
288 : :
289 : : static void
290 : 4 : valent_share_download_init (ValentShareDownload *self)
291 : : {
292 : 4 : self->items = g_ptr_array_new_with_free_func (g_object_unref);
293 : 4 : }
294 : :
295 : : /**
296 : : * valent_share_download_new:
297 : : * @device: a `ValentDevice`
298 : : *
299 : : * Create a new `ValentShareDownload`.
300 : : *
301 : : * Returns: (transfer full): a new `ValentShareDownload`
302 : : */
303 : : ValentTransfer *
304 : 4 : valent_share_download_new (ValentDevice *device)
305 : : {
306 [ - + ]: 4 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
307 : :
308 : 4 : return g_object_new (VALENT_TYPE_SHARE_DOWNLOAD,
309 : : "device", device,
310 : : NULL);
311 : : }
312 : :
313 : : /**
314 : : * valent_share_download_add_file:
315 : : * @group: a `ValentShareDownload`
316 : : * @file: a `GFile`
317 : : * @packet: a KDE Connect packet
318 : : *
319 : : * Add @file to the transfer operation.
320 : : */
321 : : void
322 : 6 : valent_share_download_add_file (ValentShareDownload *download,
323 : : GFile *file,
324 : : JsonNode *packet)
325 : : {
326 : 6 : g_autoptr (ValentTransfer) item = NULL;
327 : 6 : unsigned int position, added;
328 : 6 : int64_t number_of_files;
329 : 6 : goffset total_payload_size;
330 : :
331 [ - + ]: 6 : g_return_if_fail (VALENT_IS_SHARE_DOWNLOAD (download));
332 [ + - + - : 6 : g_return_if_fail (G_IS_FILE (file));
+ - + - ]
333 [ + - ]: 6 : g_return_if_fail (VALENT_IS_PACKET (packet));
334 : :
335 [ - + ]: 6 : if (!valent_packet_get_int (packet, "numberOfFiles", &number_of_files))
336 : 0 : number_of_files = download->number_of_files + 1;
337 : :
338 [ - + ]: 6 : if (!valent_packet_get_int (packet, "totalPayloadSize", &total_payload_size))
339 : 0 : total_payload_size = download->payload_size + valent_packet_get_payload_size (packet);
340 : :
341 : 6 : position = download->items->len;
342 : 6 : added = number_of_files - download->number_of_files;
343 : :
344 : 6 : download->number_of_files = number_of_files;
345 : 6 : download->payload_size = total_payload_size;
346 : :
347 : 6 : item = valent_device_transfer_new (download->device, packet, file);
348 : 6 : g_ptr_array_add (download->items, g_steal_pointer (&item));
349 : :
350 : : /* FIXME: this indicates the number of total transfers, not the number of
351 : : * items currently available in the list model. */
352 : 6 : g_list_model_items_changed (G_LIST_MODEL (download), position, 0, added);
353 : : }
354 : :
355 : : /**
356 : : * valent_share_download_update:
357 : : * @download: a `ValentShareDownload`
358 : : * @packet: a KDE Connect packet
359 : : *
360 : : * Update the number of files and total payload size for @download.
361 : : */
362 : : void
363 : 1 : valent_share_download_update (ValentShareDownload *self,
364 : : JsonNode *packet)
365 : : {
366 [ - + ]: 1 : g_return_if_fail (VALENT_IS_SHARE_DOWNLOAD (self));
367 [ + - ]: 1 : g_return_if_fail (VALENT_IS_PACKET (packet));
368 : :
369 [ - + ]: 1 : if (!valent_packet_get_int (packet, "numberOfFiles", &self->number_of_files))
370 : : {
371 : 0 : g_debug ("%s(): expected \"numberOfFiles\" field holding an integer",
372 : : G_STRFUNC);
373 : 0 : return;
374 : : }
375 : :
376 [ - + ]: 1 : if (!valent_packet_get_int (packet, "totalPayloadSize", &self->payload_size))
377 : : {
378 : 0 : g_debug ("%s(): expected \"totalPayloadSize\" field holding an integer",
379 : : G_STRFUNC);
380 : 0 : return;
381 : : }
382 : : }
383 : :
|