Mercurial > pidgin.yaz
view src/ft.c @ 4420:64d845f2a0fb
[gaim-migrate @ 4693]
accessibility.
committer: Tailor Script <tailor@pidgin.im>
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sun, 26 Jan 2003 04:23:20 +0000 |
parents | 9c7fcb211886 |
children | 7521e29658bc |
line wrap: on
line source
/* * gaim - file transfer functions * * Copyright (C) 2002, Wil Mahan <wtm2@duke.edu> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <string.h> #define FT_BUFFER_SIZE (4096) #include <gtk/gtk.h> #include "gaim.h" #include "proxy.h" #include "prpl.h" #ifdef _WIN32 #include "win32dep.h" #endif /* Completely describes a file transfer. Opaque to callers. */ struct file_transfer { enum { FILE_TRANSFER_TYPE_SEND, FILE_TRANSFER_TYPE_RECEIVE } type; enum { FILE_TRANSFER_STATE_ASK, /* waiting for confirmation */ FILE_TRANSFER_STATE_CHOOSEFILE, /* waiting for file dialog */ FILE_TRANSFER_STATE_TRANSFERRING, /* in actual transfer */ FILE_TRANSFER_STATE_INTERRUPTED, /* other user canceled */ FILE_TRANSFER_STATE_CANCELED, /* we canceled */ FILE_TRANSFER_STATE_DONE, /* transfer complete */ FILE_TRANSFER_STATE_CLEANUP /* freeing memory */ } state; char *who; /* file selection dialog */ GtkWidget *w; char **names; int *sizes; char *dir; char *initname; FILE *file; /* connection info */ struct gaim_connection *gc; int fd; int watcher; /* state */ int totfiles; int filesdone; int totsize; int bytessent; int bytesleft; }; static int ft_choose_file(struct file_transfer *xfer); static void ft_cancel(struct file_transfer *xfer); static void ft_delete(struct file_transfer *xfer); static void ft_callback(gpointer data, gint source, GaimInputCondition condition); static void ft_nextfile(struct file_transfer *xfer); static int ft_mkdir(const char *name); static int ft_mkdir_help(char *dir); static gboolean ft_choose_file_close(GtkWidget *widget, GdkEvent *event, gpointer user_data); static struct file_transfer *ft_new(int type, struct gaim_connection *gc, const char *who) { struct file_transfer *xfer = g_new0(struct file_transfer, 1); xfer->type = type; xfer->state = FILE_TRANSFER_STATE_ASK; xfer->gc = gc; xfer->who = g_strdup(who); xfer->filesdone = 0; xfer->w = NULL; return xfer; } struct file_transfer *transfer_in_add(struct gaim_connection *gc, const char *who, const char *initname, int totsize, int totfiles, const char *msg) { struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_RECEIVE, gc, who); char *buf; char *sizebuf; static const char *sizestr[4] = { "bytes", "KB", "MB", "GB" }; float sizemag = (float)totsize; int szindex = 0; xfer->initname = g_strdup(initname); xfer->totsize = totsize; xfer->totfiles = totfiles; xfer->filesdone = 0; /* Ask the user whether he or she accepts the transfer. */ while ((szindex < 4) && (sizemag > 1024)) { sizemag /= 1024; szindex++; } if (totsize == -1) sizebuf = g_strdup_printf(_("Unkown")); else sizebuf = g_strdup_printf("%.3g %s", sizemag, sizestr[szindex]); if (xfer->totfiles == 1) { buf = g_strdup_printf(_("%s requests that %s accept a file: %s (%s)"), who, xfer->gc->username, initname, sizebuf); } else { buf = g_strdup_printf(_("%s requests that %s accept %d files: %s (%s)"), who, xfer->gc->username, xfer->totfiles, initname, sizebuf); } g_free(sizebuf); if (msg) { char *newmsg = g_strconcat(buf, ": ", msg, NULL); g_free(buf); buf = newmsg; } do_ask_dialog(buf, NULL, xfer, _("Accept"), ft_choose_file, _("Cancel"), ft_cancel, NULL, FALSE); g_free(buf); return xfer; } struct file_transfer *transfer_out_add(struct gaim_connection *gc, const char *who) { struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_SEND, gc, who); ft_choose_file(xfer); return xfer; } /* We canceled the transfer, either by declining the initial * confirmation dialog or canceling the file dialog. */ static void ft_cancel(struct file_transfer *xfer) { debug_printf("** ft_cancel\n"); /* Make sure we weren't aborted while waiting for * confirmation from the user. */ if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, NULL); return; } xfer->state = FILE_TRANSFER_STATE_CANCELED; if (xfer->w) { gtk_widget_destroy(xfer->w); xfer->w = NULL; } if (xfer->gc->prpl->file_transfer_cancel) xfer->gc->prpl->file_transfer_cancel(xfer->gc, xfer); ft_delete(xfer); } /* This is called when the other user aborts the transfer, * possibly in the middle of a transfer. */ int transfer_abort(struct file_transfer *xfer, const char *why) { if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { /* If for some reason we have already been * here and are waiting on some event before * cleaning up, but we get another abort request, * we don't need to do anything else. */ return 1; } else if (xfer->state == FILE_TRANSFER_STATE_ASK) { /* Kludge: since there is no way to kill a * do_ask_dialog() window, we just note the * status here and clean up after the user * makes a selection. */ xfer->state = FILE_TRANSFER_STATE_INTERRUPTED; return 1; } else if (xfer->state == FILE_TRANSFER_STATE_TRANSFERRING) { if (xfer->watcher) { gaim_input_remove(xfer->watcher); xfer->watcher = 0; } if (xfer->file) { fclose(xfer->file); xfer->file = NULL; } /* XXX theoretically, there is a race condition here, * because we could be inside ft_callback() when we * free xfer below, with undefined results. Since * we use non-blocking IO, this doesn't seem to be * a problem, but it still makes me nervous--I don't * know how to fix it other than using locks, though. * -- wtm */ } else if (xfer->state == FILE_TRANSFER_STATE_CHOOSEFILE) { /* It's safe to clean up now. Just make sure we * destroy the dialog window first. */ if (xfer->w) { g_signal_handlers_disconnect_by_func(G_OBJECT(xfer->w), G_CALLBACK(ft_choose_file_close), xfer); gtk_widget_destroy(xfer->w); xfer->w = NULL; } } /* Let the user know that we were aborted, unless we already * finished or the user aborted first. */ /* if ((xfer->state != FILE_TRANSFER_STATE_DONE) && (xfer->state != FILE_TRANSFER_STATE_CANCELED)) { */ if (why) { char *msg; if (xfer->type == FILE_TRANSFER_TYPE_SEND) msg = g_strdup_printf(_("File transfer to %s aborted."), xfer->who); else msg = g_strdup_printf(_("File transfer from %s aborted."), xfer->who); do_error_dialog(msg, why, GAIM_ERROR); g_free(msg); } ft_delete(xfer); return 0; } static void ft_delete(struct file_transfer *xfer) { if (xfer->names) g_strfreev(xfer->names); if (xfer->initname) g_free(xfer->initname); if (xfer->who) g_free(xfer->who); if (xfer->sizes) g_free(xfer->sizes); g_free(xfer); } static void ft_choose_ok(gpointer a, struct file_transfer *xfer) { gboolean exists, is_dir; struct stat st; const char *err = NULL; xfer->names = gtk_file_selection_get_selections(GTK_FILE_SELECTION(xfer->w)); exists = !stat(*xfer->names, &st); is_dir = (exists) ? S_ISDIR(st.st_mode) : 0; if (exists) { if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) /* XXX overwrite/append/cancel prompt */ err = _("That file already exists; please choose another name."); else { /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ char **cur; /* First find the total number of files, * so we know how much space to allocate. */ xfer->totfiles = 0; for (cur = xfer->names; *cur; cur++) { xfer->totfiles++; } /* Now get sizes for each file. */ xfer->totsize = st.st_size; xfer->sizes = g_malloc(xfer->totfiles * sizeof(*xfer->sizes)); xfer->sizes[0] = st.st_size; for (cur = xfer->names + 1; *cur; cur++) { exists = !stat(*cur, &st); if (!exists) { err = _("File not found."); break; } xfer->sizes[cur - xfer->names] = st.st_size; xfer->totsize += st.st_size; } } } else { /* doesn't exist */ if (xfer->type == FILE_TRANSFER_TYPE_SEND) err = _("File not found."); else if (xfer->totfiles > 1) { if (!xfer->names[0] || xfer->names[1]) { err = _("You may only choose one new directory."); } else { if (ft_mkdir(*xfer->names)) err = _("Unable to create directory."); else xfer->dir = g_strconcat(xfer->names[0], "/", NULL); } } } if (err) do_error_dialog(err, NULL, GAIM_ERROR); else { /* File name looks valid */ gtk_widget_destroy(xfer->w); xfer->w = NULL; if (xfer->type == FILE_TRANSFER_TYPE_SEND) { char *desc; if (xfer->totfiles == 1) desc = *xfer->names; else /* XXX what else? */ desc = "*"; /* desc = g_path_get_basename(g_path_get_dirname(*xfer->names)); */ xfer->gc->prpl->file_transfer_out(xfer->gc, xfer, desc, xfer->totfiles, xfer->totsize); } else xfer->gc->prpl->file_transfer_in(xfer->gc, xfer, 0); /* XXX */ } } /* Called on outgoing transfers to get information about the * current file. */ int transfer_get_file_info(struct file_transfer *xfer, int *size, char **name) { *size = xfer->sizes[xfer->filesdone]; *name = xfer->names[xfer->filesdone]; return 0; } static gboolean ft_choose_file_close(GtkWidget *widget, GdkEvent *event, gpointer user_data) { ft_cancel((struct file_transfer *)user_data); return FALSE; } static void ft_cancel_button_cb(GtkButton *button, gpointer user_data) { ft_cancel((struct file_transfer *)user_data); } static int ft_choose_file(struct file_transfer *xfer) { char *curdir = g_get_current_dir(); /* should be freed */ char *initstr; /* If the connection is interrupted while we are waiting * for do_ask_dialog(), then we can't clean up until we * get here, after the user makes a selection. */ if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, NULL); return 1; } xfer->state = FILE_TRANSFER_STATE_CHOOSEFILE; if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) xfer->w = gtk_file_selection_new(_("Gaim - Save As...")); else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ { xfer->w = gtk_file_selection_new(_("Gaim - Open...")); gtk_file_selection_set_select_multiple(GTK_FILE_SELECTION(xfer->w), 1); } if (xfer->initname) { initstr = g_strdup_printf("%s/%s", curdir, xfer->initname); } else initstr = g_strconcat(curdir, "/", NULL); g_free(curdir); gtk_file_selection_set_filename(GTK_FILE_SELECTION(xfer->w), initstr); g_free(initstr); g_signal_connect(G_OBJECT(xfer->w), "delete_event", G_CALLBACK(ft_choose_file_close), xfer); g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->cancel_button), "clicked", G_CALLBACK(ft_cancel_button_cb), xfer); g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->ok_button), "clicked", G_CALLBACK(ft_choose_ok), xfer); gtk_widget_show(xfer->w); return 0; } static int ft_open_file(struct file_transfer *xfer, const char *filename, int offset) { char *err = NULL; if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) { xfer->file = fopen(filename, (offset > 0) ? "a" : "w"); if (!xfer->file) err = g_strdup_printf(_("Could not open %s for writing: %s"), filename, g_strerror(errno)); } else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ { xfer->file = fopen(filename, "r"); if (!xfer->file) err = g_strdup_printf(_("Could not open %s for reading: %s"), filename, g_strerror(errno)); } if (err) { do_error_dialog(err, NULL, GAIM_ERROR); g_free(err); return -1; } fseek(xfer->file, offset, SEEK_SET); return 0; } /* Takes a full file name, and creates any directories above it * that don't exist already. */ static int ft_mkdir(const char *name) { int ret = 0; struct stat st; mode_t m = umask(0077); char *dir; dir = g_path_get_dirname(name); if (stat(dir, &st)) ret = ft_mkdir_help(dir); g_free(dir); umask(m); return ret; } /* Two functions, one recursive, just to make a directory. Yuck. */ static int ft_mkdir_help(char *dir) { int ret; ret = mkdir(dir, 0775); if (ret) { char *index = strrchr(dir, G_DIR_SEPARATOR); if (!index) return -1; *index = '\0'; ret = ft_mkdir_help(dir); *index = G_DIR_SEPARATOR; if (!ret) ret = mkdir(dir, 0775); } return ret; } int transfer_in_do(struct file_transfer *xfer, int fd, const char *filename, int size) { char *fullname; xfer->state = FILE_TRANSFER_STATE_TRANSFERRING; xfer->fd = fd; xfer->bytesleft = size; /* XXX implement resuming incoming transfers */ #if 0 if (xfer->sizes) xfer->bytesleft -= xfer->sizes[0]; #endif /* Security check */ if (g_strrstr(filename, "..")) { xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, _("Invalid incoming filename component")); return -1; } if (xfer->totfiles > 1) fullname = g_strconcat(xfer->dir, filename, NULL); else /* Careful: filename is the name on the *other* * end; don't use it here. */ fullname = g_strdup(xfer->names[xfer->filesdone]); if (ft_mkdir(fullname)) { xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, _("Invalid incoming filename")); return -1; } if (!ft_open_file(xfer, fullname, 0)) { /* Special case: if we are receiving an empty file, * we would never enter the callback. Just avoid the * callback altogether. */ if (xfer->bytesleft == 0) ft_nextfile(xfer); else xfer->watcher = gaim_input_add(fd, GAIM_INPUT_READ, ft_callback, xfer); } else { /* Error opening file */ xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, NULL); g_free(fullname); return -1; } g_free(fullname); return 0; } int transfer_out_do(struct file_transfer *xfer, int fd, int offset) { xfer->state = FILE_TRANSFER_STATE_TRANSFERRING; xfer->fd = fd; xfer->bytesleft = xfer->sizes[xfer->filesdone] - offset; if (!ft_open_file(xfer, xfer->names[xfer->filesdone], offset)) { /* Special case: see transfer_in_do(). */ if (xfer->bytesleft == 0) ft_nextfile(xfer); else xfer->watcher = gaim_input_add(fd, GAIM_INPUT_WRITE, ft_callback, xfer); } else { /* Error opening file */ xfer->state = FILE_TRANSFER_STATE_CLEANUP; transfer_abort(xfer, NULL); return -1; } return 0; } static void ft_callback(gpointer data, gint source, GaimInputCondition condition) { struct file_transfer *xfer = (struct file_transfer *)data; int rt; char *buf = NULL; if (condition & GAIM_INPUT_READ) { if (xfer->gc->prpl->file_transfer_read) rt = xfer->gc->prpl->file_transfer_read(xfer->gc, xfer, xfer->fd, &buf); else { buf = g_new0(char, MIN(xfer->bytesleft, FT_BUFFER_SIZE)); rt = read(xfer->fd, buf, MIN(xfer->bytesleft, FT_BUFFER_SIZE)); } /* XXX What if the transfer is interrupted while we * are inside read()? How can this be handled safely? * -- wtm */ if (rt > 0) { xfer->bytesleft -= rt; fwrite(buf, 1, rt, xfer->file); } } else /* (condition & GAIM_INPUT_WRITE) */ { int remain = MIN(xfer->bytesleft, FT_BUFFER_SIZE); buf = g_new0(char, remain); fread(buf, 1, remain, xfer->file); if (xfer->gc->prpl->file_transfer_write) rt = xfer->gc->prpl->file_transfer_write(xfer->gc, xfer, xfer->fd, buf, remain); else rt = write(xfer->fd, buf, remain); if (rt > 0) xfer->bytesleft -= rt; } if (rt < 0) { if (buf != NULL) g_free(buf); return; } xfer->bytessent += rt; if (xfer->gc->prpl->file_transfer_data_chunk) xfer->gc->prpl->file_transfer_data_chunk(xfer->gc, xfer, buf, rt); if (rt > 0 && xfer->bytesleft == 0) { /* We are done with this file! */ gaim_input_remove(xfer->watcher); xfer->watcher = 0; fclose(xfer->file); xfer->file = 0; ft_nextfile(xfer); } if (buf != NULL) g_free(buf); } static void ft_nextfile(struct file_transfer *xfer) { debug_printf("file transfer %d of %d done\n", xfer->filesdone + 1, xfer->totfiles); if (++xfer->filesdone == xfer->totfiles) { char *msg; char *msg2; xfer->gc->prpl->file_transfer_done(xfer->gc, xfer); if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."), xfer->who, xfer->gc->username); else msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."), xfer->gc->username, xfer->who); xfer->state = FILE_TRANSFER_STATE_DONE; if (xfer->totfiles > 1) msg2 = g_strdup_printf(_("%d files transferred."), xfer->totfiles); else msg2 = NULL; do_error_dialog(msg, msg2, GAIM_INFO); g_free(msg); if (msg2) g_free(msg2); ft_delete(xfer); } else { xfer->gc->prpl->file_transfer_nextfile(xfer->gc, xfer); } }