/*
** ECopy V1.1 -- Economical File Copy Utility
** Copyright (c) 1994 by Sam Yee.
** Feb 20, 1994.
**
** Permission is here granted for the distribution of ECopy, provided
** that files are intacted and in unmodified state.
*/

/*******************************************************************/
/* includes */
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <ctype.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <exec/memory.h>
#include <math.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <stdlib.h>
#include <string.h>

/*******************************************************************/
/* constants */
/* for ReadArgs() */
#define OPT_FROM    0
#define OPT_TO      1
#define OPT_QUIET   2
#define OPT_BUF     3
#define OPT_CLONE   4
#define OPT_LISTNC  5   /* list files not copied */
#define OPT_MOVE    6
#define OPT_RESERVE 7
#define OPT_SUFFIX  8
#define OPT_BLOCKCAP 9  /* block capability; bytes usable in each block */
#define OPT_FORCE   10  /* force move/copy */
#define OPT_READFILE 11 /* read file names from file */
#define OPT_NCTOFILE 12 /* file name to save files not copied to */
#define OPT_COUNT   13

#define BUFFERS 8 /* 8K of buffers */
#define MINBUFS 1 /* minimum 1K buffer */

#define TEMPLATE "FROM/M,TO/A,QUIET/S,BUF=BUFFER/K/N,CLONE/S,LISTNC/S,MOVE/S,\
RES=RESERVE/K/N,SUF=SUFFIX/K,BLOCKCAP/K/N,FORCE/S,READFILE/K,NCTOFILE/K"

/*******************************************************************/
/* structure definitions */
struct FileLink
{
    struct Node             node;
    struct FileInfoBlock    *fib;
};

struct FileList
{
    struct List list;
    long        count;  /* number of nodes in list */
};

/*******************************************************************/
/* prototypes */

/* need these for #pragmas */
extern struct DOSBase  *DOSBase;
extern struct SysBase  *SysBase;

/* disable SAS/C's ^C checking */
void __regargs __chkabort(void);
void __regargs __chkabort(){}
void __regargs _CXBRK(void);
void __regargs _CXBRK(){}

BOOL CollectFiles(struct FileList *fl, char *fname);
void FreeFileList(struct FileList *flist, BOOL list_not_copied,
                  BOOL is_copy, char *nctofname);
void SortListDown(struct FileList *flist);
struct FileLink *GetBestFitFile(struct List *list, long max_size);
int GetDiskInfo(char *disk_name, struct InfoData *info);
long CalcReserveBlocks(long size, long  block_size);
BOOL CollectNamesFromFile(struct FileList *flist, char *fname);

/*******************************************************************/
/* version info */
char *version = "$VER: ECopy 1.10 (20.2.94)";

