]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
Start implementing signals inside EmpathyFTHandler.
[empathy.git] / libempathy / empathy-ft-handler.c
1 /*
2  * empathy-ft-handler.c - Source for EmpathyFTHandler
3  * Copyright (C) 2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Author: Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
20  */
21  
22 /* empathy-ft-handler.c */
23
24 #include <extensions/extensions.h>
25 #include <glib.h>
26 #include <telepathy-glib/util.h>
27
28 #include "empathy-ft-handler.h"
29 #include "empathy-dispatcher.h"
30 #include "empathy-utils.h"
31
32 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
33
34 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler)
35
36 #define BUFFER_SIZE 4096
37
38 enum {
39   PROP_TP_FILE = 1,
40   PROP_G_FILE,
41   PROP_CONTACT
42 };
43
44 enum {
45   HASHING_STARTED,
46   HASHING_PROGRESS,
47   HASHING_DONE,
48   TRANSFER_STARTED,
49   TRANSFER_PROGRESS,
50   TRANSFER_DONE,
51   TRANSFER_ERROR,
52 };
53
54 typedef struct {
55   EmpathyFTHandler *handler;
56   GFile *gfile;
57   GHashTable *request;
58 } RequestData;
59
60 typedef struct {
61   RequestData *req_data;
62   GInputStream *stream;
63   gboolean done_reading;
64   GError *error;
65   guchar *buffer;
66   GChecksum *checksum;
67 } HashingData;
68
69 /* private data */
70 typedef struct {
71   gboolean dispose_run;
72   EmpathyContact *contact;
73   GFile *gfile;
74   EmpathyTpFile *tpfile;
75   GCancellable *cancellable;
76 } EmpathyFTHandlerPriv;
77
78 /* prototypes */
79 static void schedule_hash_chunk (HashingData *hash_data);
80
81 /* GObject implementations */
82 static void
83 do_get_property (GObject *object,
84                  guint property_id,
85                  GValue *value,
86                  GParamSpec *pspec)
87 {
88   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
89
90   switch (property_id)
91     {
92       case PROP_CONTACT:
93         g_value_set_object (value, priv->contact);
94         break;
95       case PROP_G_FILE:
96         g_value_set_object (value, priv->gfile);
97         break;
98       case PROP_TP_FILE:
99         g_value_set_object (value, priv->tpfile);
100         break;
101       default:
102         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
103     }
104 }
105
106 static void
107 do_set_property (GObject *object,
108                  guint property_id, 
109                  const GValue *value,
110                  GParamSpec *pspec)
111 {
112   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
113
114   switch (property_id)
115     {
116       case PROP_CONTACT:
117         priv->contact = g_value_dup_object (value);
118         break;
119       case PROP_G_FILE:
120         priv->gfile = g_value_dup_object (value);
121         break;
122       case PROP_TP_FILE:
123         priv->tpfile = g_value_dup_object (value);
124         break;
125       default:
126         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
127     }
128 }
129
130 static void
131 do_dispose (GObject *object)
132 {
133   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
134
135   if (priv->dispose_run)
136     return;
137
138   priv->dispose_run = TRUE;
139
140   if (priv->contact) {
141     g_object_unref (priv->contact);
142     priv->contact = NULL;
143   }
144
145   if (priv->gfile) {
146     g_object_unref (priv->gfile);
147     priv->gfile = NULL;
148   }
149
150   if (priv->tpfile) {
151     empathy_tp_file_close (priv->tpfile);
152     g_object_unref (priv->tpfile);
153     priv->tpfile = NULL;
154   }
155
156   if (priv->cancellable) {
157     g_object_unref (priv->cancellable);
158     priv->cancellable = NULL;
159   }
160
161   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
162 }
163
164 static void
165 do_finalize (GObject *object)
166 {
167   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
168 }
169
170 static void
171 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
172 {
173   GObjectClass *object_class = G_OBJECT_CLASS (klass);
174   GParamSpec *param_spec;
175
176   g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
177
178   object_class->get_property = do_get_property;
179   object_class->set_property = do_set_property;
180   object_class->dispose = do_dispose;
181   object_class->finalize = do_finalize;
182
183   /* properties */
184   param_spec = g_param_spec_object ("contact",
185     "contact", "The remote contact",
186     EMPATHY_TYPE_CONTACT,
187     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
188   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
189
190   param_spec = g_param_spec_object ("gfile",
191     "gfile", "The GFile we're handling",
192     G_TYPE_FILE,
193     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
194   g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
195
196   param_spec = g_param_spec_object ("tp-file",
197     "tp-file", "The file's channel wrapper",
198     EMPATHY_TYPE_TP_FILE,
199     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
200   g_object_class_install_property (object_class, PROP_TP_FILE, param_spec);
201
202   /* signals */
203   signals[TRANSFER_STARTED] =
204     g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
205         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
206         g_cclosure_marshal_VOID__OBJECT,
207         G_TYPE_NONE,
208         1, EMPATHY_TYPE_TP_FILE);
209
210   signals[TRANSFER_DONE] =
211     g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
212         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
213         g_cclosure_marshal_VOID__OBJECT,
214         G_TYPE_NONE,
215         1, EMPATHY_TYPE_TP_FILE);
216
217   signals[TRANSFER_ERROR] =
218     g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
219         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
220         g_cclosure_marshal_VOID__OBJECT_POINTER,
221         G_TYPE_NONE,
222         2, EMPATHY_TYPE_TP_FILE, G_TYPE_POINTER);
223
224   signals[TRANSFER_PROGRESS] =
225     g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
226         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
227         _empathy_marshal_VOID__OBJECT_UINT64_UINT64,
228         G_TYPE_NONE,
229         2, EMPATHY_TYPE_TP_FILE, G_TYPE_UINT64, G_TYPE_UINT64);
230
231   signals[HASHING_STARTED] =
232     g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
233         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
234         g_cclosure_marshal_VOID__VOID,
235         G_TYPE_NONE, 0);
236
237   signals[HASHING_PROGRESS] =
238     g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
239         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
240         _empathy_marshal_VOID__UINT64_UINT64,
241         G_TYPE_NONE,
242         2, G_TYPE_UINT64, G_TYPE_UINT64);
243
244   signals[HASHING_DONE] =
245     g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
246         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
247         g_cclosure_marshal_VOID__VOID,
248         G_TYPE_NONE, 0);
249 }
250
251 static void
252 empathy_ft_handler_init (EmpathyFTHandler *self)
253 {
254   EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
255     EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
256
257   self->priv = priv;
258 }
259
260 /* private functions */
261
262 static void
263 hash_data_free (HashingData *data)
264 {
265   if (data->buffer != NULL)
266     {
267       g_free (data->buffer);
268       data->buffer = NULL;
269     }
270
271   if (data->stream != NULL)
272     {
273       g_object_unref (data->stream);
274       data->stream = NULL;
275     }
276
277   if (data->checksum != NULL)
278     {
279       g_checksum_free (data->checksum);
280       data->checksum = NULL;
281     }
282
283   if (data->error != NULL)
284     {
285       g_error_free (data->error);
286       data->error = NULL;
287     }
288
289   g_slice_free (HashingData, data);
290 }
291
292 static void
293 request_data_free (RequestData *data)
294 {
295   if (data->gfile != NULL)
296     {
297       g_object_unref (data->gfile);
298       data->gfile = NULL;
299     }
300
301   if (data->request != NULL)
302     {
303       g_hash_table_unref (data->request);
304       data->request = NULL;
305     }
306
307   g_slice_free (RequestData, data);
308 }
309
310 static RequestData *
311 request_data_new (EmpathyFTHandler *handler, GFile *gfile)
312 {
313   RequestData *ret;
314
315   ret = g_slice_new0 (RequestData);
316   ret->request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
317       (GDestroyNotify) tp_g_value_slice_free);
318   ret->handler = g_object_ref (handler);
319   ret->gfile = g_object_ref (gfile);
320
321   return ret;
322 }
323
324 static void
325 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
326                               const GError *error,
327                               gpointer user_data)
328 {
329   RequestData *req_data = user_data;
330   GError *myerr = NULL;
331   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
332
333   if (error != NULL)
334     {
335       /* TODO: error handling */
336       goto out;
337     }
338
339   priv->tpfile = g_object_ref
340       (empathy_dispatch_operation_get_channel_wrapper (operation));
341   empathy_tp_file_offer (priv->tpfile, req_data->gfile, &myerr);
342   empathy_dispatch_operation_claim (operation);
343
344 out:
345   request_data_free (req_data);
346 }
347
348 static void
349 ft_handler_push_to_dispatcher (RequestData *req_data)
350 {
351   EmpathyDispatcher *dispatcher;
352   McAccount *account;
353   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
354
355   dispatcher = empathy_dispatcher_dup_singleton ();
356   account = empathy_contact_get_account (priv->contact);
357
358   empathy_dispatcher_create_channel (dispatcher, account, req_data->request,
359       ft_handler_create_channel_cb, req_data);
360
361   g_object_unref (dispatcher);
362 }
363
364 static gboolean
365 ft_handler_check_if_allowed (EmpathyFTHandler *handler)
366 {
367   EmpathyDispatcher *dispatcher;
368   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
369   McAccount *account;
370   GStrv allowed;
371   gboolean res = TRUE;
372
373   dispatcher = empathy_dispatcher_dup_singleton ();
374   account = empathy_contact_get_account (priv->contact);
375
376   allowed = empathy_dispatcher_find_channel_class (dispatcher, account,
377       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT);
378
379   if (!tp_strv_contains ((const gchar * const *) allowed,
380       TP_IFACE_CHANNEL ".TargetHandle"))
381     res = FALSE;
382
383   g_object_unref (dispatcher);
384
385   return res;
386 }
387
388 static void
389 ft_handler_populate_outgoing_request (RequestData *req_data,
390                                       GFileInfo *file_info)
391 {
392   guint contact_handle;
393   const char *content_type;
394   const char *display_name;
395   goffset size;
396   GTimeVal mtime;
397   GValue *value;
398   GHashTable *request = req_data->request;
399   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
400
401   /* gather all the information */
402   contact_handle = empathy_contact_get_handle (priv->contact);
403
404   content_type = g_file_info_get_content_type (file_info);
405   display_name = g_file_info_get_display_name (file_info);
406   size = g_file_info_get_size (file_info);
407   g_file_info_get_modification_time (file_info, &mtime);
408
409   /* org.freedesktop.Telepathy.Channel.ChannelType */
410   value = tp_g_value_slice_new (G_TYPE_STRING);
411   g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
412   g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
413
414   /* org.freedesktop.Telepathy.Channel.TargetHandleType */
415   value = tp_g_value_slice_new (G_TYPE_UINT);
416   g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
417   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
418
419   /* org.freedesktop.Telepathy.Channel.TargetHandle */
420   value = tp_g_value_slice_new (G_TYPE_UINT);
421   g_value_set_uint (value, contact_handle);
422   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
423
424   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
425   value = tp_g_value_slice_new (G_TYPE_STRING);
426   g_value_set_string (value, content_type);
427   g_hash_table_insert (request,
428       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
429
430   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
431   value = tp_g_value_slice_new (G_TYPE_STRING);
432   g_value_set_string (value, display_name);
433   g_hash_table_insert (request,
434       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
435
436   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
437   value = tp_g_value_slice_new (G_TYPE_UINT64);
438   g_value_set_uint64 (value, (guint64) size);
439   g_hash_table_insert (request,
440       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
441
442   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
443   value = tp_g_value_slice_new (G_TYPE_UINT64);
444   g_value_set_uint64 (value, (guint64) mtime.tv_sec);
445   g_hash_table_insert (request,
446       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
447 }
448
449 static void
450 hash_job_async_close_stream_cb (GObject *source,
451                                 GAsyncResult *res,
452                                 gpointer user_data)
453 {
454   HashingData *hash_data = user_data;
455   RequestData *req_data = hash_data->req_data;
456   GError *error = NULL;
457   GValue *value;
458   GHashTable *request;
459
460   /* if we're here we for sure have done reading, check if we stopped due
461    * to an error.
462    */
463   g_input_stream_close_finish (hash_data->stream, res, &error);
464   if (error != NULL)
465     {
466       if (hash_data->error != NULL)
467         {
468           /* if we already stopped due to an error, probably we're completely
469            * hosed for some reason. just return the first read error
470            * to the user.
471            */
472           g_clear_error (&error);
473           error = hash_data->error;
474         }
475
476       goto cleanup;
477     }
478
479   if (hash_data->error != NULL)
480     {
481       error = hash_data->error;
482       goto cleanup;
483     }
484
485   /* set the checksum in the request */
486   request = req_data->request;
487
488   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */
489   value = tp_g_value_slice_new (G_TYPE_STRING);
490   g_value_set_string (value, g_checksum_get_string (hash_data->checksum));
491   g_hash_table_insert (request,
492       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
493
494 cleanup:
495   hash_data_free (hash_data);
496
497   if (error != NULL)
498     {
499       /* TODO: error handling. */
500     }
501   else
502     {
503       /* the request is complete now, push it to the dispatcher */
504       ft_handler_push_to_dispatcher (req_data);
505     }
506 }
507
508 static void
509 hash_job_async_read_cb (GObject *source,
510                         GAsyncResult *res,
511                         gpointer user_data)
512 {
513   HashingData *hash_data = user_data;
514   gssize bytes_read;
515   GError *error = NULL;
516
517   bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error);
518   if (error != NULL)
519     {
520       hash_data->error = error;
521       hash_data->done_reading = TRUE;
522       goto out;
523     }
524
525   /* TODO: notify progress */
526
527   /* we now have the chunk */
528   if (bytes_read == 0)
529     {
530       hash_data->done_reading = TRUE;
531       schedule_hash_chunk (hash_data);
532       goto out;
533     }
534   else
535     {
536       g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
537     }
538
539 out:
540   g_free (hash_data->buffer);
541   hash_data->buffer = NULL;
542
543   schedule_hash_chunk (hash_data);
544 }
545
546 static void
547 schedule_hash_chunk (HashingData *hash_data)
548 {
549   if (hash_data->done_reading)
550     {
551       g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT,
552           NULL, hash_job_async_close_stream_cb, hash_data);
553     }
554   else
555     {
556       if (hash_data->buffer == NULL)
557         hash_data->buffer = g_malloc0 (BUFFER_SIZE);
558
559       g_input_stream_read_async (hash_data->stream, hash_data->buffer,
560           BUFFER_SIZE, G_PRIORITY_DEFAULT, NULL,
561           hash_job_async_read_cb, hash_data);
562     }
563 }
564
565 static void
566 ft_handler_read_async_cb (GObject *source,
567                           GAsyncResult *res,
568                           gpointer user_data)
569 {
570   GFileInputStream *stream;
571   GError *error = NULL;
572   HashingData *hash_data;
573   GHashTable *request;
574   GValue *value;
575   RequestData *req_data = user_data;
576
577   stream = g_file_read_finish (req_data->gfile, res, &error);
578   if (error != NULL)
579     {
580       /* TODO: error handling. */
581       return;
582     }
583
584   hash_data = g_slice_new0 (HashingData);
585   hash_data->stream = G_INPUT_STREAM (stream);
586   hash_data->done_reading = FALSE;
587   hash_data->req_data = req_data;
588   /* FIXME: should look at the CM capabilities before setting the
589    * checksum type?
590    */
591   hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
592
593   request = req_data->request;
594
595   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
596   value = tp_g_value_slice_new (G_TYPE_UINT);
597   g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5);
598   g_hash_table_insert (request,
599       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
600
601   schedule_hash_chunk (hash_data);
602 }
603
604 static void
605 ft_handler_gfile_ready_cb (GObject *source,
606                            GAsyncResult *res,
607                            RequestData *req_data)
608 {
609   GFileInfo *info;
610   GError *error = NULL;
611
612   info = g_file_query_info_finish (req_data->gfile, res, &error);
613   if (error != NULL)
614     {
615       /* TODO: error handling. */
616       return;
617     }
618
619   ft_handler_populate_outgoing_request (req_data, info);
620
621   /* now start hashing the file */
622   g_file_read_async (req_data->gfile, G_PRIORITY_DEFAULT,
623       NULL, ft_handler_read_async_cb, req_data);
624 }
625
626 static void
627 ft_handler_contact_ready_cb (EmpathyContact *contact,
628                              const GError *error,
629                              gpointer user_data,
630                              GObject *weak_object)  
631 {
632   RequestData *req_data = user_data;
633   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
634
635   g_assert (priv->contact != NULL);
636   g_assert (priv->gfile != NULL);
637
638   /* check if FT is allowed before firing up the I/O machinery */
639   if (!ft_handler_check_if_allowed (req_data->handler))
640     {
641       /* TODO: error handling. */
642       request_data_free (req_data);
643       return;
644     }
645
646   /* start collecting info about the file */
647   g_file_query_info_async (req_data->gfile,
648       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
649       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
650       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
651       G_FILE_ATTRIBUTE_TIME_MODIFIED,
652       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
653       NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb,
654       req_data);
655 }
656
657 /* public methods */
658
659 EmpathyFTHandler*
660 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
661                                  GFile *source)
662 {
663   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
664   g_return_val_if_fail (G_IS_FILE (source), NULL);
665
666   return g_object_new (EMPATHY_TYPE_FT_HANDLER,
667       "contact", contact, "gfile", source, NULL);
668 }
669
670 EmpathyFTHandler *
671 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
672                                  GFile *destination)
673 {
674   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
675   g_return_val_if_fail (G_IS_FILE (destination), NULL);
676
677   return g_object_new (EMPATHY_TYPE_FT_HANDLER,
678       "tp-file", tp_file, "gfile", destination, NULL);
679 }
680
681 void
682 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler,
683                                    GCancellable *cancellable)
684 {
685   RequestData *data;
686   EmpathyFTHandlerPriv *priv;
687   GError *error = NULL;
688
689   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
690
691   priv = GET_PRIV (handler);
692   priv->cancellable = g_object_ref (cancellable);
693
694   if (priv->tpfile == NULL)
695     {
696       data = request_data_new (handler, priv->gfile);
697       empathy_contact_call_when_ready (priv->contact,
698           EMPATHY_CONTACT_READY_HANDLE,
699           ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler));
700     }
701   else
702     {
703       /* TODO: add support for resume. */
704       empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, &error);
705     }
706 }