/* Josh Pieper, (c) 2000 */

/* This file is distributed under the GPL, see file COPYING for details */

#include <stdio.h>
#include <stdlib.h>

#ifndef WIN32
#  include <unistd.h>
#  include <sys/file.h>
#else
#  include <io.h>
#endif

#if defined(HAVE_FCNTL)
#  include <fcntl.h>
#endif
#include <errno.h>
#include <pthread.h>

#include "gnut_lib.h"
#include "gnut_net.h"
#include "gnut_threads.h"
#include "gnut_transfer.h"
#include "Gnut_List.h"
#include "gnut_connection.h"
#include "conf.h"
#include "gnut.h"
#include "gnut_ui.h"

#ifndef PTHREADS_DRAFT4
pthread_mutex_t transfer_mutex=PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t transfer_mutex;
#endif

Gnut_List *transfer_list=NULL;

void free_gt(gnut_transfer **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

int gnut_transfer_num()
{
  return gnut_list_size(transfer_list);
}

/* Decay the rate if it's time to do so */
void decay_rate(gnut_transfer *gt)
{
  uint64 temp;
  
  while (gt->rate_incr_time < time(0)) {
    temp = gt->rate_bytes;
    temp *= 15LL;
    temp >>= 4LL; /* divide by 16 */
    gt->rate_bytes = temp;
    (gt->rate_incr_time)++;
  }
}

/* this is called after the file has been opened and the socket
 * set up, it either sends or receives the entire content of the
 * file, updating the gnut_transfer struc along the way...
 * It leaves the structure afterwards though, so that the user
 * can see downloads that occurred while he/she was away. */
int gnut_transfer_loop(gnut_transfer *gt, int overlap, char *overlap_buf)
{
  char *netbuf;
  int readfd,writefd;
  int ret=0;
  int ret2, left, sent;
  uint32 rate=0;
  int download;
#if !defined(HAVE_FLOCK) && defined(HAVE_FCNTL)
  struct flock lock;
#endif
  int msg_flag;
  
  gd_s(3, "gnut_xfer_loop entering gt=");
  gd_p(3, gt);
  gd_s(3, "\n");
  
  msg_flag = 0;

  if ( (gt->type & 1) == 0 ) {
    /* this is a download */
    gd_s(3,"download!\n");
    readfd=gt->sock;
    writefd=gt->fsock;
    download=1;
  } else {
    /* this is an upload */
    gd_s(3,"upload!\n");
    readfd=gt->fsock;
    writefd=gt->sock;
    download=0;
    overlap = 0;
  }

  if (overlap) {
    /* Move write file pointer back, and read in the overlap buffer */
  }
  
  netbuf=(char *)xmalloc(4096);
  left=gt->total - gt->bytes;
  gd_s(3, "gnut_xfer_loop entering loop\n");
#ifdef WIN32
#undef close
#undef read
#undef write
#endif
  while (gt->state==STATE_CONNECTED && left>0) {
    do {
      /* this recv crap is here because win32 doesn't treat
       * socks and fd's the same.... :( */
      if (download) {
	/* Read from network connection */
	ret=timed_recv(readfd, netbuf, MIN(left,4096), 0, 300);
      } else {
	/* Read from file */
	ret=read(readfd, netbuf, MIN(left,4096));
      }
    } while (errno==EINTR && ret<0);

    if (ret<0) {
      if (gnut_lib_debug>2) {
	perror("gnut_xfer_loop, read");
      }
    }

    if (ret==0) {
      break;
    }

    /* Decay the rate before adding credit for bytes just completed */
    decay_rate(gt);

    if (ret>0) {
      char *nb2;
      int going;

      sent = 0;
      nb2 = netbuf;
      going = 1;
      do {
        if (download) {
	  /* Write to file */
	  ret2=write(writefd, nb2, ret);
	} else {
	  /* Write to network connection */
	  ret2=send(writefd, nb2, ret, 0);
	}

	if (errno==EINTR && ret2<0) {
	  /* keep trying... */
	} else if ((ret2 > 0) && (ret2 < ret)) {
	  /* It didn't send all of it. */
	  nb2 += ret2; /* skip the part we've sent */
	  sent += ret2;
	  ret -= ret2; /* that many fewer bytes to send */ 
        } else {
	  /* we be done! */
	  going = 0;
	}
      } while (going);

      /* If there was an error... */
      if (ret2<=0) {
	/* ... print any appropriate error messages... */
	if (errno == EINTR) {
	  /* already handled */
	} else if (errno == EPIPE) {
	  if (conf_get_int("verbose") & 2) {
	    fprintf(stdout, UI_TR_UPLOAD_ABORT, gt->fname, gt->bytes + ret);
	  }
	} else {
	  perror("gnut_xfer_loop, send");
	}

	/* ... and exit the transfer */
	break;
      }
      sent += ret2;
      gd_s(5, "copied ");
      gd_i(5, sent);
      gd_s(5, " bytes from sock to sock\n");
      gt->bytes += sent;
      gt->rate_bytes += sent;
      left -= sent;

      if (sent > 512) {
	if (msg_flag == 0) {
	  if (gt->type==TYPE_RECV_PUSH) {
	    if (conf_get_int("verbose") & 1) {
	      printf(UI_TH_PDL_STARTED, gt->fname);
	    }
	  } else {
	    if (conf_get_int("verbose") & 2) {
	      printf(UI_HT_UPLOAD_STARTED, gt->fname);
	    }
	  }
	  msg_flag = 1;
	}
      }
    } else {
      break;
    }

    if ((gt->rate_limit)
	|| get_rate_limit(download)
	|| conf_get_int("global_bandwidth_cap")) {
      int wait_done;
	  
      wait_done = 0;
      /* we have a rate limit, and we need to see if we're
       * exceeding it */
      gd_s(3, "gnut_xfer_loop entering rate limiting code\n");
      do {
	gd_s(3, "rate limiting loop.. rate=");
	gd_i(3, rate);
	gd_s(3, "\n");

	gd_s(3, "gnut_global_rate: ");
	gd_i(3, gnut_global_rate());
	gd_s(3, "\n");

	decay_rate(gt);

	rate=gt->rate_bytes >> 4L;
		
	/* printf("transferred %i, decayed rate %i\n", gt->bytes, rate); */
		
	if ((gt->rate_limit) && (rate > gt->rate_limit)) {
	  sleep(1);
	} else if (get_rate_limit(download)
		   && (rate > get_rate_limit(download))) {
	  sleep(1);
	} else if (conf_get_int("global_bandwidth_cap") &&
		   (gnut_global_rate() > conf_get_int("global_bandwidth_cap"))) {
	  sleep(1);
	} else {
	  wait_done = 1;
	}
      } while (wait_done == 0);
      gd_s(3, "my rate is: ");
      gd_li(3, rate);
      gd_s(3, " and I want to be: ");
      gd_li(3, gt->rate_limit);
      gd_s(3, "\n");
    }
  }
  
  gd_s(3, "closing connection on ret: ");
  gd_i(3, ret);
  gd_s(3, "\n");
  free_str(&netbuf, 264);
  
  if (download) {
    shutdown(readfd, 2);
#ifdef HAVE_FLOCK
    flock(writefd, LOCK_UN);
#elif defined(HAVE_FCNTL)
    lock.l_type=F_UNLCK;
    lock.l_whence=SEEK_SET;
    lock.l_start=0;
    lock.l_len=0;
    fcntl(writefd, F_SETLK, &lock );
#endif
	
#ifdef WIN32
    closesocket(readfd);
#else
    close(readfd);
#endif

    close(writefd);
  } else {
    shutdown(writefd,2);

    /* Don't have to unlock read file because it was never locked */

#ifdef WIN32
    closesocket(writefd);
#else
    close(writefd);
#endif

    close(readfd);
  }

  gt->sock=-1;
  gt->fsock=-1;
  
  if (left==0) {
    gd_s(3,"gnut_xfer_loop returning success\n");
    return 0;
  } else {
    gd_s(3,"gnut_xfer_loop returning partial\n");
    return -1;
  }
}

gnut_transfer * gnut_transfer_new()
{
  gnut_transfer *gt;
  
  gd_s(3, "gnut_transfer_new entering\n");
  
  gt=(gnut_transfer *) xmalloc(sizeof(gnut_transfer));
  
  gt->sock=-1;
  gt->fsock=-1;
  gt->fname=NULL;
  
  gt->type=TYPE_UNSPEC;
  gt->bytes=0;
  gt->total=0;
  
  gt->rate_bytes=0;
  gt->rate_incr_time=time(0);
  gt->rate_limit=0;
  
  gt->state=STATE_UNCON;
  
  gt->tid=pthread_self();
  
  pthread_mutex_lock(&transfer_mutex);
  
  transfer_list=gnut_list_prepend(transfer_list,gt);
  
  pthread_mutex_unlock(&transfer_mutex);
  
  gd_s(3,"gnut_transfer_new returning success\n");
  return gt;
}
  
int gnut_transfer_delete(gnut_transfer *gt)
{
  gd_s(3, "gnut_transfer_delete entering\n");

  pthread_mutex_lock(&transfer_mutex);

  transfer_list=gnut_list_remove(transfer_list,gt);

  pthread_mutex_unlock(&transfer_mutex);

  if (gt->fname) {
    free_str(&(gt->fname), 265);
  }

  free_gt(&gt, 266);

  gd_s(3, "gnut_transfer_delete returning success\n");

  return 0;
}

int gnut_transfer_enumerate(int(*a)(gnut_transfer *))
{
  Gnut_List *tmp;

  pthread_mutex_lock(&transfer_mutex);
  
  for (tmp=transfer_list; tmp; tmp=tmp->next) {
    if ((*a)(tmp->data)==-1) break;
  }
  
  pthread_mutex_unlock(&transfer_mutex);
  return 0;
}
