
/*
**
**  $VER: dispatch.c 1.6 (27.9.96)
**  directory.datatype 1.0
**
**  Dispatch routine for a DataTypes class
**
**  Written 1996 by Roland 'Gizzy' Mainz
**  Original example source from David N. Junod
**
*/

/* main includes */
#include "classbase.h"

/* context for notification process */
struct NotifyTaskMessage
{
    struct Message        ntm_Message;   /* EXEC Message */
    struct ClassBase     *ntm_CB;        /* Datatypes context */
    Object               *ntm_Object;    /* Object */
    struct NotifyRequest  ntm_NR;
};

/* instance data */
struct DirectoryInstData
{
    APTR                      did_Pool;
    BOOL                      did_NewData;       /* Source data changed ?    */
    BOOL                      did_InMarkMode;    /* Currently in mark mode ? */
    STRPTR                    did_DirName;       /* Same as DTA_ObjName      */
    struct Window            *did_Window;        /* Attached to (win, req)   */
    struct Requester         *did_Requester;
    struct NotifyTaskMessage  did_NTM;           /* Notify task */
    ULONG                    *did_Methods;
    STRPTR                    did_TextBuffer;    /* Text buffer */
    ULONG                     did_TextBufferLen; /* Length of text buffer */
};


/*****************************************************************************/


struct IClass *initClass( struct ClassBase *cb )
{
    struct IClass *cl;

    /* Create our class... */
    if( cl = MakeClass( DIRECTORYDTCLASS, TEXTDTCLASS, NULL, (ULONG)sizeof( struct DirectoryInstData ), 0UL ) )
    {
      cl -> cl_Dispatcher . h_Entry = (HOOKFUNC)Dispatch;
      cl -> cl_UserData             = (ULONG)cb;

      AddClass( cl );
    }

    return( cl );
}

/*****************************************************************************/


void mysprintf( struct ClassBase *cb, STRPTR buffer, STRPTR fmt, ... )
{
    APTR args;

    args = (APTR)((&fmt) + 1);

    (void)RawDoFmt( fmt, args, (void (*))"\x16\xc0\x4e\x75", buffer );
}


/* Emulate SAS/C function stcul_d (does NOT set the return value (length)) */
#ifndef __SASC
#define stcul_d( out, ulvalue ) mysprintf( cb, buffer, "%lu", (ULONG)(ulvalue) )
#endif /* !__SASC */


/* set of supported methods (by this class) */
const
ULONG directory_includemethods[] =
{
    DTM_SELECT,
    DTM_CLEARSELECTED,
    GM_GOACTIVE,
    GM_HANDLEINPUT,
    GM_GOINACTIVE,
    OM_NEW,
    OM_DISPOSE,
    OM_UPDATE,
    OM_SET,
    (~0UL)
};

/* set of supported methods (by this class) when running on a text.datatype V41 or higher */
const
ULONG directory_includemethodsV41[] =
{
    GM_GOACTIVE,
    GM_HANDLEINPUT,
    GM_GOINACTIVE,
    OM_NEW,
    OM_DISPOSE,
    OM_UPDATE,
    OM_SET,
    (~0UL)
};


/* set of not supported methods (by this class,
 * which MUST be removed from DTAM_Methods list) 
 */
const
ULONG directory_excludemethods[] =
{
    (~0UL)
};


