/*****************************************************************************/
/*  file_transfer.c - contains the ui and network routines for file transfer */
/*  Copyright (C) 1998 Brian Masney <masneyb@newwave.net>                    */
/*                                                                           */
/*  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.                */
/*****************************************************************************/

#include "ftp.h"

extern struct ftp_transfer_data *file_transfers;
extern pthread_mutex_t transfer_mutex;
static GtkWidget *dialog;

static struct ftp_transfer_data *inittrans(struct ftp_window_data *fromwdw, struct ftp_window_data *towdw, int direction);
static void dotrans(struct ftp_transfer_data *tdata);
static void asktrans(struct ftp_transfer_data *tdata);
static void restCB(GtkWidget *widget, struct ftp_transfer_data *tdata);
static void ovrCB(GtkWidget *widget, struct ftp_transfer_data *tdata);
static void restallCB(GtkWidget *widget, struct ftp_transfer_data *tdata);
static void ovrallCB(GtkWidget *widget, struct ftp_transfer_data *tdata);
static void cancel(GtkWidget *widget, struct ftp_transfer_data *tdata);
static void *ftp_get_files(void *ptr);
static void *ftp_put_files(void *ptr);
 
void retrCB(GtkWidget *widget, struct ftp_both_windows *windows) {
   struct ftp_transfer_data *tdata;
   
   if(windows->window2->local == -1) {
      ftp_log(LOG_MISC, "Retrieve Files: Not connected to a remote site\n");
      return;
   }
   else if(windows->window2->numselected == 0) {
      ftp_log(LOG_MISC, "Retrieve Files: You must have at least one item selected\n");
      return;
   }
   tdata = inittrans(windows->window2, windows->window1, 1);
   dotrans(tdata);
}
/*****************************************************************************/
void putCB(GtkWidget *widget, struct ftp_both_windows *windows) {
   struct ftp_transfer_data *tdata;
   
   if(windows->window2->local == -1) {
      ftp_log(LOG_MISC, "Put Files: Not connected to a remote site\n");
      return;
   }
   else if(windows->window1->numselected == 0) {
      ftp_log(LOG_MISC, "Put Files: You must have at least one item selected\n");
      return;
   }
   tdata = inittrans(windows->window1, windows->window2, 0);
   dotrans(tdata);
}
/*****************************************************************************/
static struct ftp_transfer_data *inittrans(struct ftp_window_data *fromwdw, struct ftp_window_data *towdw, int direction) {
   struct ftp_file_data *newfle, *tempfle;
   struct ftp_transfer_data *tdata;
   int i;
   
