/*
** ENVMan V1.4
** -----------
**
** ENVMan is a program that copies the contens of ENVARC: to ENV: 
** It will only copy things that has changed.
**
** 
*/

#include	<exec/types.h>
#include	<exec/memory.h>
#include	<exec/nodes.h>
#include  <exec/lists.h>

#include	<dos/dos.h>
#include	<dos/exall.h>

#include	<proto/dos.h>
#include	<proto/exec.h>
#include	<proto/utility.h>

/*
extern	struct	Library			*DOSBase=NULL; 
extern	struct 	Library			*UtilityBase=NULL;
*/

/* some defines */
#define	TEMPLATE						"CB=COPYBUFFER/K/N,B=BUFFERS/K/N,ND=NODELETE/S,UPDATE/S,TEST/S"
enum{		ARG_CBUFFER	=	0,
				ARG_BUFFERS,
				ARG_NODELETE,
				ARG_UPDATE,
				ARG_TEST,
				ARG_COUNT
};

#define MAX_DIRNAMELEN			128
#define MAX_FILENAMELEN			64

const char		*ENVARC							= "ENVARC:";
const char		*ENV								=	"ENV:";

/* Version string */
char		*VERSION = "$VER: ENVMan V1.4 by W. Pernath (28.12.96)\0!";


/* disabling gcc´s commandline parser */
int	__nocommandline=0;

/* disabling gcc´s autoinit libs feature */
int __initlibraries=1;


/* Structure for use with nodes */

struct	FileNode
{
	struct		MinNode		fln_Node;
	struct		DateStamp	fln_Date;
	LONG								fln_Type;												
	ULONG								fln_Size;
	ULONG								fln_NumEntries;								/* specified, if Type >0 */
	char								fln_DirName[MAX_DIRNAMELEN];
	char								fln_FileName[MAX_FILENAMELEN];
};


/* preused vars */
ULONG							DEF_BUFSIZE		=	2048;
ULONG							DEF_BUFFERS		=	10;
ULONG							DEF_TEST			=	FALSE;
ULONG							DEF_UPDATE		=	FALSE;
ULONG							DEF_NODELETE	=	FALSE;


/* global variables */
ULONG							bufsize;
ULONG							buffers;
ULONG							nodelete, test, update;

APTR							Buffer=NULL;

ULONG							largest=0;
ULONG							nesting=0;
ULONG							files=0, dirs=0;

char							CurrentDirName[ 512 ];
char							CurrentFileName[ 1024 ];						
struct	MinList		EnvArcList;
struct	MinList		EnvList;
struct	FileNode	*CurrentDirNode=NULL;

																
/* Prototypes */

int main( void );
ULONG CalcNesting( struct MinList *);
void ProcessENV( void );
void GetFileList( struct MinList *, BPTR, char *, BOOL);
BPTR OpenEnv( struct FileNode * );
BPTR OpenEnvArc( struct FileNode * );
void CopyCloned( struct FileNode * );


/*
** CopyCloned() copies the file Name from ENVARC: to ENV:
** and sets its create time to date.
** For the buffer it allocates BufSize Bytes.
*/
void CopyCloned( struct FileNode *Target )
{
	BPTR			 inFile=NULL, outFile=NULL;
	ULONG			 size=Target->fln_Size < bufsize ? Target->fln_Size : bufsize;
	int				 erg;
	
	if ( inFile = OpenEnvArc( Target ) )				
	{
		if ( outFile = OpenEnv( Target ) )		
		{
			erg = Read( inFile, Buffer, size );  
			
			Write( outFile, Buffer, erg );
			
			if ( erg != 0 )
			{
				/* 
				** EOF not reached? 
				** Try to read and write until EOF.
				*/
			  while ( ( erg = Read( inFile, Buffer, size ) ) != 0 )
			  {				  				  	
			  	Write( outFile, Buffer, erg );
			  }						  
			}
			Close( outFile );
		}
		Close( inFile );				
		SetFileDate( CurrentFileName, (struct DateStamp *)&Target->fln_Date );					
	}			
}
 
 
/*
** OpenEnv(). Opens a FileName in the CurrentDirName (MODE_NEWFILE).
*/
BPTR OpenEnv( struct FileNode *Node )
{		
	strcpy( CurrentFileName, ENV );
	strcat( CurrentFileName, Node->fln_DirName );
	strcat( CurrentFileName, Node->fln_FileName );
	
	return( Open( CurrentFileName, MODE_NEWFILE ) );
}

