/*  068  9-Jun-87  ovfile.c

        Copyright (c) 1987 by Blue Sky Software.  All rights reserved.
*/

#include <stdio.h>
#include <dos.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ov.h"
#include "overr.h"
#include "direct.h"
#include "dosfile.h"
#include "strmem.h"

static int stowidx;
static FILE_ENT *stowfp;
static DRIVE_ENT *drvlst = NULL;
static char *notitself = "You can't copy a file to itself!";
static char *mustbedir = "Enter a drive and/or directory name!";

extern WINDOW cw;
extern int diridx;
extern char **dirlst;
extern FILE_ENT files[];
extern char *cantopen, *none_tagged;

DRIVE_ENT *findrive(int);
struct search_block *nxtfile();
char far *largest_f(unsigned int, unsigned int *);
unsigned int readbuf(int,char far *,unsigned int);
unsigned int writebuf(int,char far *,unsigned int);
char *strupr(), *strchr(), *dirplus(FILE_ENT *, char *);
char *parsepath(char *, char *, char *, int, char **, char **);
int free_f(char far *), stowfile(struct search_block *, char *);
int add2windows(char *, char *, FILE_ENT *), delfromwins(char *, char *);


/******************************************************************************
 **                        G E T F I L E S                                   **
 *****************************************************************************/

getfiles() {           /* get data for the files in the current dir */

   int firsttime;
   char pathbuf[MAX_PATHLEN+6];
   register struct search_block *sbp;

   cw.files_size = 0;                  /* no files yet */
   firsttime = TRUE;                   /* tell nxtfile() its the first call */

   stowidx = 0;                        /* start storing at the begining */
   stowfp = files;

   /* if showall mode is active, call scantree to get all files, otherwise
      scan the current dir ourselves */

   if (cw.showall) {   /* scan the current disk? */

      *pathbuf = '\0';
      strncat(pathbuf,cw.dirbuf,2);
      scantree("\\",pathbuf,0x16,stowfile);    /* scan entire disk */

   } else        /* scan the current directory */

      while (stowidx < MAX_FILES && (sbp = nxtfile("*.*",0x16,&firsttime)))
            stowfile(sbp,NULL);

   /* sort the file names if there are any, also set nfiles */

   if (cw.nfiles = stowidx)
      sort_files(NULL);

   /* set current file pointer to the first file */

   cw.curidx = 0;

   /* set file counters */

   cw.num_files = cw.nfiles;           /* files in the dir */
   cw.num_tagged = 0;                  /* no files tagged yet */
   cw.tag_size = 0;

}


/*****************************************************************************
                             S T O W F I L E
 *****************************************************************************/

static int
stowfile(sbp,dirp)     /* store a file in files[] */
register struct search_block *sbp;
char *dirp;
{
   register int len;
   static char *lastdir = NULL;
   register FILE_ENT *fp = stowfp;     /* fast stow pointer */

   /* if dirp is not NULL, this call is defining which dir is being scanned,
      add this dir name to the showall dir list - return NZ (keep going) if
      dir name added okay, 0 (stop scanning) if can't add dir name - don't
      incr diridx unless everything is okay.  The dir name passed will always
      have a trailing \ which we don't really want - don't keep it unless
      this happens to be the root dir (len == 3) */

   if (dirp) {
      if (diridx < MAX_DIR) {
         len = strlen(dirp);
         dirlst[diridx++] = lastdir = Strndup(dirp,len > 3 ? len-1 : len);
         return(1);
      }
      return(0);
   }

   /* ignore . and .. entries.  If the file mask is defined, make
      sure the file name matches.  Make sure the file attributes
      match the selection attributes */

   if (stowidx >= MAX_FILES || *sbp->fn == '.' ||
       (cw.selatrs & sbp->attrib) != sbp->attrib ||
       (*cw.mask && (cw.maskcmp != match_name(sbp->fn,cw.mask))))

      return(1);       /* don't want this one, but keep looking */

   /* we want the file, fill in files[] entry */

   fp->size = sbp->size;
   fp->flags = sbp->attrib & 0x3f;
   fp->index = stowidx++;
   fp->date = sbp->date;
   fp->time = sbp->time;
   strcpy(fp->name,sbp->fn);
   fp->dirp = cw.showall ? lastdir : cw.dirbuf;   /* point to files dir str */
   cw.files_size += fp->size;                     /* accumulate total size */

   stowfp++;                             /* update for next time */
   return(1);                            /* keep looking */
}


/******************************************************************************
 **                             I N F O                                      **
 *****************************************************************************/