/*******************************************************************/
/* main routine, what else? */
int
main(int    argc,       /* number of arguments from command invocation */
     char   *argv[])    /* array of strings of command line options */
{
    struct InfoData *info = NULL;
    struct FileList flist;
    struct FileLink *flink;
    struct RDArgs   *myrda = NULL;
    char            **from_files,
                    vol_name[256],  /* destination volume name */
                    dst_name[256];  /* destination file name */
    void            *io_buf;        /* copy buffer */
    LONG            result[OPT_COUNT],
                    rc = 10,
                    i,
                    block_cap,      /* # of usable bytes in a block */
                    blocks_free,
                    read_len,
                    buffers = BUFFERS,
                    buf_size,
                    disk_used = 0L, /* total bytes used on disk(s) */
                    disk_max = 0L;  /* maximum usable bytes on disk(s) */
    BPTR            src_fh,     /* source file handle */
                    dst_fh;     /* destination file handle */
    BOOL            quit = FALSE,
                    full,       /* disk full? */
                    broke_scan = FALSE; /* user smashed ^C ? */

    NewList(&flist.list);
    flist.count = 0;

    if (argc == 1)
    {
        Printf("\
ECopy 1.10 -- Economical File Copy Utility\n\
Copyright (c) 1992-1994 by Sam Yee.  All Rights Reserved.\n\
Freely Distributable.  Bug reports to samy@sfu.ca or 1:153/765\n\
Compiled on "__DATE__ " (" __TIME__").\n\n\
For command usage type `%s ?'\n",argv[0]);
        rc = 0;
    }
    else
    {
        myrda = (struct RDArgs *)AllocDosObject(DOS_RDARGS,NULL);
        info = (struct InfoData *)AllocVec(sizeof(struct InfoData),MEMF_PUBLIC);
    }

    if (myrda && info)
    {
            /* read from stdin instead */
        myrda->RDA_Source.CS_Buffer = NULL;
        myrda->RDA_Source.CS_Length = 0L;

            /* initialize result array */
        for (i = 0; i < OPT_COUNT; i++)
            result[i] = 0L;

            /* parse command line options */
        if (ReadArgs(TEMPLATE,result,myrda))
        {
            if (result[OPT_BUF])
                buffers = max(*((long *)result[OPT_BUF]),MINBUFS);

            buf_size = buffers*1024L;

                /* if copy buffer allocated */
            if (io_buf = AllocVec(buf_size,MEMF_PUBLIC))
            {
                from_files = (char **)result[OPT_FROM];

                    /* scan all files/directories to copy from */
                for (i = 0; from_files[i]; i++)
                {
                    if (broke_scan = CollectFiles(&flist,from_files[i]))
                        break;
                }

                if (!broke_scan && result[OPT_READFILE])
                    broke_scan = CollectNamesFromFile(&flist,
                                    (char *)result[OPT_READFILE]);

                if (!broke_scan) /* ^C not hit */
                {
                    SortListDown(&flist);   /* sort in inverse order according
                                               to size */
                    strncpy(vol_name,(char *)result[OPT_TO],
                            sizeof(vol_name)-1);
                }
                else
                    quit = TRUE;

                while (!quit)
                {
                        /* get disk info data */
                    if (!(block_cap = GetDiskInfo(vol_name,info)))
                    {
                        PrintFault(IoErr(),vol_name);
                        break;
                    }

                    if (result[OPT_BLOCKCAP])
                        block_cap = max(*((long *)result[OPT_BLOCKCAP]),1L);

                    if (CheckSignal(SIGBREAKF_CTRL_C))
                    {
                        PrintFault(ERROR_BREAK,NULL);
                        break;
                    }

                    /* reserve 1 for file header and 1 for use by OS */
                    blocks_free = (info->id_NumBlocks-info->id_NumBlocksUsed-
                                   CalcReserveBlocks(result[OPT_RESERVE] ?
                                                     (*((long *)result[OPT_RESERVE])*1024L) : 0L,
                                                     block_cap))-2;

                    full = FALSE;
                    flink = GetBestFitFile(&(flist.list),blocks_free*block_cap);

                    if (flink)
                    {
                            /* get destination file name */
                        strncpy(dst_name,vol_name,sizeof(dst_name)-1);
                        AddPart(dst_name,FilePart(flink->fib->fib_FileName),
                                sizeof(dst_name));

                            /* add suffix as required */
                        if (result[OPT_SUFFIX])
                            strncat(dst_name,(char *)result[OPT_SUFFIX],
                                    sizeof(dst_name)-1);

                        if (src_fh = Open(flink->fib->fib_FileName,MODE_OLDFILE))
                        {
                            if (!(dst_fh = Open(dst_name,MODE_NEWFILE)))
                            {
                                /* if delete/write protected and force then
                                   delete file */
                                if (((IoErr() == ERROR_DELETE_PROTECTED) ||
                                     (IoErr() == ERROR_WRITE_PROTECTED)) &&
                                    result[OPT_FORCE])
                                {
                                    SetProtection(dst_name,~(FIBF_READ|FIBF_WRITE|
                                                           FIBF_EXECUTE|FIBF_DELETE));
                                    DeleteFile(dst_name);
                                }

                                dst_fh = Open(dst_name,MODE_NEWFILE);
                            }

                            if (dst_fh)
                            {
                                BOOL    bad = FALSE;

                                if (!result[OPT_QUIET])
                                {
                                    Printf("%s..",flink->fib->fib_FileName);
                                    Flush(Output());
                                }

                                while ((read_len = Read(src_fh,io_buf,buf_size)) > 0L)
                                {
                                    if (CheckSignal(SIGBREAKF_CTRL_C))
                                    {
                                        PrintFault(ERROR_BREAK,NULL);
                                        bad = quit = TRUE;
                                        break;
                                    }
                                    else if (Write(dst_fh,io_buf,read_len) != read_len)
                                    {
                                        PrintFault(IoErr(),NULL);
                                        full = bad = TRUE;
                                        break;
                                    }
                                }

                                if (IoErr() && !read_len)
                                {
                                    PrintFault(IoErr(),NULL);
                                    bad = TRUE;
                                    /* remove file node in the list to be copied */
                                    Remove(&(flink->node));
                                    flist.count--;
                                }

                                Close(dst_fh);

                                if (bad)    /* if copy was bad */
                                {
                                    Printf("Incomplete file %s..",dst_name);
                                    Flush(Output());
                                    DeleteFile(dst_name);
                                    Printf("deleted\n");
                                }
                                else
                                {
                                    if (!result[OPT_QUIET])
                                        Printf(result[OPT_MOVE] ? "moved\n" : "copied\n");

                                    /* if clone or move then clone file */
                                    if (result[OPT_CLONE] || result[OPT_MOVE])
                                    {
                                        SetProtection(dst_name,flink->fib->fib_Protection);
                                        SetFileDate(dst_name,&(flink->fib->fib_Date));

                                        /* if there is a comment, duplicate it */
                                        if (flink->fib->fib_Comment[0])
                                            SetComment(dst_name,flink->fib->fib_Comment);
                                    }

                                    /* if moving files */
                                    if (result[OPT_MOVE])
                                    {
                                        Close(src_fh);  /* close file first */
                                        src_fh = NULL;

                                        /* attempt deletion */
                                        if (!DeleteFile(flink->fib->fib_FileName))
                                        {
                                            /* if force deletion */
                                            if (result[OPT_FORCE])
                                            {
                                                SetProtection(flink->fib->fib_FileName,
                                                              ~(FIBF_READ|FIBF_WRITE|
                                                                FIBF_EXECUTE|FIBF_DELETE));

                                                if (!DeleteFile(flink->fib->fib_FileName))
                                                    PrintFault(IoErr(),flink->fib->fib_FileName);
                                            }
                                            else
                                                PrintFault(IoErr(),flink->fib->fib_FileName);
                                        }
                                    }

                                    /* remove file node in the list to be copied */
                                    Remove(&(flink->node));
                                    flist.count--;
                                }
                            }
                            else
                            {
                                PrintFault(IoErr(),dst_name);

                                if ((IoErr() == ERROR_DELETE_PROTECTED) ||
                                    (IoErr() == ERROR_WRITE_PROTECTED))
                                {
                                    Remove(&(flink->node));
                                    flist.count--;
                                }
                            }

                            /* if file not already closed, close it */
                            if (src_fh)
                                Close(src_fh);
                        }
                        else    /* fatal error opening file */
                        {
                            PrintFault(IoErr(),flink->fib->fib_FileName);
                            Remove(&(flink->node));
                            flist.count--;

                            if (!flist.count)
                            {
                                rc = 0;
                                quit = TRUE;
                            }
                        }
                    }
                    else if (flist.count)   /* if more files to copy then
                                               disk must be full */
                        full = TRUE;
                    else
                    {
                        quit = TRUE;
                        rc = 0;
                    }

                    if (full && !quit)
                    {
                        char    key = '\0';

                        GetDiskInfo(vol_name,info);
                        disk_used += (info->id_NumBlocksUsed*info->id_BytesPerBlock);
                        disk_max += (info->id_NumBlocks*info->id_BytesPerBlock);

                        PrintFault(ERROR_DISK_FULL,vol_name);

                        while (TRUE)
                        {
                            /* display status and get response */
                            Printf("\n%ld more file%s to be copied.\n\n",
                                   flist.count, (flist.count == 1L) ? "" : "s");
                            Printf("[R]etry, [C]hange disk, or [A]bort? ");
                            Flush(Output());
                            SetMode(Output(),TRUE);
                            Read(Input(),&key,1L);
                            key = toupper(key);
                            SetMode(Output(),FALSE);
                            FPutC(Output(),key);
                            Printf("\n");

                            if (key == 'R') /* retry copy */
                            {
                                Printf("\n");
                                break;
                            }
                            else if (key == 'C')    /* change destination disk */
                            {
                                char *s;

                                /* get new disk name */
                                Printf("\nEnter new disk name [%s]: ",vol_name);
                                Flush(Output());
                                Read(Input(),dst_name,sizeof(dst_name)-1);
                                Printf("\n");
                                s = dst_name;

                                while (*s && (*s != '\r') && (*s != '\n')) s++;

                                *s = '\0';

                                if (*dst_name)
                                    strncpy(vol_name,dst_name,sizeof(vol_name)-1);

                                if (GetDiskInfo(vol_name,info))
                                    break;
                                else
                                    PrintFault(IoErr(),vol_name);
                            }
                            else if (key == 'A')    /* abort copy */
                            {
                                quit = TRUE;
                                break;
                            }
                        }
                    }

                    if (!flist.count)   /* we are done */
                    {
                        rc = 0;
                        quit = TRUE;
                    }
                }

                FreeFileList(&flist,!broke_scan && (result[OPT_LISTNC] ||
                                                    result[OPT_NCTOFILE]),
                             result[OPT_MOVE],(char *)result[OPT_NCTOFILE]);

                if (!broke_scan && disk_used && disk_max)
                {
                    ULONG x;

                    if (disk_used > 4000000)    /* check if * will overflow */
                        x = ((disk_used*100)/disk_max)*10;
                    else
                        x = (disk_used*1000)/disk_max;

                    Printf("\nEfficiency: %ld.%ld%s!\n",x/10,x - ((x/10)*10),"%");
                }

                FreeVec(io_buf);
            }
            else
                PrintFault(ERROR_NO_FREE_STORE,NULL);

            FreeArgs(myrda);
        }
        else
            PrintFault(IoErr(),NULL);
    }

    if (myrda)
        FreeDosObject(DOS_RDARGS,myrda);

    if (info)
        FreeVec(info);

    return(rc); /* return with error code, if any */
}

