/***************************************************************************
**                                                                        **
**  Print.c  -  Druckertool für die CLI bzw. Shell          Version 1.60  **
**                                                                        **
**  © Copyright 1993-1997 by StarGATE Software                            **
**                                                                        **
****************************************************************************
**                                                                        **
**  History:                                                              **
**                                                                        **
**    v1.00 (??/??/1993)                                                  **
**                                                                        **
**      * Erste Version.                                                  **
**                                                                        **
**    v1.10 (??/??/1993)                                                  **
**                                                                        **
**      * Statusdisplay & Fehlermeldungen hinzugefügt.                    **
**                                                                        **
**    v1.20 (21/05/1995)                                                  **
**                                                                        **
**      * Komplett neu programmierte Version.                             **
**      * Neuartige Statusanzeige.                                        **
**      * Datenpuffer (256 Bytes), dadurch Geschwindigkeitsgewinn von     **
**        ganzen 11 Sekunden pro Druck.                                   **
**      * Drucklisten (max. 100 Dateien).                                 **
**      * 3 Steuerflags (SHOWREQ, DELAY und DISPOFF).                     **
**                                                                        **
**    v1.30 (01/08/1995)                                                  **
**                                                                        **
**      * FILEREQ-Flag.                                                   **
**      * Kontrollroutinen verbessert.                                    **
**                                                                        **
**    v1.31 (05/08/1995)                                                  **
**                                                                        **
**      * Source teilweise in C++-Code umgewandelt. Keine Verbesserun-    **
**        gen. Wieder gelöscht.                                           **
**                                                                        **
**    v1.40 (15/08/1995)                                                  **
**                                                                        **
**      * "-cp"-Option.                                                   **
**      * "-dt"-Option.                                                   **
**      * Source teilweise etwas abgeändert.                              **
**                                                                        **
**    v1.41 (18/12/1995)                                                  **
**                                                                        **
**      * CTRL-C-Abbruch des Druckprozesses eingeführt.                   **
**      * Workbench-Melderequester.                                       **
**      * Ein paar Bugs beseitigt.                                        **
**      * Fehler-Kennummern eingeführt.                                   **
**      * Print-Fehlerliste zusammengestellt.                             **
**                                                                        **
**    v1.50 (28/12/1995)                                                  **
**                                                                        **
**      * 2 neue Steuerflags (PROTFLE und TIMEOFF).                       **
**      * "-pf"-Option.                                                   **
**      * Druckdauerausgabe (abschaltbar).                                **
**      * Speicherfressenden Bug beseitigt (kein Speicherverlust mehr).   **
**      * Sonstige Bugs entfernt.                                         **
**      * Verwendung von Fast-RAM statt Chip-RAM für Speicherallokierung. **
**      * Groß- oder Kleinschreibung von Options freigestellt.            **
**      * Fehlermeldungen erweitert.                                      **
**                                                                        **
**    v1.51 (29/10/1996)                                                  **
**                                                                        **
**      * Winzigen Bug aus der Druckdauerausgabe für Protokolldateien     **
**        entfernt.                                                       **
**                                                                        **
**    v1.60 (15/08/1997)  Erste öffentliche Version                       **
**                                                                        **
**      * Vollständig neu programmiert.                                   **
**      * 2 neue Steuerflags (NORTREQ und DOBEEP).                        **
**      * "-mx" und "-mn"-Optionen.                                       **
**      * "-dt"-Wert ab sofort in Sekunden, nicht mehr in Tics.           **
**      * Wahlweise ReqTools- oder ASL-Requester.                         **
**      * Unbegrenzte Anzahl an Dateien durch dynamische Listen.          **
**      * "-cp" bezieht sich nur noch auf vorangegangene Datei.           **
**      * "-bs"-Option (Druckpuffer beliebig groß).                       **
**      * Programmausgabe optisch verbessert.                             **
**      * Patternmatching (Mustererkennung) integriert.                   **
**      * Dummy-Flags FLAGS und OPTS eingeführt.                          **
**      * Protokolldateiausgaben erweitert (Puffergröße, Flags...etc.)    **
**      * Check der OS-Versionsnummer am Programmanfang (mind. v2.04)     **
**      * Schwerwiegenden, alten Bug aus der Druckroutine entfernt.       **
**      * Zweisprachige Version (Deutsch, Englisch) erstellt.             **
**      * Mit -gO Flag compiliert (Optimizer, Code kleiner)               **
**                                                                        **
***************************************************************************/

#include <exec/types.h>
#include <exec/memory.h>
#include <classes/exec/libraries.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <libraries/reqtools.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/asl_protos.h>
#include <pragma/reqtools_lib.h>
#include <utility/tagitem.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <clionly.h>

/*********************************************************/
/*                                                       */
/*  Versionsstring                                       */
/*                                                       */
/*********************************************************/

const UBYTE *Version = "$VER: Print v1.60 (15/08/1997)";

/*********************************************************/
/*                                                       */
/*  Libraries                                            */
/*                                                       */
/*********************************************************/

extern struct Library *SysBase;  /* Zur Versionsüberprüfung */

struct Library        *AslBase      = NULL;
struct Library        *ReqToolsBase = NULL;

LibraryBaseErrC IntuitionBase( "intuition.library", 0 );  /* C++-Code! */

/*********************************************************/
/*                                                       */
/*  Custom Structs                                       */
/*                                                       */
/*  Programminterne Structs zur persönlichen Verwendung  */
/*  durch das Programm.                                  */
/*                                                       */
/*  PrintFile     - Dateilisteneintrag                   */
/*  PrintFileList - Dateiliste (für Druckliste)          */
/*  PrintGlobal   - Globaldaten von Print (z.B. Flags)   */
/*  PrintTime     - Druckdauer                           */
/*                                                       */
/*  ACHTUNG: Diese Structs enthalten massiv C++-Elemen-  */
/*  -------- te  wie  z.B.  Memberfunktionen!!! ANSI-C-  */
/*           Compiler (wie SAS) werden versagen!!!       */
/*                                                       */
/*********************************************************/

struct PrintFile
{
  struct PrintFile *pf_prev, *pf_next;

  char  pf_path[ 256 ];
  ULONG pf_copies;
};

