#include <exec/types.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <proto/intuition.h>
#include <libraries/gadtools.h>
#include <proto/gadtools.h>
#include <dos/dos.h>
#include <dos/exall.h>
#include <dos/dostags.h>
#include <proto/dos.h>
#include <graphics/gfx.h>
#include <clib/graphics_protos.h>
#include <clib/alib_protos.h>
#include <libraries/asl.h>
#include <clib/asl_protos.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "rp.h"

#define ALL_REACTION_CLASSES
#include <reaction/reaction.h>

#define TRUNCATE            39
#define GRANULARITY_DIRS    10
#define GRANULARITY_FILES   10

MODULE void parent(void);
MODULE void root(void);
MODULE void pathasl(void);
MODULE void writeline(void);
MODULE void killsizelist(void);
MODULE void ghost(void);
MODULE void unghost(void);
MODULE void scale(ULONG bytes);
// MODULE void __inline progressbar(ULONG level);
MODULE void examine(STRPTR thename, ULONG nesting, ULONG parentdir);
MODULE void AddSpecialNode(struct List* ListPtr, STRPTR pathname, ULONG nesting, ULONG size, ULONG parentdir, ULONG key, LONG type, LONG prot);
MODULE void FreeSpecialNodes(struct List* ListPtr);
MODULE void print_node(ULONG passedkey);
MODULE void print_entry(struct SpecialNode* NodePtr);
MODULE void print_list(ABOOL close);
MODULE void busy(void);

#define UNITOPTIONS 3 // counting from 0
MODULE STRPTR UnitOptions[UNITOPTIONS + 1] =
{   "Bytes",
    "Kilobytes",
    "Megabytes",
    "Blocks"
};

MODULE struct SpecialNode
{   struct Node Node;
    ULONG       nesting,
                size,
                parentdir,
                key;
    LONG        type,
                prot;
    TEXT        pathname[1];
};