/*******************************************************************/
/* allocate a file information node that can be added to the list
   of files to copy */
struct FileLink *
AllocFileLink(void)
{
    struct FileLink *flink;

    if (flink = AllocVec(sizeof(struct FileLink),MEMF_PUBLIC|MEMF_CLEAR))
    {
        if (!(flink->fib = AllocDosObject(DOS_FIB,NULL)))
        {
            FreeVec(flink);
            flink = NULL;
        }
    }

    return(flink);
}

/*******************************************************************/
/* free the file information node allocated by AllocFileLink() */
void
FreeFileLink(struct FileLink *flink)
{
    FreeDosObject(DOS_FIB,flink->fib);
    FreeVec(flink);
}

/*******************************************************************/
/* free the list if file information nodes, with file name listing */
void
FreeFileList(struct FileList    *flist,
             BOOL               list_not_copied,    /* list those files not copied? */
             BOOL               is_copy,            /* did we copy the files?
                                                       if not, we moved them */
             char               *nctofname)         /* file to save not copied file
                                                       file names to */
{
    struct FileLink *flink;
    BOOL            broke = FALSE;
    LONG            not_copied_count = flist->count;
    BPTR            outfh = NULL;

    if (nctofname)
    {
        if (!(outfh = Open(nctofname,MODE_NEWFILE)))
            PrintFault(IoErr(),nctofname);
    }

    while (flink = (struct FileLink *)RemHead(&flist->list))
    {
        if (!broke && (list_not_copied || outfh))
        {
            if (CheckSignal(SIGBREAKF_CTRL_C))
            {
                PrintFault(ERROR_BREAK,NULL);
                broke = TRUE;
            }
            else
            {
                if (outfh)
                    FPrintf(outfh,"%s\n",flink->fib->fib_FileName);

                if (list_not_copied)
                    Printf("%s\n",flink->fib->fib_FileName);
            }
        }

        FreeFileLink(flink);
    }

    if (!broke && list_not_copied && not_copied_count)
    {
        Printf("\n%ld file%s not %sd.\n",
               not_copied_count,(not_copied_count == 1L) ? "" : "s",
               is_copy ? "move" : "copie");
    }

    if (outfh)
        Close(outfh);
}

