2 * gaim - file transfer functions
4 * Copyright (C) 2002, Wil Mahan <wtm2@duke.edu>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #define FT_BUFFER_SIZE (4096)
42 /* Completely describes a file transfer. Opaque to callers. */
43 struct file_transfer
{
44 enum { FILE_TRANSFER_TYPE_SEND
, FILE_TRANSFER_TYPE_RECEIVE
} type
;
47 FILE_TRANSFER_STATE_ASK
, /* waiting for confirmation */
48 FILE_TRANSFER_STATE_CHOOSEFILE
, /* waiting for file dialog */
49 FILE_TRANSFER_STATE_TRANSFERRING
, /* in actual transfer */
50 FILE_TRANSFER_STATE_INTERRUPTED
, /* other user canceled */
51 FILE_TRANSFER_STATE_CANCELED
, /* we canceled */
52 FILE_TRANSFER_STATE_DONE
, /* transfer complete */
53 FILE_TRANSFER_STATE_CLEANUP
/* freeing memory */
58 /* file selection dialog */
67 struct gaim_connection
*gc
;
81 static int ft_choose_file(struct file_transfer
*xfer
);
82 static void ft_cancel(struct file_transfer
*xfer
);
83 static void ft_delete(struct file_transfer
*xfer
);
84 static void ft_callback(gpointer data
, gint source
, GaimInputCondition condition
);
85 static void ft_nextfile(struct file_transfer
*xfer
);
86 static int ft_mkdir(const char *name
);
87 static int ft_mkdir_help(char *dir
);
89 static struct file_transfer
*ft_new(int type
, struct gaim_connection
*gc
,
92 struct file_transfer
*xfer
= g_new0(struct file_transfer
, 1);
94 xfer
->state
= FILE_TRANSFER_STATE_ASK
;
96 xfer
->who
= g_strdup(who
);
102 struct file_transfer
*transfer_in_add(struct gaim_connection
*gc
,
103 const char *who
, const char *initname
, int totsize
,
104 int totfiles
, const char *msg
)
106 struct file_transfer
*xfer
= ft_new(FILE_TRANSFER_TYPE_RECEIVE
, gc
,
110 static const char *sizestr
[4] = { "bytes", "KB", "MB", "GB" };
111 float sizemag
= (float)totsize
;
114 xfer
->initname
= g_strdup(initname
);
115 xfer
->totsize
= totsize
;
116 xfer
->totfiles
= totfiles
;
119 /* Ask the user whether he or she accepts the transfer. */
120 while ((szindex
< 4) && (sizemag
> 1024)) {
126 sizebuf
= g_strdup_printf(_("Unkown"));
128 sizebuf
= g_strdup_printf("%.3g %s", sizemag
, sizestr
[szindex
]);
130 if (xfer
->totfiles
== 1) {
131 buf
= g_strdup_printf(_("%s requests that %s accept a file: %s (%s)"),
132 who
, xfer
->gc
->username
, initname
, sizebuf
);
134 buf
= g_strdup_printf(_("%s requests that %s accept %d files: %s (%s)"),
135 who
, xfer
->gc
->username
, xfer
->totfiles
,
142 char *newmsg
= g_strconcat(buf
, ": ", msg
, NULL
);
147 do_ask_dialog(buf
, NULL
, xfer
, _("Accept"), ft_choose_file
, _("Cancel"), ft_cancel
);
153 struct file_transfer
*transfer_out_add(struct gaim_connection
*gc
,
156 struct file_transfer
*xfer
= ft_new(FILE_TRANSFER_TYPE_SEND
, gc
,
159 ft_choose_file(xfer
);
164 /* We canceled the transfer, either by declining the initial
165 * confirmation dialog or canceling the file dialog.
167 static void ft_cancel(struct file_transfer
*xfer
)
169 /* Make sure we weren't aborted while waiting for
170 * confirmation from the user.
172 if (xfer
->state
== FILE_TRANSFER_STATE_INTERRUPTED
) {
173 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
174 transfer_abort(xfer
, NULL
);
178 xfer
->state
= FILE_TRANSFER_STATE_CANCELED
;
180 gtk_widget_destroy(xfer
->w
);
184 if (xfer
->gc
->prpl
->file_transfer_cancel
)
185 xfer
->gc
->prpl
->file_transfer_cancel(xfer
->gc
, xfer
);
190 /* This is called when the other user aborts the transfer,
191 * possibly in the middle of a transfer.
193 int transfer_abort(struct file_transfer
*xfer
, const char *why
)
195 if (xfer
->state
== FILE_TRANSFER_STATE_INTERRUPTED
) {
196 /* If for some reason we have already been
197 * here and are waiting on some event before
198 * cleaning up, but we get another abort request,
199 * we don't need to do anything else.
203 else if (xfer
->state
== FILE_TRANSFER_STATE_ASK
) {
204 /* Kludge: since there is no way to kill a
205 * do_ask_dialog() window, we just note the
206 * status here and clean up after the user
209 xfer
->state
= FILE_TRANSFER_STATE_INTERRUPTED
;
212 else if (xfer
->state
== FILE_TRANSFER_STATE_TRANSFERRING
) {
214 gaim_input_remove(xfer
->watcher
);
221 /* XXX theoretically, there is a race condition here,
222 * because we could be inside ft_callback() when we
223 * free xfer below, with undefined results. Since
224 * we use non-blocking IO, this doesn't seem to be
225 * a problem, but it still makes me nervous--I don't
226 * know how to fix it other than using locks, though.
230 else if (xfer
->state
== FILE_TRANSFER_STATE_CHOOSEFILE
) {
231 /* It's safe to clean up now. Just make sure we
232 * destroy the dialog window first.
235 gtk_signal_disconnect_by_func(GTK_OBJECT(xfer
->w
),
236 GTK_SIGNAL_FUNC(ft_cancel
), xfer
);
237 gtk_widget_destroy(xfer
->w
);
242 /* Let the user know that we were aborted, unless we already
243 * finished or the user aborted first.
245 /* if ((xfer->state != FILE_TRANSFER_STATE_DONE) &&
246 (xfer->state != FILE_TRANSFER_STATE_CANCELED)) { */
250 if (xfer
->type
== FILE_TRANSFER_TYPE_SEND
)
251 msg
= g_strdup_printf(_("File transfer to %s aborted."), xfer
->who
);
253 msg
= g_strdup_printf(_("File transfer from %s aborted."), xfer
->who
);
254 do_error_dialog(msg
, why
, GAIM_ERROR
);
264 static void ft_delete(struct file_transfer
*xfer
)
267 g_strfreev(xfer
->names
);
269 g_free(xfer
->initname
);
277 static void ft_choose_ok(gpointer a
, struct file_transfer
*xfer
) {
278 gboolean exists
, is_dir
;
280 const char *err
= NULL
;
282 xfer
->names
= gtk_file_selection_get_selections(GTK_FILE_SELECTION(xfer
->w
));
283 exists
= !stat(*xfer
->names
, &st
);
284 is_dir
= (exists
) ? S_ISDIR(st
.st_mode
) : 0;
287 if (xfer
->type
== FILE_TRANSFER_TYPE_RECEIVE
)
288 /* XXX overwrite/append/cancel prompt */
289 err
= _("That file already exists; please choose another name.");
290 else { /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */
292 /* First find the total number of files,
293 * so we know how much space to allocate.
296 for (cur
= xfer
->names
; *cur
; cur
++) {
300 /* Now get sizes for each file. */
301 xfer
->totsize
= st
.st_size
;
302 xfer
->sizes
= g_malloc(xfer
->totfiles
303 * sizeof(*xfer
->sizes
));
304 xfer
->sizes
[0] = st
.st_size
;
305 for (cur
= xfer
->names
+ 1; *cur
; cur
++) {
306 exists
= !stat(*cur
, &st
);
308 err
= _("File not found.");
311 xfer
->sizes
[cur
- xfer
->names
] =
313 xfer
->totsize
+= st
.st_size
;
317 else { /* doesn't exist */
318 if (xfer
->type
== FILE_TRANSFER_TYPE_SEND
)
319 err
= _("File not found.");
320 else if (xfer
->totfiles
> 1) {
321 if (!xfer
->names
[0] || xfer
->names
[1]) {
322 err
= _("You may only choose one new directory.");
325 if (ft_mkdir(*xfer
->names
))
326 err
= _("Unable to create directory.");
328 xfer
->dir
= g_strconcat(xfer
->names
[0],
335 do_error_dialog(err
, NULL
, GAIM_ERROR
);
337 /* File name looks valid */
338 gtk_widget_destroy(xfer
->w
);
341 if (xfer
->type
== FILE_TRANSFER_TYPE_SEND
) {
343 if (xfer
->totfiles
== 1)
348 /* desc = g_path_get_basename(g_path_get_dirname(*xfer->names)); */
349 xfer
->gc
->prpl
->file_transfer_out(xfer
->gc
, xfer
,
350 desc
, xfer
->totfiles
,
354 xfer
->gc
->prpl
->file_transfer_in(xfer
->gc
, xfer
,
359 /* Called on outgoing transfers to get information about the
362 int transfer_get_file_info(struct file_transfer
*xfer
, int *size
,
365 *size
= xfer
->sizes
[xfer
->filesdone
];
366 *name
= xfer
->names
[xfer
->filesdone
];
370 static int ft_choose_file(struct file_transfer
*xfer
)
372 char *curdir
= g_get_current_dir(); /* should be freed */
375 /* If the connection is interrupted while we are waiting
376 * for do_ask_dialog(), then we can't clean up until we
377 * get here, after the user makes a selection.
379 if (xfer
->state
== FILE_TRANSFER_STATE_INTERRUPTED
) {
380 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
381 transfer_abort(xfer
, NULL
);
385 xfer
->state
= FILE_TRANSFER_STATE_CHOOSEFILE
;
386 if (xfer
->type
== FILE_TRANSFER_TYPE_RECEIVE
)
387 xfer
->w
= gtk_file_selection_new(_("Gaim - Save As..."));
388 else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ {
389 xfer
->w
= gtk_file_selection_new(_("Gaim - Open..."));
390 gtk_file_selection_set_select_multiple(GTK_FILE_SELECTION(xfer
->w
),
394 if (xfer
->initname
) {
395 initstr
= g_strdup_printf("%s/%s", curdir
, xfer
->initname
);
397 initstr
= g_strconcat(curdir
, "/", NULL
);
400 gtk_file_selection_set_filename(GTK_FILE_SELECTION(xfer
->w
),
404 gtk_signal_connect(GTK_OBJECT(xfer
->w
), "delete_event",
405 GTK_SIGNAL_FUNC(ft_cancel
), xfer
);
406 gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(xfer
->w
)->cancel_button
),
407 "clicked", GTK_SIGNAL_FUNC(ft_cancel
), xfer
);
408 gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(xfer
->w
)->ok_button
),
409 "clicked", GTK_SIGNAL_FUNC(ft_choose_ok
), xfer
);
410 gtk_widget_show(xfer
->w
);
415 static int ft_open_file(struct file_transfer
*xfer
, const char *filename
,
420 if (xfer
->type
== FILE_TRANSFER_TYPE_RECEIVE
) {
421 xfer
->file
= fopen(filename
,
422 (offset
> 0) ? "a" : "w");
425 err
= g_strdup_printf(_("Could not open %s for writing: %s"),
426 filename
, g_strerror(errno
));
429 else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ {
430 xfer
->file
= fopen(filename
, "r");
432 err
= g_strdup_printf(_("Could not open %s for reading: %s"),
433 filename
, g_strerror(errno
));
437 do_error_dialog(err
, NULL
, GAIM_ERROR
);
442 fseek(xfer
->file
, offset
, SEEK_SET
);
447 /* Takes a full file name, and creates any directories above it
448 * that don't exist already.
450 static int ft_mkdir(const char *name
) {
453 mode_t m
= umask(0077);
456 dir
= g_path_get_dirname(name
);
458 ret
= ft_mkdir_help(dir
);
465 /* Two functions, one recursive, just to make a directory. Yuck. */
466 static int ft_mkdir_help(char *dir
) {
469 ret
= mkdir(dir
, 0775);
472 char *index
= strrchr(dir
, G_DIR_SEPARATOR
);
476 ret
= ft_mkdir_help(dir
);
477 *index
= G_DIR_SEPARATOR
;
479 ret
= mkdir(dir
, 0775);
485 int transfer_in_do(struct file_transfer
*xfer
, int fd
,
486 const char *filename
, int size
)
490 xfer
->state
= FILE_TRANSFER_STATE_TRANSFERRING
;
492 xfer
->bytesleft
= size
;
494 /* XXX implement resuming incoming transfers */
497 xfer
->bytesleft
-= xfer
->sizes
[0];
501 if (g_strrstr(filename
, "..")) {
502 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
503 transfer_abort(xfer
, _("Invalid incoming filename component"));
507 if (xfer
->totfiles
> 1)
508 fullname
= g_strconcat(xfer
->dir
, filename
, NULL
);
510 /* Careful: filename is the name on the *other*
511 * end; don't use it here. */
512 fullname
= g_strdup(xfer
->names
[xfer
->filesdone
]);
515 if (ft_mkdir(fullname
)) {
516 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
517 transfer_abort(xfer
, _("Invalid incoming filename"));
521 if (!ft_open_file(xfer
, fullname
, 0)) {
522 /* Special case: if we are receiving an empty file,
523 * we would never enter the callback. Just avoid the
524 * callback altogether.
526 if (xfer
->bytesleft
== 0)
529 xfer
->watcher
= gaim_input_add(fd
,
533 /* Error opening file */
534 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
535 transfer_abort(xfer
, NULL
);
544 int transfer_out_do(struct file_transfer
*xfer
, int fd
, int offset
) {
545 xfer
->state
= FILE_TRANSFER_STATE_TRANSFERRING
;
547 xfer
->bytesleft
= xfer
->sizes
[xfer
->filesdone
] - offset
;
549 if (!ft_open_file(xfer
, xfer
->names
[xfer
->filesdone
], offset
)) {
550 /* Special case: see transfer_in_do().
552 if (xfer
->bytesleft
== 0)
555 xfer
->watcher
= gaim_input_add(fd
,
556 GAIM_INPUT_WRITE
, ft_callback
,
560 /* Error opening file */
561 xfer
->state
= FILE_TRANSFER_STATE_CLEANUP
;
562 transfer_abort(xfer
, NULL
);
569 static void ft_callback(gpointer data
, gint source
,
570 GaimInputCondition condition
)
572 struct file_transfer
*xfer
= (struct file_transfer
*)data
;
574 char buf
[FT_BUFFER_SIZE
];
576 if (condition
& GAIM_INPUT_READ
) {
577 rt
= read(xfer
->fd
, buf
, MIN(xfer
->bytesleft
, FT_BUFFER_SIZE
));
578 /* XXX What if the transfer is interrupted while we
579 * are inside read()? How can this be handled safely?
583 xfer
->bytesleft
-= rt
;
584 for (i
= 0; i
< rt
; i
++) {
585 fprintf(xfer
->file
, "%c", buf
[i
]);
590 else /* (condition & GAIM_INPUT_WRITE) */ {
591 int remain
= MIN(xfer
->bytesleft
, FT_BUFFER_SIZE
);
593 for (i
= 0; i
< remain
; i
++)
594 fscanf(xfer
->file
, "%c", &buf
[i
]);
596 rt
= write(xfer
->fd
, buf
, remain
);
598 xfer
->bytesleft
-= rt
;
604 xfer
->bytessent
+= rt
;
606 if (xfer
->gc
->prpl
->file_transfer_data_chunk
)
607 xfer
->gc
->prpl
->file_transfer_data_chunk(xfer
->gc
, xfer
, buf
, rt
);
609 if (rt
> 0 && xfer
->bytesleft
== 0) {
610 /* We are done with this file! */
611 gaim_input_remove(xfer
->watcher
);
619 static void ft_nextfile(struct file_transfer
*xfer
)
621 debug_printf("file transfer %d of %d done\n",
622 xfer
->filesdone
+ 1, xfer
->totfiles
);
624 if (++xfer
->filesdone
== xfer
->totfiles
) {
628 xfer
->gc
->prpl
->file_transfer_done(xfer
->gc
, xfer
);
630 if (xfer
->type
== FILE_TRANSFER_TYPE_RECEIVE
)
631 msg
= g_strdup_printf(_("File transfer from %s to %s completed successfully."),
632 xfer
->who
, xfer
->gc
->username
);
634 msg
= g_strdup_printf(_("File transfer from %s to %s completed successfully."),
635 xfer
->gc
->username
, xfer
->who
);
636 xfer
->state
= FILE_TRANSFER_STATE_DONE
;
638 if (xfer
->totfiles
> 1)
639 msg2
= g_strdup_printf(_("%d files transferred."),
644 do_error_dialog(msg
, msg2
, GAIM_INFO
);
652 xfer
->gc
->prpl
->file_transfer_nextfile(xfer
->gc
, xfer
);