/******************************************************************** Copyright (C) 2000 Bassoukos Tassos 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., 675 Mass Ave, Cambridge, MA 02139, USA. *********************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include "hotline.h" #include "network.h" #include "protocol.h" #include "connection.h" #include "messages.h" #include "transaction.h" #include "filelist.h" #include "files.h" #include "filetransfer.h" #include "filethread.h" #include "guiprefs.h" #include "smalltrans.h" #include "threads.h" #include "hldat.h" #include "tasklist.h" #include "pixmap.h" typedef struct { Connection *c; pthread_mutex_t *mutex; GSList *uploads; GSList *downloads; } FileTransferData; static FileTransferData *get_ftd(Connection *c); static void recheck_transfers(FileTransferData *ftd); /* ============================================ */ #ifndef HAVE_USLEEP int usleep (unsigned long usec){ struct timeval timeout; timeout.tv_usec = usec % (unsigned long) 1000000; timeout.tv_sec = usec / (unsigned long) 1000000; select(0, NULL, NULL, NULL, &timeout); return 0; } #endif /* ============================================ */ struct FTDCheck { FileTransferData *ftd; gboolean exists; }; static void ftd_check_exists(Connection *c, struct FTDCheck *fc){ if(connection_get_data(c,"FTD")==fc->ftd) fc->exists=TRUE; } /* This checks if an FTD exists, and if it is still valid * causes a transfer recheck. */ static gboolean ftd_check_transfers_later(gpointer data){ struct FTDCheck fc; fc.ftd=data; fc.exists=FALSE; forall_connections((ConnectionFunc)ftd_check_exists,&fc); if(fc.exists) recheck_transfers((FileTransferData*)data); return FALSE; } static void ft_remove_from_lists(FileTransfer *ft){ FileTransferData *ftd; if(ft->c==NULL) return; ftd=get_ftd(ft->c); ft->c=NULL; if(ft->is_upload==TRUE) ftd->uploads=g_slist_remove(ftd->uploads,ft); else ftd->downloads=g_slist_remove(ftd->downloads,ft); if(ftd->c!=NULL) gtk_idle_add(ftd_check_transfers_later,ftd); } /* Destroys a FileTransfer */ static void ft_close(FileTransfer *ft){ if(ft->dialog!=NULL){ gtk_widget_destroy(ft->dialog); ft->dialog=NULL; } if(ft->c!=NULL) ft_remove_from_lists(ft); if(ft->name!=NULL) free(ft->name); if(ft->remote_path!=NULL) object_destroy(ft->remote_path); if(ft->fd!=NULL) fclose(ft->fd); if(ft->localfile!=NULL) fclose(ft->localfile); if(ft->current_bytes==0 && ft->is_upload==FALSE && download_remove_null_files==TRUE && ft->local_path!=NULL){ struct stat local; stat(ft->local_path, &local); if(!local.st_size) remove(ft->local_path); } if(ft->local_path!=NULL) free(ft->local_path); if(ft->task!=NULL) gtk_widget_destroy(GTK_WIDGET(ft->task)); if(ft->timer!=NULL) g_timer_destroy(ft->timer); if(ft->current_bytes==ft->total_bytes) play_sound(HL_SOUND_FILEDONE); if(ft->has_thread || !ft->is_active) free(ft); // else the server will free us ... } int ft_is_zombie(gpointer ft) { return (((FileTransfer *)ft)->c == NULL); } /* Destroys a FileTransfer, locking the ftd. * == clean close */ static gboolean ft_prot_close(FileTransfer *ft){ FileTransferData *ftd=NULL; if(ft==NULL) return FALSE; if(ft->c!=NULL){ ftd=get_ftd(ft->c); pthread_mutex_lock(ftd->mutex); } ft_close(ft); if(ftd!=NULL) pthread_mutex_unlock(ftd->mutex); return FALSE; } /* Called when the user either aborts a task or *aborts all of them. Cleanly closes the FileTransfer */ static void ft_user_abort(Task *t,FileTransfer *ft){ FileTransferData *ftd=NULL; if(ft==NULL) return; if(ft->c!=NULL){ ftd=get_ftd(ft->c); pthread_mutex_lock(ftd->mutex); } if(ft->has_thread==TRUE){ killThread(ft->thread); } else { if(ft->c!=NULL) ft_remove_from_lists(ft); ft_close(ft); } if(ftd!=NULL) pthread_mutex_unlock(ftd->mutex); } static gboolean ft_thread_ended(FileTransfer *ft){ ft_prot_close(ft); return FALSE; } /* Called as a cancellation handler for transfer threads */ static void ft_thread_end(gpointer data){ if(data) gtk_idle_add((GtkFunction)ft_thread_ended,data); } /* Destroys a FileTransfer, without locking */ static void ft_connection_closing(FileTransfer *ft){ ft_remove_from_lists(ft); if(!ft->is_active) ft_close(ft); } /* Closes all FT in a FTD */ static void ftd_connection_closing(Connection *c,FileTransferData *ftd){ pthread_mutex_lock(ftd->mutex); ftd->c=NULL; while(ftd->uploads) ft_connection_closing((FileTransfer *)ftd->uploads->data); while(ftd->downloads) ft_connection_closing((FileTransfer *)ftd->downloads->data); connection_set_data(c,"FTD",NULL); pthread_mutex_unlock(ftd->mutex); mutex_free(ftd->mutex); free(ftd); } /* Finds or creates a FileTransferData for a connection */ static FileTransferData *get_ftd(Connection *c){ FileTransferData *ftd; if(!c) return NULL; ftd=connection_get_data(c,"FTD"); if(ftd==NULL){ ftd=malloc(sizeof(*ftd)); ftd->c=c; ftd->uploads=NULL; ftd->downloads=NULL; ftd->mutex=mutex_new(); connection_set_data(c,"FTD",ftd); gtk_signal_connect(GTK_OBJECT(c),"destroy",GTK_SIGNAL_FUNC(ftd_connection_closing),ftd); } return ftd; } /* These functions change a FileTransfer's task text */ static void ft_update_delay(FileTransfer *ft){ if(ft->task==NULL) return; if(ft->delay_remaining!=0) task_async_set_printf(ft->task,-1.0, ft->is_upload?_("Uploading %s (%d seconds for retry no. %d)..."): _("Downloading %s (%d seconds for retry no. %d)..."), ft->name,ft->delay_remaining,ft->retry_no); else task_async_set_printf(ft->task,-1.0, ft->is_upload?_("Uploading %s (retry no. %d)..."): _("Downloading %s (retry no. %d)..."), ft->name,ft->retry_no); } void ft_update_queue(FileTransfer *ft){ if(ft->server_queue_no!=0 && ft->task!=NULL) task_async_set_printf(ft->task,-1.0, ft->is_upload?_("Uploading %s (queued at slot %d)..."): _("Downloading %s (queued at slot %d)..."), ft->name,ft->server_queue_no); } /* Despite its name, this function actually creates the thread for a FileTransfer. Maybe the if should be an assert() ? */ static void ft_check_thread(FileTransferData *ftd,FileTransfer *ft){ // if(ft->server_queue_no==0 && ft->has_thread==FALSE){ if(ft->has_thread==FALSE){ pthread_mutex_lock(ftd->mutex); newThread(&ft->thread,ft->is_upload?ft_upload_thread:ft_download_thread, ft_thread_end,ft); ft->has_thread=TRUE; pthread_mutex_unlock(ftd->mutex); } } /* used to find a transfer by its xferid */ static int hfu_helper(FileTransfer *ft, int *xferid) {return ft->xferid - *xferid;} /* Called when a server updates its queue. Reads the results and applies the change to the corresponding FileTransfer */ void handle_ftq_update(Connection *c,Message *m,gpointer data){ FileTransferData *ftd=get_ftd(c); FileTransfer *ft=NULL; GSList *l; int xferid=0,queueno=0; HLObject *o; if((o=message_find_object(m,HLO_XFERID))!=NULL) xferid=o->data.number; if((o=message_find_object(m,HLO_QUEUED))!=NULL) queueno=o->data.number; pthread_mutex_lock(ftd->mutex); if((l = g_slist_find_custom(ftd->downloads, &xferid, (GCompareFunc) hfu_helper)) || (l = g_slist_find_custom(ftd->uploads, &xferid, (GCompareFunc) hfu_helper))) { ft = (FileTransfer *)l->data; ft->server_queue_no=queueno; ft_update_queue(ft); if(ft->server_queue_no==0 && o!=NULL && ft->has_thread==FALSE && ft->is_active==FALSE){ // ft_check_thread(ftd,ft); recheck_transfers(ftd); } } pthread_mutex_unlock(ftd->mutex); message_unref(m); } /* Functions used by the ft_handle_error dialog */ static gint ft_close_error_window(GtkWidget *widget,FileTransfer *ft){ if(ft->dialog!=NULL) gtk_widget_destroy(ft->dialog); return FALSE; } static gint ft_destroy_error_window(GtkWidget *widget,FileTransfer *ft){ if(ft->dialog!=NULL){ ft->dialog=NULL; ft_prot_close(ft); } return FALSE; } static gint ft_error_window_retry(GtkWidget *widget,gpointer p){ FileTransfer *ft=(FileTransfer *)p; if(ft->dialog!=NULL){ GtkWidget *d=ft->dialog; ft->dialog=NULL; gtk_widget_destroy(d); } ft->retry_no++; ft->delay_remaining=retry_delay; ft->is_active=FALSE; return FALSE; } /* Prints an error the server has sent */ static void ft_handle_error(Message *m,FileTransfer *ft){ HLObject *err=message_find_object(m,HLO_ERRORMSG); char *buf; if(ft->dialog!=NULL){ gtk_widget_destroy(ft->dialog); ft->dialog=NULL; } buf = g_strdup_printf(_("Could not transfer %s:\n%s"),ft->name,err->data.string); ft->dialog=gnome_dialog_new(_("File transfer error"),_("Keep Trying"), GNOME_STOCK_BUTTON_CLOSE,NULL); if(ft->c!=NULL && ft->c->gui!=NULL) gnome_dialog_set_parent(GNOME_DIALOG(ft->dialog), GTK_WINDOW(ft->c->gui->main_window)); gnome_dialog_button_connect(GNOME_DIALOG(ft->dialog),1, GTK_SIGNAL_FUNC(ft_close_error_window), (gpointer)ft); gnome_dialog_button_connect(GNOME_DIALOG(ft->dialog),0, GTK_SIGNAL_FUNC(ft_error_window_retry), (gpointer)ft); gtk_signal_connect(GTK_OBJECT(ft->dialog),"destroy", GTK_SIGNAL_FUNC(ft_destroy_error_window),(gpointer)ft); gtk_box_pack_start(GTK_BOX(GNOME_DIALOG(ft->dialog)->vbox), gtk_label_new(buf),TRUE,TRUE,0); free(buf); gtk_widget_show_all(ft->dialog); } /* Once the server has accepted the transfer, we can create a new thread to handle it (with ft_check_thread()) */ static void ft_transfer_reply(Transaction *t,Message *m,FileTransfer *ft){ FileTransferData *ftd=get_ftd(ft->c); HLObject *o; if(!ftd){ if(ft->is_active && !ft->has_thread) // should not happen, sanity check printf("Oops in ft_transfer_reply, please report\n"); free(ft); return; } if(message_is_error(m)){ ft->is_active=FALSE; if(ft->retry_no>0){ if(ft->retry_no>=retry_count && retry_count>0) ft->retry_no=0; else { ft->retry_no++; ft->delay_remaining=retry_delay; } } if(ft->retry_no==0) ft_handle_error(m,ft); return; } if((o=message_find_object(m,HLO_XFERID))!=NULL){ ft->xferid=o->data.number; } if((o=message_find_object(m,HLO_QUEUED))!=NULL){ ft->server_queue_no=o->data.number; // ft->is_active=FALSE; ft_update_queue(ft); /* if(ft->server_queue_no>0){ recheck_transfers(ftd); return; } */ } if(ft->is_upload && (o=message_find_object(m,HLO_RESUMEINFO))!=NULL){ resinfo_get_offsets(o,&ft->current_bytes,NULL); } ft_check_thread(ftd,ft); } /* Tries to initiate a transfer. ft_transfer_reply will get the answer and possibly create a thread to handle it*/ static void ft_start_upload(Connection *c,FileTransfer *ft){ Transaction *t=transaction_new(c,HLCT_UPLOAD); struct stat buf; message_add_object(t->request,create_string(HLO_FILENAME,ft->name)); if(ft->remote_path!=NULL) message_add_object(t->request,path_dup(ft->remote_path)); fstat(fileno(ft->localfile),&buf); message_add_object(t->request,create_number(HLO_XFERSIZE,146+strlen(ft->name)+buf.st_size)); /* + length of comment */ if(ft->current_bytes==-1){ message_add_object(t->request,create_number(HLO_RESUMEFLAG,1)); ft->current_bytes=0; } gtk_signal_connect(GTK_OBJECT(t),"response-received", GTK_SIGNAL_FUNC(ft_transfer_reply),ft); transaction_start(t); } /* Same as above for downloads. */ static void ft_start_download(Connection *c,FileTransfer *ft){ Transaction *t=transaction_new(c,HLCT_DOWNLOAD); struct stat buf; if(fstat(fileno(ft->localfile),&buf)==0) { ft->current_bytes=buf.st_size - RESUME_OVERLAP; if(ft->current_bytes < 0) ft->current_bytes = 0; fseek(ft->localfile, ft->current_bytes, SEEK_SET); } else ft->current_bytes=0; message_add_object(t->request,create_string(HLO_FILENAME,ft->name)); if(ft->remote_path!=NULL) message_add_object(t->request,path_dup(ft->remote_path)); if(ft->current_bytes>0) message_add_object(t->request,resinfo_new(ft->current_bytes,0)); gtk_signal_connect(GTK_OBJECT(t),"response-received", GTK_SIGNAL_FUNC(ft_transfer_reply),ft); transaction_start(t); } int ft_test_reply_close(gpointer func) { return (func == (gpointer)ft_start_upload || func == (gpointer)ft_start_download); } static void force_start_transfer(FileTransfer *ft){ if(ft->is_active) return; gtk_widget_destroy(ft->task_start_button); ft->task_start_button=NULL; ft->is_active=TRUE; if(ft->is_upload) ft_start_upload(ft->c,ft); else ft_start_download(ft->c,ft); } static void transfer_start_now(GtkWidget *w,gpointer ft){ force_start_transfer(ft); } static void recheck_transfers_helper(FileTransfer *ft, gboolean *started) { if(*started == TRUE) return; if(ft->is_active) { if(!ft->is_dequeued) *started=TRUE; } else if(!ft->delay_remaining) { if(!ft->is_dequeued) *started=TRUE; force_start_transfer(ft); } } /* Will check that there is at most one download and one upload for the FileTransferData. If there is less than that, the helper will try to start a transaction */ static void recheck_transfers(FileTransferData *ftd){ gboolean started=FALSE; pthread_mutex_lock(ftd->mutex); g_slist_foreach(ftd->downloads, (GFunc) recheck_transfers_helper, &started); started=FALSE; g_slist_foreach(ftd->uploads, (GFunc) recheck_transfers_helper, &started); pthread_mutex_unlock(ftd->mutex); } static void ftd_uds_helper(FileTransfer *ft, gboolean *recheck) { if(ft && ft->delay_remaining>0) { if(!--ft->delay_remaining) *recheck=TRUE; ft_update_delay(ft); } } /* Decrements the delay for waiting FileTransfers. If a delay reachs zero, then the ftd has to be checked */ static void ftd_update_delays_server(Connection *c,gpointer dummy){ FileTransferData *ftd=get_ftd(c); gboolean recheck=FALSE; pthread_mutex_lock(ftd->mutex); g_slist_foreach(ftd->downloads, (GFunc) ftd_uds_helper, &recheck); g_slist_foreach(ftd->uploads, (GFunc) ftd_uds_helper, &recheck); pthread_mutex_unlock(ftd->mutex); if(recheck) recheck_transfers(ftd); } /* Calls the above function for all servers */ static gboolean ftd_update_delays(gpointer dummy){ forall_connections(ftd_update_delays_server,NULL); return TRUE; } /* This block searchs all the servers transfers for a given file */ typedef struct { char *name; gboolean is_downloading; } Is_down; static int check_server_compare(FileTransfer *ft, char *name) {return strcmp(name, ft->local_path);} static void check_server(Connection *c,Is_down *id){ FileTransferData *ftd; if(id->is_downloading) return; ftd=get_ftd(c); pthread_mutex_lock(ftd->mutex); if(g_slist_find_custom(ftd->downloads, id->name, (GCompareFunc) check_server_compare)) id->is_downloading = TRUE; pthread_mutex_unlock(ftd->mutex); } static gboolean check_downloads(char *localname){ Is_down id; id.name=localname; id.is_downloading=FALSE; forall_connections((ConnectionFunc)check_server,&id); return id.is_downloading; } /* END OF BLOCK */ /* Called to queue a transfer. A FileTransfer structure is created, filed and added the appropriate FileTransferData sublist */ void filetransfer_start(FileData *fd,int is_upload, FileEntry *which_file){ FileTransfer *ft=NULL; FileTransferData *ftd=get_ftd(fd->c); FILE *localfile; char *path,*locfile_path,*name,*perms; static gboolean have_started=FALSE; if(have_started==FALSE){ have_started=TRUE; g_timeout_add(1000,ftd_update_delays,NULL); } if(is_upload==TRUE){ perms="rb"; name=strdup(which_file->name); } else { perms="ab+"; name=files_convert_name_to_local(which_file->name); } path=files_get_path(fd->local_files); locfile_path=g_strconcat(path,"/",name,NULL); free(name); free(path); if(is_upload==FALSE && check_downloads(locfile_path)==TRUE){ show_error(fd->c,NULL,_("Can't download twice file\n%s !"),locfile_path); free(locfile_path); return; } localfile=fopen(locfile_path,perms); if(localfile==NULL){ show_error_errno(fd->c,NULL,_("Could not open file\n%s:\n"),locfile_path); free(locfile_path); return; } ft=malloc(sizeof(FileTransfer)); ft->dialog=NULL; ft->is_upload=is_upload; ft->is_active=FALSE; ft->has_thread=FALSE; ft->is_text=FALSE; ft->server_queue_no=0; ft->is_dequeued=(is_upload==TRUE?upload_queue:download_queue)==FALSE?TRUE:FALSE; ft->current_bytes=0; ft->session_bytes=0; ft->total_bytes=0; ft->retry_no=0; ft->delay_remaining=0; ft->fd=NULL; ft->speed=0; ft->timer=NULL; ft->task_format=NULL; ft->name=strdup(which_file->name); if(fd->remote_files->path!=NULL && fd->remote_files->numpaths>0) ft->remote_path=path_new(fd->remote_files->path, fd->remote_files->numpaths); else ft->remote_path=NULL; ft->local_path=locfile_path; ft->localfile=localfile; ft->c=fd->c; if(ft->is_upload==TRUE){ int i; char *hpf=g_strconcat(which_file->name,".hpf",NULL); FileList *fl=fd->remote_files; for(i=0;inumfiles;i++) if(strcmp(fl->files[i]->name,which_file->name)==0 || strcmp(fl->files[i]->name,hpf)==0){ ft->current_bytes=-1; break; } g_free(hpf); } ft->task=task_new_for_connection(fd->c,ft->is_upload?HL_STOCK_PIXMAP_UPLOAD:HL_STOCK_PIXMAP_DOWNLOAD); task_persist_after_connection(ft->task); task_set_printf(ft->task,-1.0, ft->is_upload?_("Uploading %s..."):_("Downloading %s..."), ft->name); gtk_signal_connect(GTK_OBJECT(ft->task),"cancelled",GTK_SIGNAL_FUNC(ft_user_abort),ft); ft->task_start_button=task_add_button(ft->task,HL_STOCK_PIXMAP_FILE_TRANSFER_GREENLIGHT); gtk_signal_connect(GTK_OBJECT(ft->task_start_button),"clicked", GTK_SIGNAL_FUNC(transfer_start_now),ft); pthread_mutex_lock(ftd->mutex); if(ft->is_upload==TRUE){ ftd->uploads=g_slist_append(ftd->uploads,ft); } else { ftd->downloads=g_slist_append(ftd->downloads,ft); } pthread_mutex_unlock(ftd->mutex); recheck_transfers(ftd); }