/*******************************************************************/
/* collect a list of file information nodes from a directory given
   by 'lock'.  each file name is compared with 'pattern'. */
BOOL /* broke? */
CollectDirFiles(struct FileList *fl,    /* file link list */
                BPTR            lock,   /* lock of directory */
                char            *pattern, /* pattern buf from
                                             ParsePatternNoCase() to match
                                             each filename with */
                struct FileInfoBlock *fib)  /* fib from previous Examine () */
{
    struct FileLink *flink;
    char            pathpart[256],
                    pathname[256];
    BOOL            broke = FALSE;

    NameFromLock(lock,pathpart,sizeof(pathpart));

    /* scan the whole directory */
    while (ExNext(lock,fib))
    {
        /* user press ^C break? */
        if (CheckSignal(SIGBREAKF_CTRL_C))
        {
            PrintFault(ERROR_BREAK,NULL);
            broke = TRUE;
            break;
        }

        if (fib->fib_DirEntryType < 0) /* file? */
        {
            /* pattern matched? */
            if (MatchPatternNoCase(pattern,FilePart(fib->fib_FileName)))
            {
                if (flink = AllocFileLink())
                {
                    /* add file information node to list */
                    strcpy(pathname,pathpart);
                    AddPart(pathname,fib->fib_FileName,sizeof(pathname));
                    strncpy(fib->fib_FileName,pathname,sizeof(fib->fib_FileName)-1);
                    CopyMem(fib,flink->fib,sizeof(struct FileInfoBlock));
                    AddTail(&(fl->list),&(flink->node));
                    fl->count++;
                }
            }
        }
    }