/* class dispatcher */
DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase         *cb = (struct ClassBase *)(cl -> cl_UserData);
    struct DirectoryInstData *did;
    ULONG                     retval = 0UL;

    switch( msg -> MethodID )
    {
        case OM_NEW:
        {
            struct TagItem *ti;
            APTR            pool;
            long            ioerr = 0L;

            /* We only support a DTA_SourceType of DTST_FILE (assumes that this is the default) */
            if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
            {
              if( (ti -> ti_Data) != DTST_FILE )
              {
                /* Wrong type of handle ! */
                SetIoErr( ERROR_OBJECT_WRONG_TYPE );

                break;
              }
            }

            /* Create a memory pool for the line list (4K blocks should be enough) */
            if( pool = CreatePool( (MEMF_CLEAR | MEMF_PUBLIC), 4096UL, 4096UL ) )
            {
              if( retval = DoSuperMethodA( cl, o, msg ) )
              {
                /* Get a pointer to the object data */
                did = (struct DirectoryInstData *)INST_DATA( cl, retval );

                did -> did_Pool = pool;

                /* Set word delim */
                SetAttrs( (Object *)retval, TDTA_WordDelim, "\xa0\n", TAG_DONE );

                if( !(did -> did_Methods = CopyDTSupportedMethods( cb, (Object *)retval, (did -> did_Pool),
                                                                   (ULONG *)(((cb -> cb_SuperClassBase -> lib_Version) <= 40U)?(directory_includemethods):(directory_includemethodsV41)),
                                                                   (ULONG *)directory_excludemethods )) )
                {
                  /* Can't build DTA_Methods list */
                  CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
                  retval = 0UL;

                  ioerr = ERROR_NO_FREE_STORE;
                }
              }
              else
              {
                /* No object from rootclass */
                ioerr = IoErr();

                DeletePool( pool );
              }
            }
            else
            {
              /* CreatePool failed */
              ioerr = ERROR_NO_FREE_STORE;
            }

            if( retval == 0UL )
            {
              /* No object ? == error */
              SetIoErr( ioerr );
            }
        }
            break;

        case OM_DISPOSE:
        {
            struct List *linelist;

            /* Get a pointer to our object data */
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            /* Remove notify task */
            DeleteNotifyTask( cb, o );

            /* Don't let the super class free the line list */
            if( GetAttr( TDTA_LineList, o, (ULONG *)(&linelist) ) == 1UL )
            {
              if( linelist )
              {
                NewList( linelist );
              }
            }

            /* Text buffer will be removed (by DeletePool) */
            SetAttrs( o, TDTA_Buffer,    NULL,
                         TDTA_BufferLen, 0UL,
                         TAG_DONE );

            /* Delete the line pool */
            DeletePool( (did -> did_Pool) );

            DoSuperMethodA( cl, o, msg );
        }
            break;

        case OM_GET:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            /* Redefine the DTA_Methods attribute */
            if( ((((struct opGet *)msg) -> opg_AttrID) == DTA_Methods) && (did -> did_Methods) )
            {
              *(((struct opGet *)msg) -> opg_Storage) = (ULONG)(did -> did_Methods);

              retval = 1UL;
            }
            else
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

        case OM_NOTIFY:
        {
            struct TagItem *ti;

            if( ti = FindTagItem( TDTA_WordSelect, (((struct opUpdate *)msg) -> opu_AttrList) ) )
            {
              if( ti -> ti_Data )
              {
                struct DTSpecialInfo *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
                struct MinList       *linelist;

                /* Lock the global object data (including TDTA_LineList) so that nobody else can manipulate it */
                ObtainSemaphore( (&(si -> si_Lock)) );

                if( GetAttr( TDTA_LineList, o, (ULONG *)(&linelist) ) == 1UL )
                {
                  struct Line *worknode,
                              *nextnode;
                  ULONG        filenamelen; /* length of filename (last compared) */

                  worknode = (struct Line *)(linelist -> mlh_Head);

                  while( nextnode = (struct Line *)(worknode -> ln_Link . mln_Succ) )
                  {
                    STRPTR term; /* filename terminator */

                    if( term = strstr( (worknode -> ln_Text), "\xa0" ) )
                    {
                      filenamelen = ((ULONG)(term - (worknode -> ln_Text))) + 1UL;

                      /* match ? */
                      if( !strncmp( (worknode -> ln_Text), (STRPTR)(ti -> ti_Data), (int)(filenamelen - 1UL)) )
                      {
                        break;
                      }
                    }

                    worknode = nextnode;
                  }

                  /* Something found ? */
                  if( nextnode )
                  {
                    ULONG  pathbuffsize;
                    STRPTR pathbuff;     /* Full path filename */

                    did = (struct DirectoryInstData *)INST_DATA( cl, o );

                    pathbuffsize = strlen( (did -> did_DirName) ) +  filenamelen + 10UL; /* should be enouth */

                    if( pathbuff = (STRPTR)AllocMem( pathbuffsize, MEMF_PUBLIC ) )
                    {
                      strcpy( pathbuff, (did -> did_DirName) );

                      if( AddPart( pathbuff, (STRPTR)(ti -> ti_Data), (pathbuffsize - 6UL) ) )
                      {
                        ULONG data; /* old (ti -> ti_Data) */

                        strcat( pathbuff, "/MAIN" );

                        data = ti -> ti_Data;

                        /* patch msg
                         * BUG: Official way would be to make a copy of the msg and it's contents
                         * (e.g. (opu -> opu_AttrList))
                         */
                        ti -> ti_Data = (ULONG)pathbuff;

                          retval = DoSuperMethodA( cl, o, msg );

                        ti -> ti_Data = data;
                      }
                      else
                      {
                        retval = DoSuperMethodA( cl, o, msg );
                      }

                      FreeMem( (APTR)pathbuff, pathbuffsize );
                    }
                  }
                  else
                  {
                    /* No matching node found...
                     * notify user using DisplayBeep on object's screen
                     */
                    struct Screen *scr;

                    did = (struct DirectoryInstData *)INST_DATA( cl, o );

                    if( did -> did_Window )
                    {
                      scr = did -> did_Window -> WScreen;
                    }
                    else
                    {
                      scr = NULL;
                    }

                    DisplayBeep( scr );
                  }
                }

                ReleaseSemaphore( (&(si -> si_Lock)) );
              }
            }
            else
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

        case OM_UPDATE:
        case OM_SET:
        {
            /* Avoid OM_NOTIFY loops */
            if( DoMethod( o, ICM_CHECKLOOP ) )
            {
              break;
            }

            /* Pass the attributes to the text class and force a refresh
             * if we need it
             */
            if( (retval = DoSuperMethodA( cl, o, msg )) && (OCLASS( o ) == cl) )
            {
              struct RastPort *rp;

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct opSet *)msg) -> ops_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct opSet *)msg) -> ops_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_UPDATE;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );
              }

              retval = 0UL;
            }
        }
            break;

        case GM_LAYOUT:
        {
            /* Tell everyone that we are busy doing things */
            notifyAttrChanges( o, (((struct gpLayout *)msg) -> gpl_GInfo), 0UL,
                               GA_ID,    (G( o ) -> GadgetID),
                               DTA_Busy, TRUE,
                               TAG_DONE );

            /* Let the super-class partake */
            retval = DoSuperMethodA( cl, o, msg );

            /* We need to do this one asynchronously */
            retval += DoAsyncLayout( o, (struct gpLayout *)msg );
        }
            break;

        case DTM_PROCLAYOUT:
        {
            /* Tell everyone that we are busy doing things */
            notifyAttrChanges( o, (((struct gpLayout *)msg) -> gpl_GInfo), 0UL,
                               GA_ID,    (G( o ) -> GadgetID),
                               DTA_Busy, TRUE,
                               TAG_DONE );

            /* Let the super-class partake and then fall through to our layout method */
            DoSuperMethodA( cl, o, msg );
        }
        case DTM_ASYNCLAYOUT:
        {
            /* Layout the text */
            retval = layoutMethod( cb, cl, o, ((struct gpLayout *)msg) );
        }
            break;

        case DTM_REMOVEDTOBJECT:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            DeleteNotifyTask( cb, o );

            did -> did_Window    = NULL;
            did -> did_Requester = NULL;

            retval = DoSuperMethodA( cl, o, msg );
        }
            break;

        case GM_GOACTIVE:
        case GM_HANDLEINPUT:
        {
            struct DTSpecialInfo *si  = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
            struct gpInput       *gpi = (struct gpInput *)msg;

            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            /* Disable mark "patch" if we're running on a text.datatype higher than V40 */
            if( (cb -> cb_SuperClassBase -> lib_Version) <= 40U )
            {
              if( (did -> did_InMarkMode) == FALSE )
              {
                if( (si -> si_Flags) & DTSIF_DRAGSELECT )
                {
                  did -> did_InMarkMode = TRUE;

                  DoMethod( o, DTM_CLEARSELECTED, (gpi -> gpi_GInfo) );
                }
              }
            }

            retval = DoSuperMethodA( cl, o, msg );
        }
            break;

        case GM_GOINACTIVE:
        {
            retval = DoSuperMethodA( cl, o, msg );

            /* Disable mark "patch" if we're running on a text.datatype higher than V40 */
            if( (cb -> cb_SuperClassBase -> lib_Version) <= 40U )
            {
              struct gpGoInactive *gpgi = (struct gpGoInactive *)msg;

              did = (struct DirectoryInstData *)INST_DATA( cl, o );

              if( did -> did_InMarkMode )
              {
                struct gpLayout gpl;

                did -> did_InMarkMode = FALSE;

                gpl . MethodID    = GM_LAYOUT;
                gpl . gpl_GInfo   = gpgi -> gpgi_GInfo;
                gpl . gpl_Initial = 0L;

                DoMethodA( o, (Msg)(&gpl) );

                notifyAttrChanges( o, (gpgi -> gpgi_GInfo), 0UL,
                                   GA_ID,    (ULONG)(G( o ) -> GadgetID),
                                   DTA_Sync, 1UL,
                                   TAG_DONE );
              }
            }
        }
            break;

        case DTM_CLEARSELECTED:
        {
            /* Disable mark "patch" if we're running on a text.datatype higher than V40 */
            if( (cb -> cb_SuperClassBase -> lib_Version) <= 40U )
            {
              struct IBox     *selectdomain;
              struct RastPort *rp;

              if( GetAttr( DTA_SelectDomain, o, (ULONG *)(&selectdomain) ) == 1UL )
              {
                if( selectdomain )
                {
                  static const 
                  struct IBox all_selected = { ~0, ~0, ~0, ~0 };

                  ((struct DTSpecialInfo *)(G( o ) -> SpecialInfo)) -> si_Flags &= ~DTSIF_HIGHLIGHT;

                  SetAttrs( o, DTA_SelectDomain, (&all_selected), TAG_DONE );
                }
              }

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct dtGeneral *)msg) -> dtg_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct dtGeneral *)msg) -> dtg_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_REDRAW;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );
              }
            }
            else
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

        /* Let the superclass handle everything else */
        default:
        {
            retval = DoSuperMethodA( cl, o, msg );
        }
            break;
    }

    return( retval );
}