/*
** OpenEnvArc(). Opens a FileName in the CurrentDirName (MODE_OLDFILE).
*/
BPTR OpenEnvArc( struct FileNode *Node )
{		
	strcpy( CurrentFileName, ENVARC );
	strcat( CurrentFileName, Node->fln_DirName );
	strcat( CurrentFileName, Node->fln_FileName );
	
	return( Open( CurrentFileName, MODE_OLDFILE ) );
}
	
	
/*
** GetFileList(). It processes the global ENV/ENVARC dir-tree.
** Warning this function calls itself for each subdir it finds.
*/
void GetFileList( struct MinList *list, BPTR rootdir, char *template, BOOL calc )
{
	struct	ExAllControl	*eac=NULL;
	struct	ExAllData			*ead=NULL, *current=NULL;
	struct	FileNode			*node=NULL;
	BPTR									file, dir=NULL;
	BOOL									done=FALSE;
	WORD									i, erg, len;
	
	if ( eac = AllocDosObject( DOS_EXALLCONTROL, NULL ) )
	{
  	if ( ead = (struct ExAllData *)AllocVec( buffers * sizeof( struct ExAllData ), MEMF_ANY ) )
  	{
			/* Prepare the ExAllControl struct. */
		
			eac->eac_LastKey     = 0;
			eac->eac_Entries 		 = 0;
			eac->eac_MatchString = template;
 		
      /* Stepping into the main loop */
      while( !done )
      {
        erg = ExAll( rootdir, ead, buffers * sizeof( struct ExAllData ), ED_DATE, eac );
        
        if ( (!erg)  && ( IoErr()!=ERROR_NO_MORE_ENTRIES) ) 
        {	
        	done=TRUE;          
        }
        else
        {
        	if (!eac->eac_Entries)  
         	{
         		done = TRUE;
         	}
         	else
         	{         			
         		/* test the contens */
         		current = ead;
         		
       			while (current)		
       	    { 
       	    	/* allocate a new Node Structure */
       	    	if ( node = AllocVec( sizeof( struct FileNode ), MEMF_CLEAR|MEMF_ANY ) )
       	    	{
       	    		
       	    		/* Enter fields which are the same */
       	    		node->fln_Date.ds_Days 		= current->ed_Days;
       	    		node->fln_Date.ds_Minute 	= current->ed_Mins;
       	    		node->fln_Date.ds_Tick		= current->ed_Ticks;
       	    		node->fln_Type 						= current->ed_Type;
       	    																										
        	    	if (current->ed_Type < 0)
          	    {
         	    		/* Entry is a file */
          	    	node->fln_Size = current->ed_Size;
									strcpy( (char *)node->fln_FileName, current->ed_Name );
									strcpy( (char *)node->fln_DirName, CurrentDirName );
									
									if ( calc ){
										if ( largest < current->ed_Size ) largest = current->ed_Size;
										files++;
									}
          	    }
          	    
          	    else if ( current->ed_Type > 0 )
          	    {
          	    	/* Entry is a dir */
          	    	
          	    	/* Add to the current DirName the Name of this Dir plus an "/" */
          	    	strcat( CurrentDirName, current->ed_Name );															
          	    	strcat( CurrentDirName, "/" );
		
									strcpy( (char *)node->fln_DirName, CurrentDirName );

									if (calc ) dirs++;
									
 									len = strlen( current->ed_Name ) + 1;
 	         	    	
 	         	    	if ( dir = Lock( CurrentDirName, ACCESS_READ ) )
          	    	{
          	    	          	    		          	    		
 	         	    		GetFileList( list, dir, template, calc );
 	         	    												 	         	    			         	    		
 										UnLock( dir );									
 										dir = NULL;
 									}
 									/* remove from the current DirName the name of this Dir and the "/" */									
 									CurrentDirName[ strlen( CurrentDirName ) - len ] = '\0';
 								}
 								AddHead( (struct List *)list, (struct Node *)node );
 							}          	 
         	   	current = current->ed_Next;
         	  }
          }		
	      }
	    }
	    FreeVec( ead );
	  }
	  FreeDosObject( DOS_EXALLCONTROL, eac );
	}
}	