MODULE struct ColumnInfo SizeColumnInfo[] =
{ { 75,            // WORD   ci_Width
    "Pathname",    // STRPTR ci_Title
    0,             // ULONG  ci_Flags
  },
  { 25,
    "Size",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { -1, (STRPTR) ~0, -1
} };

IMPORT struct TextAttr     Topaz8;
IMPORT SBYTE               page;
IMPORT TEXT                weekdaystring[LEN_DATSTRING],
                           datestring[LEN_DATSTRING],
                           timestring[LEN_DATSTRING],
                           asldir[PATHNAMEFIELD + 1],
                           aslresult[PATHNAMEFIELD + 1],
                           IOBuffer[LONGESTFIELD + 1];
IMPORT struct SharedStruct shared;
IMPORT struct Screen*      ScreenPtr;
IMPORT struct Window*      MainWindowPtr;
IMPORT struct ExAllData*   EADataPtr;
IMPORT struct Menu*        MenuPtr;
IMPORT Object*             WinObject[FUNCTIONS + 1];
IMPORT ULONG               increment;

AGLOBAL struct Gadget*     size_gadgets[GIDS_8 + 1];
AGLOBAL struct SizeStruct  size =
{   0, 0, 0, 0, 0, 0, 0,
    (ULONG) FALSE, (ULONG) FALSE, (ULONG) FALSE,
    "RAM:", "RAM:", "RAM:ReportPlus.log"
};

MODULE struct List         SizeList,
                           EmptySizeList,
                           List1,
                           List2;
MODULE BPTR                DirHandle      = NULL,
                           LogFileHandle  = NULL;
MODULE TEXT                logstring[PATHNAMEFIELD + 1];
MODULE UBYTE               commastring[13 + 1];
MODULE ULONG               status         = STATUS_READY,
                           key            = 0,
                           topnesting     = 0,
                           showall        = TRUE,
                           showfiles      = TRUE;

AGLOBAL void size_init(void)
{   struct ListBrowserNode* ListBrowserNodePtr;

    NewList(&List1);
    NewList(&List2);
    NewList(&SizeList);

    /* Create a 2-column listbrowser list with only one node containing
    only column tags. */
    NewList(&EmptySizeList);
    if (!(ListBrowserNodePtr = AllocListBrowserNode
    (   2,                  // columns
        /* LBNCA_ tags are those that apply to the specific column. */
        LBNA_Column,        0,
        LBNA_Column,        1,
        TAG_END
    )))
    {   rq("Can't create ReAction listbrowser.gadget node(s)!");
    }
    AddTail(&EmptySizeList, (struct Node *) ListBrowserNodePtr); // AddTail() has no return code
}

AGLOBAL void size1(void)
{   struct Hook         Hook8Struct;
    struct List         ChooserList;
    struct ChooserNode* ChooserNodePtr[UNITOPTIONS + 1];
    ULONG               i;

    NewList(&ChooserList);
    for (i = 0; i <= UNITOPTIONS; i++)
    {   if (!(ChooserNodePtr[i] = (struct ChooserNode *) AllocChooserNode(CNA_Text, UnitOptions[i], TAG_DONE)))
        {   rq("Can't allocate chooser node!");
        }
        AddTail(&ChooserList, (struct Node *) ChooserNodePtr[i]);
    } // automatically freed by ReAction at DisposeObject() time

    InitHook(&Hook8Struct, Hook8Func, NULL);

    /* Create the window object. */
    lockscreen();

    /* We can't just use EmptyList for an empty columnar list, we
    actually need a node (with the LBNA_Column tags) to be present.

    LISTBROWSER_ScrollRaster seems not to be usable with
    multi-column lists. */

    if (!(WinObject[8] =          NewObject(WINDOW_GetClass(), NULL,
    // window
    WA_PubScreen,                 ScreenPtr,
    WA_ScreenTitle,               "Report+",
    WA_Title,                     "Report+: Path Size Report",
    WA_Activate,                  TRUE,
    WA_DepthGadget,               TRUE,
    WA_DragBar,                   TRUE,
    WA_CloseGadget,               TRUE,
    WA_SizeGadget,                TRUE,
    WA_IDCMP,                     IDCMP_RAWKEY,
    WINDOW_IDCMPHook,             &Hook8Struct,
    WINDOW_IDCMPHookBits,         IDCMP_RAWKEY,
    WINDOW_MenuStrip,             MenuPtr,
    WINDOW_Position,              WPOS_CENTERSCREEN,
    WINDOW_ParentGroup,           size_gadgets[GID_8_LY1] =
    NewObject
    (       LAYOUT_GetClass(), NULL,
            // root-layout
            LAYOUT_Orientation,        LAYOUT_ORIENT_VERT,
            LAYOUT_SpaceOuter,         TRUE,
            LAYOUT_DeferLayout,        TRUE,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),     NULL,
                LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                LAYOUT_VertAlignment,  LALIGN_CENTER,
                LAYOUT_HorizAlignment, LALIGN_CENTER,
                LAYOUT_BevelStyle,     BVS_NONE,
                LAYOUT_AddImage,
                NewObject
                (   LABEL_GetClass(),  NULL,
                    LABEL_Text,        "_Path:",
                    LABEL_Justification,LJ_LEFT,
                TAG_END),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddChild,       size_gadgets[GID_8_ST1] =
                NewObject
                (   STRING_GetClass(), NULL,
                    GA_ID,             GID_8_ST1,
                    GA_RelVerify,      TRUE,
                    GA_TabCycle,       TRUE,
                    STRINGA_TextVal,   size.path,
                    STRINGA_MinVisible,20,
                TAG_END),
                LAYOUT_AddChild,       size_gadgets[GID_8_BU1] =
                NewObject(NULL,        "button.gadget",
                    GA_ID,             GID_8_BU1,
                    GA_RelVerify,      TRUE,
                    BUTTON_AutoButton, BAG_POPFILE,
                TAG_END),
                CHILD_WeightedWidth,   0,
            TAG_END),
            CHILD_WeightedHeight,      0,
            LAYOUT_AddChild,           NewObject(LAYOUT_GetClass(), NULL,
                LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                LAYOUT_VertAlignment,  LALIGN_CENTER,
                LAYOUT_HorizAlignment, LALIGN_CENTER,
                LAYOUT_BevelStyle,     BVS_NONE,
                LAYOUT_ShrinkWrap,     TRUE,
                LAYOUT_AddChild,       size_gadgets[GID_8_CB1] =
                NewObject
                (   CHECKBOX_GetClass(),NULL,
                    // checkbox
                    GA_ID,             GID_8_CB1,
                    GA_RelVerify,      TRUE,
                    GA_Text,           "_Log to:",
                    GA_Selected,       (BOOL) size.log,
                    TAG_END
                ),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddChild,       size_gadgets[GID_8_ST2] =
                                       NewObject(STRING_GetClass(), NULL,
                    // string        
                    GA_ID,             GID_8_ST2,
                    GA_RelVerify,      TRUE,
                    GA_TabCycle,       TRUE,
                    STRINGA_TextVal,   size.logfile,
                    STRINGA_MinVisible,20,
                    GA_Disabled,       !size.log,
                    TAG_END
                ),
                LAYOUT_AddChild,       size_gadgets[GID_8_BU3] =
                NewObject
                (   NULL,              "button.gadget",
                    // button
                    GA_ID,             GID_8_BU3,
                    GA_RelVerify,      TRUE,
                    BUTTON_AutoButton, BAG_POPFILE,
                    GA_Disabled,       !size.log,
                    TAG_END
                ),
                CHILD_WeightedWidth,   0,
                TAG_END
            ),
            CHILD_WeightedHeight,      0,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),     NULL,
                LAYOUT_Orientation,    LAYOUT_ORIENT_VERT,
                LAYOUT_SpaceOuter,     TRUE,
                LAYOUT_VertAlignment,  LALIGN_CENTER,
                LAYOUT_HorizAlignment, LALIGN_CENTER,
                LAYOUT_BevelStyle,     BVS_FIELD,
                LAYOUT_AddChild,
                NewObject
                (   LAYOUT_GetClass(),     NULL,
                    LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                    LAYOUT_VertAlignment,  LALIGN_CENTER,
                    LAYOUT_HorizAlignment, LALIGN_CENTER,
                    LAYOUT_BevelStyle,     BVS_NONE,
                    LAYOUT_AddChild,       size_gadgets[GID_8_BU6] =
                    NewObject
                    (   NULL,              "button.gadget",
                        GA_ID,             GID_8_BU6,
                        GA_RelVerify,      TRUE,
                        GA_Text,           "_Root",
                        TAG_END
                    ),
                    CHILD_WeightedWidth,   50,
                    LAYOUT_AddChild,       size_gadgets[GID_8_BU7] =
                    NewObject             
                    (   NULL,              "button.gadget",
                        GA_ID,             GID_8_BU7,
                        GA_RelVerify,      TRUE,
                        GA_Text,           "P_arent",
                        TAG_END
                    ),
                    CHILD_WeightedWidth,   50,
                TAG_END),
                CHILD_WeightedHeight,      0,
                LAYOUT_AddChild,           size_gadgets[GID_8_LB2] =
                NewObject
                (   LISTBROWSER_GetClass(),NULL,
                    GA_ID,                 GID_8_LB2,
                    GA_RelVerify,          TRUE,
                    GA_TextAttr,           &Topaz8,
                    LISTBROWSER_Labels,    (ULONG) &EmptySizeList,
                    LISTBROWSER_ColumnInfo,(ULONG) &SizeColumnInfo,
                    LISTBROWSER_ColumnTitles,TRUE,
                    LISTBROWSER_MinVisible,6,
                TAG_END),
                CHILD_WeightedHeight,      100,
                CHILD_MinHeight,           160,
                LAYOUT_AddChild,
                NewObject
                (   LAYOUT_GetClass(),     NULL,
                    LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                    LAYOUT_VertAlignment,  LALIGN_LEFT,
                    LAYOUT_HorizAlignment, LALIGN_RIGHT,
                    LAYOUT_BevelStyle,     BVS_NONE,
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),     NULL,
                        LAYOUT_Orientation,    LAYOUT_ORIENT_VERT,
                        LAYOUT_VertAlignment,  LALIGN_CENTER,
                        LAYOUT_HorizAlignment, LALIGN_RIGHT,
                        LAYOUT_BevelStyle,     BVS_NONE,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Files:",
                            LABEL_Justification,LJ_CENTRE,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Directories:",
                            LABEL_Justification,LJ_CENTRE,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Block size:",
                            LABEL_Justification,LJ_CENTRE,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                    TAG_END),
                    CHILD_WeightedWidth,       0,
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),     NULL,
                        LAYOUT_Orientation,    LAYOUT_ORIENT_VERT,
                        LAYOUT_VertAlignment,  LALIGN_CENTER,
                        LAYOUT_HorizAlignment, LALIGN_RIGHT,
                        LAYOUT_BevelStyle,     BVS_NONE,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST6] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST6,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST7] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST7,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST8] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST8,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                    TAG_END),
                    CHILD_WeightedWidth,       0,
                    LAYOUT_AddImage,           NewObject
                    (   LABEL_GetClass(),      NULL,
                        LABEL_Text,            "",
                        TAG_END
                    ),
                    CHILD_WeightedWidth,       100,
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),     NULL,
                        LAYOUT_Orientation,    LAYOUT_ORIENT_VERT,
                        LAYOUT_VertAlignment,  LALIGN_CENTER,
                        LAYOUT_HorizAlignment, LALIGN_RIGHT,
                        LAYOUT_BevelStyle,     BVS_NONE,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Used in path:",
                            LABEL_Justification,LJ_RIGHT,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Free on volume:",
                            LABEL_Justification,LJ_RIGHT,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(),  NULL,
                            LABEL_Text,        "Volume capacity:",
                            LABEL_Justification,LJ_RIGHT,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                    TAG_END),
                    CHILD_WeightedWidth,       0,
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),     NULL,
                        LAYOUT_Orientation,    LAYOUT_ORIENT_VERT,
                        LAYOUT_VertAlignment,  LALIGN_CENTER,
                        LAYOUT_HorizAlignment, LALIGN_RIGHT,
                        LAYOUT_BevelStyle,     BVS_NONE,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST4] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST4,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST5] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST5,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                        LAYOUT_AddChild,       size_gadgets[GID_8_ST3] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,             GID_8_ST3,
                            GA_ReadOnly,       TRUE,
                            STRINGA_TextVal,   "-",
                            STRINGA_MinVisible,13,
                            STRINGA_Justification,GACT_STRINGRIGHT,
                            GA_TextAttr,       &Topaz8,
                        TAG_END),
                        CHILD_WeightedHeight,  0,
                    TAG_END),
                    CHILD_WeightedWidth,       0,
                TAG_END),
                CHILD_WeightedHeight,          0,
            TAG_END),
            CHILD_WeightedHeight,              70,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),     NULL,
                LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                LAYOUT_VertAlignment,  LALIGN_CENTER,
                LAYOUT_HorizAlignment, LALIGN_CENTER,
                LAYOUT_BevelStyle,     BVS_NONE,
                LAYOUT_AddChild,       size_gadgets[GID_8_CB4] =
                NewObject
                (   CHECKBOX_GetClass(),NULL,
                    // checkbox
                    GA_ID,             GID_8_CB4,
                    GA_RelVerify,      TRUE,
                    GA_Text,           "Show s_ubdirectories?",
                    GA_Selected,       (BOOL) showall,
                TAG_END),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddChild,       size_gadgets[GID_8_CB5] =
                NewObject
                (   CHECKBOX_GetClass(),NULL,
                    // checkbox
                    GA_ID,             GID_8_CB5,
                    GA_RelVerify,      TRUE,
                    GA_Text,           "Show _files?",
                    GA_Selected,       (BOOL) showfiles,
                TAG_END),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddImage,       NewObject
                (   LABEL_GetClass(),  NULL,
                    LABEL_Text,        "",
                    TAG_END
                ),
                CHILD_WeightedWidth,   100,
            TAG_END),
            CHILD_WeightedHeight,      0,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),     NULL,
                LAYOUT_Orientation,    LAYOUT_ORIENT_HORIZ,
                LAYOUT_VertAlignment,  LALIGN_CENTER,
                LAYOUT_HorizAlignment, LALIGN_CENTER,
                LAYOUT_BevelStyle,     BVS_NONE,
                LAYOUT_AddImage,
                NewObject
                (   LABEL_GetClass(),  NULL,
                    // label
                    LABEL_Text,        "_View as:",
                    LABEL_Justification,LJ_RIGHT,
                TAG_END),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddChild,       size_gadgets[GID_8_CH1] =
                NewObject
                (   CHOOSER_GetClass(),NULL,
                    GA_ID,             GID_8_CH1,
                    GA_RelVerify,      TRUE,
                    CHOOSER_PopUp,     TRUE,
                    CHOOSER_Labels,    &ChooserList,
                    CHOOSER_Selected,  size.view,
                    TAG_END
                ),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddChild,       size_gadgets[GID_8_CB2] =
                NewObject
                (   CHECKBOX_GetClass(),NULL,
                    // checkbox
                    GA_ID,             GID_8_CB2,
                    GA_RelVerify,      TRUE,
                    GA_Text,           "Include _slack?",
                    GA_Selected,       (BOOL) size.slack,
                TAG_END),
                CHILD_WeightedWidth,   0,
                LAYOUT_AddImage,       NewObject
                (   LABEL_GetClass(),  NULL,
                    LABEL_Text,        "",
                    TAG_END
                ),
                CHILD_WeightedWidth,   100,
            TAG_END),
            CHILD_WeightedHeight,     0,
            LAYOUT_AddChild,          size_gadgets[GID_8_CB3] =
            NewObject
            (   CHECKBOX_GetClass(),  NULL,
                GA_ID,                GID_8_CB3,
                GA_RelVerify,         TRUE,
                GA_Text,              "Fi_x protection bits?",
                GA_Selected,          (BOOL) size.fixprot,
            TAG_END),
            CHILD_WeightedHeight,     0,
            LAYOUT_AddChild,          size_gadgets[GID_8_FG1] =
            NewObject
            (   FUELGAUGE_GetClass(), NULL,
                GA_ID,                GID_8_FG1,
                GA_Text,              "Ready.",
                FUELGAUGE_Level,      0,
                FUELGAUGE_Percent,    FALSE,
                FUELGAUGE_Justification,FGJ_CENTER,
            TAG_END),
            CHILD_WeightedHeight,     0,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),    NULL,
                LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                LAYOUT_VertAlignment, LALIGN_CENTER,
                LAYOUT_HorizAlignment,LALIGN_CENTER,
                LAYOUT_BevelStyle,    BVS_NONE,
                LAYOUT_AddChild,      size_gadgets[GID_8_BU4] =
                NewObject
                (   NULL,             "button.gadget",
                    GA_ID,            GID_8_BU4,
                    GA_RelVerify,     TRUE,
                    GA_Text,          "_Update",
                TAG_END),
                LAYOUT_AddChild,      size_gadgets[GID_8_BU5] =
                NewObject
                (   NULL,             "button.gadget",
                    GA_ID,            GID_8_BU5,
                    GA_RelVerify,     TRUE,
                    GA_Text,          "Stop",
                    GA_Disabled,      TRUE,
                TAG_END),
            TAG_END),
            CHILD_WeightedHeight,     0,
    TAG_END)
    )))
    {   rq("Can't create ReAction objects!");
    }
    unlockscreen();
    openwindow();
    ActivateLayoutGadget(size_gadgets[GID_8_LY1], MainWindowPtr, NULL, (Object) size_gadgets[GID_8_ST1]);

    loop();
    closewindow();
    size_exit();
}

