[gaim-migrate @ 4156]
[pidgin-git.git] / src / ft.c
blob1a3a32d9a18adcddc2c6ece20c314dc123ce4fac
1 /*
2 * gaim - file transfer functions
4 * Copyright (C) 2002, Wil Mahan <wtm2@duke.edu>
5 *
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
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <string.h>
31 #define FT_BUFFER_SIZE (4096)
33 #include <gtk/gtk.h>
34 #include "gaim.h"
35 #include "proxy.h"
36 #include "prpl.h"
38 #ifdef _WIN32
39 #include "win32dep.h"
40 #endif
42 /* Completely describes a file transfer. Opaque to callers. */
43 struct file_transfer {
44 enum { FILE_TRANSFER_TYPE_SEND, FILE_TRANSFER_TYPE_RECEIVE } type;
46 enum {
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 */
54 } state;
56 char *who;
58 /* file selection dialog */
59 GtkWidget *w;
60 char **names;
61 int *sizes;
62 char *dir;
63 char *initname;
64 FILE *file;
66 /* connection info */
67 struct gaim_connection *gc;
68 int fd;
69 int watcher;
71 /* state */
72 int totfiles;
73 int filesdone;
74 int totsize;
75 int bytessent;
76 int bytesleft;
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,
90 const char *who)
92 struct file_transfer *xfer = g_new0(struct file_transfer, 1);
93 xfer->type = type;
94 xfer->state = FILE_TRANSFER_STATE_ASK;
95 xfer->gc = gc;
96 xfer->who = g_strdup(who);
97 xfer->filesdone = 0;
99 return xfer;
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,
107 who);
108 char *buf;
109 char *sizebuf;
110 static const char *sizestr[4] = { "bytes", "KB", "MB", "GB" };
111 float sizemag = (float)totsize;
112 int szindex = 0;
114 xfer->initname = g_strdup(initname);
115 xfer->totsize = totsize;
116 xfer->totfiles = totfiles;
117 xfer->filesdone = 0;
119 /* Ask the user whether he or she accepts the transfer. */
120 while ((szindex < 4) && (sizemag > 1024)) {
121 sizemag /= 1024;
122 szindex++;
125 if (totsize == -1)
126 sizebuf = g_strdup_printf(_("Unkown"));
127 else
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);
133 } else {
134 buf = g_strdup_printf(_("%s requests that %s accept %d files: %s (%s)"),
135 who, xfer->gc->username, xfer->totfiles,
136 initname, sizebuf);
139 g_free(sizebuf);
141 if (msg) {
142 char *newmsg = g_strconcat(buf, ": ", msg, NULL);
143 g_free(buf);
144 buf = newmsg;
147 do_ask_dialog(buf, NULL, xfer, _("Accept"), ft_choose_file, _("Cancel"), ft_cancel);
148 g_free(buf);
150 return xfer;
153 struct file_transfer *transfer_out_add(struct gaim_connection *gc,
154 const char *who)
156 struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_SEND, gc,
157 who);
159 ft_choose_file(xfer);
161 return 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);
175 return;
178 xfer->state = FILE_TRANSFER_STATE_CANCELED;
179 if (xfer->w) {
180 gtk_widget_destroy(xfer->w);
181 xfer->w = NULL;
184 if (xfer->gc->prpl->file_transfer_cancel)
185 xfer->gc->prpl->file_transfer_cancel(xfer->gc, xfer);
187 ft_delete(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.
201 return 1;
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
207 * makes a selection.
209 xfer->state = FILE_TRANSFER_STATE_INTERRUPTED;
210 return 1;
212 else if (xfer->state == FILE_TRANSFER_STATE_TRANSFERRING) {
213 if (xfer->watcher) {
214 gaim_input_remove(xfer->watcher);
215 xfer->watcher = 0;
217 if (xfer->file) {
218 fclose(xfer->file);
219 xfer->file = NULL;
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.
227 * -- wtm
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.
234 if (xfer->w) {
235 gtk_signal_disconnect_by_func(GTK_OBJECT(xfer->w),
236 GTK_SIGNAL_FUNC(ft_cancel), xfer);
237 gtk_widget_destroy(xfer->w);
238 xfer->w = NULL;
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)) { */
247 if (why) {
248 char *msg;
250 if (xfer->type == FILE_TRANSFER_TYPE_SEND)
251 msg = g_strdup_printf(_("File transfer to %s aborted."), xfer->who);
252 else
253 msg = g_strdup_printf(_("File transfer from %s aborted."), xfer->who);
254 do_error_dialog(msg, why, GAIM_ERROR);
255 g_free(msg);
258 ft_delete(xfer);
260 return 0;
264 static void ft_delete(struct file_transfer *xfer)
266 if (xfer->names)
267 g_strfreev(xfer->names);
268 if (xfer->initname)
269 g_free(xfer->initname);
270 if (xfer->who)
271 g_free(xfer->who);
272 if (xfer->sizes)
273 g_free(xfer->sizes);
274 g_free(xfer);
277 static void ft_choose_ok(gpointer a, struct file_transfer *xfer) {
278 gboolean exists, is_dir;
279 struct stat st;
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;
286 if (exists) {
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) */
291 char **cur;
292 /* First find the total number of files,
293 * so we know how much space to allocate.
295 xfer->totfiles = 0;
296 for (cur = xfer->names; *cur; cur++) {
297 xfer->totfiles++;
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);
307 if (!exists) {
308 err = _("File not found.");
309 break;
311 xfer->sizes[cur - xfer->names] =
312 st.st_size;
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.");
324 else {
325 if (ft_mkdir(*xfer->names))
326 err = _("Unable to create directory.");
327 else
328 xfer->dir = g_strconcat(xfer->names[0],
329 "/", NULL);
334 if (err)
335 do_error_dialog(err, NULL, GAIM_ERROR);
336 else {
337 /* File name looks valid */
338 gtk_widget_destroy(xfer->w);
339 xfer->w = NULL;
341 if (xfer->type == FILE_TRANSFER_TYPE_SEND) {
342 char *desc;
343 if (xfer->totfiles == 1)
344 desc = *xfer->names;
345 else
346 /* XXX what else? */
347 desc = "*";
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,
351 xfer->totsize);
353 else
354 xfer->gc->prpl->file_transfer_in(xfer->gc, xfer,
355 0); /* XXX */
359 /* Called on outgoing transfers to get information about the
360 * current file.
362 int transfer_get_file_info(struct file_transfer *xfer, int *size,
363 char **name)
365 *size = xfer->sizes[xfer->filesdone];
366 *name = xfer->names[xfer->filesdone];
367 return 0;
370 static int ft_choose_file(struct file_transfer *xfer)
372 char *curdir = g_get_current_dir(); /* should be freed */
373 char *initstr;
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);
382 return 1;
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);
396 } else
397 initstr = g_strconcat(curdir, "/", NULL);
398 g_free(curdir);
400 gtk_file_selection_set_filename(GTK_FILE_SELECTION(xfer->w),
401 initstr);
402 g_free(initstr);
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);
412 return 0;
415 static int ft_open_file(struct file_transfer *xfer, const char *filename,
416 int offset)
418 char *err = NULL;
420 if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) {
421 xfer->file = fopen(filename,
422 (offset > 0) ? "a" : "w");
424 if (!xfer->file)
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");
431 if (!xfer->file)
432 err = g_strdup_printf(_("Could not open %s for reading: %s"),
433 filename, g_strerror(errno));
436 if (err) {
437 do_error_dialog(err, NULL, GAIM_ERROR);
438 g_free(err);
439 return -1;
442 fseek(xfer->file, offset, SEEK_SET);
444 return 0;
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) {
451 int ret = 0;
452 struct stat st;
453 mode_t m = umask(0077);
454 char *dir;
456 dir = g_path_get_dirname(name);
457 if (stat(dir, &st))
458 ret = ft_mkdir_help(dir);
460 g_free(dir);
461 umask(m);
462 return ret;
465 /* Two functions, one recursive, just to make a directory. Yuck. */
466 static int ft_mkdir_help(char *dir) {
467 int ret;
469 ret = mkdir(dir, 0775);
471 if (ret) {
472 char *index = strrchr(dir, G_DIR_SEPARATOR);
473 if (!index)
474 return -1;
475 *index = '\0';
476 ret = ft_mkdir_help(dir);
477 *index = G_DIR_SEPARATOR;
478 if (!ret)
479 ret = mkdir(dir, 0775);
482 return ret;
485 int transfer_in_do(struct file_transfer *xfer, int fd,
486 const char *filename, int size)
488 char *fullname;
490 xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
491 xfer->fd = fd;
492 xfer->bytesleft = size;
494 /* XXX implement resuming incoming transfers */
495 #if 0
496 if (xfer->sizes)
497 xfer->bytesleft -= xfer->sizes[0];
498 #endif
500 /* Security check */
501 if (g_strrstr(filename, "..")) {
502 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
503 transfer_abort(xfer, _("Invalid incoming filename component"));
504 return -1;
507 if (xfer->totfiles > 1)
508 fullname = g_strconcat(xfer->dir, filename, NULL);
509 else
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"));
518 return -1;
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)
527 ft_nextfile(xfer);
528 else
529 xfer->watcher = gaim_input_add(fd,
530 GAIM_INPUT_READ,
531 ft_callback, xfer);
532 } else {
533 /* Error opening file */
534 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
535 transfer_abort(xfer, NULL);
536 g_free(fullname);
537 return -1;
540 g_free(fullname);
541 return 0;
544 int transfer_out_do(struct file_transfer *xfer, int fd, int offset) {
545 xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
546 xfer->fd = fd;
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)
553 ft_nextfile(xfer);
554 else
555 xfer->watcher = gaim_input_add(fd,
556 GAIM_INPUT_WRITE, ft_callback,
557 xfer);
559 else {
560 /* Error opening file */
561 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
562 transfer_abort(xfer, NULL);
563 return -1;
566 return 0;
569 static void ft_callback(gpointer data, gint source,
570 GaimInputCondition condition)
572 struct file_transfer *xfer = (struct file_transfer *)data;
573 int rt, i;
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?
580 * -- wtm
582 if (rt > 0) {
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);
597 if (rt > 0)
598 xfer->bytesleft -= rt;
601 if (rt < 0)
602 return;
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);
612 xfer->watcher = 0;
613 fclose(xfer->file);
614 xfer->file = 0;
615 ft_nextfile(xfer);
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) {
625 char *msg;
626 char *msg2;
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);
633 else
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."),
640 xfer->totfiles);
641 else
642 msg2 = NULL;
644 do_error_dialog(msg, msg2, GAIM_INFO);
645 g_free(msg);
646 if (msg2)
647 g_free(msg2);
649 ft_delete(xfer);
651 else {
652 xfer->gc->prpl->file_transfer_nextfile(xfer->gc, xfer);