info() {               /* toggle the extended info display */

   cw.info_display ^= 1;               /* toggle info on/off */

   infocnt(cw.info_display ? 1 : -1);  /* one more or less info window */

   adjust_window();                    /* resize the window data */

   update_window(1);                   /* display files in new format */
}


/******************************************************************************
 **                      C O P Y _ C U R R E N T                             **
 *****************************************************************************/

copy_current() {       /* copy the current file */

   FILE_ENT tmpfent;
   register FILE_ENT *fp;
   register DRIVE_ENT *dp;
   char *dest, *target, *todir, *tofn;

   dest = prompt("Copy current file","Copy to where? ",NULL,0,MAX_REPLY);
   if (strlen(dest) == 0)
      return;

   fp = &files[cw.curidx];
   target = parsepath(fp->dirp,fp->name,dest,isadir(fp->dirp,dest),&todir,&tofn);

   if (strcmp(fp->dirp,todir) != 0 || strcmp(fp->name,tofn) != 0) {
      if (copyfile(fp,target)) {
         tmpfent = *fp;                     /* new file will have */
         tmpfent.flags |= ARCHIVE;          /* ARCHIVE attrib on  */
         add2windows(todir,tofn,&tmpfent);
         dp = findrive(*todir);
         getvolsiz(dp->drive,&dp->vol_size,&dp->vol_free,&dp->clustersiz);
      }
   } else           /* attempt to copy file to itself! */

      show_error(0,0,1,notitself);

   free(target);
   free(todir);
   free(tofn);
}


/******************************************************************************
 **                        C O P Y _ T A G G E D                             **
 *****************************************************************************/

copy_tagged() {        /* mass copy of tagged files to somewhere */

   int i, ch;
   FILE_ENT tmpfent;
   register FILE_ENT *fp;
   register DRIVE_ENT *dp;
   char *dir, *target, *todir, *tofn;

   if (cw.num_tagged == 0)             /* are there any tagged files? */
      show_error(0,NONE_TAGGED,1,none_tagged);

   dir = prompt("Copy tagged files","Copy to which dir? ",NULL,0,MAX_REPLY);
   if (strlen(dir) == 0)
      return;

   if (!isadir(cw.dirbuf,dir))         /* user must give a directory name */
      show_error(0,MUST_BE_DIR,1,mustbedir);

   /* Okay, copy the tagged files */

   for (fp = files, i = 0; i < cw.nfiles && !brkout(); i++, fp++)

      if (fp->flags & TAGGED) {
         disp_msg(2,"Copying ",fp->name);

         target = parsepath(fp->dirp,fp->name,dir,1,&todir,&tofn);

         if (strcmp(fp->dirp,todir) != 0 || strcmp(fp->name,tofn) != 0) {

            if (copyfile(fp,target)) {
               tmpfent = *fp;                     /* new file will have */
               tmpfent.flags |= ARCHIVE;          /* ARCHIVE attrib on  */
               add2windows(todir,tofn,&tmpfent);
            }

         } else        /* trying to copy a file to itself */

            show_error(0,0,1,notitself);

         free(target);                 /* release temp strings */
         free(todir);
         free(tofn);
      }

   clr_msg();                          /* clear last copying msg */

   /* update volume stats after copy */

   dp = strlen(dir) > 1 && dir[1] == ':' ? findrive(*dir) : cw.drivep;
   getvolsiz(dp->drive,&dp->vol_size,&dp->vol_free,&dp->clustersiz);

}


/******************************************************************************
 **                         C O P Y F I L E                                  **
 *****************************************************************************/

static int
copyfile(fp,to)        /* copy from to */
register FILE_ENT *fp;
char *to;
{
   char *fn;
   char far *buffer;
   int fd, td, rc = 0;
   unsigned int bufsiz, len;
   static char *copyabort = " -- copy aborted";

   /* allocate space for the in memory copy buffer */

   buffer = largest_f(63*1024,&bufsiz);  /* get largest possible blk up 2 63k */
   if (bufsiz < 1024) {                  /* give it up if < 1k available */
      if (bufsiz)                        /* release the tiny buffer */
         free_f(buffer);
      show_error(0,0,2,"No free memory to allocate copy buffer",copyabort);
      goto error1;
   }

   /* open the from file */

   fd = open(fn = fname(fp),O_RDONLY|O_BINARY);
   free(fn);

   if (fd == -1) {
      show_error(SHOW_DOS,0,3,cantopen,fp->name,": ");
      goto error2;
   }

   /* open the output file */

   if ((td = open(to,O_CREAT|O_BINARY|O_TRUNC|O_RDWR,S_IWRITE)) == -1) {
      show_error(SHOW_DOS,0,3,cantopen,to,": ");
      goto error3;
   }

   /* copy the file, a buffer at a time */

   while (len = readbuf(fd,buffer,bufsiz))

     if (writebuf(td,buffer,len) < len) {
         show_error(0,0,3,"Error writting to ",to,": Disk may be full");
         goto error4;
      }

   setftime(td,fp->date,fp->time);     /* to file time = from file */

   rc = 1;                             /* the copy seems to have worked */

   error4: close(td);                  /* close the to file */

   if (!rc)                            /* erase to file if there was an */
      unlink(to);                      /*   error while trying to copy  */

   error3: close(fd);                  /* close the from file */
   error2: free_f(buffer);             /* release copy buffer */

   /* set new file attrib's = old file + ACRHIVE */

   if (rc)
      setattrib(to,(fp->flags & (ARCHIVE|SYSTEM|HIDDEN|RDONLY)) | ARCHIVE);

   error1: return(rc);                 /* tell caller if we worked */
}