    return(broke);
}

/*******************************************************************/
/* collect a list of file information nodes from a directory or
   just one file information node if a file is specified. */
BOOL /* broke? */
CollectFiles(struct FileList    *fl,
             char               *fname)
{
    struct FileLink *flink;
    char            parse_buf[256],
                    pathname[256],
                    *path,
                    c;
    BPTR            dlock,
                    flock;
    BOOL            error = FALSE,
                    broke = FALSE;

    if (flink = AllocFileLink())
    {
        strncpy(pathname,fname,sizeof(pathname)-1);

        /* Attempt to Lock() file or directory */
        if (dlock = Lock(pathname,ACCESS_READ))
        {
            if (Examine(dlock,flink->fib))
            {
                if (flink->fib->fib_DirEntryType > 0) /* a directory */
                    AddPart(pathname,"#?",sizeof(pathname));
            }
            else
            {
                PrintFault(IoErr(),pathname);
                error = TRUE;
            }

            UnLock(dlock);
        }

        if (!error) /* if file examinable */
        {
            path = PathPart(pathname);
            c = *path;
            *path = '\0';

            if (dlock = Lock(pathname,ACCESS_READ)) /* Lock() file or directory */
            {
                if (Examine(dlock,flink->fib))  /* examine file or directory */
                {
                    *path = c;

                    if (!(*path))
                        path = "#?";

                    /* if patterned filename specified, scan the whole directory */
                    if (ParsePatternNoCase(path,parse_buf,sizeof(parse_buf)))
                        broke = CollectDirFiles(fl,dlock,parse_buf,flink->fib);
                    else    /* file specified */
                    {
                        /* get relative file name */
                        NameFromLock(dlock,parse_buf,sizeof(parse_buf));
                        AddPart(parse_buf,path,sizeof(parse_buf));

                        if (flock = Lock(parse_buf,ACCESS_READ))    /* Lock() file */
                        {
                            if (Examine(flock,flink->fib))  /* examine file */
                            {
                                /* get absolute file name and add to list */
                                NameFromLock(flock,flink->fib->fib_FileName,
                                             sizeof(flink->fib->fib_FileName));
                                AddTail(&fl->list,&flink->node);
                                fl->count++;
                                flink = NULL;
                            }
                            else
                                PrintFault(IoErr(),parse_buf);

                            UnLock(flock);
                        }
                        else
                            PrintFault(IoErr(),parse_buf);
                    }
                }
                else
                    PrintFault(IoErr(),pathname);

                UnLock(dlock);
            }
            else
                PrintFault(IoErr(),pathname);
        }

        /* if file information node not used then free it */
        if (flink)
            FreeFileLink(flink);
    }

    return(broke);
}