   pthread_mutex_lock(&transfer_mutex);
   tdata = mymalloc(sizeof(struct ftp_transfer_data));
   tdata->hdata = mymalloc(sizeof(struct ftp_host_data));
   tdata->direction = direction;
   if(tdata->direction) {
      tdata->wdata = fromwdw;
      tdata->otherwdata = towdw;
      *tdata->hdata = *fromwdw->host;
      strncpy(tdata->dest, towdw->host->dir, sizeof(tdata->dest));
   }
   else {
      tdata->wdata = fromwdw;
      tdata->otherwdata = towdw;
      *tdata->hdata = *towdw->host;
      strncpy(tdata->dest, fromwdw->host->dir, sizeof(tdata->dest));
   }
   tdata->dest[sizeof(tdata->dest)-1] = '\0';
   tdata->hdata->totalfiles = tdata->wdata->numselected;
   tdata->hdata->last = NULL;
   tempfle = tdata->wdata->host->files;
   for(i=0; i<tdata->wdata->totalitems; i++) {
      if(!tdata->wdata->items[i]) {
         tempfle = tempfle->next;
         continue;
      }
      newfle = mymalloc(sizeof(struct ftp_file_data));
      *newfle = *tempfle;
      newfle->next = NULL;
      if(tdata->hdata->last == NULL) tdata->hdata->files = newfle;
      else tdata->hdata->last->next = newfle;
      tdata->hdata->last = newfle;
      tempfle = tempfle->next;
   }
   tdata->curfle = tdata->hdata->files;
   tdata->doall = 0;
   tdata->action = 1; /* Signal to make this listbox entry */
   tdata->created = 0; /* This isn't made yet */
   tdata->curtrans = 0;
   tdata->cursize = 0;
   tdata->cancel = 0;
   tdata->selected = 0;
   tdata->starttime = time(NULL);
   tdata->next = file_transfers;
   file_transfers = tdata;
   pthread_mutex_unlock(&transfer_mutex);
   return(tdata);
}
/*****************************************************************************/
static void dotrans(struct ftp_transfer_data *tdata) {
   struct ftp_file_data *tempfle;
   struct stat flestat;
   pthread_t tid;
   
   if(tdata->doall) {
      pthread_mutex_lock(&transfer_mutex);
      while(tdata->curfle != NULL) {
         if(stat(tdata->curfle->file, &flestat) != -1 && tdata->op) {
            tdata->curfle->restart = 1;
            tdata->curfle->startsize = flestat.st_size;
         }
         else tdata->curfle->restart = 0;
         tdata->curfle = tdata->curfle->next;
      }
      pthread_mutex_unlock(&transfer_mutex);
      if(tdata->direction) pthread_create(&tid, NULL, &ftp_get_files, tdata);
      else pthread_create(&tid, NULL, &ftp_put_files, tdata);
   }
   else {
      pthread_mutex_lock(&transfer_mutex);
      while(tdata->curfle != NULL) {
         if(tdata->direction && stat(tdata->curfle->file, &flestat) != -1) {
            tdata->curfle->restart = 1;
            tdata->curfle->startsize = flestat.st_size;
            break;
         }
         else if(!tdata->direction) {
            /* If we are uploading the file, see if it is in the other listbox */
            tempfle = tdata->otherwdata->host->files;
            while(tempfle != NULL) {
               if(strcmp(tempfle->file, tdata->curfle->file) == 0) break; 
               tempfle = tempfle->next;
            }
            if(tempfle != NULL) {
               tdata->curfle->restart = 1;
               break;
            }
            else {
               tdata->curfle->restart = 0;
               tdata->curfle = tdata->curfle->next;
            }
         }
         else {
            tdata->curfle->restart = 0;
            tdata->curfle = tdata->curfle->next;
         }
      }         
      pthread_mutex_unlock(&transfer_mutex);
      if(tdata->curfle == NULL) {
         if(tdata->direction) pthread_create(&tid, NULL, &ftp_get_files, tdata);
         else pthread_create(&tid, NULL, &ftp_put_files, tdata);
      }
      else asktrans(tdata);
   }
}
/*****************************************************************************/
static void asktrans(struct ftp_transfer_data *tdata) {
   char tempstr[MAXSTR];

   snprintf(tempstr, sizeof(tempstr), "File %s already exists on %s", tdata->curfle->file, tdata->direction ? "local computer" : "remote computer");
   MakeYesNoDialog(&dialog, "Transfer Files", tempstr, 5,
      ovrCB, "Overwrite", tdata,
      restCB, "Resume", tdata,
      ovrallCB, "Overwrite All", tdata,
      restallCB, "Resume All", tdata,
      cancel, "Cancel", tdata);
}
/*****************************************************************************/
static void restCB(GtkWidget *widget, struct ftp_transfer_data *tdata) {
   gtk_grab_remove(dialog);
   gtk_widget_destroy(dialog);
   pthread_mutex_lock(&transfer_mutex);
   tdata->curfle->restart = 1;
   tdata->curfle = tdata->curfle->next;
   pthread_mutex_unlock(&transfer_mutex);
   dotrans(tdata);
}
/*****************************************************************************/
static void ovrCB(GtkWidget *widget, struct ftp_transfer_data *tdata) {
   gtk_grab_remove(dialog);
   gtk_widget_destroy(dialog);
   pthread_mutex_lock(&transfer_mutex);
   tdata->curfle->restart = 0;
   tdata->curfle = tdata->curfle->next;
   pthread_mutex_unlock(&transfer_mutex);
   dotrans(tdata);
}
/*****************************************************************************/
static void restallCB(GtkWidget *widget, struct ftp_transfer_data *tdata) {
   gtk_grab_remove(dialog);
   gtk_widget_destroy(dialog);
   pthread_mutex_lock(&transfer_mutex);
   tdata->doall = 1;
   tdata->op = 1;
   pthread_mutex_unlock(&transfer_mutex);
   dotrans(tdata);
}
/*****************************************************************************/
static void ovrallCB(GtkWidget *widget, struct ftp_transfer_data *tdata) {
   gtk_grab_remove(dialog);
   gtk_widget_destroy(dialog);
   pthread_mutex_lock(&transfer_mutex);
   tdata->doall = 1;
   tdata->op = 0;
   pthread_mutex_unlock(&transfer_mutex);
   dotrans(tdata);
}
/*****************************************************************************/
static void cancel(GtkWidget *widget, struct ftp_transfer_data *tdata) {
   gtk_grab_remove(dialog);
   gtk_widget_destroy(dialog);
   pthread_mutex_lock(&transfer_mutex);
   tdata->action = -1;
   pthread_mutex_unlock(&transfer_mutex);
}
/*****************************************************************************/
static void *ftp_get_files(void *ptr) {
   char tempstr[MAXSTR], buf[8192], gotstr[MAXSTR], ofstr[MAXSTR], *pos;
   struct ftp_transfer_data *tdata;
   int datafd, i, j=0;
   FILE *writefd;
   float kbs;
   ssize_t n;

   tdata = (struct ftp_transfer_data *) ptr;
   pthread_detach(pthread_self());
   pthread_mutex_lock(&transfer_mutex);
   tdata->curfle = tdata->hdata->files;
   pthread_mutex_unlock(&transfer_mutex);
   if(ftp_connect(tdata->wdata, tdata->hdata, tdata)) {
      if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, NULL, 0, "TYPE %c\r\n", tdata->hdata->type == FTP_ASCII ? 'A' : 'I') != '2') {
         queue_log(LOG_MISC, "Error: Could not set %s mode\n", tdata->hdata->type == FTP_ASCII ? "ascii" : "binary");
      }
      else while(!tdata->cancel && tdata->curfle != NULL) {
         if((datafd = ftp_init_data_conn(tdata->wdata, tdata->hdata)) == 0) break;

         snprintf(tempstr, sizeof(tempstr), "%s/%s", tdata->dest, tdata->curfle->file);
         tempstr[sizeof(tempstr)-1] = '\0';
         if(!(writefd = fopen(tempstr, tdata->curfle->restart ? "a" : "w"))) {
            queue_log(LOG_MISC, "Cannot create %s: %s\n", tempstr, sys_errlist[errno]);
            break;
         }
         if(tdata->curfle->restart) {
            if(tdata->hdata->type == FTP_ASCII) {
               pthread_mutex_lock(&transfer_mutex);
               tdata->startsize += file_countlf(writefd, 0);
               pthread_mutex_unlock(&transfer_mutex);
            }
            snprintf(tempstr, sizeof(tempstr), "REST %ld\r\n", tdata->startsize);
            tempstr[sizeof(tempstr)-1] = '\0';
            queue_log(LOG_SEND, tempstr);
            if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, buf, sizeof(buf), tempstr) != '3') {
               queue_log(LOG_RECV, "%s\n", buf);
               queue_log(LOG_MISC, "Cannot start file transfer at %ld for file %s from %s\n", tdata->startsize, tdata->curfle->file, tdata->hdata->host);
               break;
            }
            queue_log(LOG_RECV, "%s\n", buf);
         }
         snprintf(tempstr, sizeof(tempstr), "RETR %s\r\n", tdata->curfle->file);
         tempstr[sizeof(tempstr)-1] = '\0';
         queue_log(LOG_SEND, tempstr);
         if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, buf, sizeof(buf), tempstr) != '1') {
            queue_log(LOG_RECV, "%s\n", buf);
            queue_log(LOG_MISC, "Cannot get file %s\n", tdata->curfle->file);
            break;
         }
         queue_log(LOG_RECV, "%s\n", buf);

         /* The size of the file should be sent in ( ) with the return line
             after the RETR command */
         pos = strchr(buf, '(');

         pthread_mutex_lock(&transfer_mutex);
         if(pos == NULL) tdata->cursize = 0;
         else tdata->cursize = strtol(pos+1, (char **) NULL, 10);
         tdata->starttime = time(NULL);
         tdata->curtrans = 0;
         pthread_mutex_unlock(&transfer_mutex);
         insert_commas(tdata->cursize, ofstr, sizeof(ofstr));
         
         while(!tdata->cancel && (n = read(datafd, &buf, sizeof(buf))) > 0) {
            pthread_mutex_lock(&transfer_mutex);
            tdata->curtrans += n;
            if(tdata->curfle->restart) insert_commas(tdata->curtrans+tdata->startsize,
               gotstr, sizeof(gotstr));
            else insert_commas(tdata->curtrans, gotstr, sizeof(gotstr));
            kbs = ((float) tdata->curtrans / 1024) / (float) (time(NULL)-tdata->starttime);
            snprintf(tdata->progressstr, sizeof(tdata->progressstr), "Received %s of %s at %.2fkbs",
               gotstr, ofstr, kbs);
            pthread_mutex_unlock(&transfer_mutex);
            
            if(tdata->hdata->type == FTP_ASCII) { 
               for(i=0, j=0; i<n; i++) if(buf[i] != '\r') buf[j++] = buf[i];
               buf[j] = '\0';
            }
            if(!fwrite(buf, 1, tdata->hdata->type == FTP_ASCII ? j : n, writefd)) break;
         }   
         fclose(writefd);
         close(datafd);
         if(ftp_read_response(0, tdata->wdata, tdata->hdata, NULL, 0) != '2') {
            queue_log(LOG_MISC, "Could not download %s\n", tdata->curfle->file);
         }
         else {
            queue_log(LOG_MISC, "Successfully downloaded %s\n", tdata->curfle->file);
         }
         pthread_mutex_lock(&transfer_mutex);
         tdata->curfle = tdata->curfle->next;
         pthread_mutex_unlock(&transfer_mutex);
      }
      ftp_disconnect(tdata->wdata, tdata->hdata, 0);
   }

   pthread_mutex_lock(&transfer_mutex);
   tdata->action = -1;
   pthread_mutex_unlock(&transfer_mutex);
   pthread_exit(NULL);
}
/*****************************************************************************/
static void *ftp_put_files(void *ptr) {
   char tempstr[MAXSTR], buf[8192], gotstr[MAXSTR], ofstr[MAXSTR], *pos, *newbuf;
   struct ftp_transfer_data *tdata;
   int datafd, i, j=0;
   FILE *readfd;
   float kbs;
   ssize_t n, newsize;

   tdata = (struct ftp_transfer_data *) ptr;
   pthread_detach(pthread_self());
   pthread_mutex_lock(&transfer_mutex);
   tdata->curfle = tdata->hdata->files;
   pthread_mutex_unlock(&transfer_mutex);
   if(ftp_connect(tdata->wdata, tdata->hdata, tdata)) {
      if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, NULL, 0, "TYPE %c\r\n", tdata->hdata->type == FTP_ASCII ? 'A' : 'I') != '2') {
         queue_log(LOG_MISC, "Error: Could not set %s mode\n", tdata->hdata->type == FTP_ASCII ? "ascii" : "binary");
      }
      else while(!tdata->cancel && tdata->curfle != NULL) {
         if((datafd = ftp_init_data_conn(tdata->wdata, tdata->hdata)) == 0) break;

         snprintf(tempstr, sizeof(tempstr), "%s/%s", tdata->dest, tdata->curfle->file);
         tempstr[sizeof(tempstr)-1] = '\0';
         if(!(readfd = fopen(tempstr, "r"))) {
            queue_log(LOG_MISC, "Error: Cannot open local file %s: %s\n", tempstr, sys_errlist[errno]);
            break;
         }
         if(tdata->curfle->restart) {
            if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, buf, sizeof(buf), "SIZE %s\r\n", tdata->curfle->file) != '2') {
               queue_log(LOG_MISC, "Cannot get size of %s on %s\n", tdata->curfle->file, tdata->hdata->host);
               break;
            }

            pos = buf;
            pos += 4;
            pthread_mutex_lock(&transfer_mutex);
            tdata->startsize = strtol(pos, (char **) NULL, 10);
            if(tdata->hdata->type == FTP_ASCII) {
               tdata->startsize += file_countlf(readfd, tdata->startsize);
            }
            pthread_mutex_unlock(&transfer_mutex);
            snprintf(tempstr, sizeof(tempstr), "REST %ld\r\n", tdata->startsize);
            tempstr[sizeof(tempstr)-1] = '\0';
            queue_log(LOG_SEND, tempstr);
            if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, buf, sizeof(buf), tempstr) != '3') {
               queue_log(LOG_RECV, "%s\n", buf);
               queue_log(LOG_MISC, "Cannot start file transfer at %ld for file %s from %s\n", tdata->startsize, tdata->curfle->file, tdata->hdata->host);
               break;
            }
            queue_log(LOG_RECV, "%s\n", buf);
         }
         snprintf(tempstr, sizeof(tempstr), "STOR %s\r\n", tdata->curfle->file);
         tempstr[sizeof(tempstr)-1] = '\0';
         queue_log(LOG_SEND, tempstr);
         if(ftp_sendcommand(0, tdata->wdata, tdata->hdata, buf, sizeof(buf), tempstr) != '1') {
            queue_log(LOG_RECV, "%s\n", buf);
            queue_log(LOG_MISC, "Cannot put file %s to %s\n", tdata->curfle->file, tdata->hdata->host);
            break;
         }
         queue_log(LOG_RECV, "%s\n", buf);

         pthread_mutex_lock(&transfer_mutex);
         tdata->cursize = tdata->curfle->size;
         tdata->starttime = time(NULL);
         tdata->curtrans = 0;
         pthread_mutex_unlock(&transfer_mutex);
         insert_commas(tdata->cursize, ofstr, sizeof(ofstr));

         while(!tdata->cancel && (n = fread(&buf, 1, sizeof(buf), readfd)) > 0) {
            pthread_mutex_lock(&transfer_mutex);
            tdata->curtrans += n;
            if(tdata->curfle->restart) insert_commas(tdata->curtrans+tdata->startsize,
               gotstr, sizeof(gotstr));
            else insert_commas(tdata->curtrans, gotstr, sizeof(gotstr));
            kbs = ((float) tdata->curtrans / 1024) / (float) (time(NULL)-tdata->starttime);
            snprintf(tdata->progressstr, sizeof(tdata->progressstr), "Sent %s of %s at %.2fkbs",
               gotstr, ofstr, kbs);
            pthread_mutex_unlock(&transfer_mutex);

            if(tdata->hdata->type == FTP_ASCII) { 
               newsize = 1;
               for(i=0; i<n; i++) {
                  newsize++;
                  if(i > 0 && buf[i] == '\n' && buf[i-1] != '\r') newsize++;
               }
               newbuf = mymalloc(newsize);
               for(i=0,j=0; i<n; i++) {
                  if(i > 0 && buf[i] == '\n' && buf[i-1] != '\r') newbuf[j++] = '\r';
                  newbuf[j++] = buf[i];
               }
               newbuf[newsize-1] = '\0';
            }
            else {
               newbuf = buf;
               newsize = n;
            }

            if(!write(datafd, newbuf, newsize)) {
               if(tdata->hdata->type == FTP_ASCII) free(newbuf);
               queue_log(LOG_MISC, "Cannot write %s to %s\n", tdata->curfle->file, tdata->hdata->host);
               break;
            }
            if(tdata->hdata->type == FTP_ASCII) free(newbuf);
         }   
         fclose(readfd);
         close(datafd);
         if(ftp_read_response(0, tdata->wdata, tdata->hdata, NULL, 0) != '2') {
            queue_log(LOG_MISC, "Could not upload %s to %s\n", tdata->curfle->file, tdata->hdata->host);
         }
         else {
            queue_log(LOG_MISC, "Successfully uploaded %s to %s\n", tdata->curfle->file, tdata->hdata->host);
         }
         pthread_mutex_lock(&transfer_mutex);
         tdata->curfle = tdata->curfle->next;
         pthread_mutex_unlock(&transfer_mutex);
      }
      ftp_disconnect(tdata->wdata, tdata->hdata, 0);
   }

   pthread_mutex_lock(&transfer_mutex);
   tdata->action = -1;
   pthread_mutex_unlock(&transfer_mutex);
   pthread_exit(NULL);
}
/*****************************************************************************/