/******************************************************************************
 **                    E R A S E _ C U R R E N T                             **
 *****************************************************************************/

erase_current() {      /* erase the current file */

   int ch;
   char askmsg[30];
   register FILE_ENT *fp;

   fp = &files[cw.curidx];

   strcpy(askmsg,"Erase ");
   strcat(askmsg,fp->name);
   strcat(askmsg," ? (y/N): ");
   ch = ask(askmsg);

   if (yes(ch))
      if (delfile(fp)) {                       /* delete the file */
         delfromwins(fp->dirp,fp->name);       /* remove from windows */
         getvolsiz(*cw.dirbuf,&cw.drivep->vol_size, /* vol stats have changed */
            &cw.drivep->vol_free,&cw.drivep->clustersiz);
      }
}


/*****************************************************************************
                          E R A S E _ T A G G E D
 *****************************************************************************/

erase_tagged() {       /* erase the tagged files */

   int ch;
   register int i;
   register FILE_ENT *fp;

   if (cw.num_tagged == 0)             /* are there any tagged files? */
      show_error(0,NONE_TAGGED,1,none_tagged);

   ch = ask("Erase all tagged files? (y/N): ");
   if (!yes(ch))
      return;

   /* scan files[] looking for tagged files, when found, delete it */

   for (i = 0, fp = files; i < cw.nfiles && !brkout(); i++, fp++)
      if (fp->flags & TAGGED) {
         disp_msg(2,"Erasing ",fp->name);
         if (delfile(fp))
            delfromwins(fp->dirp,fp->name);
      }

   clr_msg();                                  /* clear last erasing msg */

   getvolsiz(*cw.dirbuf,&cw.drivep->vol_size,  /* volume stats have changed */
      &cw.drivep->vol_free,&cw.drivep->clustersiz);
}


/*****************************************************************************
                               D E L F I L E
 *****************************************************************************/

static int
delfile(fp)            /* delete a file */
register FILE_ENT *fp;
{
   int rc;
   char *fn;

   fn = fname(fp);                     /* in case show-all active */

   if (fp->flags & DIR)                /* special case if file is a DIR */
      rc = rmdir(fn);                  /* try to remove a directory     */
   else
      rc = unlink(fn);                 /* delete a file                 */

   free(fn);

   if (rc)                             /* delete okay? */
      show_error(SHOW_DOS,0,3,"Unable to erase ",fp->name,": ");

   return(rc == 0);
}


/******************************************************************************
                              R E N _ C U R
 ******************************************************************************/

ren_cur() {            /* rename (or move) the current file */

   int rc;
   register FILE_ENT *fp;
   char *newname, fn[MAX_NAMELEN+1];
   static char ptxt[] = "Enter new file name or directory name for ";
   char *fnp, *target, *todir, *tofn, *dirp, pmsg[sizeof(ptxt)+MAX_NAMELEN+1];

   fp = &files[cw.curidx];

   strcpy(pmsg,ptxt);                          /* build prompt string */
   strcat(pmsg,strcpy(fn,fp->name));

   newname = strupr(prompt("Rename current file",pmsg,NULL,0,MAX_REPLY));
   if (strlen(newname) == 0)
      return;

   /* figure out the full target name, the dir, and the fn */

   rc = isadir(dirp=fp->dirp,newname); /* need to know if this is a dir name */

   target = parsepath(dirp,fn,newname,rc,&todir,&tofn);

   fnp = fname(fp);                    /* from name */

   rc = rename(fnp,target);            /* rename/move it */

   if (rc == 0) {                      /* rename okay? */

      /* note: fn is a local array instead of a pointer to fp->name because
         add2windows() may shift the entries in files[] around thereby making
         fp->name point to the wrong file - same goes for local dirp */

      add2windows(todir,tofn,fp);      /* add to new window(s) */
      delfromwins(dirp,fn);            /* del from old window(s) */

   } else              /* unable to rename */

      show_error(SHOW_DOS,0,3,"Unable to rename ",fn,": ");

   free(target);               /* release temp strings */
   free(todir);
   free(tofn);
   free(fnp);
}