struct FileNode *GetNodeByName( struct MinList *list, struct FileNode *from )
{
	struct	FileNode	*node, *erg=NULL;
	BOOL							done=FALSE;
	
	if ( node = (struct FileNode *)list->mlh_Head )
	{
		while( ( node->fln_Node.mln_Succ != NULL ) && (!done ) )
		{
			if ( from->fln_Type > 0 )
			{
				if ( !Stricmp( (char *)from->fln_DirName, (char *)node->fln_DirName ) )
				{
					done = TRUE;
					erg	 = node;
					break;
				}
			}
			else if ( from->fln_Type < 0 ) 
			{
				
				if ( !Stricmp( (char *)from->fln_DirName, (char *)node->fln_DirName ) )
				{				
					if ( !Stricmp( (char *)from->fln_FileName, (char *)node->fln_FileName ) )
					{			
  					done = TRUE;
			  		erg = node;
			  		break;
			  	}
			  }
			}
			node = (struct FileNode *)node->fln_Node.mln_Succ;
		}
	}
	return ( erg );
}

/* 
** ParseLists(). This function parses the two generated Lists.
** It will look into the contens for any Changes/Updates/etc.
*/
void ParseLists( struct MinList *from, struct MinList *to )
{
	struct	FileNode *dummy, *node, *res;
	BPTR		dir=NULL;
	int			i;
	
	if (node = (struct FileNode *)from->mlh_Head ) 
	{
		while ( node->fln_Node.mln_Succ != NULL )
		{
			dummy = (struct FileNode *)node->fln_Node.mln_Succ;
			
			res = GetNodeByName( to, node );
			if (!res )
			{
				/* Entry does not exists */
				
				if ( node->fln_Type > 0 )
				{
					/* Node is a Dir entry. */
					
					if (test) 
						Printf("Makedir ENV:%s\n", (char *)node->fln_DirName );
					else
					{
						strcpy( (char *)CurrentDirName, ENV );
						strcat( (char *)CurrentDirName, node->fln_DirName );
						/* strip the last "/" */
						CurrentDirName[ strlen( CurrentDirName ) - 1 ] = '\0';
						dir = CreateDir( CurrentDirName );
						if ( dir ) UnLock( dir );
					}
				}
				else
				{
					/* Node is a file entry */
					
					if (test ) 
						Printf("Copy ENVARC:%s%s TO ENV:%s%s\n", 
														(char *)node->fln_DirName, (char *)node->fln_FileName, 
														(char *)node->fln_DirName, (char *)node->fln_FileName);
					else
					{
						CopyCloned( node );
					}
				}
			}		
			else
			{
				if ( node->fln_Type < 0 )
				{
    			/* Entry does exists. */
    			i = CompareDates( (struct DateStamp *)&node->fln_Date, (struct DateStamp *)&res->fln_Date ); 
    			if ( ( i<0 ) || ( i>0 ) )
    			{
    				/* The Entries are not created at equal dates. */
    				if (!test ) 
    					CopyCloned( node );
    				else
							Printf("Copy ENVARC:%s%s TO ENV:%s%s CLONE\n", 
														(char *)node->fln_DirName, (char *)node->fln_FileName, 
														(char *)node->fln_DirName, (char *)node->fln_FileName);

    			}
    		}
			}
			node = dummy;
		}
	}
}

void FreeList( struct MinList *list )
{
	struct FileNode *dummy, *node;

	if ( node = (struct FileNode *)list->mlh_Head )	
	{	
		while ( node->fln_Node.mln_Succ != NULL )
		{
			dummy = (struct FileNode *)node->fln_Node.mln_Succ;
			FreeVec( node );
			node = dummy;
		}
	}
}

