/*
** Name: CompareDirs (short: compd)
** Description: Compares two directories to find differences
** Author: Martin Gierich
** $VER: compd 1.1 (5.10.97)
** Copying: Freeware, no commercial usage. Use it at your own risc.
**
** History:
** V1.0 (10.9.97): Initial release
** V1.1 (5.10.97): ReadArgs() interface added, NOIDENT option added
*/

#include <string.h>
#include <stdlib.h>
#include <dos.h>

#include <exec/types.h>
#include <dos/rdargs.h>

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


#define TEMPLATE "DIR1/A,DIR2/A,RESULTFILE,NOIDENT/S"
enum
{
	OPT_Dir1,	//first dir to scan
	OPT_Dir2,	//second dir to scan
	OPT_Res,	//filename to write results to
	OPT_NoId,	//do not show identical files
	NUM_Opts
};


struct entry
{
 struct entry *next;
 long size;
 long othersize;
 char name[0];
};

int  fastmemcmp(char *buffer1, char *buffer2, long size);
BOOL scandir(char dirname[], struct entry **root);
BOOL scanfiles(struct entry **files, char *dirname, char *fullname, char* partname);
BOOL insertfile(struct entry **files, char *name, long size);
void showfiles(struct entry *files);
BOOL checkfiles(char *dir1, char* dir2, struct entry *root1, struct entry *root2, BOOL noident);
BOOL comparefile(struct entry *file1, struct entry *file2, BPTR lock1, BPTR lock2, char *buffer);
void closeoutfile(void);


#define printf Printf
#define fprintf FPrintf
extern const char *version="$VER: CompareDirs 1.1 (5.10.97)\n";
const long bufsize=500000L;
BPTR outfile;




/* Main program parses commandline and calls subroutines */
int main (void)
{
 struct entry *root1=NULL, *root2=NULL;
 LONG opts[NUM_Opts];
 struct RDArgs *rdargs;

 memset (opts, 0, sizeof(opts));
 if (rdargs=ReadArgs(TEMPLATE, opts, NULL))
 {
  if (scandir((char *)opts[OPT_Dir1], &root1)) return(20);
  if (scandir((char *)opts[OPT_Dir2], &root2)) return(20);
  //printf("\n\nFiles in 1:\n\n"); showfiles(root1);
  //printf("\n\nFiles in 2:\n\n"); showfiles(root2);

  if (opts[OPT_Res])
  {
   outfile=Open((char *)opts[OPT_Res], MODE_NEWFILE);
   if (!outfile)
   {
    printf("Error opening %s !\n", opts[OPT_Res]);
    FreeArgs(rdargs);
    return(20);
   }
   if ( !onexit(closeoutfile) )
   {
    printf("No trap !\n");
    Close(outfile);
    FreeArgs(rdargs);
    return(20);
   }
  }
  else
    outfile=Output();

  if ( checkfiles((char *)opts[OPT_Dir1], (char *)opts[OPT_Dir2],
                  root1, root2, (BOOL)opts[OPT_NoId]) )
  {
   FreeArgs(rdargs);
   return(20);
  }

  FreeArgs(rdargs);
  return(0);
 }
 else
 {
  PrintFault (IoErr (), NULL);
  return(20);
 }

}



/* Can be called on exit */
void closeoutfile(void)
{
 Close(outfile);
}



/* Scan all files in a directory */
BOOL scandir(char dirname[], struct entry **root)
{
 static char fullname[1000];

 printf("Scanning directory %s\n", dirname);
 strcpy(fullname, dirname);

 if (scanfiles(root, fullname, fullname, fullname))
 {
  printf("Error during scanning %s !\n", dirname);
  return(TRUE);
 }

 return(FALSE);
}



/* Recursively scan all files and dirs */
BOOL scanfiles(struct entry **files, char* dirname, char *fullname, char* partname)
{
 __aligned struct FileInfoBlock fib;
 BPTR lock;
 LONG len;

 if (! (lock=Lock(dirname, ACCESS_READ)) )
 {
  printf("Cannot find %s !\n", fullname);
  return(TRUE);
 }

 if (!Examine(lock, &fib))
 {
  printf("Examining %s failed !\n", fullname);
  UnLock(lock);
  return(TRUE);
 }

 while (ExNext(lock, &fib))
 {
  strcpy(partname, fib.fib_FileName);

  if (fib.fib_DirEntryType>=0 && fib.fib_DirEntryType!=3)
  {
   lock=CurrentDir(lock);
   len=strlen(partname);
   partname[len]='/';
   partname[len+1]='\0';
   if (insertfile(files, fullname, 0L))
   {
    lock=CurrentDir(lock);
    UnLock(lock);
    return(TRUE);
   }
   if (scanfiles(files, partname, fullname, partname+len+1))
   {
    lock=CurrentDir(lock);
    UnLock(lock);
    return(TRUE);
   }
   lock=CurrentDir(lock);
  }

  else
  {
   if (insertfile(files, fullname, fib.fib_DirEntryType==3 ?  0 : fib.fib_Size))
   {
    UnLock(lock);
    return(TRUE);
   }
  }

 }

 UnLock(lock);
 if (IoErr()!=ERROR_NO_MORE_ENTRIES) return(TRUE);
 return(FALSE);
}



