/*
 *  CPDIST.C
 */

/*
 * (c)Copyright 1992-93 by Tobias Ferber.
 *
 * This file is part of CPDIST.
 *
 * CPDIST 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 1 of the License,
 * or (at your option) any later version.
 *
 * CPDIST 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 CPDIST; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "cpdist.h"
#include "filecopy.h"

/* If you don't want CPDIST to interact with the user's keyboard via
 * getkey() then you should compile this file with the -DNO_GETKEY option.
 * copy_files() (the only function that uses getkey() will then prompt
 * waiting for a string instead of a character
 */

#ifndef NO_GETKEY
#include "getkey.h"
#endif

static char rcs_id[]= "$VER: $Id$";

/* scanner modes */

typedef enum { outer_mode,    /* reading word seperating chars */
               word_mode,     /* collecting chars for a word */
               string_mode,   /* collecting chars for a string */
               remark_mode,   /* waiting for the end of line */
               comment_mode,  /* waiting for the matching '*' and '/' */
               return_mode,   /* word or string complete */
               error_mode     /* an error occured! return NULL */
             } smode_t;


char *getword(FILE *fp)
/* This function will skip everything up to the beginning of the next word
 * that is not inside of a comment and then copy the following word into a
 * static buffer and terminate it with a '\0' character.  A pointer to this
 * buffer will be returned.
 * A comment is (as in C) everything that stands between a '/' + '*' token
 * and the matching '*' + '/'.  C++ like comments (named remarks here) are
 * also suported.  (They begin with two slashes '/' + '/' and end with the
 * end of the line they appear in.)
 * Note:  This file will use lerror() to report error messages to the user.
 * If anyone else but this function will read from the given file 'fp' then
 * the line numbers reported by lerror() may become wrong! */
{
  static char buf[128];      /* should be enough for _one_ word */
  static int line= 1;        /* line number */
  int n=0;                   /* #of chars in buf[] */
  unsigned char c;           /* currently read character */

  smode_t smode= outer_mode; /* scanner mode */

  while(smode!=return_mode && smode!=error_mode && !feof(fp))
  {
    c= fgetc(fp);

    if(feof(fp) && c!=(unsigned char)EOF)
    { echo("In module " __FILE__ ": feof()==TRUE but fgetc()==0x%02x",
        (int)(c&0xFF));
      c= (unsigned char)EOF;
    }

    switch(c)
    {
      case ' ': case '\t': case ':':
        switch(smode)
        {
          case word_mode:
            if(c==':') buf[n++]=':';
            buf[n++]= '\0';
            smode= return_mode;
            break;
          case string_mode:
            buf[n++]= c;
            break;
        }
        break;

      case '\n': case '\r':
        switch(smode)
        {
          case word_mode:
            buf[n++]= '\0';
            smode= return_mode;
            break;
          case string_mode:
            lerror(line,"unterminated string; missing quotes `\"'");
            smode= error_mode;
            break;
          case remark_mode:
            smode= outer_mode;
            break;
        }
        line++;
        break;

      case '\"':
        switch(smode)
        {
          case outer_mode:
            smode= string_mode;
            break;
          case string_mode:
            buf[n++]= '\0';
            smode= return_mode;
            break;
          case word_mode:
            buf[n++]= '\0';
            ungetc(c,fp);
            smode= return_mode;
            break;
        }
        break;

      case '/':
        switch(smode)
        {
          case outer_mode:
            switch(c= fgetc(fp))
            {
              case '*':
                smode= comment_mode;
                break;
              case '/':
                smode= remark_mode;
                break;
              default:
                ungetc(c,fp);
                break;
            }
            break;
          case word_mode:
          case string_mode:
            buf[n++]= c;
            break;
        }
        break;

      case '*':
        switch(smode)
        {
          case comment_mode:
            switch(c= fgetc(fp))
            {
              case '/':
                smode= outer_mode;
                break;
              default:
                ungetc(c,fp);
                break;
            }
            break;
          case word_mode:
          case string_mode:
            buf[n++]= c;
            break;
        }
        break;

      case (unsigned char)EOF:
        switch(smode)
        {
          case word_mode:
            buf[n++]= '\0';
            smode= return_mode;
            break;
          case string_mode:
            lerror(line,"unterminated string at EOF; closing quotes `\"' missing");
            smode= error_mode;
            break;
          case comment_mode:
            lerror(line,"unterminated comment at EOF; closing `*/' missing");
            smode= error_mode;
            break;
        }
        break;

      default:
        switch(smode)
        {
          case outer_mode:
            smode= word_mode;
            /* fall through */
          case word_mode:
          case string_mode:
            buf[n++]= c;
            break;
        }
        break;
    }
  }

  buf[n]= '\0'; /* better add one more... */

  return (smode==error_mode) ? (char *)0L : buf;
}

