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

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

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

/* Envoy filesystem (PRIVATE ??) packets */
#define ACTION_UID2UNAME (20002L)
#define ACTION_GID2GNAME (20003L)

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

/* local prototypes */
static void  mysprintf( struct ClassBase *, STRPTR, STRPTR, ... );
static void  ReadENVPrefs( struct ClassBase *, struct DirectoryInstData * );
static ULONG layoutMethod( struct ClassBase *, struct IClass *, Object *, struct gpLayout * );
static ULONG notifyAttrChanges( Object *, struct GadgetInfo *, ULONG, Tag, ... );
static BOOL  GetEnvoyUserGroupName( struct ClassBase *, BPTR, STRPTR, STRPTR, ULONG, LONG, STRPTR );
static BOOL  MatchFIB( struct ClassBase *, struct DirectoryInstData *, struct FileInfoBlock * );

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

/* context for notification process */
struct NotifyTaskMessage
{
    struct Message        ntm_Message;   /* EXEC Message         */
    struct ClassBase     *ntm_CB;        /* Datatypes context    */
    Object               *ntm_Object;    /* Links back to object */
    struct NotifyRequest  ntm_NR;        /* DOS notify request   */
};

/* instance data */
struct DirectoryInstData
{
    APTR                      did_Pool;             /* Object memory pool (misc usage)                             */
    BOOL                      did_NewData;          /* Source data changed ?                                       */
    BOOL                      did_InMarkMode;       /* Currently in mark mode ?                                    */
    BOOL                      did_Progressive;      /* Was DTA_Progressive set ?                                   */
    BOOL                      did_Loading;          /* Loading in process                                          */
    STRPTR                    did_DirName;          /* Same as DTA_Name                                            */
    struct Window            *did_Window;           /* Attached to (win, req)                                      */
    struct Requester         *did_Requester;        /* dito.                                                       */
    struct NotifyTaskMessage  did_NTM;              /* Notify task context                                         */
    ULONG                    *did_Methods;          /* Our array of supported methods                              */
    ULONG                    *did_LoadingMethods;   /* Our array of supported methods during progressive loading   */
    STRPTR                    did_TextBuffer;       /* Text buffer                                                 */
    ULONG                     did_TextBufferLen;    /* Length of text buffer                                       */
    UBYTE                     did_DoUsers;          /* Preference items (should be flags...)                       */
    UBYTE                     did_DoGroups;         /*                   dito.                                     */
    UBYTE                     did_DoDataType;       /*                   dito.                                     */
    UBYTE                     did_DoNotifyUpdate;   /*                   dito.                                     */
    UBYTE                     did_DoAGLinks;        /*                   dito.                                     */
    UBYTE                     did_DoNoIcons;        /*                   dito.                                     */
    UWORD                     did_UseCounter;       /* usecounter to be compatible with datatypes.library < 45.4   */
    STRPTR                    did_DoPattern;        /* Match pattern                                               */
    STRPTR                    did_DoAcceptPattern;  /* Accept only thses names                                     */
    STRPTR                    did_DoRejectPattern;  /* Reject these names                                          */
};


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


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

    /* Create our class... */
    if( cl = MakeClass( DIRECTORYDTCLASS, TEXTDTCLASS, NULL, (ULONG)sizeof( struct DirectoryInstData ), 0UL ) )
    {
#define DTSTACKSIZE (16384UL)
      cl -> cl_Dispatcher . h_Entry    = (HOOKFUNC)StackSwapDispatch; /* see stackswap.c */
      cl -> cl_Dispatcher . h_SubEntry = (HOOKFUNC)Dispatch;          /* see stackswap.c */
      cl -> cl_Dispatcher . h_Data     = (APTR)DTSTACKSIZE;           /* see stackswap.c */
      cl -> cl_UserData                = (ULONG)cb;                   /* class library base as expected by datatypes.library */

      AddClass( cl );
    }

    return( cl );
}

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

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

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

    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 DTA_Methods list)
 */
const
ULONG directory_excludemethods[] =
{
    (~0UL)
};


/* set of not supported methods during (progressive) loading
 */