/* Insert a new filename into the list */
BOOL insertfile(struct entry **files, char *name, long size)
{
 struct entry *newfile, *file, *prevfile;

 chkabort();
 //printf(" %s\n", name);
 newfile=malloc(sizeof(struct entry)+strlen(name)+1);
 if (!newfile)
 {
  printf("Out of memory !\n");
  return(TRUE);
 }
 newfile->size=size;
 newfile->othersize=-1L;
 strcpy(newfile->name, name);

 prevfile=(struct entry *)files;
 file=prevfile->next;
 while ( file!=NULL && Stricmp(name, file->name)<0 )
 {
  prevfile=file;
  file=file->next;
 }
 newfile->next=file;
 prevfile->next=newfile;
 return(FALSE);
}

/* Show file list */
void showfiles(struct entry *files)
{
 struct entry *file;

 file=files;
 while (file!=NULL)
 {
  chkabort();
  if (file->othersize!=-1)
    fprintf(outfile, " %s  (%ld <-> %ld)\n", file->name, file->size, file->othersize);
  else
    fprintf(outfile, " %s  (%ld)\n", file->name, file->size);
  file=file->next;
 }
}



/* Check dir structure and files for differences */
BOOL checkfiles(char *dir1, char *dir2, struct entry *file1, struct entry *file2, BOOL noident)
{
 struct entry *ident=NULL, *onlyin1=NULL, *onlyin2=NULL, *differ=NULL;
 struct entry *nfile1, *nfile2;
 int rel;
 BPTR lock1, lock2;
 char *buffer;

 printf("Comparing files\n");
 lock1=Lock(dir1, ACCESS_READ);
 if (!lock1)
 {
  printf("Error accessing %s !\n", dir1);
  return(TRUE);
 }

 lock2=Lock(dir2, ACCESS_READ);
 if (!lock2)
 {
  printf("Error accessing %s !\n", dir2);
  UnLock(lock1);
  return(TRUE);
 }
 
 buffer=halloc(2*bufsize);
 if (!buffer)
 {
  printf("Out of memory !\n");
  UnLock(lock1);
  UnLock(lock2);
  return(TRUE);
 }

  while (file1!=NULL && file2!=NULL)
  {
   nfile1=file1->next;
   nfile2=file2->next;
   rel=Stricmp(file1->name, file2->name);

   if (rel==0)
   {
    if ( comparefile(file1, file2, lock1, lock2, buffer) )
    {
     file1->next=ident;
     ident=file1;
    }
    else
    {
     file1->next=differ;
     differ=file1;
    }
    file1=nfile1;
    file2=nfile2;
   }

   else if (rel>0)
   {
    file1->next=onlyin1;
    onlyin1=file1;
    file1=nfile1;
   }

   else
   {
    file2->next=onlyin2;
    onlyin2=file2;
    file2=nfile2;
   }

  }

  while (file1!=NULL)
  {
   nfile1=file1->next;
   file1->next=onlyin1;
   onlyin1=file1;
   file1=nfile1;
  }

  while (file2!=NULL)
  {
   nfile2=file2->next;
   file2->next=onlyin2;
   onlyin2=file2;
   file2=nfile2;
  }

 free(buffer);
 UnLock(lock1);
 UnLock(lock2);

 printf("Showing results\n");
 if (!noident)
 {
  fprintf(outfile, "\n\nIdentical files:\n\n");
  showfiles(ident);
 }
 fprintf(outfile, "\n\nFiles only in %s :\n\n", dir1);
 showfiles(onlyin1);
 fprintf(outfile, "\n\nFiles only in %s :\n\n", dir2);
 showfiles(onlyin2);
 fprintf(outfile, "\n\nDifferent files:\n\n");
 showfiles(differ);

 return(FALSE);
}



/* Compare two files byte by byte */
BOOL comparefile(struct entry *file1, struct entry *file2, BPTR lock1, BPTR lock2, char *buffer1)
{
 BPTR fh1, fh2;
 BPTR oldlock;
 BOOL result=FALSE;
 LONG res1, res2;
 char *name;
 char *buffer2;

 chkabort();
 if (file1->size!=file2->size)
 {
  file1->othersize=file2->size;
  goto exit2;
 }
 if (!file1->size) return(TRUE);
 name=file1->name;

 oldlock=CurrentDir(lock1);
 fh1=Open(name, MODE_OLDFILE);
 if (!fh1)
 {
  printf("Error opening %s !\n", name);
  CurrentDir(oldlock);
  goto exit2;
 }

 CurrentDir(lock2);
 fh2=Open(name, MODE_OLDFILE);
 if (!fh2)
 {
  printf("Error opening %s !\n", name);
  CurrentDir(oldlock);
  goto exit3;
 }
 CurrentDir(oldlock);

 buffer2=buffer1+bufsize;
 do
 {
  res1=Read(fh1, buffer1, bufsize);
  res2=Read(fh2, buffer2, bufsize);
  if (res1==-1 || res2==-1)
  {
   printf("Read error in %s !", name);
   goto exit4;
  }
  if (res1!=res2)
  {
   file1->othersize=file2->size;
   goto exit4;
  }
  if (res1 && fastmemcmp(buffer1, buffer2, res1)) goto exit4;
 }
 while (res1);
 result=TRUE;

 exit4: Close(fh2);
 exit3: Close(fh1);
 exit2: return(result);
}