/* layout method (DTM_ASYNCLAYOUT) */
ULONG layoutMethod( struct ClassBase *cb, struct IClass *cl, Object *o, struct gpLayout *gpl )
{
    /* Context */
    struct DTSpecialInfo       *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
    struct DirectoryInstData   *did = (struct DirectoryInstData *)INST_DATA( cl, o );

    /* Attributes obtained from super-class */
    struct TextAttr            *tattr;
    struct TextFont            *font;
    struct MinList             *linelist;
    struct IBox                *domain;
    STRPTR                      objname;

    /* Line information */
    ULONG                       xoffset = 0UL;
    ULONG                       yoffset = 0UL;
    UBYTE                       fgpen = 1U,     /* Should be one of the drawinfo pens */
                                bgpen = 0U;

    /* Misc */
    ULONG                       nomwidth,
                                nomheight;

    ULONG                       maxx = 0UL,
                                maxy;

    ULONG                       total = 0UL;
    ULONG                       bsig  = 0UL;

    LONG                        errorlevel = RETURN_OK,
                                error      = 0L;

    struct TagItem              NotifyTags[ 16 ];

    /* Get all the attributes that we are going to need for a successful layout */
    if( GetDTAttrs( o,
                    DTA_TextAttr,   (&tattr),
                    DTA_TextFont,   (&font),
                    DTA_Domain,     (&domain),
                    DTA_ObjName,    (&objname),
                    TDTA_LineList,  (&linelist),
                    TAG_DONE ) == 5UL )
    {
      struct RastPort trp;

      /* Lock the global object data so that nobody else can manipulate it */
      ObtainSemaphore( (&(si -> si_Lock)) );

      /* Initialize the temporary RastPort */
      InitRastPort( (&trp) );
      SetFont( (&trp), font );

      /* Calculate the nominal size */
      nomheight = (ULONG)(24UL  * (font -> tf_YSize) );
      nomwidth  = (ULONG)(140UL * (font -> tf_XSize) );

      /* Store name */
      did -> did_DirName = objname;

      /* Store GadgetInfo context */
      if( (did -> did_Window) == NULL )
      {
        did -> did_Window    = gpl -> gpl_GInfo -> gi_Window;
        did -> did_Requester = gpl -> gpl_GInfo -> gi_Requester;
      }

      /* We only need to perform layout if this is the initial layout call
       * or if the source data (directory) has been changed
       */
      if( (gpl -> gpl_Initial) || (did -> did_NewData) )
      {
        struct Line *line;
        BPTR         dirlock;
        APTR         tmppool;

        did -> did_NewData = FALSE;

        /* Delete the old line list */
        while( line = (struct Line *)RemHead( (struct List *)linelist ) )
        {
          FreePooled( (did -> did_Pool), (APTR)line, (ULONG)sizeof( struct Line ) );
        }

        if( tmppool = CreatePool( MEMF_ANY, 2048UL, 2048UL ) )
        {
          /* Lock dir given by DTA_ObjName */
          if( dirlock = Lock( objname, SHARED_LOCK ) )
          {
            struct FileInfoBlock *fib;

            if( fib = (struct FileInfoBlock *)AllocDosObject( DOS_FIB, NULL ) )
            {
              if( Examine( dirlock, fib ) )
              {
                /* dirlock MUST be a directory lock */
                if( (fib -> fib_DirEntryType) >= 0L )
                {
                  STRPTR version;         /* obj version number    */
                  STRPTR tbuff;           /* text.datatype buffer  */
                  ULONG  tbuffsize = 0UL; /* size of tbuff         */
                  LONG   exioerr;         /* ExNext Io error       */
                  BOOL   exsucc;          /* ExNext success        */

                  /* Set DTA_ObjAnnotation to (fib -> fib_Comment),
                   * DTA_ObjVersion to ISO file versions ("file;6" for file "file", version 6)
                   */
                  if( version = strstr( objname, ";" ) )
                  {
                    /* Get char following ';' */
                    version++;
                  }
                  else
                  {
                    version = "0";
                  }

                  SetAttrs( o, DTA_ObjAnnotation, (fib -> fib_Comment),
                               DTA_ObjVersion,    version,
                               TAG_DONE );

                  /* Step through the directory,
                   * stop if we reach the end of dir,
                   * or if be got SIGBREAKF_CTRL_C signal (abort layout)
                   * or if any error occurs
                   */
                  while( (exsucc = ExNext( dirlock, fib )),        /* Get next directory entry    */
                         (exioerr = IoErr()),                      /* Get ExNext error code       */
                         (bsig = CheckSignal( SIGBREAKF_CTRL_C )), /* Check to see if layout has been aborted */
                         exsucc && (exioerr == 0L) &&              /* ExNext failure ?            */
                         (bsig == 0UL) &&                          /* Abort layout signal ?       */
                         (errorlevel == RETURN_OK) )               /* Any other error ?           */
                  {
                    ULONG  filenamelen,
                           memsize;
                    STRPTR buff,
                           infobuff;

                    filenamelen = (ULONG)strlen( (fib -> fib_FileName) );

                    memsize = filenamelen + 140UL; /* filenamlen +
                                                    * strlen( typename )        [max 12 bytes] +
                                                    * filesize                  [max 12 bytes] +
                                                    * userid                    [max 12 bytes] +
                                                    * groupid                   [max 12 bytes] +
                                                    * flags                     [     8 bytes] +
                                                    * group access              [     4 bytes] +
                                                    * user access               [     4 bytes] +
                                                    * date (3*(LEN_DATSTRING+2))[    54 bytes] +
                                                    * spaces                    [    13 bytes] +
                                                    * terminator                [     1 byte ] +
                                                    * safety space              [    12 bytes]
                                                    * ------------------------------------------
                                                    * filenamelen + [140 bytes]
                                                    */

                    /* Temp buffer for line contents, will be freed at DeletePool time */
                    if( buff = (STRPTR)AllocPooled( tmppool, memsize ) )
                    {
                      /* Allocate a new line segment from our memory pool */
                      if( line = (struct Line *)AllocPooled( (did -> did_Pool), (ULONG)sizeof( struct Line ) ) )
                      {
                        ULONG  swidth,
                               textlen;
                        STRPTR typename;
                        TEXT   sizebuff[ 12 ],
                               uidbuff[ 12 ],
                               gidbuff[ 12 ];
                        STRPTR datebuff;

                        memset( (void *)buff, ' ', 32 );
                        buff[ 31 ] = '\0';
                        strcpy( buff, (fib -> fib_FileName) );

                        infobuff = buff + filenamelen;
                        *infobuff++ = '\xa0'; /* file name word terminator */

                        if( (infobuff - buff) < 30UL )
                        {
                          infobuff = &buff[ 30 ];
                        }

                        /* Determinate filesystem object type */
                        switch( (fib -> fib_DirEntryType) )
                        {
                          case ST_ROOT:
                          {
                              typename = "root";
                          }
                              break;

                          case ST_USERDIR:
                          {
                              typename = "dir";
                          }
                              break;

                          case ST_SOFTLINK:
                          {
                              typename = "softlink"; /* looks like dir, but may point to a file! */
                          }
                              break;

                          case ST_LINKDIR:
                          {
                              typename = "linkdir";  /* hard link to dir */
                          }
                              break;

                          case ST_FILE:
                          {
                              typename = "file";
                          }
                              break;

                          case ST_LINKFILE:
                          {
                              typename = "linkfile"; /* hard link to file */
                          }
                              break;

                          case ST_PIPEFILE:
                          {
                              typename = "pipefile"; /* for pipes that support ExamineFH */
                          }
                              break;

                          default:
                          {
                              /* (fib -> fib_DirEntryType) does not match any ST_#? type above,
                               * return a simple, generic type
                               */
                              if( (fib -> fib_DirEntryType) < 0L )
                              {
                                typename = "file like";
                              }
                              else
                              {
                                typename = "dir like";
                              }
                          }
                              break;
                        }

                        /* Fill buffers (file size, user id and group id */
                        if( (fib -> fib_DirEntryType) < 0L ) /* Format size only if used */
                        {
                          stcul_d( sizebuff, (ULONG)(fib -> fib_Size) );
                        }

                        stcul_d( uidbuff, (ULONG)(fib -> fib_OwnerUID) );
                        stcul_d( gidbuff, (ULONG)(fib -> fib_OwnerGID) );

                        mysprintf( cb, infobuff, "%10s %8s <%8s> <%8s> %lc%lc%lc%lc%lc%lc%lc%lc %lc%lc%lc%lc %lc%lc%lc%lc ",
                                   typename,
                                   /* size */
                                   (((fib -> fib_DirEntryType) < 0L)?(sizebuff):("")),
                                   /* UID, GID */
                                   ((fib -> fib_OwnerUID)?(uidbuff):("No Owner")),
                                   ((fib -> fib_OwnerGID)?(gidbuff):("No Group")),
                                   /* protection/flags/user access */
                                   (ULONG)(((fib -> fib_Protection) & 7L)              ?('-'):('-')), /* hold flag */
                                   (ULONG)(((fib -> fib_Protection) & FIBF_SCRIPT)     ?('s'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_PURE)       ?('p'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_ARCHIVE)    ?('a'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_READ)       ?('-'):('r')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_WRITE)      ?('-'):('w')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_EXECUTE)    ?('-'):('e')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_DELETE)     ?('-'):('d')),
                                   /* group access */
                                   (ULONG)(((fib -> fib_Protection) & FIBF_GRP_READ)   ?('r'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_GRP_WRITE)  ?('w'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_GRP_EXECUTE)?('e'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_GRP_DELETE) ?('d'):('-')),
                                   /* other access */
                                   (ULONG)(((fib -> fib_Protection) & FIBF_OTR_READ)   ?('r'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_OTR_WRITE)  ?('w'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_OTR_EXECUTE)?('e'):('-')),
                                   (ULONG)(((fib -> fib_Protection) & FIBF_OTR_DELETE) ?('d'):('-')) );

                        /* Write date to buffer */
                        datebuff = infobuff + strlen( infobuff ); /* Append date */
                        sprintfdate( cb, datebuff, (&(fib -> fib_Date)) );

                        /* Calculate the length of the text string */
                        textlen = (ULONG)((&infobuff[ strlen( infobuff ) ]) - buff);

                        /* Calculate the width of the text string */
                        swidth = (ULONG)TextLength( (&trp), buff, textlen );

                        line -> ln_Text = buff;

                        line -> ln_TextLen = textlen;
                        line -> ln_XOffset = xoffset;
                        line -> ln_YOffset = yoffset + (font -> tf_Baseline);
                        line -> ln_Width   = swidth;
                        line -> ln_Height  = font -> tf_YSize;
                        line -> ln_Flags   = LNF_LF;
                        line -> ln_FgPen   = fgpen;
                        line -> ln_BgPen   = bgpen;
                        line -> ln_Style   = FS_NORMAL;
                        line -> ln_Data    = NULL;

                        /* Add the line to the list */
                        AddTail( (struct List *)linelist, (struct Node *)(&(line -> ln_Link)) );

                        /* Increment the line width */
                        xoffset += swidth;

                        maxx = (xoffset > maxx)?(xoffset):(maxx);

                        /* Linefeed (new line) */
                        yoffset += font -> tf_YSize;
                        xoffset = 0UL;
                        total++;

                        /* Bump the size of the text.datatype buffer */
                        tbuffsize += (textlen + 1UL);
                      }
                      else
                      {
                        /* No line node */
                        errorlevel = RETURN_ERROR;
                        error      = ERROR_NO_FREE_STORE;
                      }
                    }
                    else
                    {
                      /* No temp buffer */
                      errorlevel = RETURN_ERROR;
                      error      = ERROR_NO_FREE_STORE;
                    }
                  }

                  /* Alloc TDTA_Buffer buffer */
                  if( tbuffsize )
                  {
                    if( tbuff = (STRPTR)AllocPooled( (did -> did_Pool), tbuffsize ) )
                    {
                      struct Line *worknode,
                                  *nextnode;
                      STRPTR       s;

                      s        = tbuff;
                      worknode = (struct Line *)(linelist -> mlh_Head);

                      /* Copy line nodes's text into buffer (temp text is freed during DeletePool( tmppool )) */
                      while( nextnode = (struct Line *)(worknode -> ln_Link . mln_Succ) )
                      {
                        STRPTR text;

                        text = worknode -> ln_Text;

                        strcpy( s, text );

                        worknode -> ln_Text = s;

                        s += (worknode -> ln_TextLen);
                        *s++ = '\n'; /* Add newline (assumes that all line nodes have the LNF_LF flag set) */

                        worknode = nextnode;
                      }
                    }
                  }
                  else
                  {
                    tbuffsize = 1UL;

                    if( tbuff = (STRPTR)AllocPooled( (did -> did_Pool), tbuffsize ) )
                    {
                      tbuff[ 0 ] = ' ';
                    }
                  }

                  if( tbuff )
                  {
                    /* Free old text buffer
                     * (can be BEFORE OM_SET, TDTA_Buffer, ..., ... because we've locked the class data)
                     */
                    if( (did -> did_TextBuffer) && (did -> did_TextBufferLen) )
                    {
                      FreePooled( (did -> did_Pool), (APTR)(did -> did_TextBuffer), (did -> did_TextBufferLen) );
                    }

                    SetAttrs( o, TDTA_Buffer,     tbuff,
                                 TDTA_BufferLen,  tbuffsize,
                                 TAG_DONE );

                    did -> did_TextBuffer    = tbuff;
                    did -> did_TextBufferLen = tbuffsize;
                  }
                  else
                  {
                    /* Can't alloc new text.datatype buffer*/
                    errorlevel = RETURN_ERROR;
                    error      = ERROR_NO_FREE_STORE;
                  }

                  /* Check if we reached the end of dir (ExNext returns IoErr() == ERROR_NO_MORE_ENTRIES) */
                  if( (exsucc == FALSE) && (exioerr != ERROR_NO_MORE_ENTRIES) )
                  {
                    /* ExNext error */
                    errorlevel = RETURN_ERROR;
                    error      = exioerr;
                  }
                }
                else
                {
                  /* Not a directory lock */
                  errorlevel = RETURN_FAIL;
                  error      = ERROR_OBJECT_WRONG_TYPE;
                }
              }
              else
              {
                /* Examine failed */
                errorlevel = RETURN_FAIL;
                error      = IoErr();
              }

              FreeDosObject( DOS_FIB, (APTR)fib );
            }
            else
            {
              /* Can't alloc FIB */
              errorlevel = RETURN_FAIL;
              error      = ERROR_NO_FREE_STORE;
            }

            UnLock( dirlock );
          }
          else
          {
            /* Can't Lock named object */
            errorlevel = RETURN_FAIL;
            error      = IoErr();
          }

          DeletePool( tmppool );
        }
        else
        {
          /* Can't create temp pool */
          errorlevel = RETURN_FAIL;
          error      = ERROR_NO_FREE_STORE;
        }
      }
      else
      {
        /* No layout to perform */
        total = si -> si_TotVert;
        maxx  = si -> si_TotHoriz;
      }

      maxy = total * (font -> tf_YSize);

      /* Compute the lines and columns type information */
      si -> si_VertUnit  = (LONG)(font -> tf_YSize);
      si -> si_VisVert   = (LONG)((domain -> Height) / (si -> si_VertUnit));
      si -> si_TotVert   = (LONG)(maxy / (si -> si_VertUnit));

      si -> si_HorizUnit = 1L;
      si -> si_VisHoriz  = (LONG)(domain -> Width);
      si -> si_TotHoriz  = (LONG)maxx;

      /* Build notify attrs */
      NotifyTags[  0 ] . ti_Tag  = DTA_VisibleVert;
      NotifyTags[  0 ] . ti_Data = (si -> si_VisVert);
      NotifyTags[  1 ] . ti_Tag  = DTA_TotalVert;
      NotifyTags[  1 ] . ti_Data = (si -> si_TotVert);
      NotifyTags[  2 ] . ti_Tag  = DTA_NominalVert;
      NotifyTags[  2 ] . ti_Data = nomheight;
      NotifyTags[  3 ] . ti_Tag  = DTA_VertUnit;
      NotifyTags[  3 ] . ti_Data = (si -> si_VertUnit);
      NotifyTags[  4 ] . ti_Tag  = DTA_VisibleHoriz;
      NotifyTags[  4 ] . ti_Data = (si -> si_VisHoriz);
      NotifyTags[  5 ] . ti_Tag  = DTA_TotalHoriz;
      NotifyTags[  5 ] . ti_Data = (si -> si_TotHoriz);
      NotifyTags[  6 ] . ti_Tag  = DTA_NominalHoriz;
      NotifyTags[  6 ] . ti_Data = nomwidth;
      NotifyTags[  7 ] . ti_Tag  = DTA_HorizUnit;
      NotifyTags[  7 ] . ti_Data = (ULONG)(si -> si_HorizUnit);
      NotifyTags[  8 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorLevel );
      NotifyTags[  8 ] . ti_Data = errorlevel;
      NotifyTags[  9 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorNumber );
      NotifyTags[  9 ] . ti_Data = error;
      NotifyTags[ 10 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorString );
      NotifyTags[ 10 ] . ti_Data = (ULONG)objname;
      NotifyTags[ 11 ] . ti_Tag  = GA_ID;
      NotifyTags[ 11 ] . ti_Data = (ULONG)(G( o ) -> GadgetID);
      NotifyTags[ 12 ] . ti_Tag  = XTAG( (errorlevel == RETURN_OK), DTA_Title );
      NotifyTags[ 12 ] . ti_Data = (ULONG)objname;
      NotifyTags[ 13 ] . ti_Tag  = DTA_Busy;
      NotifyTags[ 13 ] . ti_Data = (ULONG)FALSE;
      NotifyTags[ 14 ] . ti_Tag  = DTA_Sync;
      NotifyTags[ 14 ] . ti_Data = (ULONG)TRUE;
      NotifyTags[ 15 ] . ti_Tag  = TAG_DONE;
      NotifyTags[ 15 ] . ti_Data = 0UL;

      /* Release the global data lock */
      ReleaseSemaphore( (&(si -> si_Lock)) );

      /* Were we aborted? */
      if( bsig == 0UL )
      {
        /* Not aborted, so tell the world of our newest attributes */
        notifyAttrChanges( o, (gpl -> gpl_GInfo), 0UL, TAG_MORE, NotifyTags );

        /* Create notify task at initial layout time */
        if( gpl -> gpl_Initial )
        {
          CreateNotifyTask( cb, o );
        }
      }
    }

    return( total );
}