typedef struct fnode {
  struct fnode *next;
  char *fname;
} fnode;


/* collect states */

typedef enum { okay,
               no_such_file,
               out_of_memory,
               syntax_error,
             } state_t;

fnode *collect_files(FILE *fp)
/* Here we collect the filenames in the given distfile pointer 'fp' and
 * add them to a list of fnode structures.  This function reports errors
 * (if there are any) via the echo() function (see main.c or so) */
{
  char *fname;

  fnode *flist = (fnode *)0L,
        *last  = (fnode *)0L;

  int do_collect= 1;    /* collect files (1) or skip until next label (0) */
  int files= 0;         /* #of collected files */
  int errors= 0;        /* #of syntax errors in distfile */
  int black_sheeps= 0;  /* #of inaccessable items (e.g. dirs) */

  state_t state= okay;

  do
  { fname= getword(fp);

    if(fname && *fname)
    {
      if(fname[strlen(fname)-1]==':')
        do_collect= ln_member(fname);

      else if(do_collect)
      {
        if(CP_CHECKEXISTS)
        {
          FILE *tf;
          if( tf= fopen(fname,"r") )
            fclose(tf);
          else
          { perror(fname);
            black_sheeps++;
            state= no_such_file;
          }
        }

        if(state != no_such_file)
        {
          fnode *this;

          if( this= (fnode *)malloc(sizeof(fnode)) )
          {
            if( this->fname= (char *)malloc((strlen(fname)+1)*sizeof(char)) )
            {
              strcpy(this->fname, fname);
              this->next= (fnode *)0L;

              if(last) last->next= this;
              else flist= this;
              last= this;
              files++;
            }
            else
            { free(this);
              /* this= (fnode *)0L; */
              state= out_of_memory;
            }
          }
          else state= out_of_memory;
        }
      }
    }
    else if(!feof(fp))
    { errors++;
      if(!CP_IGNOREERRORS)
        state= syntax_error;
    }

    if((state==no_such_file) && CP_KEEPGOING)
      state= okay;

  } while((state==okay) && !feof(fp));

  if(state!=okay)
  {
    /* purge the file list */
    while(flist)
    { last= flist;
      flist= flist->next;
      if(last->fname)
        free(last->fname);
      free(last);
    }

    if(state==out_of_memory)
      echo("Ran out of memory after having collected %d %s",
        files, (files==1) ? "filename" : "filenames");
    echo("going down.");
  }

  if(errors && CP_IGNOREERRORS)
    echo("%d %s -- You should revise your Distfile...",
      errors, (errors==1) ? "Error" : "Errors");

  if(black_sheeps && CP_KEEPGOING)
    echo("Couldn't access %d %s",
      black_sheeps, (black_sheeps==1) ? "item" : "items");

  return flist;
}

#ifdef STRIP_PATHNAMES
char *strfn(char *s)
/* returns the filename w/o leading pathname */
{ char *t;
  for(t=s; *s!='\0'; s++)
    if(*s==':' || *s=='/' || *s=='\\')
      t= &s[1];
  return t;
}
#else
#define strfn(s) (s)
#endif /* STRIP_PATHNAMES */