/*******************************************************************/
/* callback routine for qsort() to sort file information nodes
   by size */
int
sld_cmp(struct FileLink **f1,
        struct FileLink **f2)
{
    return((*f1)->fib->fib_Size-(*f2)->fib->fib_Size);
}

/*******************************************************************/
/* sort list of file information nodes inversely by size */
void
SortListDown(struct FileList *flist)
{
    struct FileLink **flink_array;
    long            i;

    if (flist->count)
    {
        if (flink_array = AllocVec(flist->count*sizeof(struct FileLink **),
                                   MEMF_PUBLIC|MEMF_CLEAR))
        {
            /* create array to sort */
            for (i = 0; i < flist->count; i++)
                flink_array[i] = (struct FileLink *)RemHead(&(flist->list));

            /* sort array in ascending order*/
            qsort(flink_array,flist->count,sizeof(struct FileLink **),sld_cmp);

            /* rebuild file list in reverse order */
            for (i = 0; i < flist->count; i++)
                AddHead(&(flist->list),(struct Node *)flink_array[i]);

            FreeVec(flink_array);
        }
    }
}

/*******************************************************************/
/* find the best file to fit into current disk */
struct FileLink *
GetBestFitFile(struct List  *list,
               long         max_size)
{
    struct FileLink *flink;

    flink = (struct FileLink *)list->lh_Head;

    while (flink->node.ln_Succ)   /* not end of list? */
    {
        if (flink->fib->fib_Size <= max_size)
            break;

        flink = (struct FileLink *)flink->node.ln_Succ;
    }

    return(flink->node.ln_Succ ? flink : NULL);
}

/*******************************************************************/
/* get information about disk and also return the size of each block
   that is usable */
int /* block capability */
GetDiskInfo(char            *disk_name,
            struct InfoData *info)
{
    BPTR    lock;
    int     block_cap = 0;

    if (lock = Lock(disk_name,ACCESS_READ))
    {
        if (Info(lock,info))
        {
            switch (info->id_DiskType)
            {
                case ID_DOS_DISK:
                case ID_INTER_DOS_DISK:
                case ID_FASTDIR_DOS_DISK:
                    block_cap = 488;
                    break;

                case ID_FFS_DISK:
                case ID_INTER_FFS_DISK:
                case ID_FASTDIR_FFS_DISK:
                    block_cap = 504;
                    break;

                case ID_MSDOS_DISK:
                    block_cap = 512;    /* this is a guess,
                                           based on tests */
                    break;

                default:
                    block_cap = info->id_BytesPerBlock;
                    break;
            }
        }

        UnLock(lock);
    }

    return(block_cap);
}

/*******************************************************************/
/* calculate how many blocks to reserve */
long
CalcReserveBlocks(long  size,   /* minimum size to reserve */
                  long  block_size) /* usable bytes in each block */
{
    long reserve = 0L;

    if (size > 0L)
    {
        reserve = size/block_size;

        if (size%block_size)
            reserve++;
    }

    return(reserve);
}

/*******************************************************************/
/* get the list of files to copy from a file */
BOOL /* broke? */
CollectNamesFromFile(struct FileList *flist,
                     char            *fname)    /* filename to
                                                   read names from */
{
    BPTR    fh;
    char    line[256],
            *s;
    BOOL    broke = FALSE;

    if (fh = Open(fname,MODE_OLDFILE))
    {
        while (FGets(fh,line,sizeof(line)))
        {
            /* remove CR and/or LF if any */
            s = line;
            while (*s && (*s != '\r') && (*s != '\n')) s++;
            *s = '\0';

            if (broke = CollectFiles(flist,line))
                break;
        }

        Close(fh);
    }
    else
        PrintFault(IoErr(),fname);

    return(broke);
}

/*******************************************************************/
/* END */
