#define NAME         "xadUnFile"
#define DISTRIBUTION "(Freeware) "
#define REVISION     "4"

/* Programmheader

	Name:		xadUnFile
	Author:		SDI
	Distribution:	Freeware
	Description:	dearchives file archives
	Compileropts:	-
	Linkeropts:	-gsi -l amiga

 1.0   13.09.98 : first version
 1.1   18.11.98 : added FILE parameter and directory creation
 1.2   03.02.99 : added corrupt message
 1.3   07.02.99 : added missing "group crunched" handling
 1.4   16.03.99 : errors no longer abort decrunching
*/

#include <proto/xadmaster.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <exec/memory.h>
#include <dos/dosasl.h>
#include <utility/hooks.h>
#include "SDI_defines.h"
#include "SDI_compiler.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

struct xadMasterBase *	xadMasterBase = 0;
struct DosLibrary *	 DOSBase = 0;
struct ExecBase *	 SysBase  = 0;

#define PARAM	"FROM/A,DESTDIR,PASSWORD/K,FILE/M,NE=NOEXTERN/S," 	\
		"INFO=LIST/S,OW=OVERWRITE/S,NOTREE/S,ASKMAKEDIR/S,"	\
		"NOCOMMENT/S,NOPROT/S,NODATE/S,NOABS/S"

#define OPTIONS \
  "FROM       The input archive file (no patterns allowed)\n"		\
  "DESTDIR    The destination directory, not needed with INFO\n"	\
  "PASSWORD   A password for encrypted archives\n"			\
  "FILE       Multiple filenames (with patterns) to be extracted\n"	\
  "NOEXTERN   Turns off usage of external clients\n"			\
  "INFO       Shows archive information without extracting\n"		\
  "OVERWRITE  Files are overwritten without asking\n"			\
  "NOTREE     Files are extracted without subdirectories\n"		\
  "ASKMAKEDIR You get asked before a directory is created\n"		\
  "NOCOMMENT  No filenote comments are extracted or displayed\n"	\
  "NOPROT     Protection information gets not extracted\n"		\
  "NODATE     Creation date information gets not extracted\n"		\
  "NOABS      Do not extract absolute path name parts\n"

struct xHookArgs {
  STRPTR name;
  ULONG flags;
  ULONG finish;
};

struct Args {
  STRPTR   from;
  STRPTR   destdir;
  STRPTR   password;
  STRPTR * file;
  ULONG    noextern;
  ULONG    info;
  ULONG    overwrite;
  ULONG    notree;
  ULONG    askmakedir;
  ULONG    nocomment;
  ULONG    noprot;
  ULONG    nodate;
  ULONG    noabs;
};

ASM(ULONG) progrhook(REG(a0, struct Hook *),
  REG(a1, struct xadProgressInfo *));
LONG CheckName(STRPTR *pat, STRPTR name);