int copy_files(fnode *flist, char *pname)
/* Here we copy all files in 'flist' to the given path 'pname'.
 * The destination filename will be generated by concatenation of
 * 'pname' and the actual filename in a node of 'flist'.  So 'pname'
 * must have a trailing slash '/', colon ':' or backslash '\' or
 * whatever.
 * While copying the files, all nodes in flist will be free()d. */
{
  char outname[MAXIMUM_PATHNAME_LENGTH];
  char *catpoint;
  int numfiles= 0;
  long numbytes= 0;
  int err= 0;

  strcpy(outname, pname);
  catpoint= &outname[strlen(outname)];

  if(!CP_DRYRUN)
    (void)fc_setbuf(global_buffersize);

  while(flist && !err)
  {
    int dothis= 1;       /* 1=copy/replace, 0=skip, -1=??? */

    fnode *this= flist;
    flist= flist->next;

    if(this->fname)
    {
      strcpy(catpoint, strfn(this->fname));

      if(!CP_REPLACEALL && !CP_DRYRUN)
      {
        FILE *fp;
        if(fp= fopen(outname,"r"))
        {
          int key;
          fclose(fp);

          fprintf(stderr,"%s already exists.  replace it? (y/n/a/q) ",outname);
          fflush(stderr);

          do {

#ifdef NO_GETKEY
            char answerbuf[10];
            fgets(answerbuf, sizeof(answerbuf), stdin);
            key= (int)answerbuf[0];

#else /* !NO_GETKEY */
            key= (int)getkey();

#endif /* NO_GETKEY */

            switch( key )
            {
              case 'y': case 'Y':
#ifndef NO_GETKEY
                puts("yes");
#endif
                dothis= 1;
                break;
              case 'n': case 'N':
#ifndef NO_GETKEY
                puts("no");
#endif
                dothis= 0;
                break;
              case 'a': case 'A':
#ifndef NO_GETKEY
                puts("all");
#endif
                global_opts |= OPT_REPLACEALL;
                dothis= 1;
                break;
              case 'q': case 'Q':
#ifndef NO_GETKEY
                puts("quit");
#endif
                dothis= 0;
                err= 1;     /* positive error codes are not ignored */
                break;
              default:
                dothis= -1; /* hack: means 'invalid choice' */
                break;
            }

          } while( dothis < 0 );
        }
      }

      if(dothis)
      {
        if(!CP_SILENT)
        {
          printf( (CP_DRYRUN ? (strchr(outname,' ') ? "\"%s\" " : "%s ")
                             : "   %s"), outname );
          fflush(stdout);
        }

        if(!CP_DRYRUN)
        {
          long n;
          FILE *src, *dst;

          if(src= fopen(this->fname,"rb"))
          {
            if(dst= fopen(outname,"wb"))
            {
              if( (n= filecopy(src,dst,0L)) < 0L )
              {
                if(!CP_SILENT)
                  putchar('\n');
                perror( (n==(-1L)) ? this->fname : outname );
                echo("Error copying `%s'.",this->fname);
                err= n;
              }
              else
              { numbytes+= n;
                if(!CP_SILENT)
                { printf("  (%ld)",n);
                  fflush(stdout);
                }
              }
              fclose(dst);
            }
            else
            { if(!CP_SILENT)
                putchar('\n');
              echo("Can't write to `%s'.",outname);
              err= -2L;
            }
            fclose(src);
          }
          else
          { if(!CP_SILENT)
              putchar('\n');
            echo("Can't access input file `%s'.",this->fname);
            err= -1L;
          }
        }

        if(!err)
        {
          ++numfiles;

          if(!CP_SILENT && !CP_DRYRUN)
            putchar('\n');
        }

        if(CP_SILENT && !CP_DRYRUN)
        { printf("%d %s copied\r",numfiles, (numfiles==1) ? "file" : "files");
          fflush(stdout);
        }
      }
      free(this->fname);
    }
    free(this);

    /* Negative error codes indicate I/O errors which are ignored
     * if w/ the '--keep-going' option given in the command-line.
     * Positive values indicate fatal errors and can be used to
     * force exiting from this function */

    if(err<0 && CP_KEEPGOING)
      err= 0L;
  }

  if(!CP_DRYRUN)
    (void)fc_setbuf(0L);

  if(!CP_DRYRUN || (CP_DRYRUN && CP_SILENT))
    printf("%d %s%s",numfiles, (numfiles==1) ? "file" : "files",
      (CP_DRYRUN) ? ".\n" : " copied.");

  if(!CP_DRYRUN)
    printf(" (%ld %s)\n", numbytes, (numbytes==1) ? "byte" : "bytes");

  return err;
}


int prepare_distribution(char *fname, char *pname)
{
  FILE *fp;
  int err= 0;

  if( fp= fopen(fname,"r") )
  {
    fnode *flist;
    if( flist= collect_files(fp) )
    {
      err= copy_files(flist, pname);
    }
    fclose(fp);
  }
  else
  { perror(fname);
    echo("Can't access `%s'\n",fname);
    err= 1;
  }
  return err;
}