/******************************************************************************
                              R E N _ T A G
 ******************************************************************************/

ren_tag() {            /* rename (move) all tagged files to another dir */

   int i, rc;
   register FILE_ENT *fp;
   char *newdir, *fnp, *target, *todir, *dirp, fn[MAX_NAMELEN+1];

   if (cw.num_tagged == 0)             /* are there any tagged files? */
      show_error(0,NONE_TAGGED,1,none_tagged);

   newdir = strupr(prompt("Move tagged files","Enter new directory name",
            NULL,0,MAX_REPLY));

   if (strlen(newdir) == 0)
      return;

   /* the name given must be a directory name */

   if (!isadir(cw.dirbuf,newdir))
      show_error(0,MUST_BE_DIR,1,mustbedir);

   /* Okay, move the tagged files */

   for (i = 0, fp = files; i < cw.nfiles && !brkout(); i++, fp++)

      if (fp->flags & TAGGED) {                /* this file tagged? */

         /* figure out the target names */

         dirp = fp->dirp;
         target = parsepath(dirp,strcpy(fn,fp->name),newdir,1,&todir,NULL);

         fnp = fname(fp);              /* from name */
         rc = rename(fnp,target);      /* move the file */

         if (rc == 0) {                /* move okay? */

            /* note: fn is a local array instead of a pointer to fp->name 'cause
            add2windows() may shift the entries in files[] around thereby making
            fp->name point to the wrong file */

            add2windows(todir,fn,fp);  /* add to new window(s) */
            delfromwins(dirp,fn);      /* del from old window(s) */

         } else        /* error, unable to rename/move this file */

            show_error(SHOW_DOS,0,3,"Unable to move ",fn,": ");

         free(target);                 /* must be tough on malloc & friends */
         free(todir);
         free(fnp);
      }
}


/******************************************************************************
                                L O G I N
 *****************************************************************************/


login() {              /* login to another directory */

   char *new_dir, *todir;

   new_dir = prompt(NULL,"Login to which drive/directory? ",NULL,0,MAX_REPLY);

   /* if they give a relative dir spec and showall is in effect, we want
      the relative dir spec to be relative to the current file's dir, not
      the current dir */

   if (strlen(todir = new_dir)) {
      if (cw.showall && cw.curidx < cw.nfiles)
         parsepath(files[cw.curidx].dirp,"",new_dir,1,&todir,NULL);

      switch_dir(todir);

      if (todir != new_dir)            /* free mem if parsepath called */
         free(todir);
   }
}


/******************************************************************************
 **                       S W I T C H _ D I R                                **
 *****************************************************************************/

switch_dir(dir)        /* change the current directory */
char *dir;
{
   int rc;
   register DRIVE_ENT *dp;

   if ((rc = change_dir(dir)) == 0) {  /* dir switched okay? */

      if (cw.showall)                  /* turn off showall mode if it was */
         showoff();                    /*   in effect in this window */

      getcwd(cw.dirbuf,MAX_PATHLEN);   /* get the current dir name */
      initdrive(*cw.dirbuf);           /* (re)init DRIVE_ENT for this drive */
      getfiles();                      /* load the files[] structure */
      adjust_window();                 /* resize window data */
      update_header();                 /* update the header  */
      update_window(1);                /* and the window data */

   } else {                            /* can't switch, tell user */

      show_error(SHOW_DOS,0,3,"Can't change to ",dir,": ");
   }
   return(rc);
}


/*****************************************************************************
                            I N I T D R I V E
 *****************************************************************************/

initdrive(drive)       /* initialize DRIVE_ENT for 'drive' */
int drive;
{
   register DRIVE_ENT *dp;

   dp = findrive(*cw.dirbuf);       /* get DRIVE_ENT address 4 drive */
   cw.drivep = dp;                  /* keep address in window block */

   get_set_vol(dp->volbuf,NULL);            /* get the new volume name */
   getvolsiz(*cw.dirbuf,&dp->vol_size,      /*  and the volume size stats */
             &dp->vol_free,&dp->clustersiz);/*  incase they are different */

}