/* Notify attribute changes (OM_NOTIFY) */
ULONG notifyAttrChanges( Object *o, struct GadgetInfo *ginfo, ULONG flags, Tag tag1, ... )
{
    struct opUpdate opu;

    opu . MethodID     = OM_NOTIFY;
    opu . opu_AttrList = (struct TagItem *)(&tag1);
    opu . opu_GInfo    = ginfo;
    opu . opu_Flags    = flags;

    return( DoMethodA( o, (Msg)(&opu) ) );
}


/* Create object's notification process */
void CreateNotifyTask( struct ClassBase *cb, Object *o )
{
    if( cb && o )
    {
      struct DirectoryInstData *did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );

      Forbid();

        if( (did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task) == NULL )
        {
          memset( (void *)(&(did -> did_NTM . ntm_NR)), 0, sizeof( struct NotifyRequest ) );

          did -> did_NTM . ntm_Message . mn_Node . ln_Type              = NT_UNKNOWN;
          did -> did_NTM . ntm_Message . mn_ReplyPort                   = NULL;
          did -> did_NTM . ntm_Message . mn_Length                      = (UWORD)sizeof( struct NotifyTaskMessage );
          did -> did_NTM . ntm_CB                                       = cb;
          did -> did_NTM . ntm_Object                                   = o;
          did -> did_NTM . ntm_NR . nr_Name                             = did -> did_DirName;
          did -> did_NTM . ntm_NR . nr_Flags                            = NRF_SEND_SIGNAL;
          did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_SignalNum = SIGBREAKB_CTRL_E;

          if( did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task = (struct Task *)CreateNewProcTags( NP_Entry,     DoNotifyTask,
                                                                                                           NP_Name,      (cb -> cb_Lib . cl_Class -> cl_ID),
                                                                                                           NP_Priority,  2UL,
                                                                                                           NP_WindowPtr, (did -> did_Window),
                                                                                                           TAG_DONE ) )
          {
            PutMsg( (&(((struct Process *)(did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task)) -> pr_MsgPort)), (&(did -> did_NTM . ntm_Message)) );
          }
        }

      Permit();
    }
}


