/* Duplicator.c

V1.0 - 15.9.92
V1.1 - 1.4.94: kann unter 1.3 fehlerfrei abbrechen

OS2-Utility

Aufruf:

Duplicator <datei> [...] <verzeichnis>

Duplicator kopiert zunächst die genannten Datein ins genannte Verzeichnis.
Danach überwacht er die Dateien mit Notification auf (Schreib-) Zugriffe und
kopiert sie in diesem Falle erneut. Ein gleichzeitiges Umbenennen der
Dateien ist nicht vorgesehen.

Duplicator kann durch ^c / Break <n> C abgebrochen werden.

Die Größe des Kopierpuffers beträgt BUFFSIZE Bytes. Der Puffer wird nur
für solange alloziert gehalten, wie er benutzt wird.

*/

#define BUFFSIZE 32768

#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/notify.h>
#include <utility/tagitem.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <stdlib.h>
#include <string.h>

struct ExtNotifyRequest {
			struct ExtNotifyRequest *enr_Next;
			BOOL			enr_NotifyStarted;
			struct NotifyRequest	enr_NotifyRequest;
		       };

BPTR			OldOut,Out;
struct ExtNotifyRequest *first_enr=NULL;
struct MsgPort		*MyPort=NULL;

void CleanUp(void)
{
    struct ExtNotifyRequest *tmp_enr;
    struct Message *msg;
    BPTR myout;

    /* Abschied */
    Printf("Duplicator endet"); Flush(Output());
    if(first_enr)
	{
	/* die ExtNotifyRequests sukzessiv freigeben */
	while(tmp_enr=first_enr)
	    {
	    first_enr=first_enr->enr_Next;
	    if(tmp_enr->enr_NotifyStarted)
		EndNotify(&(tmp_enr->enr_NotifyRequest));
	    FreeVec(tmp_enr);
	    }
	}
    /* ggf. den MsgPort freigeben */
    if(MyPort)  DeleteMsgPort(MyPort);
    /* ggf. Ausgabewindow schließen */
    if(myout=SelectOutput(OldOut))
	{
	Delay(150);
	Close(Out);
	}
}

void Error(char *str, void *arg)
{ /* Fehlerausstieg */
    Printf(str,arg);
    CleanUp();
    exit(RETURN_ERROR);
}

LONG DirEntryType(UBYTE *name)
{ /* ermittelt den fib_DirEntryType zum gegebenen Namen, 0-> Fehler */
    BPTR lock;
    struct FileInfoBlock *fib;
    LONG DET=0;

    if(lock=Lock(name,ACCESS_READ))
	{
	if(fib=AllocDosObjectTags(DOS_FIB,TAG_END))
	    {
	    if(Examine(lock,fib))   DET=fib->fib_DirEntryType;
	    FreeDosObject(DOS_FIB,fib);
	    }
	UnLock(lock);
	}
    return(DET);
}

#define IsPath(name) (DirEntryType(name)>0)
#define IsFile(name) (DirEntryType(name)<0)

UBYTE *GetENR(UBYTE *name)
{ /* wenn möglich, einen ExtNotifyRequest allozieren, den MessagePort
    und Namen eintragen und die Notification starten.
    MyPort muß existieren !
    Ergebnis: der Eintrag nr_FullName/0 bei Fehler */
    struct ExtNotifyRequest *enr;

    if(!IsFile(name))
	{
	Printf("%s ist keine (existente) Datei\n",name);
	return(NULL);
	}
    if(!(enr=(struct ExtNotifyRequest *)
	     AllocVec(sizeof(struct ExtNotifyRequest),MEMF_CLEAR)))
	{
	Printf("Duplicator kann den Notify-Request zu %s nicht allozieren\n",name);
	return(NULL);
	}
    enr->enr_Next=first_enr;
    first_enr=enr;
    enr->enr_NotifyStarted=FALSE;
    enr->enr_NotifyRequest.nr_Name=name;
    enr->enr_NotifyRequest.nr_stuff.nr_Msg.nr_Port=MyPort;
    enr->enr_NotifyRequest.nr_Flags=NRF_SEND_MESSAGE;
    enr->enr_NotifyStarted=StartNotify(&(enr->enr_NotifyRequest));
    if(!enr->enr_NotifyStarted)
	{
	Printf("Duplicator kann die Notification von %s nicht beginnen\n",name);
	/* Notify-Request freigeben: */
	first_enr=enr->enr_Next;
	FreeVec(enr);
	return(NULL);
	}
    return(enr->enr_NotifyRequest.nr_FullName);
}

