/*
 *  FILECOPY.C
 *
 *  (c)Copyright 1990,93 by Tobias Ferber,  All Rights Reserved.
 */

#include "filecopy.h"
#include <stdio.h>

#ifdef AMIGA
#include <exec/types.h>
#include <exec/memory.h>
#endif /* AMIGA */

/* private statics */
static uchar *copybuffer= (uchar *)0L;
static ulong buffersize= 0L;

static char rcs_id[]= "$Id: filecopy.c 1.2 93/09/19 21:33:11 tf Exp $";

/*
 *
 * FUNCTION
 *
 *   fc_setbuf -- allocate or free a copy buffer for filecopy()
 *
 * SYNOPSIS
 *   #include "filecopy.h"
 *
 *   result= fc_setbuf(numbytes)
 *   ulong result;
 *   ulong numbytes;
 *
 * DESCRIPTION
 *
 *   This function tries to allocate 'numbytes' bytes for the local
 *   copy buffer.  If you pass 0L with the 'numbytes' parameter the
 *   actual copy buffer will be free()d.
 *   The current size of the copy buffer will be returned.
 *
 * NOTE
 *
 *   It is absolutely legal to re-size your copy buffer by calling this
 *   function only once (with your new buffer size) instead of calling
 *   it twice (with numbytes==0L first)
 *   Note also that this function does *NOT* check whether 'numbytes'
 *   is larger than the local limit. (see "filecopy.h")
 *
 * SEE ALSO
 *
 *   filecopy(), "filecopy.h"
 *
 */

ulong fc_setbuf(numbytes)
ulong numbytes;
{
#ifdef AMIGA
  if(copybuffer && buffersize)
    FreeMem(copybuffer, buffersize);

#else /* !AMIGA */
  if(copybuffer)
    free(copybuffer);

#endif /* AMIGA */

  copybuffer= (uchar *)0L;
  buffersize= 0L;


  if(numbytes)
  {

#ifdef AMIGA
    copybuffer= (uchar *)AllocMem(numbytes*sizeof(uchar), MEMF_PUBLIC);

#else /* !AMIGA */
    copybuffer= (uchar *)malloc(numbytes*sizeof(uchar));

#endif /* AMIGA */

    if(copybuffer)
      buffersize= numbytes*sizeof(uchar);
  }
  return buffersize;
}

/*
*/

#ifdef DEBUG
/* My favorite way of reporting that we're alive: */
static void turn(on)
int on;
{
  static char bars[]= "|/-\\|/-\\";
  static int b= 0;

  if(on)
  {
    fprintf(stderr,"%c\b",bars[b++]);

    if(b >= sizeof(bars)-1)
      b= 0;
  }
  else fprintf(stderr," \b");

  fflush(stderr);
}

#endif /* DEBUG */


/*
 *
 * FUNCTION
 *
 *   filecopy -- copy n bytes of data from one file pointer to another
 *
 * SYNOPSIS
 *   #include "filecopy.h"
 *
 *   result= filecopy(src, dst, n)
 *   long result;
 *   long n;
 *   FILE *src, *dst;
 *
 * DESCRIPTION
 *
 *   This function tries to copy 'n' bytes from given source stream 'src'
 *   to the destination 'dst'.
 *   If 'n' is == 0 then filecopy() will compute this value assuming that
 *   all data following the current position of 'src' is ment to be copied.
 *   If a copy buffer has been allocated successfully via fc_setbuf() then
 *   filecopy() will take advantage of of it.  On the other hand, if no
 *   buffer was allocated it will do so on it's own and tries to get either
 *   'n' or (if 'n' is too large) MAXIMUM_BUFFERSIZE bytes.  (See the notes
 *   about this value in "filecopy.h".)
 *   If the allocation fails then filecopy() will copy your data byte for
 *   byte.  Normally the number of written bytes will be returned but
 *
 * NOTE
 *
 *   This function tries to be very careful concerning errors.  If an error
 *   occurs it will exit immediately returning either -1L in case of a read
 *   error in the 'src' file or -2L in case of a write error with the 'dst'
 *   file pointer.
 *   Also filecopy() will *NOT* print out anything, even not if there was
 *   an error.  (There may of course be some output if your OS feels a need
 *   to do so.) It's up to the caller to use perror() or sth. similar to
 *   inform the user about what went wrong.
 *
 * NOTE ALSO
 *
 *   This function assumes 'src' not to be a tty; especially not if you pass
 *   n=0 with the arguments.  In other words, filecopy() will fail if seeking
 *   with 'src' is impossible.
 *   You can however pass a value n<0 in order to force a byte for byte
 *   transfer without seeking.
 *
 */