/* Drop object's notification task (by sending CTRL_C and waiting for it's death) if one exists */
void DeleteNotifyTask( struct ClassBase *cb, Object *o )
{
    if( cb && o )
    {
      struct DirectoryInstData *did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );

      Forbid();

      if( did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task )
      {
        Signal( (did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task), SIGBREAKF_CTRL_C );

        Permit();

        while( (did -> did_NTM . ntm_Message . mn_Node . ln_Type) != NT_FREEMSG )
        {
          Delay( (TICKS_PER_SECOND / 4UL) );
        }
      }
      else
      {
        Permit();
      }
    }
}


/* format datestamp */
void sprintfdate( struct ClassBase *cb, STRPTR buffer, struct DateStamp *ds )
{
    TEXT StrDay[ LEN_DATSTRING ],
         StrDate[ LEN_DATSTRING ],
         StrTime[ LEN_DATSTRING ];

    struct DateTime dt;

    dt . dat_Stamp   = *ds;
    dt . dat_Format  = FORMAT_DOS;
    dt . dat_Flags   = 0U;
    dt . dat_StrDay  = StrDay;
    dt . dat_StrDate = StrDate;
    dt . dat_StrTime = StrTime;

    if( DateToStr( (&dt) ) )
    {
      mysprintf( cb, buffer, "%10s %8s %8s", StrDay, StrDate, StrTime );
    }
}