void Copy(UBYTE *from, UBYTE *to)
{ /* Datei des Namens *from nach Pfad *to kopieren */
    UBYTE *toname;
    VOID *FilePuffer;
    int mem_needed;
    BPTR FromFile,ToFile;
    LONG last_read,copied=0;

  Printf("Zu kopieren: %s -> %s\n",from,to);
  mem_needed=strlen(from)+strlen(to)+4;
  if(toname=(UBYTE *)AllocVec(mem_needed,MEMF_CLEAR))
    {
    strcpy(toname,to);
    if(AddPart(toname,FilePart(from),mem_needed))
      {
      if(FilePuffer=AllocVec(BUFFSIZE,MEMF_CLEAR))
	{
	if(FromFile=Open(from,MODE_OLDFILE))
	  {
	  if(ToFile=Open(toname,MODE_NEWFILE))
	    {
	    do{
	      last_read=Read(FromFile,FilePuffer,BUFFSIZE);
	      if(last_read==0)  continue;
	      if(Write(ToFile,FilePuffer,last_read)!=last_read)
		{
		Printf("Schreibfehler\n");
		last_read=0;
		}
	      else copied+=last_read;
	      } while(last_read);
	    Printf("%ld Bytes von %s nach %s kopiert\n",copied,from,toname);
	    Close(ToFile);
	    }
	  else Printf("Duplicator kann Zieldatei %s nicht öffnen\n",toname);
	  Close(FromFile);
	  }
	else Printf("Duplicator kann Quelldatei %s nicht öffnen\n",toname);
	FreeVec(FilePuffer);
	}
      else Printf("Duplicator kann Kopierpuffer nicht allozieren\n");
      }
    else Printf("Dos will einen zu langen Dateinamen basteln\n");
    FreeVec(toname);
    }
  else Printf("Duplicator kann Namenspuffer nicht allozieren\n");
}

void HandleNotifies(char *todir)
{
    register struct NotifyMessage *msg;

    while(msg=(struct NotifyMessage *)GetMsg(MyPort))
	{
	Copy(msg->nm_NReq->nr_FullName,todir);
	ReplyMsg((struct Message *)msg);
	}
}

extern struct Library *DOSBase;

int main(int argc, char *argv[])
{
    int argcnt;
    UBYTE *thisname;
    LONG GotSigs,ValidSigs;

    if(DOSBase->lib_Version<36)
	{
	Write(Output(),"OS2 required\n",13);
	exit(RETURN_FAIL);
	}

    /* versuchen, ein Window zu öffnen: [wenn's schiefgeht dennoch
	weitermachen !] */
    OldOut=SelectOutput(Out=
			Open("CON:320/11/320/100/Duplicator",MODE_NEWFILE));

    if(argc<3)
	{
	Printf("Aufruf: Duplicator <datei> [...]\n\t\t\t<verzeichnis>\n");
	CleanUp();
	return(RETURN_WARN);
	}

    if(!IsPath(argv[argc-1]))
	Error("%s ist kein (existenter) Pfad\n",argv[argc-1]);
    /* Messageport organisieren */
    if(!(MyPort=CreateMsgPort()))
	Error("Duplicator kann seinen MsgPort nicht anlegen\n",NULL);
    /* die Requests anlegen */
    for(argcnt=1;argcnt<argc-1;++argcnt)
	if(thisname=GetENR(argv[argcnt]))
	    {
	    Copy(thisname,argv[argc-1]);
	    Printf("Notification von %s hat begonnen\n",thisname);
	    }

    if(first_enr)
	{
	/* die relevanten Signale ausfiltern */
	ValidSigs=(SIGBREAKF_CTRL_C | (1<<MyPort->mp_SigBit));

	/* auf Abbruch oder Notify-Message warten */
	for(GotSigs=(SetSignal(0,0) & ValidSigs);
	    (GotSigs & SIGBREAKF_CTRL_C)==0;
	    GotSigs=Wait(ValidSigs)             )
	    {
	    HandleNotifies(argv[argc-1]);
	    if(GotSigs & SIGBREAKF_CTRL_C)  continue;
	    while(GotSigs=(SetSignal(0,0) & ValidSigs))
		{
		HandleNotifies(argv[argc-1]);
		if(GotSigs & SIGBREAKF_CTRL_C)  break;
		}
	    }
	Printf("^C ... ");  Flush(Output());
	}
    else Printf("Keine der Notifications ist möglich, Sie \23332mRindvieh\23331m !\n");

    CleanUp();
    return(RETURN_OK);
}