struct PrintFileList
{
  struct PrintFile *pfl_first, *pfl_last;

  PrintFileList() : pfl_first(NULL), pfl_last(NULL) {}

  void Free()
  {
    struct PrintFile *work = pfl_first,
                     *next;

    while( work )
    {
      next = work->pf_next;

      FreeMem( work, sizeof( PrintFile ) );

      work = next;
    }

    pfl_first = pfl_last = NULL;
  }

  ~PrintFileList()
  {
    Free();
  }

  BOOL Add( char *path )
  {
    struct PrintFile *work = NULL;

    if( work = (PrintFile *) AllocMem( sizeof( PrintFile ), MEMF_PUBLIC|MEMF_CLEAR ) )
    {
      strcpy( work->pf_path, path );
      work->pf_copies = 1;

      if( !pfl_first )
      {
        work->pf_prev = work->pf_next = NULL;
        pfl_first     = pfl_last      = work;
      }
      else
      {
        struct PrintFile *old = pfl_last;

        old->pf_next  = work;
        work->pf_prev = old;
        work->pf_next = NULL;

        pfl_last      = work;
      }

      return( TRUE );
    }

    return( FALSE );
  }
};

struct PrintGlobal
{
  ULONG pg_bufsize;

  BOOL  pg_flags[7];

  ULONG pg_delaytime;
  char  pg_protfpath[ 256 ];

  ULONG pg_minsize;
  ULONG pg_maxsize;

  PrintGlobal() : pg_bufsize(256), pg_delaytime(0), pg_minsize(0), pg_maxsize(0)
  {
    for( int x=0 ; x<7 ; x++ )
      pg_flags[x] = FALSE;

    strcpy( pg_protfpath, "SYS:S/PProtocol" );
  }
};

struct PrintTime
{
  int pt_hours;
  int pt_minutes;
  int pt_seconds;
};

/*********************************************************/
/*                                                       */
/*  Globale Structs                                      */
/*                                                       */
/*********************************************************/

struct PrintGlobal      global;
struct PrintFileList    files;
struct PrintTime        ptime;

struct tm               time1, time2;

struct FileRequester   *AslFileReq = NULL;
struct WBArg           *AslMChoise = NULL;

struct rtFileRequester *RTFileReq  = NULL;
struct rtFileList      *RTMChoise  = NULL;

struct TagItem          AslFileReqTags[] =
{
  ASLFR_TitleText, (ULONG) "Select file(s) to print...",
  ASLFR_PositiveText, (ULONG) "Okay",
  ASLFR_NegativeText, (ULONG) "Cancel",
  ASLFR_InitialLeftEdge, 0,
  ASLFR_InitialTopEdge, 11,
  ASLFR_InitialWidth, 390,
  ASLFR_InitialHeight, 190,
  ASLFR_InitialDrawer, (ULONG) "RAM:",
  ASLFR_DoMultiSelect, TRUE,
  ASLFR_DoPatterns, TRUE,
  TAG_DONE
};

struct TagItem          RTFileReqTags[] =
{
  RT_Window, NULL,
  RTFI_Flags, FREQF_PATGAD|FREQF_MULTISELECT,
  RT_Underscore, '_',
  TAG_DONE
};

/*********************************************************/
/*                                                       */
/*  Globale Variablen                                    */
/*                                                       */
/*********************************************************/

char *data = NULL;  /* Puffer für die Druckdaten */

/*********************************************************/
/*                                                       */
/*  Funktionsprototypen                                  */
/*                                                       */
/*********************************************************/

ULONG  AppRequest( Window *, char *, char *, char *, ULONG, ... );
void   AppExit();

void   AnalyseArgs( int, char **, PrintFileList *, PrintGlobal * );
BOOL   PatternMatch( char *, char *, PrintFileList * );
BOOL   IsFile( FileInfoBlock * );
USHORT FileInfos( char *, FILE *, PrintGlobal * );
BOOL   Print( FILE *, FILE *, FILE *, BOOL *, APTR, ULONG );
void   CalcTimeDiff( tm *, tm *, PrintTime * );

/* main()
**
** argc -- Anzahl an Argumenten, die dem Programm übergeben wurden
** argv -- Ein Array, das die Argumente enthält
**
** Rückgabewert: -keiner-
**
** Shell-Startup
*/