/* Build list of supported methods */
ULONG *CopyDTSupportedMethods( struct ClassBase *cb, Object *o, APTR pool, ULONG *includemethods, ULONG *excludemethods )
{
    if( o && pool )
    {
      ULONG *methods;

      if( methods = GetDTMethods( o ) )
      {
        ULONG  numincludemethods,
               nummethods;

        ULONG *copymethods;

        nummethods        = NumMethods( methods );
        numincludemethods = NumMethods( includemethods );

        if( copymethods = (ULONG *)AllocPooled( pool, ((nummethods + numincludemethods + 2UL) * sizeof( ULONG )) ) )
        {
          ULONG *x;

          /* Copy methods, including their terminator */
          memcpy( (void *)copymethods, (void *)methods, (size_t)((nummethods + 1UL) * sizeof( ULONG )) );

          if( includemethods )
          {
            /* Find terminator (== ~0UL) of copymethods */
            if( x = FindMethod( copymethods, (~0UL) ) )
            {
              memcpy( (void *)x, (void *)includemethods, (size_t)((numincludemethods + 1UL) * sizeof( ULONG )) );
            }
          }

          if( excludemethods )
          {
            while( (*excludemethods) != (~0UL) )
            {
              if( x = FindMethod( copymethods, (*excludemethods) ) )
              {
                *x = OM_NEW;
              }

              excludemethods++;
            }
          }

          return( copymethods );
        }
      }
    }

    return( NULL );
}