/******************************************************************************
                            F I N D R I V E
 *****************************************************************************/

DRIVE_ENT *
findrive(drive)        /* find or allocate a DRIVE_ENT for drive */
int drive;
{
   register DRIVE_ENT *dp, *ldp = NULL;

   dp = drvlst;                        /* try to find the DRIVE_ENT in the */
   while (dp != NULL) {                /*   linked list of DRIVE_ENTs */
      if (dp->drive == drive)
         break;
      ldp = dp;
      dp = dp->next;
   }

   if (dp == NULL) {                   /* allocate a new DRIVE_ENT */
      dp = (DRIVE_ENT *) Malloc(sizeof(DRIVE_ENT));
      dp->next = NULL;
      dp->drive = drive;
      if (ldp)
         ldp->next = dp;
      else
         drvlst = dp;
   }

   return(dp);
}


/*****************************************************************************
                               F I N D I R
 *****************************************************************************/

char *
findir(dir)    /* find a dirlst entry that matches dir */
char *dir;
{
   register int i;
   register char **dp;

   for (dp = dirlst, i = diridx; i; i--, dp++)
      if (strcmp(dir,*dp) == 0)
         return(*dp);

   return("");                 /* better never happen! */
}


/******************************************************************************
 **                         S E T _ V O L                                    **
 *****************************************************************************/

set_vol() {            /* set the volume label */

   char *label;

   label = prompt("Set volume label","Enter new volume label: ",NULL,0,11);
   if (strlen(label) == 0)
      return;

   get_set_vol(cw.drivep->volbuf,label);    /* one call to set it */
   get_set_vol(cw.drivep->volbuf,NULL);     /* another to see what comes back */

   gotorc(VOL_ROW,1);                  /* display volume label */
   out_str(cw.drivep->volbuf,11,' ');
}


/*****************************************************************************
                          M A T C H _ N A M E
 *****************************************************************************/

match_name(np,pp)      /* determine if file name matched pattern */
register char *np, *pp;
{
   /* Why you might ask is this code like this, well..., I have plans to port
      this code to a system that has file names up to 79 characters long, and
      I thought this type of code would be easier to port than something that
      knew MS-DOS names are 8+3 */

   while (*np) {
      if (*np == *pp)                          /* name char match pattern? */
         goto match;
      else
         if (*np == '.') {                     /* watch to switches to the */
            while (*pp == '*' || *pp == '?')   /* extension, the pattern   */
               pp++;                           /* must have one also */
            if (*pp != '.')
               return(0);                      /* no pattern ext, no match */
            goto match;
         } else
            if (*pp == '*') {                  /* allow wildcards */
               np++;                           /* NOTE: doesn't advance the */
               continue;                       /*       pattern pointer */
            } else
               if (*pp == '?')
                  goto match;
               else
                  return(0);                   /* don't match, not wild */
   match:
      np++;                                    /* chars match, advance */
      pp++;
   }

   /* the name string matched so far, make sure the pattern only has
      wildcards left (if anything) */

   while (*pp)
      if (*pp != '*' && *pp != '?' && *pp != '.')
         return(0);
      else
        pp++;

   return(1);                          /* A MATCH! */
}


/*****************************************************************************
                           P A C K F I L E S
 *****************************************************************************/

packfiles() {          /* pack the files structure */

   FILE_ENT *endp;
   register FILE_ENT *p, *q;

   p = q = files;
   endp = &files[cw.nfiles];

   while (q < endp)
      if (*p->name) {
         if (p == q)
            p = ++q;
         else
            p++;
      } else
         if (*q->name == '\0')
            q++;
         else {
            *p++ = *q;
            *q->name = '\0';
            q++;
         }

   cw.nfiles = p - files;              /* new # of files in files[] */
}


/*****************************************************************************
                                F N A M E
 *****************************************************************************/

char * ALTCALL
fname(fp)              /* return the address of a file name */
register FILE_ENT *fp;
{
   return(dirplus(fp,fp->name));       /* maybe this routine is too simple */
}

/*****************************************************************************
                             D I R P L U S
 *****************************************************************************/

char *
dirplus(fp,pp)         /* return a string which is the files dir + whatever */
register FILE_ENT *fp;
char *pp;
{
   register char *dp;

   if (cw.showall) {
      dp = (char *) Malloc(strlen(fp->dirp)+strlen(pp)+2);
      strcpy(dp,fp->dirp);
      if (dp[strlen(dp)-1] != '\\')
         strcat(dp,"\\");
      strcat(dp,pp);
      return(dp);
   } else
      return(Strdup(pp));
}