void main( int argc, char *argv[] )
{
  struct PrintFile *work    = NULL;  /* Ein einzelnes Dateilistenelement */

  FILE             *input,  /* Die Streams der einzelnen Dateien */
                   *output,
                   *protkl;

  ULONG             file    = 0;  /* Dateicounter */
  USHORT            retval  = 0;  /* Return-Value von FileInfos() */

  unsigned int      acttime = 0;  /* Für time() */

  /* Bitte Vorsicht: Die nachstehende Funktion ist C++-Code! */

  if( LibraryBaseC::areAllOpen() )
  {
    /* OS-Version überprüfen. Print arbeitet nur */
    /* mit 2.04 oder besser.                     */

    if( SysBase->lib_Version < 37 )
    {
      AppRequest( NULL, "Message", "Print needs OS 2.04 (v37) or better!\nYou only have version %ld.%ld.\nThat's not enough!", "Cancel", SysBase->lib_Version, SysBase->lib_Revision );
      exit( 0 );
    }

    /* ASL.Library öffnen. Ist diese nicht zu finden, wird das   */
    /* Programm abgebrochen.                                     */
    /*                                                           */
    /* Zugegeben: Es wäre sinnvoller, nur abzubrechen, wenn auch */
    /* die ReqTools.Library nicht existiert. In dieser Version   */
    /* von Print bleibt das allerdings so. Kritik diesbezüglich  */
    /* nehme ich gerne entgegen.                                 */

    if( AslBase = OpenLibrary( "asl.library", 37 ) )
    {
      /* ReqTools.Library öffnen. Wird Sie nicht gefunden, so bricht  */
      /* das Programm NICHT ab! Allerdings kann in einem solchen Fall */
      /* weder der ReqTools-Systemrequester noch der ReqTools-File-   */
      /* requster benutzt werden. Es ist, als ob das Flag NORTREQ ge- */
      /* setzt wurde.                                                 */

      ReqToolsBase = OpenLibrary( "reqtools.library", 0 );

      printf( "\n[1mPrint v1.60[0m\n" );
      printf( "  [1mWritten by StarGATE Software[0m\n\n" );

      if( argc > 1 )
      {
        if( argv[1][0] != '?' )
        {
          /* DOS-Argumente analysieren und alles brauchbare herausfiltern. */

          AnalyseArgs( argc-1, argv, &files, &global );

          /* Drucker öffnen */

          if( output = fopen( "PRT:", "w" ) )
          {
            /* Falls das PROTFLE-Flag übergeben wurde, Protokolldatei öff- */
            /* nen und erste Daten dorthin senden.                         */

            if( global.pg_flags[4] )
            {
              if( !( protkl = fopen( global.pg_protfpath, "w" ) ) )
              {
                printf( " [1mERROR:[0m Failed to open protocol file \"%s\"!!!\n\n", global.pg_protfpath );

                fclose( output );

                AppExit();
              }
              else
              {
                /* Die nachstehenden zwei Zeilen haben wohlgemerkt NICHTS  */
                /* mit der Druckdauerausgabe zu tun. Sie sind nur dazu da, */
                /* das Datum und die Zeit für den Protokollkopf auszule-   */
                /* sen. Ich wollte hierfür keine zusätzliche tm-Variable   */
                /* anlegen, da es auch so geht.                            */

                time( &acttime );
                time1 = *(struct tm *) localtime( &acttime );

                fprintf( protkl, " Print v1.60 protocol\n\n" );

                fprintf( protkl, " File: %s\n", global.pg_protfpath );
                fprintf( protkl, " Date: %02ld.%02ld.%ld\n", time1.tm_mday, time1.tm_mon+1, time1.tm_year );
                fprintf( protkl, " Time: %02ld:%02ld:%02ld\n\n", time1.tm_hour, time1.tm_min, time1.tm_sec );

                fprintf( protkl, " --------------------------------------\n\n" );

                fprintf( protkl, " Buffer size : %ld bytes\n\n", global.pg_bufsize );

                fprintf( protkl, " SHOWREQ     : %s\n", ( global.pg_flags[0] ? "On" : "Off" ) );
                fprintf( protkl, " DELAY       : %s\n", ( global.pg_flags[1] ? "On" : "Off" ) );
                fprintf( protkl, " DISPOFF     : %s\n", ( global.pg_flags[2] ? "On" : "Off" ) );
                fprintf( protkl, " TIMEOFF     : %s\n", ( global.pg_flags[3] ? "On" : "Off" ) );
                fprintf( protkl, " PROTFLE     : On\n" );
                fprintf( protkl, " NORTREQ     : %s\n", ( global.pg_flags[5] ? "On" : "Off" ) );
                fprintf( protkl, " DOBEEP      : %s\n\n", ( global.pg_flags[6] ? "On" : "Off" ) );

                fprintf( protkl, " Delay time  : %ld second(s)\n\n", global.pg_delaytime );

                fprintf( protkl, " Min. size   : %ld bytes\n", global.pg_minsize );
                fprintf( protkl, " Max. size   : %ld bytes\n\n", global.pg_maxsize );

                fprintf( protkl, " --------------------------------------\n\n" );
              }
            }

            /* Speicher für Datenpuffer allokieren. Der Puffer wird stets */
            /* um einen Faktor größer Allokiert, um mit fgets() (siehe    */
            /* unten bei Print()) korrekt arbeiten zu können.             */

            if( !( data = (char *) AllocMem( global.pg_bufsize+1, MEMF_PUBLIC|MEMF_CLEAR ) ) )
            {
              printf( " [1mERROR:[0m Failed to allocate memory for data buffer!!!\n\n" );

              fclose( output );

              if( global.pg_flags[4] )
              {
                fprintf( protkl, " *Memory allocation for data buffer failed*" );
                fclose( protkl );
              }

              AppExit();
            }

            /* Das erste Element der Dateiliste aufnehmen */

            work = files.pfl_first;

            /* Falls das TIMEOFF-Flag nicht übergeben wurde, aktuelle Zeit */
            /* (Startzeit des Druckprozesses) auslesen und speichern.      */

            if( !global.pg_flags[3] )
            {
              time( &acttime );
              time1 = *(struct tm *) localtime( &acttime );
            }

            /* Dateiliste durcharbeiten. Das letzte Element der Liste hat   */
            /* einen "next"-Pointer, der auf NULL gesetzt wurde. Die while- */
            /* Schleife bricht daher ab, sobald dieses erreicht ist.        */

            while( work )
            {
              /* Quelldatei öffnen */

              if( input = fopen( work->pf_path, "r" ) )
              {
                /* Falls das DISPOFF-Flag nicht angegeben wurde, Datei analysie- */
                /* ren und einen kurzen Statusbericht ausgeben (auch in die Pro- */
                /* tokolldatei, falls vorhanden).                                */

                if( !global.pg_flags[2] )
                {
                  printf( " Number        : [1m%ld[0m\n\n", file+1 );

                  if( ( retval = FileInfos( work->pf_path, ( global.pg_flags[4] ? protkl : NULL ), &global ) ) == 0 )
                  {
                    fclose( input );
                    fclose( output );

                    if( global.pg_flags[4] )
                      fclose( protkl );

                    FreeMem( data, global.pg_bufsize+1 );

                    AppExit();
                  }
                }

                /* FileInfos() kann 3 Rückgabewerte haben: 0, 1 und 2. Bei 0 ist */
                /* ein Fehler aufgetreten, bei 1 ist alles okay und bei 2 ist    */
                /* die Datei außerhalb des mit den Options -mn und -mx festge-   */
                /* legten Bereiches. In einem solchen Fall muß die gesamte Datei */
                /* ignoriert und übersprungen werden.                            */
                /*                                                               */
                /* Das Ergebnis von FileInfos() steht in "retval".               */

                if( retval == 1 )
                {
                  /* Die nachfolgende for()-Schleife wird so oft ausgeführt, wie */
                  /* Exemplare (Kopien, via "-cp"-Option) für eine betreffende   */
                  /* Datei angegeben wurden. Bei einem einzigen Exemplar läuft   */
                  /* sie nur einmal durch.                                       */

                  for( int cntr=0 ; cntr<work->pf_copies ; cntr++ )
                  {
                    /* Lesekopf auf den Dateianfang stellen. Dies ist bei dem    */
                    /* ersten Exemplar egal, bei dem zweiten oder x-ten aber un- */
                    /* vermeidbar, da ansonsten der Kopf am Dateiende bleibt.    */

                    fseek( input, 0, SEEK_SET );

                    /* Datei drucken */

                    if( !Print( input, output, protkl, global.pg_flags, data, global.pg_bufsize ) )
                    {
                      fclose( input );
                      fclose( output );

                      if( global.pg_flags[4] )
                        fclose( protkl );

                      FreeMem( data, global.pg_bufsize+1 );

                      AppExit();
                    }

                    /* Abarbeiten der Flags wie DOBEEP und SHOWREQ. Diese sollen */
                    /* allerdings nur beachtet werden, sofern das eben gedruckte */
                    /* Exemplar nicht das letzte der letzten Datei in der Liste  */
                    /* war.                                                      */

                    if( ( work != files.pfl_last ) || ( (cntr+1)<work->pf_copies ) )
                    {
                      /* DOBEEP */

                      if( global.pg_flags[6] )
                        printf( "\a" );

                      /* DELAY */

                      if( global.pg_flags[1] )
                        Delay( 50 * global.pg_delaytime );

                      /* SHOWREQ */

                      if( global.pg_flags[0] )
                      {
                        if( !AppRequest( NULL, "Request", "Please prepare everything for\nthe next printing cycle...", "Continue|Cancel", 0 ) )
                        {
                          if( AppRequest( NULL, "Request", "Really abort printing process?", "Yes|No", 0 ) )
                          {
                            if( global.pg_flags[4] )
                              fprintf( protkl, "*BREAK*\n" );

                            printf( "\n [1m*Printing aborted!*[0m\n\n" );

                            fclose( input );
                            fclose( output );

                            if( global.pg_flags[4] )
                              fclose( protkl );

                            FreeMem( data, global.pg_bufsize+1 );

                            AppExit();
                          }
                        }
                      }
                    }
                  }
                }
                else
                {
                  printf( " [1mFile not in \"-mn/-mx\" area! Will be skipped![0m\n" );

                  if( global.pg_flags[4] )
                    fprintf( protkl, "File not in -mn/-mx area!\n" );
                }
              }
              else
              {
                if( !AppRequest( NULL, "Message", "ERROR:\n\nFailed to open\n%s", "Continue|Cancel", (ULONG) work->pf_path ) )
                {
                  if( AppRequest( NULL, "Request", "Really abort printing process?", "Yes|No", 0 ) )
                  {
                    printf( " [1m*Printing aborted!*[0m\n\n" );

                    if( global.pg_flags[4] )
                    {
                      fprintf( protkl, "*BREAK*\n" );
                      fclose( protkl );
                    }

                    fclose( output );

                    FreeMem( data, global.pg_bufsize+1 );

                    AppExit();
                  }
                }

                printf( " [1mFile skipped![0m\n\n" );

                if( global.pg_flags[4] )
                  fprintf( protkl, " *Skipped*\n" );
              }

              /* Drucken erfolgreich (oder Datei übersprungen). Datei schlie- */
              /* ßen und ein Formfeed ausführen.                              */

              fclose( input );

              printf( "\n" );

              /* Den nächsten Eintrag aus der Liste holen. Ist dieser NULL, */
              /* bricht die while()-Schleife ab (siehe oben).               */

              work = work->pf_next;

              /* Dateicounter inkrementieren (erhöhen :-) */

              file++;
            }

            /* Trennstrich nach den Dateiangaben ziehen */

            if( global.pg_flags[4] )
              fprintf( protkl, " --------------------------------------\n\n" );

            /* Auch hier gilt: Wurde das TIMEOFF-Flag nicht angegeben, aktuelle */
            /* Zeit (Endzeit des Druckprozesses) auslesen und speichern. Danach */
            /* mit der Funktion CalcTimeDiff() die Differenz zwischen End- und  */
            /* Startzeit berechnen (Druckdauer) und das Ergebnis ausgeben.      */

            if( !global.pg_flags[3] )
            {
              time( &acttime );
              time2 = *(struct tm *) localtime( &acttime );

              CalcTimeDiff( &time1, &time2, &ptime );

              printf( " Time needed   : %02ld hour(s), %02ld minute(s), %02ld second(s)\n\n", ptime.pt_hours, ptime.pt_minutes, ptime.pt_seconds );

              if( global.pg_flags[4] )
              {
                fprintf( protkl, "\n Time needed   : %02ld hour(s), %02ld minute(s), %02ld second(s)\n\n", ptime.pt_hours, ptime.pt_minutes, ptime.pt_seconds );
                fprintf( protkl, " --------------------------------------\n" );
              }
            }

            /* Drucken vollständig beendet. Alle Dateien erfolgreich abgearbei- */
            /* tet (oder übersprungen). Dateien schließen, Programm beenden.    */

            if( global.pg_flags[4] )
              fprintf( protkl, "\n Done!\n" );

            printf( " [1mPrinting finished![0m\n\n" );

            fclose( output );

            if( global.pg_flags[4] )
              fclose( protkl );

            FreeMem( data, global.pg_bufsize+1 );

            AppExit();
          }
          else
          {
            printf( " [1mERROR:[0m Failed to use printer!!!\n\n" );
            AppExit();
          }
        }
        else
        {
          printf( " [32mUSAGE :[0m [1mPrint [FILES] {<Files>} [FLAGS] [<Flags>] [OPTS] [<Options>][0m\n\n" );

          printf( "  [1mFLAGS:[0m\n\n" );

          printf( "    FILEREQ - Open file requester for file selection (ASL- or ReqTools)\n" );
          printf( "    SHOWREQ - Open a requester between each printing cycle\n" );
          printf( "    DELAY   - Wait some time between each printing cycle (see also \"-dt\")\n" );
          printf( "    DISPOFF - Switch off state display\n" );
          printf( "    TIMEOFF - Switch off \"Needed time\" message (ALSO in protocol!)\n" );
          printf( "    PROTFLE - Create protocol of printing process (see \"-pf\")\n" );
          printf( "    NORTREQ - Do NOT use ReqTools requesters (ASL instead)\n" );
          printf( "    DOBEEP  - Do a BEEP between every printing cycle\n\n" );

          printf( "    Flags MUST be written [32mupper case[0m!!!\n\n" );

          printf( "  [1mOPTS.:[0m\n\n" );

          printf( "    -cp <copies> - Print previous file <copies> times (max. 999)\n" );
          printf( "    -dt <Sec.>   - DELAY flag waits <Sec.> seconds\n" );
          printf( "    -pf <Path>   - Path for the protocol (default: \"SYS:S/PProtocol\")\n" );
          printf( "    -mx <Size>   - Print only files with <Size> bytes maximum\n" );
          printf( "    -mn <Size>   - Print only files with <Size> bytes minimum\n" );
          printf( "    -bs <Size>   - Size of data buffer (1 byte minimum)\n\n" );

          printf( "    Options can be written both upper and lower case!!!\n\n" );

          printf( "  [32m© Copyright 1993-1997 by StarGATE Software[0m\n\n" );

          AppExit();
        }
      }
      else
      {
        printf( " [1mERROR:[0m Not enough arguments!!!\n\n" );
        AppExit();
      }
    }
    else
    {
      AppRequest( NULL, "Message", "Failed to open\nAsl.Library", "Cancel", 0 );
      AppExit();
    }
  }
}