const
ULONG directory_exclude_loading_methods[] =
{
    GM_GOACTIVE,
    GM_HANDLEINPUT,
    GM_GOINACTIVE,

    DTM_SELECT,
    DTM_CLEARSELECTED,

    DTM_COPY,
    DTM_WRITE,
    DTM_PRINT,
    DTM_TRIGGER,

    (~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 )
    {
/****** directory.datatype/OM_NEW ********************************************
*
*    NAME
*        OM_NEW -- Create a directory.datatype object.
*
*    FUNCTION
*        The OM_NEW method is used to create an instance of the
*        directory.datatype class.  After checking given attributes, this
*        method is passed to the superclass. After this, directory.datatype
*        object finishes.
*        Note that OM_NEW will be rejected if any subclass is build on
*        directory.datatype (this behaviour is not implemented yet).
*
*    ATTRIBUTES
*        The following attributes can be specified at creation time.
*
*        DTA_SourceType (ULONG) -- Determinates the type of DTA_Handle
*            attribute. A source type of DTST_FILE or DTST_RAM  is
*            supported. If any other type was set in a given DTA_SourceType,
*            OM_NEW will be rejected.
*            Defaults to DTST_FILE.
*
*    RESULT
*        If the object was created a pointer to the object is returned,
*        otherwise NULL is returned.
*
******************************************************************************
*
*/
        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) and DTST_RAM */
            if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
            {
              if( ((ti -> ti_Data) != DTST_FILE) && 
                  ((ti -> ti_Data) != DTST_RAM) )
              {
                /* Wrong type of handle ! */
                SetIoErr( ERROR_OBJECT_WRONG_TYPE );

                break;
              }
            }

            /* Be sure that this isn't a subclass of directory.datatype (not implemented yet) */
            if( o == (Object *)cl )
            {
              /* 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;
                  did -> did_Progressive = GetTagData( DTA_Progressive, FALSE, (((struct opSet *)msg) -> ops_AttrList) );

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

                  did -> did_Methods = CopyDTMethods( GetDTMethods( (Object *)retval ),
                                                      (ULONG *)(((cb -> cb_SuperClassBase -> lib_Version) <= 40U)?(directory_includemethods):(directory_includemethodsV41)),
                                                      (ULONG *)directory_excludemethods );

                  did -> did_LoadingMethods = CopyDTMethods( (did -> did_Methods), NULL, (ULONG *)directory_exclude_loading_methods );

                  if( !((did -> did_Methods) && (did -> did_LoadingMethods)) )
                  {
                    /* Can't build DTA_Methods list */
                    CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
                    retval = 0UL;

                    ioerr = ERROR_NO_FREE_STORE;
                  }

                  ReadENVPrefs( cb, did );
                }
                else
                {
                  /* No object from rootclass */
                  ioerr = IoErr();

                  DeletePool( pool );
                }
              }
              else
              {
                /* CreatePool failed */
                ioerr = ERROR_NO_FREE_STORE;
              }
            }
            else
            {
              /* Don't allow subclasses */
              ioerr = ERROR_NOT_IMPLEMENTED;
            }

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

/****** directory.datatype/OM_DISPOSE ****************************************
*
*    NAME
*        OM_DISPOSE -- Delete a directory.datatype object.
*
*    FUNCTION
*        The OM_DISPOSE method is used to delete an instance of the
*        directory.datatype class.  This method is pass to the superclass when
*        it has completed.
*        This method frees all line nodes, pools and other resources
*        allocated. If the notify task is still running, OM_DISPOSE will
*        remove it from the system.
*
*    RESULT
*        The object is deleted. 0UL is returned.
*
******************************************************************************
*
*/
        case OM_DISPOSE:
        {
            struct List *linelist;
            LONG         saved_ioerr = IoErr();

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

            /* Wait a 1/10 sec until all layout processes are done with this object */
            while( did -> did_UseCounter )
            {
              Delay( (TICKS_PER_SECOND / 10UL) );
            }

            /* Let other tasks partake (e.g. the layout processes should really finish here...) */
            Delay( (TICKS_PER_SECOND / 5UL) );

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

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

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

            FreeDTMethods( (did -> did_LoadingMethods) );
            FreeDTMethods( (did -> did_Methods) );

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

            /* Pass msg to superclass... */
            DoSuperMethodA( cl, o, msg );

            SetIoErr( saved_ioerr );
        }
            break;

/****** directory.datatype/OM_GET ********************************************
*
*    NAME
*        OM_GET -- Obtain the value of an attribute
*
*    FUNCTION
*        The OM_DISPOSE method is used to get the value of an attribute
*        of the directory.datatype or one of it's superclasses.
*
*    ATTRIBUTES
*        DTA_Methods -- The list of supported methods is redefined to
*            hold the new set of supported methods (including DTM_SELECT
*             etc.).
*
*        DTA_Data (Object *) -- The object itself. When the object
*            is still loading data (e.g. if DTA_Progressive is TRUE), this 
*            pointer is NULL !
*
*    RESULT
*        0 is returned if the attribute's value cannot be obtained,
*        non-zero otherwise.
*
******************************************************************************
*
*/
        case OM_GET:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            switch( ((struct opGet *)msg) -> opg_AttrID )
            {
              /* Redefine the DTA_Methods attribute */
              case DTA_Methods:
              {
                  if( did -> did_Methods )
                  {
                    *(((struct opGet *)msg) -> opg_Storage) = (ULONG)(did -> did_Methods);
                    retval = 1UL;
                  }
              }
                  break;

              /* progressive loading support: DTA_Data points normally to the object itself (this is done
               * by "datatypesclass"). This class overrides datatypesclass. DTA_Data is NULL when the
               * object is still loading, and points to object again if loading has been finished.
               * See datatypes.library V45 docs for a description of the "progressive loading and display"
               * support.
               */
              case DTA_Data:
              {
                  *(((struct opGet *)msg) -> opg_Storage) = (ULONG)((did -> did_Loading)?(NULL):(o));
                  retval = 1UL;
              }
                  break;
            }

            /* If we did not recognize the attribute, ask our superclass for response... */
            if( retval == 0UL )
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