/*
** DeleteUnused(). It searches the lists and deletes all files/dirs
** that are no longer in the to-List.
** NOTE: We have to parse the lists end-to-top, because the dirs
** will appear at the top and we should delete the files at first.
*/
void DeleteUnused( struct MinList *from, struct MinList *to )
{
	struct	FileNode *dummy, *node, *res;
	BOOL							erg=TRUE;

	if (node = (struct FileNode *)from->mlh_TailPred ) 
	{
		while ( node->fln_Node.mln_Pred != NULL )
		{
			dummy = (struct FileNode *)node->fln_Node.mln_Pred;
			
			res = GetNodeByName( to, node );
		  if (!res)
		  {
		  	/* Node does not longer exists in ENVARC: so delete it in ENV: */
				if (test) 
					Printf("Delete ENV:%s%s\n", (char *)node->fln_DirName, (char *)node->fln_FileName );
				else
				{
					if (node->fln_Type > 0 )
					{
						/* Try to delete the directory */
						strcpy( CurrentFileName, ENV );
						strcat( CurrentFileName, (char *)node->fln_DirName );
						CurrentFileName[ strlen( CurrentFileName ) - 1 ] = '\0';
					}
					else
					{
						strcpy( CurrentFileName, ENV );
						strcat( CurrentFileName, (char *)node->fln_DirName );
						strcat( CurrentFileName, (char *)node->fln_FileName );
					}
					erg=DeleteFile( CurrentFileName );
					if (!erg) Printf("Could not delete %s.\n", (char *)CurrentFileName );					
				}				
			}
			node = dummy;
		}
	}
}


void CopyAll( struct MinList *from )
{
	struct	FileNode *dummy, *node, *res;
	BPTR		dir=NULL;
	int			i;


	if (test ) 
		Printf("Copy the contens of %s to %s.\n", ENVARC, ENV );
	else
	{	
		if (node = (struct FileNode *)from->mlh_Head ) 
		{
			while ( node->fln_Node.mln_Succ != NULL )
			{
				dummy = (struct FileNode *)node->fln_Node.mln_Succ;
				
				if ( node->fln_Type > 0 )
				{
					/* create dir */
					strcpy( CurrentFileName, ENV );
					strcat( CurrentFileName, (char *)node->fln_DirName );
					CurrentFileName[ strlen( CurrentFileName ) - 1 ] = '\0';
					if ( dir = CreateDir( CurrentFileName ) ) 
						UnLock( dir );
				}
				else
				{
					/* copy file */
					CopyCloned( node );
				}
				node = dummy;				
      }
    }
	}	
}

		
void ProcessENV( void )
{
	BPTR									env=NULL, envarc=NULL, olddir;
	
	if ( envarc = Lock( ENVARC, ACCESS_READ ) )
	{
		if ( env = Lock( ENV, ACCESS_READ ) )
		{				
			olddir = CurrentDir( envarc );

			if (test ){
				Printf("Scanning %s...", ENVARC );	
				Flush( Output() );
			}
			
			GetFileList( (struct MinList *)&EnvArcList, envarc, NULL, TRUE );
			if (test ) {
				Printf(" done.\n" );
				nesting = CalcNesting( (struct MinList *)&EnvArcList );
			}
			
			if (!update)
			{

				if (test ){
					Printf("Scanning %s...", ENV );						
					Flush( Output() );
				}
				
				CurrentDir( env );
				GetFileList( (struct MinList *)&EnvList, env, NULL, FALSE );
				if (test ) Printf(" done.\n\n");
				
				ParseLists( (struct MinList *)&EnvArcList, (struct MinList *)&EnvList );
      }
      else
      {
      	CopyAll( (struct MinList *)&EnvArcList );
      }
      
			if (!nodelete){
				DeleteUnused( (struct MinList *)&EnvList, ( struct MinList *)&EnvArcList );
			}			

			CurrentDir( olddir );	
			FreeList( &EnvList );		
			FreeList( &EnvArcList );
		  UnLock( env );			
    }  
  	UnLock( envarc );
  }
}