/* AppRequest()
**
** window  -- Window, auf dem der Requester geöffnet werden soll
** title   -- Titeltext des Requesters
** reqtxt  -- Der anzuzeigende Text
** gadgets -- Die Gadgets des Requesters
** reqarg  -- Erstes Argument des Requesters
** ...     -- Weitere, beliebig viele Argumente
**
** Rückgabewert: ID des angewählten Gadgets (0-x)
**
** Anzeigen einer Meldung in einem standard System-Requester
*/

ULONG AppRequest( Window *window, char *title, char *reqtxt, char *gadgets, ULONG arg1, ... )
{
  /* Sofern die ReqTools-Library nicht geöffnet wurde, */
  /* den SystemRequester von Intuition benutzen.       */

  if( !ReqToolsBase )
  {
    struct EasyStruct sysreq;

    sysreq.es_StructSize   = sizeof( struct EasyStruct );
    sysreq.es_Flags        = NULL;
    sysreq.es_Title        = title;
    sysreq.es_TextFormat   = reqtxt;
    sysreq.es_GadgetFormat = gadgets;

    return( EasyRequestArgs( window, &sysreq, NULL, (struct TagItem *)&arg1 ) );
  }
  else
  {
    struct TagItem  reqtags[5];
    ULONG          *reqargs = &arg1;

    reqtags[0].ti_Tag  = RT_Window;
    reqtags[0].ti_Data = (ULONG) window;
    reqtags[1].ti_Tag  = RTEZ_ReqTitle;
    reqtags[1].ti_Data = (ULONG) title;
    reqtags[2].ti_Tag  = RT_ReqPos;
    reqtags[2].ti_Data = REQPOS_CENTERSCR;
    reqtags[3].ti_Tag  = RTEZ_Flags;
    reqtags[3].ti_Data = EZREQF_CENTERTEXT;
    reqtags[4].ti_Tag  = TAG_DONE;

    return( rtEZRequestA( reqtxt, gadgets, NULL, reqargs, reqtags ) );
  }
}