/****** directory.datatype/OM_NOTIFY *****************************************
*
*    NAME
*        OM_NOTIFY -- Notify new values of multiple attributes
*
*    FUNCTION
*        OM_NOTIFY is used by the gadgetclass instance to send notification
*        update messages to the ICA_TARGET object.
*
*        directory.datatype redefines this method do to a amigaguide.datatype
*        "kluge": The TDTA_WordSelect's value will be replaced with the
*        full path of the selected object (if is was a filesystem object,
*        otherwise a DisplayBeep is triggered) including the /MAIN string
*        which causes amigaguide.datatype to link to the specified object.
*
*    RESULT
*        Returns the return value of the superclass.
*
******************************************************************************
*
*/
        case OM_NOTIFY:
        {
            struct TagItem *ti;

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

            if( (did -> did_DoAGLinks) && (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( GetDTAttrs( o, TDTA_LineList, (ULONG *)(&linelist), TAG_DONE ) )
                {
                  struct Line *worknode,
                              *nextnode;
                  ULONG        filenamelen = 0UL; /* 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 */

                    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 );

                    /* Get window's screen (if a window exists) */
                    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:
        {
            /* Avoid OM_NOTIFY loops */
            if( DoMethod( o, ICM_CHECKLOOP ) )
            {
              break;
            }
        }
        case OM_SET:
        {
            /* Pass the attributes to the text class and force a refresh
             * if we need it
             */
            /* Redraw on request (by superclass) ...*/
            if( retval = DoSuperMethodA( cl, o, msg ) )
            {
/* The following statement is commented out because directory.datatype does not allow
 * subclasses. Thus, the following statement is NOP unless subclasses are supported...
 */
#ifdef COMMENTED_OUT
              if( OCLASS( o ) == cl )
#endif /* COMMENTED_OUT */
              {
                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 );
                }

                /* We did the refresh... */
                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: /* Application calls DTM_PROCLAYOUT manually */
        {
            /* 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: /* Inside the process started by DoAsyncLayout */
        {
            /* 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 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 )
            {
              struct DTSpecialInfo *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);

              /* Anyone working on the data ? */
              if( AttemptSemaphore( (&(si -> si_Lock)) ) )
              {
                /* Be __really__ sure that noone (layout process) is working on the data... */
                if( !((si -> si_Flags) & (DTSIF_LAYOUT | DTSIF_LAYOUTPROC)) )
                {
                  /* Be sure that we're not loading at the moment */
                  if( (did -> did_Loading) == FALSE )
                  {
                    if( (did -> did_InMarkMode) == FALSE )
                    {
                      /* Marking ? */
                      if( (si -> si_Flags) & DTSIF_DRAGSELECT )
                      {
                        /* Set mark flag (that later refreshing will be done...) */
                        did -> did_InMarkMode = TRUE;

                        /* Clear previous selected area */
                        DoMethod( o, DTM_CLEARSELECTED, (gpi -> gpi_GInfo) );
                      }
                    }
                  }
                }

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

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

        case GM_GOINACTIVE:
        {
            /* Let the superclass partake... */
            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 )
            {
              did = (struct DirectoryInstData *)INST_DATA( cl, o );

              if( did -> did_InMarkMode )
              {
                struct gpGoInactive *gpgi = (struct gpGoInactive *)msg;
                struct gpLayout      gpl;

                did -> did_InMarkMode = FALSE;

                /* Layout object ("size" layout)... */
                gpl . MethodID    = GM_LAYOUT;
                gpl . gpl_GInfo   = gpgi -> gpgi_GInfo;
                gpl . gpl_Initial = 0L;

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

                /* Force a redraw by the application... */
                notifyAttrChanges( o, (gpgi -> gpgi_GInfo), 0UL,
                                   GA_ID,    (ULONG)(G( o ) -> GadgetID),
                                   DTA_Sync, TRUE,
                                   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 DTSpecialInfo *si            = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
              struct IBox          *selectdomain;
              struct RastPort      *rp;

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

              if( GetDTAttrs( o, DTA_SelectDomain, (ULONG *)(&selectdomain), TAG_DONE ) )
              {
                if( selectdomain )
                {
                  const
                  struct IBox all_selected = { ~0, ~0, ~0, ~0 };

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

                  SetDTAttrs( o, NULL, NULL, DTA_SelectDomain, (&all_selected), TAG_DONE );
                }
              }

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

              /* 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;

        case DTM_COPY:
        case DTM_WRITE:
        case DTM_PRINT:
        case DTM_TRIGGER:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            /* Be __really__ sure that noone (layout process) is working on the data...
             * and be sure that we're not loading at the moment
             */
            if( (!((((struct DTSpecialInfo *)(G( o ) -> SpecialInfo)) -> si_Flags) & (DTSIF_LAYOUT | DTSIF_LAYOUTPROC))) &&
                ((did -> did_Loading) == FALSE) )
            {
              STRPTR tbuff;
              ULONG  tbuffsize;

              (void)GetDTAttrs( o, TDTA_Buffer,    (&tbuff),
                                   TDTA_BufferLen, (&tbuffsize),
                                   TAG_DONE );

              if( tbuff && tbuffsize )
              {
                retval = DoSuperMethodA( cl, o, msg );
              }
            }
            else
            {
              /* Object is "busy" */
              SetIoErr( ERROR_OBJECT_IN_USE );
            }
        }
            break;

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

    return( retval );
}


/* layout method (DTM_ASYNCLAYOUT) */
static
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                      name;
    BPTR                        dirlock;

    /* 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;
                                
    ULONG                       filllevel = 0UL;

    Forbid();

      did -> did_UseCounter++; /* Object-in-use counter. This is a compatibility patch to get rid of
                                * possible problems when running in progressive loading mode and
                                * datatypes.library launches more than one layout process
                                */

    Permit();

    /* Get all the attributes that we are going to need for a successful layout */
    if( GetDTAttrs( o,
                    DTA_Handle,     (&dirlock),
                    DTA_TextAttr,   (&tattr),
                    DTA_TextFont,   (&font),
                    DTA_Domain,     (&domain),
                    DTA_Name,       (&name),
                    TDTA_LineList,  (&linelist),
                    TAG_DONE ) == 6UL )
    {
      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 = name;

      /* 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;
        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( did -> did_Progressive )
        {
          did -> did_Loading = TRUE;
          si -> si_Flags    &= ~DTSIF_LAYOUT; /* WARNING: This is __VERY__ tricky.
                                               * Clearing DTSIF_LAYOUT tells datatypes.library that it can start
                                               * a new layout process. Only datatypes.library >= V45.4 is known
                                               * to be safe in this case...
                                               */

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

        if( tmppool = CreatePool( MEMF_ANY, 2048UL, 2048UL ) )
        {
          struct FileInfoBlock *fib;
          BPTR                  olddirlock;

          /* Change currentdir (to dir to read, mainly for later ObtainDataTypeA usage */
          olddirlock = CurrentDir( dirlock );

          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( name, ";" ) )
                {
                  /* Get char following ';' */
                  version++;
                }
                else
                {
                  version = "0";
                }

                SetDTAttrs( o, NULL, NULL, 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 ?                         */
                {
                  if( MatchFIB( cb, did, fib ) )
                  {
                    ULONG  filenamelen,
                           memsize;
                    STRPTR buff,
                           infobuff;

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

                    memsize = filenamelen + 174UL; /* filenamlen                               +
                                                    * strlen( typename )        [max 12 bytes] +
                                                    * filesize                  [max 12 bytes] +
                                                    * dt name                   [max 24 bytes] +
                                                    * dt group                  [max 10 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 + [174 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[ 14 ],
                                                gidbuff[ 14 ];
                        STRPTR                  datebuff;
                        BPTR                    dtlock;
                        LONG                    dterr = 0L;

                        /* DataTypes information */
                        struct DataTypeHeader  *dth = NULL;
                        struct DataType        *dtn;
                        TEXT                    dtbuff[ 256 ]; /* temp. buffer for datatypes data */

                        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";                        break; /* looks like dir, but may point to a file! */
                          case ST_LINKDIR:  typename = "linkdir";                         break; /* hard link to dir */
                          case ST_FILE:     typename = "file";                            break;
                          case ST_LINKFILE: typename = "linkfile";                        break; /* hard link to file */
                          case ST_PIPEFILE: typename = "pipefile";                        break; /* for pipes that support ExamineFH */
                          default:          typename = (((fib -> fib_DirEntryType) < 0L)?
                                                       ("file like"):
                                                       ("dir like"));                     break; /* (fib -> fib_DirEntryType) does not match any ST_#? type above,
                                                                                                  * return a simple, generic type
                                                                                                  */
                        }

                        /* 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) );
                        }

                        /* Get user name (by uid) */
                        if( did -> did_DoUsers )
                        {
                          if( fib -> fib_OwnerUID )
                          {
                            TEXT temp[ 32 ];

                            if( GetEnvoyUserGroupName( cb, dirlock, name, (fib -> fib_FileName), (ULONG)(fib -> fib_OwnerUID), ACTION_UID2UNAME, temp ) )
                            {
                              mysprintf( cb, uidbuff, "<%8.8s> ", temp );
                            }
                            else
                            {
                              mysprintf( cb, uidbuff, "<%8lu> ", (fib -> fib_OwnerUID) );
                            }
                          }
                          else
                          {
                            strcpy( uidbuff, "<No Owner> " );
                          }
                        }

                        /* Get group name (by gid) */
                        if( did -> did_DoGroups )
                        {
                          if( fib -> fib_OwnerGID )
                          {
                            TEXT temp[ 32 ];

                            if( GetEnvoyUserGroupName( cb, dirlock, name, (fib -> fib_FileName), (ULONG)(fib -> fib_OwnerGID), ACTION_GID2GNAME, temp ) )
                            {
                              mysprintf( cb, gidbuff, "<%8.8s> ", temp );
                            }
                            else
                            {
                              mysprintf( cb, gidbuff, "<%8lu> ", (fib -> fib_OwnerGID) );
                            }
                          }
                          else
                          {
                            strcpy( gidbuff, "<No Group> " );
                          }
                        }

                        if( did -> did_DoDataType )
                        {
                          if( dtlock = Lock( (fib -> fib_FileName), SHARED_LOCK ) )
                          {
                            /* Determine the DataType of the file */
                            if( dtn = ObtainDataTypeA( DTST_FILE, (APTR)dtlock, NULL ) )
                            {
                              dth = dtn -> dtn_Header;

                              mysprintf( cb, dtbuff, "%24.24s %10.10s ", (dth -> dth_Name), GetDTString( (dth -> dth_GroupID) ) );

                              /* Release the DataType */
                              ReleaseDataType( dtn );
                            }
                            else
                            {
                              /* ObtainDataTypeA failed */
                              dterr = IoErr();
                            }

                            UnLock( dtlock );
                          }
                          else
                          {
                            /* Lock failed */
                            dterr = IoErr();
                          }

                          if( dterr )
                          {
                            TEXT errstr[ 256 ];

                            Fault( IoErr(), "", errstr, sizeof( errstr ) );

                            mysprintf( cb, dtbuff, "%35.35s ", errstr );
                          }
                        }

                        /* Fill the info buffer */
                        {
                          ULONG protection = fib -> fib_Protection;

                          mysprintf( cb, infobuff, "%10s %8s %s%s%s%lc%lc%lc%lc%lc%lc%lc%lc %lc%lc%lc%lc %lc%lc%lc%lc ",
                                     typename,
                                     /* size */
                                     (((fib -> fib_DirEntryType) < 0L)?(sizebuff):("")),
                                     /* Datatypes */
                                     ((dth || dterr)?(dtbuff):("")),
                                     /* UID, GID */
                                     ((did -> did_DoUsers) ?(uidbuff):("")),
                                     ((did -> did_DoGroups)?(gidbuff):("")),
                                     /* protection/flags/user access */
                                     (LONG)((protection & 7L)              ?('-'):('-')), /* hold flag */
                                     (LONG)((protection & FIBF_SCRIPT)     ?('s'):('-')),
                                     (LONG)((protection & FIBF_PURE)       ?('p'):('-')),
                                     (LONG)((protection & FIBF_ARCHIVE)    ?('a'):('-')),
                                     (LONG)((protection & FIBF_READ)       ?('-'):('r')),
                                     (LONG)((protection & FIBF_WRITE)      ?('-'):('w')),
                                     (LONG)((protection & FIBF_EXECUTE)    ?('-'):('e')),
                                     (LONG)((protection & FIBF_DELETE)     ?('-'):('d')),
                                     /* group access */
                                     (LONG)((protection & FIBF_GRP_READ)   ?('r'):('-')),
                                     (LONG)((protection & FIBF_GRP_WRITE)  ?('w'):('-')),
                                     (LONG)((protection & FIBF_GRP_EXECUTE)?('e'):('-')),
                                     (LONG)((protection & FIBF_GRP_DELETE) ?('d'):('-')),
                                     /* other access */
                                     (LONG)((protection & FIBF_OTR_READ)   ?('r'):('-')),
                                     (LONG)((protection & FIBF_OTR_WRITE)  ?('w'):('-')),
                                     (LONG)((protection & FIBF_OTR_EXECUTE)?('e'):('-')),
                                     (LONG)((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;

                        if( did -> did_Progressive )
                        {
                          ObtainSemaphore( (&(si -> si_Lock)) );
                        }

                        /* 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++;
                        
                        if( did -> did_Progressive )
                        {
                          /* Inc fill level of object. This (buggy) implementation
                           * assumes ~256 entries per directory.
                           */
                          filllevel = MAX( (filllevel + 0x00FFFFFF), filllevel );

                          /* Compute the lines and columns type information */
                          maxy = total * (font -> tf_YSize);

                          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;

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

                          /* Tell the world of our newest attributes
                           * NOTE: Building the taglist when the (si -> si_Lock) semaphore is not locked is not race-condition free,
                           *       but this isn't very serious in THIS case...
                           */
                          notifyAttrChanges( o, (gpl -> gpl_GInfo), 0UL,
                                             DTA_VisibleVert,                              (si -> si_VisVert),
                                             DTA_TotalVert,                                (si -> si_TotVert),
                                             DTA_NominalVert,                              nomheight,
                                             DTA_VertUnit,                                 (si -> si_VertUnit),
                                             DTA_VisibleHoriz,                             (si -> si_VisHoriz),
                                             DTA_TotalHoriz,                               (si -> si_TotHoriz),
                                             DTA_NominalHoriz,                             nomwidth,
                                             DTA_HorizUnit,                                (ULONG)(si -> si_HorizUnit),
                                             GA_ID,                                        (ULONG)(G( o ) -> GadgetID),
                                             DTA_Title,                                    (ULONG)"Loading...",
                                             DTA_Busy,                                     (ULONG)TRUE,  /* still loading */
                                             DTA_Sync,                                     (ULONG)FALSE, /* still loading */
                                             DTA_Data,                                     NULL,         /* still loading */
                                             DTA_CurrentLevel,                             filllevel,
                                             TAG_DONE );
                        }

                        /* 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) );
                  }

                  SetDTAttrs( o, NULL, NULL, 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;
          }

          /* Restore old current directory */
          (void)CurrentDir( olddirlock );

          DeletePool( tmppool );
        }
        else
        {
          /* Can't create temp pool */
          errorlevel = RETURN_FAIL;
          error      = ERROR_NO_FREE_STORE;
        }

        if( did -> did_Progressive )
        {
          ObtainSemaphore( (&(si -> si_Lock)) );
          did -> did_Loading = FALSE;
        }
      }
      else
      {
        /* No layout to perform */
        total = si -> si_TotVert;
        maxx  = si -> si_TotHoriz;
      }

      /* Compute the lines and columns type information */
      maxy = total * (font -> tf_YSize);

      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;

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

      /* Were we aborted? */
      if( (bsig == 0UL) && ((did -> did_Loading) == FALSE) )
      {
        /* Not aborted, so tell the world of our newest attributes
         * NOTE: Building the taglist when the (si -> si_Lock) semaphore is not locked is not race-condition free,
         *       but this isn't very serious in THIS case...
         */
        notifyAttrChanges( o, (gpl -> gpl_GInfo), 0UL,
                           DTA_VisibleVert,                              (si -> si_VisVert),
                           DTA_TotalVert,                                (si -> si_TotVert),
                           DTA_NominalVert,                              nomheight,
                           DTA_VertUnit,                                 (si -> si_VertUnit),
                           DTA_VisibleHoriz,                             (si -> si_VisHoriz),
                           DTA_TotalHoriz,                               (si -> si_TotHoriz),
                           DTA_NominalHoriz,                             nomwidth,
                           DTA_HorizUnit,                                (ULONG)(si -> si_HorizUnit),
                           XTAG( errorlevel, DTA_ErrorLevel ),           errorlevel,
                           XTAG( errorlevel, DTA_ErrorNumber ),          error,
                           XTAG( errorlevel, DTA_ErrorString ),          (ULONG)name,
                           GA_ID,                                        (ULONG)(G( o ) -> GadgetID),
                           XTAG( (errorlevel == RETURN_OK), DTA_Title ), (ULONG)name,
                           DTA_Busy,                                     (ULONG)FALSE, /* loading done */
                           DTA_Sync,                                     (ULONG)TRUE,  /* loading done */
                           DTA_Data,                                     (ULONG)o,     /* loading done */
                           DTA_CurrentLevel,                             0xFFFFFFFF,   /* object full */
                           TAG_DONE );

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

    Forbid();

      did -> did_UseCounter--; /* Object-in-use counter. This is a compatibility patch to get rid of
                                * possible problems when running in progressive loading mode and
                                * datatypes.library launches more than one layout process
                                */

    Permit();

    return( total );
}


/* Notify attribute changes (OM_NOTIFY) */
static
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) ) );
}


/****** directory.datatype/preferences ***************************************
*
*   NAME
*       preferences
*
*   DESCRIPTION
*       The ENV:Classes/DataTypes/directory.prefs file contains global
*       settings for the datatype.
*       It can be superset by a local variable with the same name.
*
*   TEMPLATE
*       USERS/S,GROUPS/S,DATATYPE/S,DONOTIFYUPDATE=NOTIFY/S,AMIGAGUIDELINKS/S,
*       NOICONS/S,P=PAT=PATTERN/K,ACCEPTPATTERN/K,REJECTPATTERN/K
*
*       USERS  -- Show user name (when using envoy file system) or user id.
*
*       GROUPS -- Show group name (when using envoy file system) or group id.
*
*       DATATYPE -- Show datatype information
*
*       DONOTIFYUPDATE
*       NOTIFY -- Update contents when a filesystem notification occurs
*
*       AMIGAGUIDELINKS -- Turn on the amigaguide kluge: If a filesystem
*            object name (file, dir etc.) was double-clicked, the triggered
*            OM_NOTIFY msg's TDTA_WordSelected attribute will be replaced
*            by the full path and a "/MAIN" string. This will cause amigaguide
*            to link to the specified file.
*
*       NOICONS -- Reject all workbench icon files
*
*       P
*       PAT
*       PATTERN -- Only names which match this pattern will be included.
*
*       ACCEPTPATTERN -- Only names which match this pattern will be included
*
*       REJECTPATTERN -- Names which match this pattern will be rejected
*
*   BUGS
*       - An invalid prefs file won't generate an error.
*
*       - The prefs file is limitted to one line and 254 chars.
*
*       - Any error during preferences processing is not notified to the
*         user and may use the default (for this option(s)).
*
*******************************************************************************
*
*/


static
void ReadENVPrefs( struct ClassBase *cb, struct DirectoryInstData *did )
{
    struct RDArgs envvarrda =
    {
      NULL,
      256L,
      0L,
      0L,
      NULL,
      0L,
      NULL,
      RDAF_NOPROMPT
    };

    struct
    {
      long   *users;
      long   *groups;
      long   *datatype;
      long   *donotifyupdate;
      long   *amigaguidelinks;
      long   *noicons;
      STRPTR  pattern;
      STRPTR  acceptpattern;
      STRPTR  rejectpattern;
    } dirargs = { 0 };

    STRPTR varbuff;

    if( varbuff = (STRPTR)AllocMem( 258UL, (MEMF_PUBLIC | MEMF_CLEAR) ) )
    {
      envvarrda . RDA_Source . CS_Buffer = varbuff;

      if( GetVar( "Classes/DataTypes/directory.prefs", varbuff, 256L, 0UL ) != (-1L) )
      {
        strcat( varbuff, "\n" );

        if( ReadArgs( "USERS/S,"
                      "GROUPS/S,"
                      "DATATYPE/S,"
                      "DONOTIFYUPDATE=NOTIFY/S,"
                      "AMIGAGUIDELINKS/S,"
                      "NOICONS/S,"
                      "P=PAT=PATTERN/K,"
                      "ACCEPTPATTERN/K,"
                      "REJECTPATTERN/K", (LONG *)(&dirargs), (&envvarrda) ) )
        {
          if( dirargs . users )
          {
            did -> did_DoUsers = TRUE;
          }

          if( dirargs . groups )
          {
            did -> did_DoGroups = TRUE;
          }

          if( dirargs . datatype )
          {
            did -> did_DoDataType = TRUE;
          }

          if( dirargs . donotifyupdate )
          {
            did -> did_DoNotifyUpdate = TRUE;
          }

          if( dirargs . amigaguidelinks )
          {
            did -> did_DoAGLinks = TRUE;
          }

          if( dirargs . noicons )
          {
            did -> did_DoNoIcons = TRUE;
          }

          if( dirargs . pattern )
          {
            ULONG destlen = (ULONG)(strlen( (dirargs . pattern) ) * 2UL) + 8UL; /* length of ParsePatternNoCase destination (tokenized pattern) */

            if( did -> did_DoPattern = (STRPTR)AllocPooled( (did -> did_Pool), (destlen + 1UL) ) )
            {
              (void)ParsePatternNoCase( (dirargs . pattern), (did -> did_DoPattern), destlen );
            }
          }

          if( dirargs . acceptpattern )
          {
            ULONG destlen = (ULONG)(strlen( (dirargs . pattern) ) * 2UL) + 8UL; /* length of ParsePatternNoCase destination (tokenized pattern) */

            if( did -> did_DoAcceptPattern = (STRPTR)AllocPooled( (did -> did_Pool), (destlen + 1UL) ) )
            {
              (void)ParsePatternNoCase( (dirargs . acceptpattern), (did -> did_DoAcceptPattern), destlen );
            }
          }

          if( dirargs . rejectpattern )
          {
            ULONG destlen = (ULONG)(strlen( (dirargs . pattern) ) * 2UL) + 8UL; /* length of ParsePatternNoCase destination (tokenized pattern) */

            if( did -> did_DoRejectPattern = (STRPTR)AllocPooled( (did -> did_Pool), (destlen + 1UL) ) )
            {
              (void)ParsePatternNoCase( (dirargs . rejectpattern), (did -> did_DoRejectPattern), destlen );
            }
          }

          FreeArgs( (&envvarrda) );
        }
      }
      
      FreeMem( varbuff, 258UL );
    }
}


/* 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 );
    }
}


/* Check if the given fib matches the pattern(s) set in the prefs file */
static
BOOL MatchFIB( struct ClassBase *cb, struct DirectoryInstData *did, struct FileInfoBlock *fib )
{
    if( cb && did && fib )
    {
      STRPTR name = fib -> fib_FileName; /* short cut to fib_FileName */
      ULONG  len  = strlen( name );      /* fib_FileName length */

      /* Reject icons ? */
      if( did -> did_DoNoIcons )
      {
        /* Only files can be icons, so check for files */
        if( (fib -> fib_DirEntryType) < 0L )
        {
          /* Check for a ".info" string at the end of the file name (case insensitive) */
          if( len >= 5UL ) /* len >= strlen( ".info" ) */
          {
            if( !Stricmp( (&name[ (len - 5UL) ]), ".info" ) )
            {
              return( FALSE );
            }
          }
        }
      }

      /* Pattern ? */
      if( did -> did_DoPattern )
      {
        if( !MatchPatternNoCase( (did -> did_DoPattern), name ) )
        {
          return( FALSE );
        }
      }

      /* Accept pattern ? */
      if( did -> did_DoAcceptPattern )
      {
        if( !MatchPatternNoCase( (did -> did_DoAcceptPattern), name ) )
        {
          return( FALSE );
        }
      }

      /* Reject pattern ? */
      if( did -> did_DoRejectPattern )
      {
        if( MatchPatternNoCase( (did -> did_DoRejectPattern), name ) )
        {
          return( FALSE );
        }
      }
    }

    return( TRUE );
}


/* Converts envoy uid/gid to "real" user/group names
 * __EXPERIMENTAL__ and a hack
 * (See autodoc (V1.7) for details)
 */
static
BOOL GetEnvoyUserGroupName( struct ClassBase *cb, BPTR lock, STRPTR dirname, STRPTR filename, ULONG ugid, LONG action, STRPTR buff )
{
    BOOL success = FALSE;

    if( lock && ugid && buff )
    {
      STRPTR pathbuff;
      ULONG  buffsize = ((dirname)?(strlen( dirname )):(0UL)) + ((dirname)?(strlen( filename )):(0UL)) + 8UL;

      /* Alloc temp buffer for dir and dir entry name merging (AddPart) */
      if( pathbuff = (STRPTR)AllocMem( buffsize, MEMF_PUBLIC ) )
      {
        struct MsgPort *replyport;

        strcpy( pathbuff, dirname );

        /* Merge directory and file name together */
        if( AddPart( pathbuff, filename, buffsize ) )
        {
          /* Create reply port for our packet */
          if( replyport = CreateMsgPort() )
          {
            struct DosPacket *dp;

            /* Create DOS StandardPacket */
            if( dp = (struct DosPacket *)AllocDosObject( DOS_STDPKT, NULL ) )
            {
              /* Fill our Packet:
               * dp_Type: Which conversion ? (ACTION_UID2UNAME, ACTION_GID2GNAME)
               * dp_Arg1: user id or group id from fib_Owner(UID|GID)
               * dp_Arg2: INPUT: name of filesystem object OUTPUT: Requested user/group name
               * remaining fields are NULL
               */
              dp -> dp_Type = action;         /* Either ACTION_UID2UNAME or ACTION_GID2GNAME */
              dp -> dp_Arg1 = (LONG)ugid;     /* user or group id */
              dp -> dp_Arg2 = (LONG)pathbuff; /* IN: name of fh object / OUT: user/group name */

              /* Do packet (send/wait) */
              dp -> dp_Port = replyport;
              PutMsg( (((struct FileLock *)(BADDR( lock ))) -> fl_Task), (dp -> dp_Link) );
              WaitPort( replyport );

              /* Success ? */
              if( (dp -> dp_Res1) == DOSTRUE )
              {
                stccpy( buff, pathbuff, 31UL );
                success = TRUE;
              }
              else
              {
                *buff = '\0'; /* Be safe ! */
              }

              FreeDosObject( DOS_STDPKT, (APTR)dp );
            }

            DeleteMsgPort( replyport );
          }
        }

        FreeMem( (APTR)pathbuff, buffsize );
      }
    }

    return( success );
}


/* 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),
                                                                                                           NP_StackSize, 16384UL,
                                                                                                           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();

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


/* 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              *pr;
    struct NotifyTaskMessage    *ntm;
    struct ClassBase            *cb;
    Object                      *o;
    struct DTSpecialInfo        *si;
    struct DirectoryInstData    *did;

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

    /* sync with parent */
    WaitPort( (&(pr -> pr_MsgPort)) );
    ntm = (struct NotifyTaskMessage *)GetMsg( (&(pr -> pr_MsgPort)) );

    /* Pull all required info from the message */
    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)) );
}