ULONG NumMethods( ULONG *methods )
{
    ULONG num = 0UL;

    if( methods )
    {
      while( (*methods) != (~0UL) )
      {
        methods++;
        num++;
      }
    }

    return( num );
}


ULONG *FindMethod( ULONG *methods, ULONG MethodID )
{
    if( methods )
    {
      while( ((*methods) != (~0UL)) && ((*methods) != MethodID) )
      {
        methods++;
      }

      if( (*methods) == MethodID )
      {
        return( methods );
      }
    }

    return( NULL );
}


/* notification task main entry */
DISPATCHERFLAGS
void DoNotifyTask( void )
{
/* Follwowing #undef forces that following functions set up SysBase manually */
#undef SysBase
    struct ExecBase             *SysBase;
    struct Process              *thisprocess;
    struct NotifyTaskMessage    *ntm;
    struct ClassBase            *cb;
    Object                      *o;
    struct DTSpecialInfo        *si;
    struct DirectoryInstData    *did;

    SysBase     = *(struct ExecBase **)4UL;
    thisprocess = (struct Process *)FindTask( NULL );

    /* sync with parent */
    WaitPort( (&(thisprocess -> pr_MsgPort)) );

    if( ntm = (struct NotifyTaskMessage *)GetMsg( (&(thisprocess -> pr_MsgPort)) ) )
    {
      cb  = ntm -> ntm_CB;
      o   = ntm -> ntm_Object;
      did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );
      si  = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);

      if( StartNotify( (&(ntm -> ntm_NR)) ) )
      {
        ULONG signals;       /* Signals from Wait */
        UWORD rejected = 0U; /* Number of rejected attepts to load the directory, max == 4
                              * This allows that heavely changed directories are not 
                              * updated for every notification, but the object's contents 
                              * are after 4 * 0.5 sec == 2 sec up to date
                              */

        for( ;; )
        {
          /* Wait for notification or abort... */
          signals = Wait( (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E) );

          /* End notififation ? */
          if( signals & SIGBREAKF_CTRL_C )
          {
            break;
          }

          /* Notification ? */
          if( signals & SIGBREAKF_CTRL_E )
          {
            if( did -> did_Window )
            {
              struct gpLayout gpl;

              if( rejected == 0U )
              {
                struct opUpdate opu;
                struct TagItem  updatetags[ 4 ];

                /* Build notify msg */
                updatetags[ 0 ] . ti_Tag  = GA_ID;
                updatetags[ 0 ] . ti_Data = (ULONG)(G( o ) -> GadgetID);
                updatetags[ 1 ] . ti_Tag  = DTA_Title;
                updatetags[ 1 ] . ti_Data = (ULONG)"Updating...";
                updatetags[ 2 ] . ti_Tag  = DTA_Busy;
                updatetags[ 2 ] . ti_Data = TRUE;
                updatetags[ 3 ] . ti_Tag  = TAG_DONE;
                updatetags[ 3 ] . ti_Data = 0UL;

                opu . MethodID     = OM_NOTIFY;
                opu . opu_AttrList = updatetags;
                opu . opu_GInfo    = NULL;
                opu . opu_Flags    = 0UL;

                /* Tell everyone that we are busy doing things */
                DoDTMethodA( o, (did -> did_Window), (did -> did_Requester), (Msg)(&opu) );
              }

              /* Set flag "we've new data" */
              ObtainSemaphore( (&(si -> si_Lock)) );

                did -> did_NewData = TRUE;

              ReleaseSemaphore( (&(si -> si_Lock)) );

              /* Let other tasks (filesystem etc.) partake */
              Delay( TICKS_PER_SECOND / 2UL );

              /* Something changed ?
               * SIGBREAKF_CTRL_C                => abort notify task
               * SIGBREAKF_CTRL_E                => new notify, wait again
               * ((did -> did_NewData) == FALSE) => GM_LAYOUT has been executed while we're waiting
               */
              if( (SetSignal( 0UL, 0UL ) & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E)) || ((did -> did_NewData) == FALSE) )
              {
                rejected++;  /* This update attemp has been rejected */

                /* Maximum number of rejections reached ? */
                if( rejected < 4U )
                {
                  continue;
                }
              }

              rejected = 0U; /* Reset reject counter */

              gpl . MethodID    = GM_LAYOUT;
              gpl . gpl_GInfo   = NULL;
              gpl . gpl_Initial = 0L;

              DoDTMethodA( o, (did -> did_Window), (did -> did_Requester), (Msg)(&gpl) );
            }
          }
        }

        EndNotify( (&(ntm -> ntm_NR)) );
      }
    }

    /* Reply message and terminate */
    Forbid();

      did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task = NULL;

      ReplyMsg( (&(ntm -> ntm_Message)) );
}