/* AppExit()
**
** -Benötigt keine Argumente-
**
** Rückgabewert: -keiner-
**
** Programm beenden und verlassen
*/

void AppExit()
{
  if( ReqToolsBase )
    CloseLibrary( ReqToolsBase );

  if( AslBase )
    CloseLibrary( AslBase );

  exit( 0 );
}

/* AnalyseArgs()
**
** num  -- Anzahl der Argumente
** args -- Ein Array mit den Argumenten
** list -- Pointer auf die Ziel-Dateienliste
** glob -- Pointer auf PrintGlobal-Struct
**
** Rückgabewert: -keiner-
**
** Analysieren von DOS-Argumenten
*/

void AnalyseArgs( int num, char **args, PrintFileList *list, PrintGlobal *glob )
{
  /* Alle Argumente durchlaufen */

  for( int cntr=1 ; cntr<=num ; cntr++ )
  {
    /* Option... */

    if( args[cntr][0] == '-' )
    {
      if( ( toupper( args[cntr][1] ) == 'C' ) && ( toupper( args[cntr][2] ) == 'P' ) )
      {
        /* Wird eine -cp-Anweisung noch vor einer einzigen */
        /* Datei übergeben, wird es einfach ignoriert. An- */
        /* sonsten wird es der zuletzt in die Liste einge- */
        /* tragenen Datei anvertraut.                      */

        if( list->pfl_last )
        {
          ULONG copies = atol( args[cntr+1] );

          /* Max. 999 Exemplare. Braucht irgend jemand auf diesem */
          /* Planeten mehr???? :-)                                */

          copies = ( copies>999 ? 999 : ( !copies ? 1 : copies ) );

          list->pfl_last->pf_copies = copies;
        }

        cntr++;
      }
      else if( ( toupper( args[cntr][1] ) == 'D' ) && ( toupper( args[cntr][2] ) == 'T' ) )
      {
        glob->pg_delaytime = atol( args[cntr+1] );
        cntr++;
      }
      else if( ( toupper( args[cntr][1] ) == 'P' ) && ( toupper( args[cntr][2] ) == 'F' ) )
      {
        strcpy( glob->pg_protfpath, args[cntr+1] );
        cntr++;
      }
      else if( ( toupper( args[cntr][1] ) == 'M' ) && ( toupper( args[cntr][2] ) == 'X' ) )
      {
        glob->pg_maxsize = atol( args[cntr+1] );

        /* Vorsicht: Der Wert von -mn, falls angegeben, darf na- */
        /* türlich nicht größer sein, als der von -mx. Das wäre  */
        /* ja auch Blödsinn.                                     */

        if( glob->pg_minsize )
        {
          if( glob->pg_minsize >= glob->pg_maxsize )
          {
            printf( " [1mERROR:[0m Minimum size can't be greater than maximum size!!!\n\n" );
            AppExit();
          }
        }

        cntr++;
      }
      else if( ( toupper( args[cntr][1] ) == 'M' ) && ( toupper( args[cntr][2] ) == 'N' ) )
      {
        glob->pg_minsize = atol( args[cntr+1] );

        /* Umgekehrt gilt auch hier: Der Wert von -mx darf nicht */
        /* kleiner sein, als der von -mn.                        */

        if( glob->pg_maxsize )
        {
          if( glob->pg_maxsize <= glob->pg_minsize )
          {
            printf( " [1mERROR:[0m Minimum size can't be greater than maximum size!!!\n\n" );
            AppExit();
          }
        }

        cntr++;
      }
      else if( ( toupper( args[cntr][1] ) == 'B' ) && ( toupper( args[cntr][2] ) == 'S' ) )
      {
        glob->pg_bufsize = atol( args[cntr+1] );

        /* Wird die "-bs"-Option nicht angegeben oder auf 0 ge- */
        /* setzt, wird der Puffer standardmäßig mit 256 Zeichen */
        /* veranlagt.                                           */

        if( !glob->pg_bufsize )
          glob->pg_bufsize = 256;

        /* Sollte irgend ein obercooler Schussel einen Puffer de- */
        /* finieren wollen, der größer ist, als der gesamte (!)   */
        /* zur Verfügung stehende Speicher, meckert Print natür-  */
        /* lich. An dieser Stelle ein freundliches "Hallo" an     */
        /* alle "Amiga-500-mit-512KB"-Besitzer ;-). Wie wär's     */
        /* denn mit einer Speichererweiterung? (hehehe)           */

        if( glob->pg_bufsize > AvailMem( MEMF_ANY ) )
        {
          printf( " [1mERROR:[0m Data buffer too big (Only %ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );
          AppExit();
        }

        /* Der Puffer muß, zumindest in dieser Version von Print, */
        /* mindestens die Größe 2 haben (aus programmiertechni-   */
        /* schen Gründen). Ist er kleiner, wird er automatisch    */
        /* auf diesen Wert gesetzt.                               */

        if( glob->pg_bufsize < 2 )
          glob->pg_bufsize = 2;

        cntr++;
      }
      else
      {
        printf( " [1mERROR:[0m Unknown option '%s'!!!\n\n", args[cntr] );
        AppExit();
      }
    }
    else if( !strcmp( args[cntr], "FILES" ) )
    {
      continue;
    }
    else if( !strcmp( args[cntr], "FLAGS" ) )
    {
      continue;
    }
    else if( !strcmp( args[cntr], "OPTS" ) )
    {
      continue;
    }
    else if( !strcmp( args[cntr], "FILEREQ" ) )
    {
      /* Da hat sich jemand für das FILEREQ-Flag entschieden. Sehr */
      /* weise. Je nachdem, welcher Requester verfügbar ist, ent-  */
      /* weder den ASL- oder den ReqTools-FileRequester öffnen.    */

      char filename[ 256 ];

      if( ReqToolsBase && !glob->pg_flags[5] )
      {
        if( RTFileReq = (rtFileRequester *) rtAllocRequestA( RT_FILEREQ, NULL ) )
        {
          struct rtFileList *templist;

          filename[0] = '\0';

          RTMChoise = (rtFileList *) rtFileRequestA( RTFileReq, filename, "Select file(s) to print...", RTFileReqTags );

          templist = RTMChoise;

          while( templist )
          {
            strcpy( filename, RTFileReq->Dir );
            AddPart( filename, templist->Name, 130 );

            /* Dateinamen in die Dateiliste eintragen */

            if( !list->Add( filename ) )
            {
              printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );

              rtFreeFileList( RTMChoise );
              rtFreeRequest( RTFileReq );

              AppExit();
            }

            templist = templist->Next;
          }

          rtFreeFileList( RTMChoise );
          rtFreeRequest( RTFileReq );
        }
        else
        {
          printf( " [1mERROR:[0m Memory allocation for ReqTools requester failed!!!\n\n" );
          AppExit();
        }
      }
      else
      {
        if( AslFileReq = (FileRequester *) AllocAslRequest( ASL_FileRequest, AslFileReqTags ) )
        {
          if( AslRequest( AslFileReq, 0L ) )
          {
            if( AslFileReq->rf_NumArgs > 0 )
            {
              AslMChoise = AslFileReq->rf_ArgList;

              for( ULONG entry=0 ; entry<AslFileReq->rf_NumArgs ; entry++ )
              {
                strcpy( filename, AslFileReq->rf_Dir );
                AddPart( filename, AslMChoise[ entry ].wa_Name, 130 );

                /* Dito... */

                if( !list->Add( filename ) )
                {
                  printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );

                  FreeAslRequest( AslFileReq );
                  AppExit();
                }
              }

              FreeAslRequest( AslFileReq );
            }
            else
            {
              strcpy( filename, AslFileReq->rf_Dir );
              AddPart( filename, AslFileReq->rf_File, 130 );

              if( !list->Add( filename ) )
              {
                printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );

                FreeAslRequest( AslFileReq );
                AppExit();
              }

              FreeAslRequest( AslFileReq );
            }
          }
          else
            FreeAslRequest( AslFileReq );
        }
        else
        {
          printf( " [1mERROR:[0m Memory allocation for ASL requester failed!!!\n\n" );
          AppExit();
        }
      }
    }
    else if( !strcmp( args[cntr], "SHOWREQ" ) )
    {
      glob->pg_flags[0] = TRUE;
    }
    else if( !strcmp( args[cntr], "DELAY" ) )
    {
      glob->pg_flags[1] = TRUE;
    }
    else if( !strcmp( args[cntr], "DISPOFF" ) )
    {
      glob->pg_flags[2] = TRUE;
    }
    else if( !strcmp( args[cntr], "TIMEOFF" ) )
    {
      glob->pg_flags[3] = TRUE;
    }
    else if( !strcmp( args[cntr], "PROTFLE" ) )
    {
      glob->pg_flags[4] = TRUE;
    }
    else if( !strcmp( args[cntr], "NORTREQ" ) )
    {
      glob->pg_flags[5] = TRUE;
    }
    else if( !strcmp( args[cntr], "DOBEEP" ) )
    {
      glob->pg_flags[6] = TRUE;
    }
    else
    {
      /* Dateiangabe auf mögliche Suchmuster hin überprüfen. So- */
      /* fern PatternMatch() FALSE zurückgibt, enthält die Anga- */
      /* bei KEIN Muster und wird als normale Datei behandelt.   */

      if( !PatternMatch( args[cntr], "", list ) )
      {
        if( !list->Add( args[cntr] ) )
        {
          printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );
          AppExit();
        }
      }
    }
  }

  /* Ist die Dateiliste nach der Überprüfung aller Argumente */
  /* immer noch leer, so wurden dem Programm keine Dateien   */
  /* übergeben. Das geht natürlich nicht...                  */

  if( !list->pfl_first )
  {
    printf( " [1mERROR:[0m No files passed!!!\n\n" );
    AppExit();
  }
}