/* 
** This returns the max. count of files in the 
** subdirs of the supplied ListHeader.
*/
ULONG	CalcNesting( struct MinList *list )
{
	struct	FileNode	*node, *dummy;
	ULONG							res = DEF_BUFFERS,
										cur	=	0,
										lst	= 0;
		
	if ( node = (struct FileNode *)list->mlh_Head )
	{
		while ( node->fln_Node.mln_Succ != NULL )
		{
			dummy = (struct FileNode *)node->fln_Node.mln_Succ;
 
			if ( node->fln_Type > 0 )
			{
				/* current node is a dir. */
				if ( lst <= cur ) lst = cur;
				cur = 0;
			}
			else
			{
				/* current node is a file. */
				cur++;
			}
			
			node = dummy;
		}
		if ( lst <= cur ) lst = cur;
		res = lst < DEF_BUFFERS ? DEF_BUFFERS : lst;
	}
	return( res );
}          

int main( void )
{
	struct	RDArgs		*rdargs=NULL;
	LONG							*(opts[ARG_COUNT]);
	ULONG							mem = 0;

	if ( DOSBase = OpenLibrary( "dos.library", 37 ) )
	{
  	if ( UtilityBase = OpenLibrary( "utility.library", 37 ) )
  	{
      opts[ ARG_CBUFFER ] = &DEF_BUFSIZE;
      opts[ ARG_BUFFERS ] = &DEF_BUFFERS;
      opts[ ARG_NODELETE] = (long *)DEF_NODELETE;
      opts[ ARG_UPDATE  ] = (long *)DEF_UPDATE;
      opts[ ARG_TEST    ] = (long *)DEF_TEST;
      
      if (rdargs = ReadArgs( TEMPLATE, (LONG *)opts, NULL ) )
      {		
      	FreeArgs( rdargs );
      }
      
      bufsize = *opts[ARG_CBUFFER];			
      buffers = *opts[ARG_BUFFERS];
      test		= (ULONG)opts[ARG_TEST];
      nodelete= (ULONG)opts[ARG_NODELETE];
      update  = (ULONG)opts[ARG_UPDATE  ];
      
      if (!bufsize ) bufsize=DEF_BUFSIZE;
      if (!buffers ) buffers=DEF_BUFFERS;
      if (update )   nodelete = TRUE;
      
      NewList( &EnvArcList );
      NewList( &EnvList );
  
  		if ( Buffer = ( APTR )AllocVec( bufsize, MEMF_ANY ) )
  		{
  			if (test ){
  			 	Printf("\nENVMan V1.4 ©1996 by Wanja Pernath.\n\n");
  			 	Printf("Testing with\t%ld Bytes Buffer for copy.\n\t\t%ld Buffers for ExAll()\n", (ULONG) bufsize, (ULONG )buffers );
  			 	Printf("\t\tNODELETE %s\n\t\tUPDATE %s\n\n", nodelete ? "on" : "off", update ? "on" : "off" );  			 	
				}										  			 	

      	ProcessENV();

      	if (test ){
      		
      		/* Print the results and calculate the minimum needed memory. */
      		mem = 8192 + largest + ((((nesting + ( files / dirs ) ) * sizeof( struct ExAllData ) ) + (( dirs + files ) * sizeof( struct FileNode ) )) * 2);
      		
					Printf("\nNumber of Files           : %ld.\n", files );
      		Printf("Number of Directories     : %ld.\n", dirs );
      		Printf("\nLargest Copy-Buffer needed: %ld Bytes.\n", largest ); 
					Printf("ExAll()-Buffers needed    : %ld.\n", nesting );
					Printf("\nUsing this results as options, assumes you have at least %ld Bytes\nof available memory.\n\n", mem );
					
				}
        FreeVec( Buffer );
      }    
    	CloseLibrary( UtilityBase );
    }
    CloseLibrary( DOSBase );
	}
	return( 0 );	
}