AGLOBAL void updatesize(void)
{   ABOOL                 failed      = FALSE;
    struct FileInfoBlock* FIBPtr      = NULL;
    struct InfoData*      InfoDataPtr;
    struct ExAllControl*  eac;
    struct SpecialNode*   NodePtr;

    ghost();
    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 12,
        GA_Text,         "Initializing...",
        TAG_END
    );

    killsizelist();
    if ((List1.lh_Head)->ln_Succ) // the list is non-empty
    {   FreeSpecialNodes(&List1);
    }
    if ((List2.lh_Head)->ln_Succ) // the list is non-empty
    {   FreeSpecialNodes(&List2);
    }
    topnesting = 0;

    // 1: Ghost relevant gadgets, for duration of the operation.

    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 33,
        GA_Text,         "Reading directories...",
        TAG_END
    ); // we don't know how many files in advance...

    // 2: Now get a Lock() on the path.

    size.numdirs = size.numfiles = 0;
    /* From RKRM I&A, p. 65: */
    if
    (   !(DirHandle = (BPTR) Lock(size.path, ACCESS_READ))
     || !(FIBPtr    = AllocDosObject(DOS_FIB, NULL))
     || !(            Examine(DirHandle, FIBPtr))
     ||  (FIBPtr->fib_DirEntryType <= 0) // if not a directory
    )
    {   failed = TRUE;
    }

    strcpy(size.oldpath, size.path);
    if (FIBPtr)
    {   FreeDosObject(DOS_FIB, FIBPtr);
        // FIBPtr = NULL;
    }
    if (failed)
    {   DisplayBeep(ScreenPtr);
        unghost();

        strcpy(size.path, size.oldpath);
        SetGadgetAttrs
        (   size_gadgets[GID_8_ST1], MainWindowPtr, NULL,
            STRINGA_TextVal, size.path,
            TAG_END
        );
        SetGadgetAttrs
        (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Level, 0,
            GA_Text,         "Failed!",
            TAG_END
        );
        return;
    }
    SetGadgetAttrs
    (   size_gadgets[GID_8_ST1], MainWindowPtr, NULL,
        STRINGA_TextVal, size.path,
        TAG_END
    );

    /* 3: Now we call Info() to ascertain the free bytes.
          InfoDataPtr must be longword-aligned. */

    if (!(InfoDataPtr = AllocVec(sizeof(struct InfoData), MEMF_CLEAR | MEMF_PUBLIC)))
    {   rq("Out of memory!");
    }
    if (Info(DirHandle, InfoDataPtr))
    {   size.blocksize = InfoDataPtr->id_BytesPerBlock;
        size.free      =
        (InfoDataPtr->id_NumBlocks - InfoDataPtr->id_NumBlocksUsed) *
         InfoDataPtr->id_BytesPerBlock;
        size.capacity  =
         InfoDataPtr->id_NumBlocks *
         InfoDataPtr->id_BytesPerBlock;
    } else
    {   rq("Can't get volume info!");
    }
    FreeVec(InfoDataPtr);
    // InfoDataPtr = NULL;

    comma(size.blocksize, commastring);
    SetGadgetAttrs // "dirs"
    (   size_gadgets[GID_8_ST8], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );

    /* 4: Now we are ready to begin the main part of the operation. */

    size.total = 0;

    // This must be allocated with AllocDosObject()
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   rq("Can't allocate DOS object!");
    }

    // examine path
    AddSpecialNode(&List1, "", 0, 0, 0, 0, ST_USERDIR, 0);
    key = 0;
    for (NodePtr = List1.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
    {   if (NodePtr->type == ST_USERDIR)
        {   examine
            (   NodePtr->pathname,
                NodePtr->nesting,
                NodePtr->key
            );
    }   }

    if (status == STATUS_BUSY)
    {   busy();
    }

    if (status != STATUS_BUSY)
    {   SetGadgetAttrs // "used in path"
        (   size_gadgets[GID_8_ST4], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        SetGadgetAttrs // "free on volume"
        (   size_gadgets[GID_8_ST5], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        SetGadgetAttrs // "volume capacity"
        (   size_gadgets[GID_8_ST3], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        SetGadgetAttrs // "files"
        (   size_gadgets[GID_8_ST6], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        SetGadgetAttrs // "directories"
        (   size_gadgets[GID_8_ST7], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        SetGadgetAttrs // "block size"
        (   size_gadgets[GID_8_ST8], MainWindowPtr, NULL,
            STRINGA_TextVal, "-",
            TAG_END
        );
        killsizelist();
    }

    unghost();
    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,     0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 100,
        GA_Text,         "Done",
        TAG_END
    );

    if (LogFileHandle)
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
}   }

AGLOBAL void size_loop(ULONG gid)
{   ULONG                   i, length;
    STRPTR                  stringptr;
    struct ListBrowserNode* NodePtr;

    switch(gid)
    {
    case GID_8_BU1: // path ASL
        pathasl();
    break;
    case GID_8_BU3:
        asl("~(#?.info)");
        SetGadgetAttrs // `Log to file:' (string)
        (   size_gadgets[GID_8_ST2], MainWindowPtr, NULL,
            STRINGA_TextVal, size.logfile,
        TAG_END);
    break;
    case GID_8_BU4: // update
        updatesize();
    break;
    // GID_8_BU5 is stop
    case GID_8_BU6:
        root();
    break;
    case GID_8_BU7:
        parent();
    break;
    case GID_8_ST1: // path string
        if (!(GetAttr
        (   STRINGA_TextVal, size_gadgets[GID_8_ST1], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(size.path, stringptr);
        updatesize();
    break;
    case GID_8_ST2: // output string
        if (!(GetAttr
        (   STRINGA_TextVal, size_gadgets[GID_8_ST2], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(size.logfile, stringptr);
    break;
    case GID_8_CB1:
        if (!(GetAttr
        (   GA_Selected, size_gadgets[GID_8_CB1], &size.log
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }

        SetGadgetAttrs // `Log to file:' (string)
        (   size_gadgets[GID_8_ST2], MainWindowPtr, NULL,
            GA_Disabled, !size.log,
            TAG_END
        );
        /* For some reason, we need to explicitly refresh the string gadget
        visuals here */
        RefreshGadgets((struct Gadget *) size_gadgets[GID_8_ST2], MainWindowPtr, NULL);

        SetGadgetAttrs // `Log to file:' (ASL button)
        (   size_gadgets[GID_8_BU3], MainWindowPtr, NULL,
            GA_Disabled, !size.log,
            TAG_END
        );
        if (size.log)
        {   ActivateLayoutGadget(size_gadgets[GID_8_LY1], MainWindowPtr, NULL, (Object) size_gadgets[GID_8_ST2]);
        }
    break;
    case GID_8_CB2:
        if (!(GetAttr
        (   GA_Selected, size_gadgets[GID_8_CB2], (ULONG *) &size.slack
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_8_CB3:
        if (!(GetAttr
        (   GA_Selected, size_gadgets[GID_8_CB3], (ULONG *) &size.fixprot
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_8_CB4:
        if (!(GetAttr
        (   GA_Selected, size_gadgets[GID_8_CB4], (ULONG *) &showall
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        print_list(TRUE);
    break;
    case GID_8_CB5:
        if (!(GetAttr
        (   GA_Selected, size_gadgets[GID_8_CB5], (ULONG *) &showfiles
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        print_list(TRUE);
    break;
    case GID_8_CH1:
        if (!(GetAttr
        (   CHOOSER_Selected, size_gadgets[GID_8_CH1], (ULONG *) &size.view
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        print_list(TRUE);
    break;
    case GID_8_LB2:
        // We get a message even if the user clicked on an empty part of
        // the gadget.

        if (!(GetAttr
        (   LISTBROWSER_SelectedNode, size_gadgets[GID_8_LB2], (ULONG *) &NodePtr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        if (NodePtr)
        {   GetListBrowserNodeAttrs((struct Node *) NodePtr, LBNA_Column, 0, LBNCA_Text, &stringptr, TAG_DONE);
            length = strlen(stringptr);
            for (i = 0; i <= length; i++)
            {   if (*(stringptr + i) == '*')
                {   if (!(AddPart(size.path, stringptr + i + 1, PATHNAMEFIELD)))
                    {   rq("Can't add filename to pathname!");
                    }
                    updatesize();
                    break;
        }   }   }
    break;
    default:
    break;
}   }

AGLOBAL void size_exit(void)
{   if (DirHandle)
    {   UnLock(DirHandle);
        DirHandle = NULL;
    }
    if (LogFileHandle)
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
    }
    if ((List1.lh_Head)->ln_Succ) // the list is non-empty
    {   FreeSpecialNodes(&List1);
    }
    if ((List2.lh_Head)->ln_Succ) // the list is non-empty
    {   FreeSpecialNodes(&List2);
    }

    // Only call this if the list gadget is detached or not present.
    // (ie. after closewindow()).
    if ((SizeList.lh_Head)->ln_Succ) // if the list is non-empty
    {   clearreactionlist(&SizeList);
}   }

AGLOBAL void size_die(void)
{   if ((EmptySizeList.lh_Head)->ln_Succ) // if the list is non-empty
    {   clearreactionlist(&EmptySizeList);
    }

    IOBuffer[0]  = (UBYTE) size.log;
    IOBuffer[6]  = (UBYTE) showfiles;
    IOBuffer[15] = (UBYTE) showall;
    IOBuffer[21] = (UBYTE) size.slack;
    IOBuffer[22] = (UBYTE) size.view;
    IOBuffer[24] = (UBYTE) size.fixprot;
}

AGLOBAL void size_config(void)
{   size.log     = (ULONG) IOBuffer[0];
    showfiles    = (ULONG) IOBuffer[6];
    showall      = (ULONG) IOBuffer[15];
    size.slack   = (ULONG) IOBuffer[21];
    size.view    = (ULONG) IOBuffer[22];
    size.fixprot = (ULONG) IOBuffer[24];
}

MODULE void parent(void)
{   BPTR DirHandle,
         ParentHandle;

    if (!(DirHandle = (BPTR) Lock(size.path, ACCESS_READ)))
        rq("Can't lock directory!");
    if (!(ParentHandle = ParentDir(DirHandle)))
    {   /* It returned a NULL lock; ie. a lock on SYS:
        That is not what we want. */
        ParentHandle = DirHandle;
    }
    if (!NameFromLock(ParentHandle, size.path, PATHNAMEFIELD))
        rq("Can't get name from lock!");
    UnLock(DirHandle);
    // DirHandle = NULL;

    updatesize();
}

MODULE void root(void)
{   BPTR  DirHandle,
          ParentHandle;
    ABOOL rootdone     = FALSE;

    if (!(DirHandle = (BPTR) Lock(size.path, ACCESS_READ)))
        rq("Can't lock directory!");
    do
    {   if (!(ParentHandle = ParentDir(DirHandle)))
        {   /* It returned a NULL lock; ie. a lock on SYS:
               That is not what we want. */
            ParentHandle = DirHandle;
            rootdone = TRUE;
        } else DirHandle = ParentHandle;
    } while (!rootdone);
    if (!NameFromLock(ParentHandle, size.path, PATHNAMEFIELD))
        rq("Can't get name from lock!");
    UnLock(DirHandle);
    // DirHandle = NULL;

    updatesize();
}

MODULE void pathasl(void)
{   strcpy(asldir, size.path);
    if (dirasl()) // path for ASL must be valid?
    {   strcpy(size.path, aslresult);
        SetGadgetAttrs
        (   size_gadgets[GID_8_ST1], MainWindowPtr, NULL,
            STRINGA_TextVal, size.path,
            TAG_END
        );
        updatesize();
}   }

MODULE void writeline(void)
{   /* The file is opened just before the first call to writeline()
    (when the date, time, etc. is written). Then we keep the file open
    and do our scan. Then we write out the results a line at a time,
    then the free and total bytes. */

    if (FPuts(LogFileHandle, logstring))
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
        rq("Can't append to file!");
}   }

MODULE void killsizelist(void)
{   SetGadgetAttrs
    (   size_gadgets[GID_8_LB2], MainWindowPtr, NULL,
        LISTBROWSER_Labels, &EmptySizeList,
        TAG_END
    );
    if ((SizeList.lh_Head)->ln_Succ) // if the list is non-empty
    {   clearreactionlist(&SizeList);
}   }

AGLOBAL ULONG Hook8Func(struct Hook *h, VOID *o, VOID *msg)
{   /* "When the hook is called, the data argument points to the
    window object and message argument to the IntuiMessage." */

    UWORD code, qual;
    ULONG scroll = 0; // to avoid spurious compiler warnings

    geta4(); // wait till here before doing anything
    code = ((struct IntuiMessage *) msg)->Code;
    qual = ((struct IntuiMessage *) msg)->Qualifier;
    switch(code)
    {
    case SCAN_HELP:
        if (status == STATUS_READY)
        {   helpabout();
        }
    break;
    case SCAN_ESCAPE:
        if (status == STATUS_READY)
        {   if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
            {   cleanexit(EXIT_SUCCESS);
            } else page = 0;
        } else
        {   // assert(status == STATUS_BUSY);
            status = STATUS_STOPPING;
        }
    break;
    case SCAN_P:
        ActivateLayoutGadget(size_gadgets[GID_8_LY1], MainWindowPtr, NULL, (Object) size_gadgets[GID_8_ST1]);
    break;
    case SCAN_V:
        if (status == STATUS_READY)
        {   if (!(qual & IEQUALIFIER_LSHIFT) && !(qual & IEQUALIFIER_RSHIFT))
            {   if (size.view < UNITOPTIONS)
                    size.view++;
                else size.view = 0;
            } else
            {   if (size.view > 0)
                    size.view--;
                else size.view = UNITOPTIONS;
            }
            SetGadgetAttrs
            (   size_gadgets[GID_8_CH1], MainWindowPtr, NULL,
                CHOOSER_Selected, size.view,
                TAG_END
            );
        }
    break;
    case SCAN_PERIOD:
        pathasl();
    break;
    case SCAN_UP:
        if (qual & IEQUALIFIER_CONTROL)
        {   scroll = LBP_TOP;
        } elif (qual & IEQUALIFIER_LSHIFT || qual & IEQUALIFIER_RSHIFT)
        {   scroll = LBP_PAGEUP;
        } else scroll = LBP_LINEUP;
    break;
    case SCAN_DOWN:
        if (qual & IEQUALIFIER_CONTROL)
        {   scroll = LBP_BOTTOM;
        } elif (qual & IEQUALIFIER_LSHIFT || qual & IEQUALIFIER_RSHIFT)
        {   scroll = LBP_PAGEDOWN;
        } else scroll = LBP_LINEDOWN;
    break;
    default:
    break;
    }

    if (code == SCAN_UP || code == SCAN_DOWN)
    {   SetGadgetAttrs
        (   size_gadgets[GID_8_LB2],      // pointer to gadget
            MainWindowPtr,                // pointer to window (not window object!)
            NULL,                         // pointer to requester
            LISTBROWSER_Position, scroll, // tags
            TAG_DONE                      // done
        );
    }

    return(1);
}

MODULE void ghost(void)
{   status = STATUS_BUSY;

    SetGadgetAttrs // "update"
    (   size_gadgets[GID_8_BU4], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // "stop"
    (   size_gadgets[GID_8_BU5], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
}

MODULE void unghost(void)
{   status = STATUS_READY;

    SetGadgetAttrs // "update"
    (   size_gadgets[GID_8_BU4], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    SetGadgetAttrs // "stop"
    (   size_gadgets[GID_8_BU5], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
}

MODULE void scale(ULONG bytes)
{   if (size.view == 0) // bytes
    {   comma(bytes, commastring);
    } elif (size.view == 1) // kilobytes
    {   comma(bytes / 1024, commastring);
    } elif (size.view == 2) // megabytes
    {   comma(bytes / 1048576, commastring);
    } else
    {   // assert(size.view == 3); // blocks
        // assert(size.blocksize); // to avoid division by zero
        comma(bytes / size.blocksize, commastring);
        if (bytes % size.blocksize)
        {   comma((bytes / size.blocksize) + 1, commastring);
        } else // exactly on a block boundary
        {   comma(bytes / size.blocksize, commastring);
}   }   }

/*
MODULE void __inline progressbar(ULONG level)
{   if (!(level % increment))
    {   SetGadgetAttrs
        (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Level, level,
            TAG_END
        );
}   }
*/

MODULE void examine(STRPTR thename, ULONG nesting, ULONG parentdir)
{   BOOL                 more;      // BOOL, not ABOOL
    BPTR                 DirHandle; // = NULL;
    struct ExAllControl* eac;       // = NULL;
    struct ExAllData*    ead;
    ULONG                baselength    = strlen(size.path),
                         breakval,
                         thesize;
    TEXT                 activefile[PATHNAMEFIELD + 1],
                         lockstring[PATHNAMEFIELD + 1];
    LONG                 ed,
                         prot;

    breakval = ra_checkbreak();
    if (breakval == 2)
    {   cleanexit(EXIT_SUCCESS);
    } elif (breakval == 1)
    {   status = STATUS_STOPPING;
        SetGadgetAttrs
        (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
            GA_Text, "Stopping...",
            TAG_END
        );
    }
    if (status == STATUS_STOPPING)
    {   return;
    }

    strcpy(lockstring, size.path);
    strcat(lockstring, thename);

    if (!(DirHandle = (BPTR) Lock(lockstring, ACCESS_READ)))
    {   // Printf("Can't lock %s!\n", lockstring);
        rq("Can't lock directory!");
    }
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Can't allocate DOS object!");
    }

    if (size.fixprot)
    {   ed = ED_PROTECTION; // we want name, type, size and protection
    } else
    {   ed = ED_SIZE;       // we want name, type and size
    }

    eac->eac_LastKey = 0;
    do
    {   more = ExAll(DirHandle, (struct ExAllData *) EADataPtr, 4096, ed, eac);
        if (!more && IoErr() != ERROR_NO_MORE_ENTRIES)
        {   FreeDosObject(DOS_EXALLCONTROL, eac);
            eac = NULL;
            UnLock(DirHandle);
            DirHandle = NULL;
            rq("Can't examine path!"); /* ExAll() failed abnormally */
        }
        if (eac->eac_Entries == 0)
        {   ; /* ExAll() failed normally with no entries */
            continue; /* more is USUALLY zero */
        }
        ead = (struct ExAllData *) EADataPtr;

        do
        {   /* use ead here */

            strcpy(activefile, lockstring);
            if (!AddPart(activefile, ead->ed_Name, PATHNAMEFIELD))
            {   FreeDosObject(DOS_EXALLCONTROL, eac);
                eac = NULL;
                UnLock(DirHandle);
                DirHandle = NULL;
                rq("Can't add filename/dirname to path!");
            }
            if (ead->ed_Type == ST_USERDIR)
            {   size.numdirs++;
                if (!(size.numdirs % GRANULARITY_DIRS))
                {   comma(size.numdirs, commastring);
                    SetGadgetAttrs
                    (   size_gadgets[GID_8_ST7], MainWindowPtr, NULL,
                        STRINGA_TextVal, commastring,
                        TAG_END
                    );
                }

                AddSpecialNode
                (   &List1,                      // list
                    &activefile[baselength + 1], // filename
                    nesting + 1,                 // nesting
                    0,                           // size
                    parentdir,                   // parentdir
                    ++key,                       // key
                    ST_USERDIR,                  // type
                    0                            // protection bits
                );
                if (topnesting < nesting + 1)
                {   topnesting = nesting + 1;
            }   }
            elif (ead->ed_Type == ST_FILE)
            {   size.numfiles++;
                if (!(size.numfiles % GRANULARITY_FILES))
                {   comma(size.numfiles, commastring);
                    SetGadgetAttrs
                    (   size_gadgets[GID_8_ST6], MainWindowPtr, NULL,
                        STRINGA_TextVal, commastring,
                        TAG_END
                    );
                }
                if (size.slack)
                {   if (ead->ed_Size % size.blocksize == 0)
                    {   thesize = ead->ed_Size;
                    } else
                    {   thesize = ((ead->ed_Size / size.blocksize) + 1) * size.blocksize;
                }   }
                else
                {   thesize = ead->ed_Size;
                }
                size.total += thesize;

                if (size.fixprot)
                {   prot = ead->ed_Prot;
                } else
                {   prot = 0;
                }

                AddSpecialNode
                (   &List1,                      // list
                    &activefile[baselength + 1], // filename
                    nesting + 1,                 // nesting
                    thesize,                     // size
                    parentdir,                   // parentdir
                    ++key,                       // key
                    ST_FILE,                     // type
                    prot                         // protection bits
                );
                if (topnesting < nesting + 1)
                {   topnesting = nesting + 1;
            }   }

            /* get next ead */
            ead = ead->ed_Next;
        } while(ead);
    } while(more);

    FreeDosObject(DOS_EXALLCONTROL, eac);
    // eac = NULL;
    UnLock(DirHandle);
    // DirHandle = NULL;
}

MODULE void AddSpecialNode(struct List* ListPtr, STRPTR pathname, ULONG nesting, ULONG size, ULONG parentdir, ULONG key, LONG type, LONG prot)
{   struct SpecialNode* NodePtr;
    ULONG               needed;

    needed = sizeof(struct SpecialNode) + strlen(pathname);

    if (!(NodePtr = AllocVec(needed, MEMF_CLEAR | MEMF_PUBLIC)))
        rq("Out of memory!");
    strcpy(NodePtr->pathname, pathname);
    NodePtr->nesting      = nesting;
    NodePtr->size         = size;
    NodePtr->parentdir    = parentdir;
    NodePtr->key          = key;
    NodePtr->type         = type;
    NodePtr->prot         = prot;
    NodePtr->Node.ln_Name = NULL;
    NodePtr->Node.ln_Type = NT_USER;
    NodePtr->Node.ln_Pri  = 0;
    AddTail((struct List *) ListPtr, (struct Node *) NodePtr);
}
MODULE void FreeSpecialNodes(struct List* ListPtr)
{   /* RKM Libraries, p. 496:
	
    "Free the entire list, including the header. The header is not
    updated as the list is freed. This function demonstrates how to
    avoid referencing freed memory when deallocating nodes." */

    struct SpecialNode *WorkNodePtr, *NextNodePtr;
	
    WorkNodePtr = (struct SpecialNode *) (ListPtr->lh_Head); /* first node */
    while (NextNodePtr = (struct SpecialNode *) (WorkNodePtr->Node.ln_Succ))
    {   FreeVec(WorkNodePtr);
        WorkNodePtr = NextNodePtr;
    }
    NewList(ListPtr);
}

MODULE void print_node(ULONG passedkey)
{   struct SpecialNode *NodePtr,
                       *ChildNodePtr;

    for (NodePtr = List2.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
    {   if (NodePtr->key == passedkey)
        {   break;
    }   }

    if (NodePtr->key != 0)
    {   print_entry(NodePtr);
    }

    // print child dirs
    for (ChildNodePtr = List2.lh_Head; ChildNodePtr->Node.ln_Succ; ChildNodePtr = ChildNodePtr->Node.ln_Succ)
    {   if (ChildNodePtr->key != 0)
        {   if (ChildNodePtr->parentdir == passedkey && (showall || ChildNodePtr->nesting <= 1))
            {   if (ChildNodePtr->type == ST_USERDIR)
                {   print_node(ChildNodePtr->key);
                } elif (showfiles)
                {   print_entry(ChildNodePtr);
}   }   }   }   }

MODULE void print_entry(struct SpecialNode* NodePtr)
{   ULONG                   i, length,
                            where = 0; // to avoid spurious compiler warnings
    struct ListBrowserNode* ListBrowserNodePtr;

    logstring[0] = 0;
    if (NodePtr->nesting >= 2)
    {   for (i = 2; i <= NodePtr->nesting; i++)
        {   strcat(logstring, " ");
    }   }
    if (NodePtr->type == ST_USERDIR)
    {   strcat(logstring, "*");
    } else
    {   strcat(logstring, "+");
    }
    
    strcat(logstring, FilePart(NodePtr->pathname));

    scale(NodePtr->size);
    for (i = 0; i <= 13; i++)
    {   if (commastring[i] != ' ')
        {   where = i; // where is index of first non-space character
            break;
    }   }

    if (!(ListBrowserNodePtr = AllocListBrowserNode
    (   2,                       // columns,
        LBNA_Column,             0,
            LBNCA_CopyText,      TRUE,
            LBNCA_Text,          logstring,
        LBNA_Column,             1,
            LBNCA_CopyText,      TRUE,
            LBNCA_Text,          &commastring[where],
            LBNCA_Justification, LCJ_RIGHT,
        TAG_END
    )))
    {   rq("Can't create ReAction listbrowser.gadget node(s)!");
    }
    AddTail(&SizeList, (struct Node *) ListBrowserNodePtr); // AddTail() has no return code

    if (size.log)
    {   length = strlen(logstring);
        if (length < 50)
        {   for (i = 1; i <= 41 - length; i++)
            {   strcat(logstring, " ");
        }   }
        strcat(logstring, commastring);
        strcat(logstring, "\n");
        writeline();
}   }

MODULE void print_list(ABOOL close)
{   getdate();
    if (size.log)
    {   strcpy(logstring, "Path: \"");
        strcat(logstring, size.path);
        strcat(logstring, "\" at ");
        strcat(logstring, timestring);
        strcat(logstring, " on ");
        strcat(logstring, weekdaystring);
        strcat(logstring, " ");
        strcat(logstring, datestring);
        strcat(logstring, ":\n");
        if (!(LogFileHandle = (BPTR) Open(size.logfile, MODE_READWRITE)))
            rq("Can't open file for appending!");
        Seek(LogFileHandle, 0, OFFSET_END);
        writeline();
    }

    killsizelist();

    print_node(0);

    SetGadgetAttrs
    (   size_gadgets[GID_8_LB2], MainWindowPtr, NULL,
        LISTBROWSER_Labels, &SizeList,
        TAG_END
    );

    scale(size.total);
    SetGadgetAttrs // "used in path"
    (   size_gadgets[GID_8_ST4], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );
    scale(size.free);
    SetGadgetAttrs // "free on volume"
    (   size_gadgets[GID_8_ST5], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );
    scale(size.capacity);
    SetGadgetAttrs // "volume capacity"
    (   size_gadgets[GID_8_ST3], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );
    comma(size.numfiles, commastring);
    SetGadgetAttrs // "files"
    (   size_gadgets[GID_8_ST6], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );
    comma(size.numdirs, commastring);
    SetGadgetAttrs // "dirs"
    (   size_gadgets[GID_8_ST7], MainWindowPtr, NULL,
        STRINGA_TextVal, commastring,
        TAG_END
    );

    if (size.log)
    {   strcpy(logstring, "\nUsed in path:                            ");
        scale(size.total);
        strcat(logstring, commastring);
        strcat(logstring, "\nFree on volume:                          "); // these must be adjusted according to TRUNCATE
        scale(size.free);
        strcat(logstring, commastring);
        strcat(logstring, "\nVolume capacity:                         "); // these must be adjusted according to TRUNCATE
        scale(size.capacity);
        strcat(logstring, commastring);
        strcat(logstring, "\nFiles:                                   "); // these must be adjusted according to TRUNCATE
        comma(size.numfiles, commastring);
        strcat(logstring, commastring);
        strcat(logstring, "\nDirectories:                             "); // these must be adjusted according to TRUNCATE
        comma(size.numdirs, commastring);
        strcat(logstring, commastring);
        strcat(logstring, "\nBlock size:                              "); // these must be adjusted according to TRUNCATE
        comma(size.blocksize, commastring);
        strcat(logstring, commastring);
        strcat(logstring, "\n\n");
        writeline();
    }
    if (close && LogFileHandle)
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
}   }

MODULE void busy(void)
{   ULONG               breakval, largestsize,
                        largestkey = 0; // to avoid spurious compiler warnings
    SLONG               nesting;
    struct SpecialNode *NodePtr,
                       *ParentNodePtr;
    TEXT                activefile[PATHNAMEFIELD + 1];

    if (size.fixprot)
    {   SetGadgetAttrs
        (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Min,   0,
            FUELGAUGE_Max,   100,
            FUELGAUGE_Level, 45,
            GA_Text,         "Fixing protection bits...",
            TAG_END
        );

        if ((List1.lh_Head)->ln_Succ) // the list is non-empty
        {   // walk the files list
            for (NodePtr = List1.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
            {   if (NodePtr->type == ST_FILE)
                {   /* The rwed bits are SET to DENY access,
                    CLEARED to PERMIT access.

                    128 064 032 016 008 004 002 001
                    -H- -S- -P- -A- -R- -W- -E- -D- */

                    if
                    (   (NodePtr->prot & FIBF_READ)
                     || (NodePtr->prot & FIBF_WRITE)
                     || (NodePtr->prot & FIBF_EXECUTE)
                     || (NodePtr->prot & FIBF_DELETE)
                    )
                    {   NodePtr->prot &= ~FIBF_READ;
                        NodePtr->prot &= ~FIBF_WRITE;
                        NodePtr->prot &= ~FIBF_EXECUTE;
                        NodePtr->prot &= ~FIBF_DELETE;

                        strcpy(activefile, size.path);
                        if (!AddPart(activefile, NodePtr->pathname, PATHNAMEFIELD))
                        {   rq("Can't add filename to pathname!");
                        }

                        if (!(SetProtection(activefile, NodePtr->prot)))
                        {   rq("Can't fix protection bits!");
    }   }   }   }   }   }

    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 50,
        GA_Text,         "Calculating sizes...",
        TAG_END
    );

    // calculate sizes
    for (nesting = topnesting; nesting >= 0; nesting--)
    {   breakval = ra_checkbreak();
        if (breakval == 2)
        {   cleanexit(EXIT_SUCCESS);
        } elif (breakval == 1)
        {   status = STATUS_STOPPING;
            SetGadgetAttrs
            (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
                GA_Text, "Stopping...",
                TAG_END
            );
        }
        if (status == STATUS_STOPPING)
        {   return;
        }

        // walk the files list
        for (NodePtr = List1.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
        {   if (NodePtr->nesting == nesting)
            {   for (ParentNodePtr = List1.lh_Head; ParentNodePtr->Node.ln_Succ; ParentNodePtr = ParentNodePtr->Node.ln_Succ)
                {   if (ParentNodePtr->type == ST_USERDIR && ParentNodePtr->key == NodePtr->parentdir)
                    {    break;
                }   }
                ParentNodePtr->size += NodePtr->size;
    }   }   }

    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 66,
        GA_Text,         "Sorting results...",
        TAG_END
    );

    // sort by size
    while ((List1.lh_Head)->ln_Succ) // while the list is non-empty
    {   breakval = ra_checkbreak();
        if (breakval == 2)
        {   cleanexit(EXIT_SUCCESS);
        } elif (breakval == 1)
        {   status = STATUS_STOPPING;
            SetGadgetAttrs
            (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
                GA_Text, "Stopping...",
                TAG_END
            );
        }
        if (status == STATUS_STOPPING)
        {   return;
        }

        // walk through the list to determine the largest entry
        largestsize = 0;
        for (NodePtr = List1.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
        {   if (NodePtr->size >= largestsize)
            {   largestsize = NodePtr->size;
                largestkey  = NodePtr->key;
        }   }

        // walk through the list and remove the largest entry
        for (NodePtr = List1.lh_Head; NodePtr->Node.ln_Succ; NodePtr = NodePtr->Node.ln_Succ)
        {   if (NodePtr->key == largestkey)
            {   Remove((struct Node *) NodePtr);
                AddTail(&List2, (struct Node *) NodePtr);
                break;
    }   }   }

    SetGadgetAttrs
    (   size_gadgets[GID_8_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 75,
        GA_Text,         "Generating list...",
        TAG_END
    );
    SetGadgetAttrs // "stop"
    (   size_gadgets[GID_8_BU5], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    print_list(FALSE);
}