/* PatternMatch()
**
** pattern   -- Dateimuster (à la '#?.xyz')
** directory -- Quellverzeichnis
** list      -- Pointer auf die Ziel-Dateienliste
**
** Rückgabewert: TRUE=Erfolgreich, FALSE=Fehlgeschlagen
**
** Musterübereinstimmungen suchen
*/

BOOL PatternMatch( char *pattern, char *directory, PrintFileList *list )
{
  struct AnchorPath *AnPath = NULL;

  BPTR               lock;
  BOOL               retval = FALSE;

  /* Ääääch... was diese ganzen Funktionen genau machen, weiß */
  /* ich auch nicht so recht. Den Source hierzu habe ich im   */
  /* Amiga Magazin gefunden und nur hier und da etwas verbes- */
  /* sert. Der Sinn ist aber dennoch klar: Die zu einem DOS-  */
  /* suchmuster (à la "#?.xyz") passenden Dateien finden und  */
  /* in die Dateiliste von Print einfügen.                    */

  if( lock = Lock( directory, SHARED_LOCK ) )
  {
    lock = CurrentDir( lock );

    if( AnPath = (AnchorPath *) AllocMem( sizeof( AnchorPath )+255, MEMF_CLEAR ) )
    {
      AnPath->ap_BreakBits = SIGBREAKF_CTRL_C;
      AnPath->ap_Strlen    = 255;

      if( MatchFirst( pattern, AnPath ) == DOSFALSE )
      {
        retval = TRUE;

        for( ;; )
        {
          if( IsFile( &AnPath->ap_Info ) )
          {
            if( !list->Add( AnPath->ap_Buf ) )
            {
              printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );

              MatchEnd( AnPath );
              FreeMem( AnPath, sizeof( AnchorPath )+255 );

              lock = CurrentDir( lock );
              UnLock( lock );

              AppExit();
            }
          }

          if( MatchNext( AnPath ) != DOSFALSE )
            break;
        }

        MatchEnd( AnPath );
      }

      FreeMem( AnPath, sizeof( AnchorPath )+255 );

      lock = CurrentDir( lock );
      UnLock( lock );

      return( retval );
    }
    else
    {
      printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );

      lock = CurrentDir( lock );
      UnLock( lock );

      AppExit();
    }
  }
}

