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