long filecopy(src, dst, n)
long n;
FILE *src, *dst;
{
  long result= 0L;     /* The value returned by this function:
                        * if non-negative the #of bytes written,
                        * -1 = read error,  -2 = write error */

  ulong remember= 0L;  /* #of bytes allocated by *this* function */
  long bytesleft= 0L;  /* #of bytes left for copying */

  if(n==0)
  {
    /* Compute the #of bytes left for copying */

#ifdef BUGGY_FTELL
    do {
      (void)fgetc(src);
      if(!feof(src))
        ++bytesleft;
    } while(!feof(src) || ferror(src))

#else /* ftell() works fine */
    if( fseek(src,0L,2L) >= 0) /* 2 == OFFSET_END */
      bytesleft= ftell(src);
    else
      result= -1L;

#endif /* BUGGY_FTELL */

    if(!ferror(src) && bytesleft>0)
    { if(fseek(src,-bytesleft,1L) < 0) /* 1 == OFFSET_CURRENT */
        result= -1L;
    }
    else result= -1L;
  }
  else /* n!=0 */
    bytesleft= (n > 0) ? n : 0L;


  if(!result)
  {
    long nbytes= 0L;

    if(!copybuffer && bytesleft>0)
    {
      remember= fc_setbuf( (bytesleft > MAXIMUM_BUFFERSIZE) ?
        MAXIMUM_BUFFERSIZE : bytesleft );
    }

    /* If the allocation failed (or if n was negative) then we'll copy
     * the data byte for byte which of course is hyper slow ;)
     */

    if(!copybuffer || buffersize < 2L)
    {
      uchar c;
      int done= 0;

      do {
        c= fgetc(src);
        if(!feof(src))
        { fputc(c,dst);
          nbytes++;
          if(n>0 && nbytes>=n)
            done= 1;
#ifdef DEBUG
          turn(1);
#endif /* DEBUG */
        }
      } while(!done && !feof(src) && !ferror(src) && !ferror(dst));

      /* This might change later when checking ferror(): */
      result= nbytes;
    }

    else /* use the buffer */
    {
      long psize;  /* packet size */

      do {
        psize= (bytesleft > buffersize) ? buffersize : bytesleft;
        bytesleft-= psize;

        /* I think it's a good idea to use fread() and fwrite() with
         * psize packets of size 1 here instead of 1 packet of size psize
         * because these functions return the number of *complete* packets
         * that were read (or written).  This way we can also handle
         * partial packets if needed. */

        nbytes= fread(copybuffer, 1L, psize, src);

        if(!ferror(src))
        {
          /* write exactly as many bytes as we read before */

          nbytes= fwrite(copybuffer, 1L, nbytes, dst);
          result += nbytes;
        }

        /* A partial read or written packet indicates that we're done,
         * it does however not neccessarily mean that there was an error.
         * Changing the result to a negative value can be done later
         * by testing ferror(). */

        if(nbytes!=psize)
          bytesleft= 0;

#ifdef DEBUG
        turn(1);
#endif

      } while(bytesleft>0 && !ferror(src) && !ferror(dst));
    }

    if(ferror(src))
      result= -1L;
    else if(ferror(dst))
      result= -2L;
  }

  /* free the copy buffer if it was allocated by us */
  if(remember)
    fc_setbuf(0L);

#ifdef DEBUG
  turn(0);
#endif

  return result;
}