/* IsFile()
**
** fib -- Pointer auf ein FileInfoBlock-Struct
**
** Rückgabewert: TRUE=Erfolgreich, FALSE=Fehlgeschlagen
**
** Überprüfen, ob ein Pfad auf eine Datei weist
*/

BOOL IsFile( FileInfoBlock *fib )
{
  return( ( fib->fib_DirEntryType == ST_FILE     ) ||
          ( fib->fib_DirEntryType == ST_LINKFILE ) ||
          ( fib->fib_DirEntryType == ST_SOFTLINK ) );
}

/* FileInfos()
**
** path -- Pfad und Name einer gültigen Datei
** prot -- Pointer auf den Stream der Protokolldatei
** glob -- Pointer auf das PrintGlobal-Struct
**
** Rückgabewert: 0=Fehlgeschlagen, 1=Erfolgreich, 2=mn/mx-Überschreitung
**
** Ausgabe von dateispezifischen Informationen
*/

USHORT FileInfos( char *path, FILE *prot, PrintGlobal *glob )
{
  struct FileInfoBlock *fib = NULL;

  BPTR                  lock;

  if( fib = (FileInfoBlock *) AllocMem( sizeof( FileInfoBlock ), MEMF_CLEAR|MEMF_PUBLIC ) )
  {
    if( lock = Lock( path, ACCESS_READ ) )
    {
      if( Examine( lock, fib ) )
      {
        if( fib->fib_DirEntryType < 0 )
        {
          printf( " Filename      : [32m%s[0m\n", fib->fib_FileName );
          printf( " Filesize      : [32m%ld[0m bytes\n\n", fib->fib_Size );

          if( prot )
            fprintf( prot, " %s ( %ld bytes )... ", fib->fib_FileName, fib->fib_Size );

          UnLock( lock );
          FreeMem( fib, sizeof( FileInfoBlock ) );

          /* Liegt die Datei im Rahmen von -mn und -mx? Falls */
          /* nein, gibt die Funktion den Wert 2 zurück und    */
          /* die Datei wird vom Druckprozess ausgeschlossen.  */

          if( glob->pg_minsize || glob->pg_maxsize )
          {
            if( ( fib->fib_Size < glob->pg_minsize ) || ( fib->fib_Size > glob->pg_maxsize ) )
              return( 2 );
          }

          /* Alles bestens... */

          return( 1 );
        }
        else
        {
          printf( " [1mERROR:[0m \"%s\" is a directory, not a file!!!\n\n", path );

          UnLock( lock );
          FreeMem( fib, sizeof( FileInfoBlock ) );

          return( 0 );
        }
      }
      else
      {
        printf( " [1mERROR:[0m Can't examine \"%s\"!!!\n\n", path );

        UnLock( lock );
        FreeMem( fib, sizeof( FileInfoBlock ) );

        return( 0 );
      }
    }
    else
    {
      printf( " [1mERROR:[0m Lock on \"%s\" failed!!!\n\n", path );

      FreeMem( fib, sizeof( FileInfoBlock ) );

      return( 0 );
    }
  }
  else
  {
    printf( " [1mERROR:[0m Out of memory (%ld bytes available)!!!\n\n", AvailMem( MEMF_ANY ) );
    return( 0 );
  }
}

