1 /*
2 Copyright (C) 2016 Red Hat, Inc.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include "spice-file-transfer-task-priv.h"
21
22 /**
23 * SECTION:file-transfer-task
24 * @short_description: Monitoring file transfers
25 * @title: File Transfer Task
26 * @section_id:
27 * @see_also: #SpiceMainChannel
28 * @stability: Stable
29 * @include: spice-client.h
30 *
31 * SpiceFileTransferTask is an object that represents a particular file
32 * transfer between the client and the guest. The properties and signals of the
33 * object can be used to monitor the status and result of the transfer. The
34 * Main Channel's #SpiceMainChannel::new-file-transfer signal will be emitted
35 * whenever a new file transfer task is initiated.
36 *
37 * Since: 0.31
38 */
39
40 struct _SpiceFileTransferTask
41 {
42 GObject parent;
43
44 uint32_t id;
45 gboolean completed;
46 gboolean pending;
47 GFile *file;
48 SpiceMainChannel *channel;
49 GFileInputStream *file_stream;
50 GFileCopyFlags flags;
51 GCancellable *cancellable;
52 GAsyncReadyCallback callback;
53 gpointer user_data;
54 char *buffer;
55 uint64_t read_bytes;
56 uint64_t file_size;
57 gint64 start_time;
58 gint64 last_update;
59 GError *error;
60 };
61
62 struct _SpiceFileTransferTaskClass
63 {
64 GObjectClass parent_class;
65 };
66
67 G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
68
69 #define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
70
71 enum {
72 PROP_TASK_ID = 1,
73 PROP_TASK_CHANNEL,
74 PROP_TASK_CANCELLABLE,
75 PROP_TASK_FILE,
76 PROP_TASK_TOTAL_BYTES,
77 PROP_TASK_TRANSFERRED_BYTES,
78 PROP_TASK_PROGRESS,
79 };
80
81 enum {
82 SIGNAL_FINISHED,
83 LAST_TASK_SIGNAL
84 };
85
86 static guint task_signals[LAST_TASK_SIGNAL];
87
88 /*******************************************************************************
89 * Helpers
90 ******************************************************************************/
91
92 static SpiceFileTransferTask *
spice_file_transfer_task_new(SpiceMainChannel * channel,GFile * file,GFileCopyFlags flags,GCancellable * cancellable)93 spice_file_transfer_task_new(SpiceMainChannel *channel,
94 GFile *file,
95 GFileCopyFlags flags,
96 GCancellable *cancellable)
97 {
98 static uint32_t xfer_id = 1; /* Used to identify task id */
99 GCancellable *task_cancellable = cancellable;
100 SpiceFileTransferTask *self;
101
102 /* if a cancellable object was not provided for the overall operation,
103 * create a separate object for each file so that they can be cancelled
104 * separately */
105 if (!task_cancellable)
106 task_cancellable = g_cancellable_new();
107
108 self = g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK,
109 "id", xfer_id++,
110 "file", file,
111 "channel", channel,
112 "cancellable", task_cancellable,
113 NULL);
114 self->flags = flags;
115
116 /* if we created a GCancellable above, unref it */
117 if (!cancellable)
118 g_object_unref(task_cancellable);
119
120 return self;
121 }
122
spice_file_transfer_task_query_info_cb(GObject * obj,GAsyncResult * res,gpointer user_data)123 static void spice_file_transfer_task_query_info_cb(GObject *obj,
124 GAsyncResult *res,
125 gpointer user_data)
126 {
127 SpiceFileTransferTask *self;
128 GFileInfo *info;
129 GTask *task;
130 GError *error = NULL;
131
132 task = G_TASK(user_data);
133 self = g_task_get_source_object(task);
134
135 g_return_if_fail(self->pending == TRUE);
136 self->pending = FALSE;
137
138 info = g_file_query_info_finish(G_FILE(obj), res, &error);
139 if (self->error) {
140 g_clear_object(&info);
141 g_clear_error(&error);
142 /* Return error previously reported */
143 g_task_return_error(task, g_error_copy(self->error));
144 g_object_unref(task);
145 return;
146 } else if (error) {
147 g_task_return_error(task, error);
148 g_object_unref(task);
149 return;
150 }
151
152 self->file_size =
153 g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
154
155 /* SpiceFileTransferTask's init is done, handshake for file-transfer will
156 * start soon. First "progress" can be emitted ~ 0% */
157 g_object_notify(G_OBJECT(self), "total-bytes");
158 g_object_notify(G_OBJECT(self), "progress");
159
160 g_task_return_pointer(task, info, g_object_unref);
161 g_object_unref(task);
162 }
163
spice_file_transfer_task_read_file_cb(GObject * obj,GAsyncResult * res,gpointer user_data)164 static void spice_file_transfer_task_read_file_cb(GObject *obj,
165 GAsyncResult *res,
166 gpointer user_data)
167 {
168 SpiceFileTransferTask *self;
169 GTask *task;
170 GError *error = NULL;
171
172 task = G_TASK(user_data);
173 self = g_task_get_source_object(task);
174
175 g_return_if_fail(self->pending == TRUE);
176
177 self->file_stream = g_file_read_finish(G_FILE(obj), res, &error);
178 if (self->error) {
179 g_clear_error(&error);
180 /* Return error previously reported */
181 self->pending = FALSE;
182 g_task_return_error(task, g_error_copy(self->error));
183 g_object_unref(task);
184 return;
185 } else if (error) {
186 self->pending = FALSE;
187 g_task_return_error(task, error);
188 g_object_unref(task);
189 return;
190 }
191
192 g_file_query_info_async(self->file,
193 "standard::*",
194 G_FILE_QUERY_INFO_NONE,
195 G_PRIORITY_DEFAULT,
196 self->cancellable,
197 spice_file_transfer_task_query_info_cb,
198 task);
199 }
200
spice_file_transfer_task_read_stream_cb(GObject * source_object,GAsyncResult * res,gpointer userdata)201 static void spice_file_transfer_task_read_stream_cb(GObject *source_object,
202 GAsyncResult *res,
203 gpointer userdata)
204 {
205 SpiceFileTransferTask *self;
206 GTask *task;
207 gssize nbytes;
208 GError *error = NULL;
209
210 task = G_TASK(userdata);
211 self = g_task_get_source_object(task);
212
213 g_return_if_fail(self->pending == TRUE);
214 self->pending = FALSE;
215
216 nbytes = g_input_stream_read_finish(G_INPUT_STREAM(self->file_stream), res, &error);
217 if (self->error) {
218 g_clear_error(&error);
219 /* On any pending error on SpiceFileTransferTask */
220 g_task_return_error(task, g_error_copy(self->error));
221 g_object_unref(task);
222 return;
223 } else if (error) {
224 g_task_return_error(task, error);
225 g_object_unref(task);
226 return;
227 }
228
229 self->read_bytes += nbytes;
230
231 if (spice_util_get_debug()) {
232 const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
233 gint64 now = g_get_monotonic_time();
234
235 if (interval < now - self->last_update) {
236 gchar *basename = g_file_get_basename(self->file);
237 self->last_update = now;
238 SPICE_DEBUG("read %.2f%% of the file %s",
239 100.0 * self->read_bytes / self->file_size, basename);
240 g_free(basename);
241 }
242 }
243
244 g_task_return_int(task, nbytes);
245 g_object_unref(task);
246 }
247
248 /* main context */
spice_file_transfer_task_close_stream_cb(GObject * object,GAsyncResult * close_res,gpointer user_data)249 static void spice_file_transfer_task_close_stream_cb(GObject *object,
250 GAsyncResult *close_res,
251 gpointer user_data)
252 {
253 SpiceFileTransferTask *self;
254 GError *error = NULL;
255
256 self = user_data;
257
258 if (object) {
259 GInputStream *stream = G_INPUT_STREAM(object);
260 g_input_stream_close_finish(stream, close_res, &error);
261 if (error) {
262 /* This error dont need to report to user, just print a log */
263 SPICE_DEBUG("close file error: %s", error->message);
264 g_clear_error(&error);
265 }
266 }
267
268 if (self->error == NULL && spice_util_get_debug()) {
269 gint64 now = g_get_monotonic_time();
270 gchar *basename = g_file_get_basename(self->file);
271 double seconds = (double) (now - self->start_time) / G_TIME_SPAN_SECOND;
272 gchar *file_size_str = g_format_size(self->file_size);
273 gchar *transfer_speed_str = g_format_size(self->file_size / seconds);
274
275 g_warn_if_fail(self->read_bytes == self->file_size);
276 SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)",
277 basename, file_size_str, seconds, transfer_speed_str);
278
279 g_free(basename);
280 g_free(file_size_str);
281 g_free(transfer_speed_str);
282 }
283 g_object_unref(self);
284 }
285
286
287 /*******************************************************************************
288 * Internal API
289 ******************************************************************************/
290
291 G_GNUC_INTERNAL
spice_file_transfer_task_completed(SpiceFileTransferTask * self,GError * error)292 void spice_file_transfer_task_completed(SpiceFileTransferTask *self,
293 GError *error)
294 {
295 self->completed = TRUE;
296
297 /* In case of multiple errors we only report the first error */
298 if (self->error)
299 g_clear_error(&error);
300 if (error) {
301 gchar *path = g_file_get_path(self->file);
302 SPICE_DEBUG("File %s xfer failed: %s",
303 path, error->message);
304 g_free(path);
305 self->error = error;
306 }
307
308 if (self->pending) {
309 /* Complete but pending is okay only if error is set */
310 if (self->error == NULL) {
311 self->error = g_error_new(SPICE_CLIENT_ERROR,
312 SPICE_CLIENT_ERROR_FAILED,
313 "Cannot complete task in pending state");
314 }
315 return;
316 }
317
318 if (!self->file_stream) {
319 spice_file_transfer_task_close_stream_cb(NULL, NULL, self);
320 goto signal;
321 }
322
323 g_input_stream_close_async(G_INPUT_STREAM(self->file_stream),
324 G_PRIORITY_DEFAULT,
325 self->cancellable,
326 spice_file_transfer_task_close_stream_cb,
327 self);
328 self->pending = TRUE;
329 signal:
330 g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->error);
331 /* SpiceFileTransferTask unref is done after input stream is closed */
332 }
333
334 G_GNUC_INTERNAL
spice_file_transfer_task_get_id(SpiceFileTransferTask * self)335 guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self)
336 {
337 g_return_val_if_fail(self != NULL, 0);
338 return self->id;
339 }
340
341 G_GNUC_INTERNAL
spice_file_transfer_task_get_channel(SpiceFileTransferTask * self)342 SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self)
343 {
344 g_return_val_if_fail(self != NULL, NULL);
345 return self->channel;
346 }
347
348 G_GNUC_INTERNAL
spice_file_transfer_task_get_cancellable(SpiceFileTransferTask * self)349 GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self)
350 {
351 g_return_val_if_fail(self != NULL, NULL);
352 return self->cancellable;
353 }
354
355 /* Helper function which only creates a SpiceFileTransferTask per GFile
356 * in @files and returns a HashTable mapping task-id to the task itself
357 * The SpiceFileTransferTask created here has two references, one should be
358 * freed by spice_file_transfer_task_close_stream_cb() after
359 * spice_file_transfer_task_completed() is called and the other reference
360 * belongs to the caller and should be freed upon GHashTable destruction */
361 G_GNUC_INTERNAL
spice_file_transfer_task_create_tasks(GFile ** files,SpiceMainChannel * channel,GFileCopyFlags flags,GCancellable * cancellable)362 GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
363 SpiceMainChannel *channel,
364 GFileCopyFlags flags,
365 GCancellable *cancellable)
366 {
367 GHashTable *xfer_ht;
368 gint i;
369
370 g_return_val_if_fail(files != NULL && files[0] != NULL, NULL);
371
372 xfer_ht = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
373 for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
374 SpiceFileTransferTask *xfer_task;
375 guint32 task_id;
376
377 xfer_task = spice_file_transfer_task_new(channel, files[i], flags, cancellable);
378 task_id = spice_file_transfer_task_get_id(xfer_task);
379 g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), g_object_ref(xfer_task));
380 }
381 return xfer_ht;
382 }
383
384 G_GNUC_INTERNAL
spice_file_transfer_task_init_task_async(SpiceFileTransferTask * self,GAsyncReadyCallback callback,gpointer userdata)385 void spice_file_transfer_task_init_task_async(SpiceFileTransferTask *self,
386 GAsyncReadyCallback callback,
387 gpointer userdata)
388 {
389 GTask *task;
390
391 g_return_if_fail(self != NULL);
392 g_return_if_fail(self->pending == FALSE);
393
394 task = g_task_new(self, self->cancellable, callback, userdata);
395
396 self->pending = TRUE;
397 g_file_read_async(self->file,
398 G_PRIORITY_DEFAULT,
399 self->cancellable,
400 spice_file_transfer_task_read_file_cb,
401 task);
402 }
403
404 G_GNUC_INTERNAL
spice_file_transfer_task_init_task_finish(SpiceFileTransferTask * self,GAsyncResult * result,GError ** error)405 GFileInfo *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask *self,
406 GAsyncResult *result,
407 GError **error)
408 {
409 GTask *task = G_TASK(result);
410
411 g_return_val_if_fail(self != NULL, NULL);
412 return g_task_propagate_pointer(task, error);
413 }
414
415 /* Any context */
416 G_GNUC_INTERNAL
spice_file_transfer_task_read_async(SpiceFileTransferTask * self,GAsyncReadyCallback callback,gpointer userdata)417 void spice_file_transfer_task_read_async(SpiceFileTransferTask *self,
418 GAsyncReadyCallback callback,
419 gpointer userdata)
420 {
421 GTask *task;
422
423 g_return_if_fail(self != NULL);
424 if (self->pending) {
425 g_task_report_new_error(self, callback, userdata,
426 spice_file_transfer_task_read_async,
427 SPICE_CLIENT_ERROR,
428 SPICE_CLIENT_ERROR_FAILED,
429 "Cannot read data in pending state");
430 return;
431 }
432
433 /* Notify the progress prior the read to make the info be related to the
434 * data that was already sent. To notify the 100% (completed), channel-main
435 * should call read-async when it expects EOF. */
436 g_object_notify(G_OBJECT(self), "progress");
437 g_object_notify(G_OBJECT(self), "transferred-bytes");
438
439 task = g_task_new(self, self->cancellable, callback, userdata);
440
441 if (self->read_bytes == self->file_size) {
442 /* channel-main might request data after reading the whole file as it
443 * expects EOF. Let's return immediately its request as we don't want to
444 * reach a state where agent says file-transfer SUCCEED but we are in a
445 * PENDING state in SpiceFileTransferTask due reading in idle */
446 g_task_return_int(task, 0);
447 g_object_unref(task);
448 return;
449 }
450
451 self->pending = TRUE;
452 g_input_stream_read_async(G_INPUT_STREAM(self->file_stream),
453 self->buffer,
454 FILE_XFER_CHUNK_SIZE,
455 G_PRIORITY_DEFAULT,
456 self->cancellable,
457 spice_file_transfer_task_read_stream_cb,
458 task);
459 }
460
461 G_GNUC_INTERNAL
spice_file_transfer_task_read_finish(SpiceFileTransferTask * self,GAsyncResult * result,char ** buffer,GError ** error)462 gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask *self,
463 GAsyncResult *result,
464 char **buffer,
465 GError **error)
466 {
467 gssize nbytes;
468 GTask *task = G_TASK(result);
469
470 g_return_val_if_fail(self != NULL, -1);
471
472 nbytes = g_task_propagate_int(task, error);
473 if (nbytes >= 0 && buffer != NULL)
474 *buffer = self->buffer;
475
476 return nbytes;
477 }
478
479 G_GNUC_INTERNAL
spice_file_transfer_task_is_completed(SpiceFileTransferTask * self)480 gboolean spice_file_transfer_task_is_completed(SpiceFileTransferTask *self)
481 {
482 g_return_val_if_fail(self != NULL, FALSE);
483
484 /* File transfer is considered over at the first time it is marked as
485 * complete with spice_file_transfer_task_completed. */
486 return self->completed;
487 }
488
489 /*******************************************************************************
490 * External API
491 ******************************************************************************/
492
493 /**
494 * spice_file_transfer_task_get_progress:
495 * @self: a file transfer task
496 *
497 * Convenience function for retrieving the current progress of this file
498 * transfer task.
499 *
500 * Returns: A fractional value between 0 and 1.0
501 *
502 * Since: 0.31
503 **/
spice_file_transfer_task_get_progress(SpiceFileTransferTask * self)504 double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self)
505 {
506 g_return_val_if_fail(SPICE_IS_FILE_TRANSFER_TASK(self), 0.0);
507
508 if (self->file_size == 0)
509 return 0.0;
510
511 return (double)self->read_bytes / self->file_size;
512 }
513
514 /**
515 * spice_file_transfer_task_cancel:
516 * @self: a file transfer task
517 *
518 * Cancels the file transfer task. Note that depending on how the file transfer
519 * was initiated, multiple file transfer tasks may share a single
520 * #SpiceFileTransferTask::cancellable object, so canceling one task may result
521 * in the cancellation of other tasks.
522 *
523 * Since: 0.31
524 **/
spice_file_transfer_task_cancel(SpiceFileTransferTask * self)525 void spice_file_transfer_task_cancel(SpiceFileTransferTask *self)
526 {
527 g_return_if_fail(SPICE_IS_FILE_TRANSFER_TASK(self));
528
529 g_cancellable_cancel(self->cancellable);
530 }
531
532 /**
533 * spice_file_transfer_task_get_filename:
534 * @self: a file transfer task
535 *
536 * Gets the name of the file being transferred in this task
537 *
538 * Returns: (transfer full): The basename of the file
539 *
540 * Since: 0.31
541 **/
spice_file_transfer_task_get_filename(SpiceFileTransferTask * self)542 char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self)
543 {
544 g_return_val_if_fail(SPICE_IS_FILE_TRANSFER_TASK(self), NULL);
545
546 return g_file_get_basename(self->file);
547 }
548
549 /**
550 * spice_file_transfer_task_get_total_bytes:
551 * @self: a file transfer task
552 *
553 * Gets the total size in bytes of the file transfer.
554 *
555 * Returns: The total size of the file transfer
556 *
557 * Since: 0.33
558 **/
spice_file_transfer_task_get_total_bytes(SpiceFileTransferTask * self)559 guint64 spice_file_transfer_task_get_total_bytes(SpiceFileTransferTask *self)
560 {
561 g_return_val_if_fail(SPICE_IS_FILE_TRANSFER_TASK(self), 0);
562 return self->file_size;
563 }
564
565
566 /**
567 * spice_file_transfer_task_get_transferred_bytes:
568 * @self: a file transfer task
569 *
570 * Gets the number of bytes that have been transferred so far.
571 *
572 * Returns: The number of transferred bytes
573 *
574 * Since: 0.33
575 **/
spice_file_transfer_task_get_transferred_bytes(SpiceFileTransferTask * self)576 guint64 spice_file_transfer_task_get_transferred_bytes(SpiceFileTransferTask *self)
577 {
578 g_return_val_if_fail(SPICE_IS_FILE_TRANSFER_TASK(self), 0);
579 return self->read_bytes;
580 }
581
582 /*******************************************************************************
583 * GObject
584 ******************************************************************************/
585
586 static void
spice_file_transfer_task_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)587 spice_file_transfer_task_get_property(GObject *object,
588 guint property_id,
589 GValue *value,
590 GParamSpec *pspec)
591 {
592 SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
593
594 switch (property_id)
595 {
596 case PROP_TASK_ID:
597 g_value_set_uint(value, self->id);
598 break;
599 case PROP_TASK_FILE:
600 g_value_set_object(value, self->file);
601 break;
602 case PROP_TASK_TOTAL_BYTES:
603 g_value_set_uint64(value, spice_file_transfer_task_get_total_bytes(self));
604 break;
605 case PROP_TASK_TRANSFERRED_BYTES:
606 g_value_set_uint64(value, spice_file_transfer_task_get_transferred_bytes(self));
607 break;
608 case PROP_TASK_PROGRESS:
609 g_value_set_double(value, spice_file_transfer_task_get_progress(self));
610 break;
611 default:
612 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
613 }
614 }
615
616 static void
spice_file_transfer_task_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)617 spice_file_transfer_task_set_property(GObject *object,
618 guint property_id,
619 const GValue *value,
620 GParamSpec *pspec)
621 {
622 SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
623
624 switch (property_id)
625 {
626 case PROP_TASK_ID:
627 self->id = g_value_get_uint(value);
628 break;
629 case PROP_TASK_FILE:
630 self->file = g_value_dup_object(value);
631 break;
632 case PROP_TASK_CHANNEL:
633 self->channel = g_value_dup_object(value);
634 break;
635 case PROP_TASK_CANCELLABLE:
636 self->cancellable = g_value_dup_object(value);
637 break;
638 default:
639 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
640 }
641 }
642
643 static void
spice_file_transfer_task_dispose(GObject * object)644 spice_file_transfer_task_dispose(GObject *object)
645 {
646 SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
647
648 g_clear_object(&self->file);
649 g_clear_object(&self->file_stream);
650 g_clear_error(&self->error);
651
652 G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->dispose(object);
653 }
654
655 static void
spice_file_transfer_task_finalize(GObject * object)656 spice_file_transfer_task_finalize(GObject *object)
657 {
658 SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
659
660 g_free(self->buffer);
661
662 G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->finalize(object);
663 }
664
665 static void
spice_file_transfer_task_constructed(GObject * object)666 spice_file_transfer_task_constructed(GObject *object)
667 {
668 SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
669
670 if (spice_util_get_debug()) {
671 gchar *basename = g_file_get_basename(self->file);
672 self->start_time = g_get_monotonic_time();
673 self->last_update = self->start_time;
674
675 SPICE_DEBUG("transfer of file %s has started", basename);
676 g_free(basename);
677 }
678 }
679
680 static void
spice_file_transfer_task_class_init(SpiceFileTransferTaskClass * klass)681 spice_file_transfer_task_class_init(SpiceFileTransferTaskClass *klass)
682 {
683 GObjectClass *object_class = G_OBJECT_CLASS(klass);
684
685 object_class->get_property = spice_file_transfer_task_get_property;
686 object_class->set_property = spice_file_transfer_task_set_property;
687 object_class->finalize = spice_file_transfer_task_finalize;
688 object_class->dispose = spice_file_transfer_task_dispose;
689 object_class->constructed = spice_file_transfer_task_constructed;
690
691 /**
692 * SpiceFileTransferTask:id:
693 *
694 * The ID of the file transfer task
695 *
696 * Since: 0.31
697 **/
698 g_object_class_install_property(object_class, PROP_TASK_ID,
699 g_param_spec_uint("id",
700 "id",
701 "The id of the task",
702 0, G_MAXUINT, 0,
703 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
704 G_PARAM_STATIC_STRINGS));
705
706 /**
707 * SpiceFileTransferTask:channel:
708 *
709 * The main channel that owns the file transfer task
710 *
711 * Since: 0.31
712 **/
713 g_object_class_install_property(object_class, PROP_TASK_CHANNEL,
714 g_param_spec_object("channel",
715 "channel",
716 "The channel transferring the file",
717 SPICE_TYPE_MAIN_CHANNEL,
718 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
719 G_PARAM_STATIC_STRINGS));
720
721 /**
722 * SpiceFileTransferTask:cancellable:
723 *
724 * A cancellable object used to cancel the file transfer
725 *
726 * Since: 0.31
727 **/
728 g_object_class_install_property(object_class, PROP_TASK_CANCELLABLE,
729 g_param_spec_object("cancellable",
730 "cancellable",
731 "The object used to cancel the task",
732 G_TYPE_CANCELLABLE,
733 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
734 G_PARAM_STATIC_STRINGS));
735
736 /**
737 * SpiceFileTransferTask:file:
738 *
739 * The file that is being transferred in this file transfer task
740 *
741 * Since: 0.31
742 **/
743 g_object_class_install_property(object_class, PROP_TASK_FILE,
744 g_param_spec_object("file",
745 "File",
746 "The file being transferred",
747 G_TYPE_FILE,
748 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
749 G_PARAM_STATIC_STRINGS));
750
751 /**
752 * SpiceFileTransferTask:total-bytes:
753 *
754 * The total size in bytes of this file transfer.
755 *
756 * Since: 0.33
757 **/
758 g_object_class_install_property(object_class, PROP_TASK_TOTAL_BYTES,
759 g_param_spec_uint64("total-bytes",
760 "Total bytes",
761 "The size in bytes of the file transferred",
762 0, G_MAXUINT64, 0,
763 G_PARAM_READABLE |
764 G_PARAM_STATIC_STRINGS));
765
766
767 /**
768 * SpiceFileTransferTask:transferred-bytes:
769 *
770 * The number of bytes that have been transferred so far.
771 *
772 * Since: 0.33
773 **/
774 g_object_class_install_property(object_class, PROP_TASK_TRANSFERRED_BYTES,
775 g_param_spec_uint64("transferred-bytes",
776 "Transferred bytes",
777 "The number of bytes transferred",
778 0, G_MAXUINT64, 0,
779 G_PARAM_READABLE |
780 G_PARAM_STATIC_STRINGS));
781
782
783 /**
784 * SpiceFileTransferTask:progress:
785 *
786 * The current state of the file transfer. This value indicates a
787 * fraction, and ranges from 0 to 1.0. Listen for change notifications on
788 * this property to be updated whenever the file transfer progress changes.
789 *
790 * Since: 0.31
791 **/
792 g_object_class_install_property(object_class, PROP_TASK_PROGRESS,
793 g_param_spec_double("progress",
794 "Progress",
795 "The percentage of the file transferred",
796 0.0, 1.0, 0.0,
797 G_PARAM_READABLE |
798 G_PARAM_STATIC_STRINGS));
799
800 /**
801 * SpiceFileTransferTask::finished:
802 * @task: the file transfer task that emitted the signal
803 * @error: (transfer none): the error state of the transfer. Will be %NULL
804 * if the file transfer was successful.
805 *
806 * The #SpiceFileTransferTask::finished signal is emitted when the file
807 * transfer has completed transferring to the guest.
808 *
809 * Since: 0.31
810 **/
811 task_signals[SIGNAL_FINISHED] = g_signal_new("finished", SPICE_TYPE_FILE_TRANSFER_TASK,
812 G_SIGNAL_RUN_FIRST,
813 0, NULL, NULL,
814 g_cclosure_marshal_VOID__BOXED,
815 G_TYPE_NONE, 1,
816 G_TYPE_ERROR);
817 }
818
819 static void
spice_file_transfer_task_init(SpiceFileTransferTask * self)820 spice_file_transfer_task_init(SpiceFileTransferTask *self)
821 {
822 self->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE);
823 }
824