ULONG start(void)
{
  ULONG ret = RETURN_FAIL;
  struct DosLibrary *dosbase;

  SysBase = (*((struct ExecBase **) 4));
  { /* test for WB and reply startup-message */
    struct Process *task;
    if(!(task = (struct Process *) FindTask(0))->pr_CLI)
    {
      WaitPort(&task->pr_MsgPort);
      Forbid();
      ReplyMsg(GetMsg(&task->pr_MsgPort));
      return RETURN_FAIL;
    }
  }

  if((dosbase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
  {
    LONG err = 0;
    struct xadMasterBase *xadmasterbase;

    DOSBase = dosbase;
    if((xadmasterbase = (struct xadMasterBase *)
    OpenLibrary("xadmaster.library", 1)))
    {
      struct Args args;
      struct RDArgs *rda;
      
      memset(&args, 0, sizeof(struct Args));
      xadMasterBase = xadmasterbase;

      if((rda = (struct RDArgs *) AllocDosObject(DOS_RDARGS, 0)))
      {
        rda->RDA_ExtHelp = OPTIONS;

        if(ReadArgs(PARAM, (LONG *) &args, rda))
        {
	  if(args.destdir || args.info)
	  {
	    struct xadArchiveInfo *ai;
	
	    if((ai = (struct xadArchiveInfo *)
	    xadAllocObjectA(XADOBJ_ARCHIVEINFO, 0)))
	    {
	      if(!(err = xadGetInfo(ai, XAD_INFILENAME, args.from,
	      XAD_NOEXTERN, args.noextern, args.password ? XAD_PASSWORD :
	      TAG_IGNORE, args.password, TAG_DONE)))
	      {
	        if(ai->xai_Flags & XADAIF_FILECORRUPT)
	          Printf("!!! The archive file has some corrupt data. !!!\n");
	        if(args.info)
	        {
	          struct xadFileInfo *xfi;
	          ULONG grsize = 0;
	          Printf("ArchiverName:   %s\n"
		  "Size     CrndSize Ratio Date       Time     Name\n",
		  ai->xai_Client->xc_ArchiverName);

	          xfi = ai->xai_FileInfo;
	          while(xfi && !CTRL_C)
	          {
	            if(!(xfi->xfi_Flags & XADFIF_GROUPED))
	              grsize = 0;
		    if(xfi->xfi_Flags & XADFIF_DIRECTORY)
		    {
		      if(!args.notree)
	                Printf("   <dir>    <dir>       %02ld.%02ld.%04ld %02ld:%02ld:%02ld %s\n",
	                xfi->xfi_Date.xd_Day, xfi->xfi_Date.xd_Month,
	                xfi->xfi_Date.xd_Year, xfi->xfi_Date.xd_Hour,
	                xfi->xfi_Date.xd_Minute, xfi->xfi_Date.xd_Second,
	                xfi->xfi_FileName);
	            }
		    else if(xfi->xfi_Flags & XADFIF_GROUPED)
		    {
	              Printf("%8ld   merged  n/a  %02ld.%02ld.%04ld %02ld:%02ld:%02ld %s\n",
	              xfi->xfi_Size, xfi->xfi_Date.xd_Day, xfi->xfi_Date.xd_Month,
	              xfi->xfi_Date.xd_Year, xfi->xfi_Date.xd_Hour,
	              xfi->xfi_Date.xd_Minute, xfi->xfi_Date.xd_Second,
	              args.notree ? FilePart(xfi->xfi_FileName) :
	              xfi->xfi_FileName);
	              grsize += xfi->xfi_Size;
	              if(xfi->xfi_Flags & XADFIF_ENDOFGROUP)
	              {
		        ULONG i = 0, j = 0;
		      
		        if(xfi->xfi_GroupCrSize < grsize)
		        {
		          i = ((grsize - xfi->xfi_GroupCrSize)*1000)/grsize;
		          j = i % 10;
		          i /= 10;
		        }
	                Printf("%8ld %8ld %2ld.%1ld%%\n", grsize, xfi->xfi_GroupCrSize,
	                i, j);
	                grsize = 0;
	              }
		    }
		    else
		    {
		      ULONG i = 0, j = 0;
		      
		      if(xfi->xfi_CrunchSize < xfi->xfi_Size)
		      {
		        i = ((xfi->xfi_Size - xfi->xfi_CrunchSize)*1000)/xfi->xfi_Size;
		        j = i % 10;
		        i /= 10;
		      }
	             
	              Printf("%8ld %8ld %2ld.%1ld%% %02ld.%02ld.%04ld %02ld:%02ld:%02ld %s\n",
	              xfi->xfi_Size, xfi->xfi_CrunchSize, i, j,
	              xfi->xfi_Date.xd_Day, xfi->xfi_Date.xd_Month,
	              xfi->xfi_Date.xd_Year, xfi->xfi_Date.xd_Hour,
	              xfi->xfi_Date.xd_Minute, xfi->xfi_Date.xd_Second,
	              args.notree ? FilePart(xfi->xfi_FileName) :
	              xfi->xfi_FileName);
	            }
	            if(xfi->xfi_Comment && !args.nocomment)
	              Printf(": %s\n", xfi->xfi_Comment);
#ifdef DEBUG
		    if(xfi->xfi_Flags)
		    {
	              Printf("Flags:          ");
	              if(xfi->xfi_Flags & XADFIF_CRYPTED)
	                Printf("XADFIF_CRYPTED ");
	              if(xfi->xfi_Flags & XADFIF_DIRECTORY)
	                Printf("XADFIF_DIRECTORY ");
	              if(xfi->xfi_Flags & XADFIF_LINK)
	                Printf("XADFIF_LINK ");
	              if(xfi->xfi_Flags & XADFIF_INFOTEXT)
	                Printf("XADFIF_INFOTEXT ");
	              if(xfi->xfi_Flags & XADFIF_GROUPED)
	                Printf("XADFIF_GROUPED ");
	              if(xfi->xfi_Flags & XADFIF_ENDOFGROUP)
	                Printf("XADFIF_ENDOFGROUP ");
	              if(xfi->xfi_Flags & XADFIF_NODATE)
	                Printf("XADFIF_NODATE ");
	              Printf("\n");
	            }
#endif
	            if(xfi->xfi_Flags & XADFIF_CRYPTED)
	              Printf("The entry is encrypted\n");
	            xfi = xfi->xfi_Next;
	          }
	          ret = 0;
	        }
	        else
	        {
		  struct Hook prhook;
		  struct xadFileInfo *fi;
		  UBYTE filename[256];
		  struct xHookArgs xh;
		
		  ret = 0;
		  xh.name = filename;
		  xh.flags = xh.finish = 0;

	          memset(&prhook, 0, sizeof(struct Hook));
	          prhook.h_Entry = (ULONG (*)()) progrhook;
	          prhook.h_Data = &xh;
		  fi = ai->xai_FileInfo;
		  while(fi && !CTRL_C && !xh.finish)
		  {
		    if(!args.file || CheckName(args.file, args.notree ?
		    FilePart(fi->xfi_FileName) : fi->xfi_FileName))
		    {
		      CopyMem(args.destdir, filename, strlen(args.destdir)+1);
		      if(args.notree)
		        AddPart(filename, FilePart(fi->xfi_FileName), 256);
		      else if(!args.noabs)
		        AddPart(filename, fi->xfi_FileName, 256);
		      else
		      {
		        STRPTR fname = filename, f;

			if(*args.destdir)
			{
		          fname += strlen(args.destdir)-1;
		          if(*fname != ':' && *fname != '/')
		            *(++fname) = '/';
		          ++fname;
		        }
		        for(f = fi->xfi_FileName; *f; ++f)
		          *(fname++) = *f == ':' ? '/' : *f;
		        *fname = 0;
		      }
		      if(fi->xfi_Flags & XADFIF_DIRECTORY)
		      {
		        if(!args.notree)
		        {
		          BPTR a;
		          LONG err = 0, i = 0;
		          UBYTE r;
    			  while(filename[i] && !err)
    			  {
      			    for(;filename[i] && filename[i] != '/'; ++i)
        		      ;
      			    r = filename[i];
      			    filename[i] = 0;
			    if((a = Lock(filename, SHARED_LOCK)))
          		      UnLock(a);
        		    else if((a = CreateDir(filename)))
            		      UnLock(a);
          		    else
            		      err = 1;
            		    filename[i++] = r;
		          }
		          if(err)
		            Printf("failed to create directory '%s'\n",
		            fi->xfi_FileName);
		          else
		            Printf("Created directory   : %s\n", filename);
		        }
		      } 
		      else if(fi->xfi_Flags & XADFIF_LINK)
		      {
		        Printf("Skipped Link\n");
		      }
		      else
		      {
		        struct DateStamp d;

		        if(!xadFileUnArc(ai, XAD_OUTFILENAME, filename,
	                XAD_ENTRYNUMBER, fi->xfi_EntryNumber, XAD_MAKEDIRECTORY,
	                !args.askmakedir, XAD_OVERWRITE, args.overwrite,
	                XAD_PROGRESSHOOK, &prhook, TAG_DONE))
	                {
	                  if(!args.nodate && !(fi->xfi_Flags & XADFIF_NODATE)
	                  && !xadConvertDates(XAD_DATEXADDATE, &fi->xfi_Date,
	                  XAD_GETDATEDATESTAMP, &d, TAG_DONE))
	                    SetFileDate(filename, &d);
	                  if(!args.noprot)
	                    SetProtection(filename, fi->xfi_Protection);
	                  if(fi->xfi_Comment && !args.nocomment)
	                    SetComment(filename, fi->xfi_Comment);
	                  /* SetOwner ??? */
	                }
	              }
	            }
	            fi = fi->xfi_Next;
	          }
	        }
	        xadFreeInfo(ai);
	      } /* xadGetInfo */

	      xadFreeObjectA(ai, 0);
            } /* xadAllocObject */
          }
          else
            SetIoErr(ERROR_REQUIRED_ARG_MISSING);

          FreeArgs(rda);
        } /* ReadArgs */
        FreeDosObject(DOS_RDARGS, rda);
      } /* AllocDosObject */

      if(CTRL_C)
        SetIoErr(ERROR_BREAK);

      if(err)
	Printf("An error occured: %s\n", xadGetErrorText(err));
      else if(ret)
        PrintFault(IoErr(), 0);

      CloseLibrary((struct Library *) xadmasterbase);
    } /* OpenLibrary xadmaster */
    else
      Printf("Could not open xadmaster.library\n");
    CloseLibrary((struct Library *) dosbase);
  } /* OpenLibrary dos */
  return ret;
}

/* Because of SAS-err, this cannot be SAVEDS */
ASM(ULONG) progrhook(REG(a0, struct Hook *hook),
REG(a1, struct xadProgressInfo *pi))
{
  ULONG ret = 0;
  STRPTR name = ((struct xHookArgs *) (hook->h_Data))->name;

  switch(pi->xpi_Mode)
  {
  case XADPMODE_ASK:
    ret |= ((struct xHookArgs *) (hook->h_Data))->flags;
    if((pi->xpi_Status & XADPIF_OVERWRITE) && !(ret & XADPIF_OVERWRITE))
    {
      Printf("File '%s' already exists, overwrite? (Y|A|S|\033[1mN\033[0m|Q): ",
      name);
      Flush(Output());
      SetMode(Input(), TRUE);
      switch(FGetC(Input()))
      {
      case 'a': case 'A':
	((struct xHookArgs *) (hook->h_Data))->flags |= XADPIF_OVERWRITE;
      case 'y': case 'Y': ret |= XADPIF_OVERWRITE; break;
      case 's': case 'S': ret |= XADPIF_SKIP; break;
      case 'q': case 'Q': ((struct xHookArgs *) (hook->h_Data))->finish = 1;
      }
      SetMode(Input(), FALSE);
    }
    if((pi->xpi_Status & XADPIF_MAKEDIRECTORY) &&
    !(ret & XADPIF_MAKEDIRECTORY))
    {
      Printf("Directory of file '%s' does not exist, create? (Y|A|S|\033[1mN\033[0m|Q): ",
      name);
      Flush(Output());
      SetMode(Input(), TRUE);
      switch(FGetC(Input()))
      {
      case 'a': case 'A':
	((struct xHookArgs *) (hook->h_Data))->flags |= XADPIF_MAKEDIRECTORY;
      case 'y': case 'Y': ret |= XADPIF_MAKEDIRECTORY; break;
      case 's': case 'S': ret |= XADPIF_SKIP; break;
      case 'q': case 'Q': ((struct xHookArgs *) (hook->h_Data))->finish = 1;
      }
      SetMode(Input(), FALSE);
    }
    break;
  case XADPMODE_PROGRESS:
    Printf("\r\033[KWrote %8ld of %8ld bytes: %s",
    pi->xpi_CurrentSize, pi->xpi_FileInfo->xfi_Size, name);
    Flush(Output());
    break;
  case XADPMODE_END: Printf("\r\033[KWrote %8ld bytes: %s\n",
    pi->xpi_CurrentSize, name);
    break;
  case XADPMODE_ERROR: Printf("\r\033[K%s: %s\n", name,
    xadGetErrorText(pi->xpi_Error));
    break;
  }

  if(!CTRL_C) /* clear ok flag */
    ret |= XADPIF_OK;

  return ret;
}

/* would be better to store the pattern parse stuff and do it only once,
but so it is a lot easier */
LONG CheckName(STRPTR *pat, STRPTR name)
{
  UBYTE buf[500];
  while(*pat)
  {
    if(ParsePatternNoCase(*(pat++), buf, 500) >= 0)
    {
      if(MatchPatternNoCase(buf, name))
        return 1;
    } /* A scan failure means no recognition, should be an error print here */
  }
  return 0;
}