/* Print()
**
** inp    -- Pointer auf den Stream der Input-Datei
** outp   -- Pointer auf den Stream der Output-Datei
** prot   -- Pointer auf den Stream der Protokoll-Datei
** flags  -- Array mit den globalen Flags von Print
** buffer -- Pointer auf den initialisierten Datenpuffer
** bufsiz -- Größe des Puffers in Bytes
**
** Rückgabewert: TRUE=Erfolgreich, FALSE=Fehlgeschlagen
**
** Ausdrucken einer Datei
*/

BOOL Print( FILE *inp, FILE *outp, FILE *prot, BOOL flags[7], APTR buffer, ULONG bufsiz )
{
  ULONG bytesgone = 0;
  LONG  actbytes  = 0;

  /* Die nachfolgende Variable wird für einen kleinen Trick benötigt. */
  /* Siehe dazu unten...                                              */

  char  lastchar;

  /* Der Druckprozess kann mit CTRL-C abgebrochen werden */

  while( !( SetSignal( 0L, 0L ) & SIGBREAKF_CTRL_C ) )
  {
    /* Daten einlesen */

    if( fgets( (char *) buffer, bufsiz, inp ) )
    {
      /* Nun kommt der clevere Teil, der auch einen uralten Fehler von */
      /* Print behebt: fgets() liest eine einzelne Textzeile entweder  */
      /* bis zum FormFeed (\n) oder bis zum "bufsiz"-ten Zeichen ein,  */
      /* sofern die Zeile länger ist, als bufsiz. Die gelesenen Daten  */
      /* enthalten allerdings KEIN FormFeed mehr! Gibt man sie via der */
      /* Funktion fprintf() und ohne ein '\n' aus, endet das zuguter-  */
      /* letzt in einem Abbruch des Druckprozesses. Benutzt man hinge- */
      /* gen ein '\n', kann es vorkommen, daß eine Zeile, die länger   */
      /* als bufsiz Zeichen ist, mittendrin abgebrochen und in der     */
      /* nächsten Zeile fortgesetzt wird :-(.                          */
      /*                                                               */
      /* Der Trick (die Notlösung?) ist nun die: Nach dem Einlesen ei- */
      /* ner Zeile mit fgets() wird mit fseek() ein einziges Zeichen   */
      /* zurückgesprungen und überprüft, ob es sich dabei um ein Form- */
      /* feed gehandelt hat. Falls ja, verwendet fprintf() das '\n'-   */
      /* Zeichen. Falls nein, wird einfach weitergelesen.              */
      /*                                                               */
      /* Sollte dieser Fehler, der seit Print v1.20 existiert und bis  */
      /* heute unentdeckt bleiben konnte, irgend jemandem zur Last ge- */
      /* fallen sein, möchte ich mich entschuldigen.                   */

      fseek( inp, -1, SEEK_CUR );
      lastchar = fgetc( inp );

      if( lastchar == '\n' )
        actbytes = fprintf( outp, "%s\n", buffer );
      else
        actbytes = fprintf( outp, "%s", buffer );

      /* Ab hier geht alles ganz normal weiter. Durch das fgetc() ist */
      /* der Dateilesekopf auch wieder an der richtigen Stelle und    */
      /* wir müssen kein weiteres fseek() ausführen.                  */

      if( actbytes > 0 )
      {
        /* Status ausgeben */

        if( !flags[2] )
        {
          bytesgone += actbytes;
          printf( "\r Bytes printed : [32m%ld[0m...", bytesgone );
        }
      }
      else
      {
        printf( "\n\n [1mERROR:[0m Error while printing!!!\n\n" );

        if( flags[4] )
          fprintf( prot, "Error while printing!" );

        return( FALSE );
      }
    }
    else
    {
      /* Sind wir am Dateiende angelangt oder hat sich ein */
      /* Fehler ereignet?                                  */

      if( feof( inp ) )
      {
        /* Aha, alles okay. */

        if( !flags[2] )
          printf( " [33mDONE[0m\n" );

        if( flags[4] )
          fprintf( prot, "DONE\n" );

        return( TRUE );
      }
      else
      {
        /* Whoops, ein Fehler! */

        printf( "\n\n [1mERROR:[0m Error while reading data!!!\n\n" );

        if( flags[4] )
          fprintf( prot, "Error while reading data!" );

        return( FALSE );
      }
    }
  }

  /* Abbruch durch CTRL-C... */

  printf( " [33m*BREAK*[0m\n\n" );
  printf( " [1mPrinting aborted!!![0m\n\n" );

  SetSignal( 0L, SIGBREAKF_CTRL_C );

  if( flags[4] )
    fprintf( prot, "*BREAK*" );

  return( FALSE );
}

/* CalcTimeDiff()
**
** stime -- Pointer auf ein tm-Struct mit der Startzeit
** etime -- Pointer auf ein tm-Struct mit der Endzeit
** ptime -- Pointer auf ein PrintTime-Struct
**
** Rückgabewert: -keiner-
**
** Errechnen der Differenz zwischen Start- und Endzeit
*/

void CalcTimeDiff( tm *stime, tm *etime, PrintTime *ptime )
{
  int startsecs = ((stime->tm_hour * 3600) + (stime->tm_min * 60) + (stime->tm_sec)),
      endsecs   = ((etime->tm_hour * 3600) + (etime->tm_min * 60) + (etime->tm_sec)),
      diffsecs  = (endsecs - startsecs);

  ptime->pt_hours   = diffsecs/3600;
  ptime->pt_minutes = ((diffsecs - (ptime->pt_hours * 3600))/60);
  ptime->pt_seconds = (diffsecs - ((ptime->pt_hours * 3600) + (ptime->pt_minutes * 60)));
}

/********************************************************************/
/*                                                                  */
/*  Geschrieben von Marco Miljak                                    */
/*  © Copyright 15/08/1997                                          */
/*                                                                  */
/********************************************************************/

