#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/dosextens.h>
#include <dos/exall.h>
#include <dos/datetime.h>
#include <graphics/gfx.h>

#include <clib/alib_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/dos_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/exec_protos.h>

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

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

#define FILESMODE_BUILD   0
#define FILESMODE_COMPARE 1

MODULE void updatefiles(void);
MODULE void stage1(void);
MODULE void files_work(void);         // for stage1()
MODULE void stage2(void);
MODULE void dobuffer(ULONG whichpen); // for stage2()
MODULE void stage3(void);
MODULE void stopping(STRPTR status);
MODULE void files_updatelog(void);
MODULE void updateghosting(void);
MODULE void gadgetstate(ABOOL starting);
MODULE void AddFilesNode(struct List* ListPtr, STRPTR pathname, STRPTR datetime, STRPTR version, STRPTR size);
MODULE void FreeFilesNodes(struct List* ListPtr);

IMPORT ABOOL                ram;
IMPORT SBYTE                page;
IMPORT TEXT                 IOBuffer[LONGESTFIELD + 1],
                            aslresult[PATHNAMEFIELD + 1];
IMPORT ULONG                increment;
IMPORT struct Screen*       ScreenPtr;
IMPORT struct ExAllData*    EADataPtr;
IMPORT struct VisualInfo*   VisualInfoPtr;
IMPORT struct NewGadget     Gadget;
IMPORT struct Menu*         MenuPtr;
IMPORT Object*              WinObject[FUNCTIONS + 1];
IMPORT struct Library      *ButtonBase,
                           *CheckBoxBase,
                           *ChooserBase,
                           *IconBase,
                           *LabelBase,
                           *LayoutBase,
                           *ListBrowserBase,
                           *StringBase,
                           *WindowBase;

AGLOBAL struct Gadget*      files_gadgets[GIDS_10 + 1];

MODULE BOOL                 outerghost       = FALSE,
                            innerghost       = FALSE;
MODULE struct List          DirList,
                            FoundList,
                            ResultList,
                            EmptyResultList,
                            SnapshotList;
MODULE ABOOL                ResultNodes      = FALSE,
                            EmptyResultNodes = FALSE;
MODULE ULONG                filesfound,
                            status           = STATUS_READY;
MODULE TEXT                 datestringholder[LEN_DATSTRING],
                            timestringholder[LEN_DATSTRING];

MODULE struct FilesNode
{   struct Node Node;
    ABOOL       matched;
    TEXT        pathname[256 + 1],
                datetime[18 + 1],
                version[256 + 1],
                size[13 + 1];
};
MODULE struct
{   ULONG show[4], // anything we want to use GetAttr() with must be ULONG
          log, checkversion, checkdatetime, checksize,
          entries, mode;
    TEXT  logfile[PATHNAMEFIELD + 1],
          snapshot[PATHNAMEFIELD + 1],
          basepath[PATHNAMEFIELD + 1];
} files =
{   TRUE, TRUE, TRUE, TRUE,
    TRUE, TRUE, TRUE, TRUE,
    0, FILESMODE_COMPARE,
    "RAM:ReportPlus.log", "PROGDIR:ReportPlus.snapshot", "SYS:"
};
MODULE  BPTR           TheHandle             = NULL;
MODULE  TEXT           stringholder1[PATHNAMEFIELD + 1],
                       stringholder2[PATHNAMEFIELD + 1],
                       lockstring[PATHNAMEFIELD + 1],
                       liststring[7][PATHNAMEFIELD + 1];

#define COLUMN_PATHNAME   0
#define COLUMN_DATETIME   1
#define COLUMN_SIZE       2
#define COLUMN_VERSION    3
#define COLUMN_SSDATETIME 4
#define COLUMN_SSSIZE     5
#define COLUMN_SSVERSION  6

MODULE struct ColumnInfo ResultColumnInfo[] =
{ { 36,            // WORD   ci_Width
    "Pathname",    // STRPTR ci_Title
    0,             // ULONG  ci_Flags
  },
  { 10,
    "Date/Time",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { 7,
    "Size",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { 15,
    "Version",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { 10,
    "Exp. Date/Time",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { 7,
    "Exp. Size",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { 15,
    "Expected Version",
    0, /* Last column must not have CIF_DRAGGABLE set (CIF_DRAGGABLE applies
          to the right border of the relevant column). */
  },
  { -1, (STRPTR) ~0, -1
} };

MODULE STRPTR ModeOptions[3] =
{   (STRPTR) "Make snapshot of base path",
    (STRPTR) "Compare base path against snapshot",
    NULL
};

MODULE struct
{   ULONG red, green, blue, pennumber;
} penn[5] =
{   {0x55555555, 0xFFFFFFFF, 0x55555555, -1}, // green  (OK)        .
    {0xFFFFFFFF, 0x88888888, 0x00000000, -1}, // orange (changed)   !
    {0x55555555, 0xFFFFFFFF, 0xFFFFFFFF, -1}, // cyan   (3rd-party) +
    {0xFFFFFFFF, 0x44444444, 0x44444444, -1}, // red    (missing)   -
    {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, -1}, // white  (built)    n/a
};

// from rp.c
IMPORT SBYTE               page;
IMPORT struct Window*      MainWindowPtr;
IMPORT TEXT                weekdaystring[LEN_DATSTRING],
                           datestring[LEN_DATSTRING],
                           timestring[LEN_DATSTRING];

AGLOBAL void files1(void)
{   struct Hook Hook10Struct;

    updateghosting();

    /* Create the window object. */
    lockscreen();
    gadtools();
    InitHook(&Hook10Struct, Hook10Func, NULL);

    if (!(WinObject[10] =
    NewObject
    (
    WINDOW_GetClass(), NULL,
    WA_PubScreen,                 ScreenPtr,
    WA_ScreenTitle,               "Report+",
    WA_Title,                     "Report+: System Files Report",
    WA_Activate,                  TRUE,
    WA_DepthGadget,               TRUE,
    WA_DragBar,                   TRUE,
    WA_CloseGadget,               TRUE,
    WA_SizeGadget,                TRUE,
    WA_IDCMP,                     IDCMP_RAWKEY,
    WINDOW_IDCMPHook,             &Hook10Struct,
    WINDOW_IDCMPHookBits,         IDCMP_RAWKEY,
    WINDOW_MenuStrip,             MenuPtr,
    WINDOW_Position,              WPOS_CENTERSCREEN,
    WINDOW_ParentGroup,           files_gadgets[GID_10_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
            LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
            LAYOUT_SpaceOuter,    TRUE,
            LAYOUT_VertAlignment, LALIGN_CENTER,
            LAYOUT_HorizAlignment,LALIGN_CENTER,
            LAYOUT_BevelStyle,    BVS_NONE,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),    NULL,
                LAYOUT_Orientation,   LAYOUT_ORIENT_VERT,
                LAYOUT_SpaceOuter,    TRUE,
                LAYOUT_VertAlignment, LALIGN_CENTER,
                LAYOUT_HorizAlignment,LALIGN_LEFT,
                LAYOUT_BevelStyle,    BVS_NONE,
                LAYOUT_AddChild,
                NewObject
                (   LAYOUT_GetClass(),    NULL,
                    LAYOUT_Orientation,   LAYOUT_ORIENT_VERT,
                    LAYOUT_SpaceOuter,    TRUE,
                    LAYOUT_VertAlignment, LALIGN_CENTER,
                    LAYOUT_HorizAlignment,LALIGN_LEFT,
                    LAYOUT_BevelStyle,    BVS_FIELD,
                    LAYOUT_AddImage,
                    NewObject
                    (   LABEL_GetClass(),     NULL,
                        LABEL_Text,           "Show:",
                    TAG_END),
                    CHILD_WeightedHeight,     0,
                    LAYOUT_AddChild,          files_gadgets[GID_10_CB1] =
                    NewObject
                    (   CHECKBOX_GetClass(),  NULL,
                        GA_ID,                GID_10_CB1,
                        GA_RelVerify,         TRUE,
                        CHECKBOX_BackgroundPen, penn[0].pennumber,
                        GA_Text,              "_OK (.)",
                        GA_Selected,          (BOOL) files.show[0],
                    TAG_END),
                    CHILD_WeightedHeight,     0,
                    LAYOUT_AddChild,          files_gadgets[GID_10_CB2] =
                    NewObject
                    (   CHECKBOX_GetClass(),  NULL,
                        GA_ID,                GID_10_CB2,
                        GA_RelVerify,         TRUE,
                        CHECKBOX_BackgroundPen, penn[1].pennumber,
                        GA_Text,              "_Changed (!)",
                        GA_Selected,          (BOOL) files.show[1],
                        TAG_END
                    ),
                    CHILD_WeightedHeight,     0,
                    LAYOUT_AddChild,          files_gadgets[GID_10_CB3] =
                    NewObject
                    (   CHECKBOX_GetClass(),  NULL,
                        GA_ID,                GID_10_CB3,
                        GA_RelVerify,         TRUE,
                        CHECKBOX_BackgroundPen, penn[2].pennumber,
                        GA_Text,              "_3rd-party (+)",
                        GA_Selected,          (BOOL) files.show[2],
                        TAG_END
                    ),
                    CHILD_WeightedHeight,     0,
                    LAYOUT_AddChild,          files_gadgets[GID_10_CB4] =
                    NewObject
                    (   CHECKBOX_GetClass(),  NULL,
                        GA_ID,                GID_10_CB4,
                        GA_RelVerify,         TRUE,
                        CHECKBOX_BackgroundPen, penn[3].pennumber,
                        GA_Text,              "_Missing (-)",
                        GA_Selected,          (BOOL) files.show[3],
                    TAG_END),
                    CHILD_WeightedHeight,     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_LEFT,
                    LAYOUT_BevelStyle,    BVS_FIELD,
                    LAYOUT_AddImage,
                    NewObject
                    (   LABEL_GetClass(),     NULL,
                        LABEL_Text,           "Compare:",
                    TAG_END),
                    CHILD_WeightedHeight,     0,


                    LAYOUT_AddChild,      files_gadgets[GID_10_CB8] =
                    NewObject
                    (   CHECKBOX_GetClass(), NULL,
                        GA_ID,            GID_10_CB8,
                        GA_RelVerify,     TRUE,
                        GA_Text,          "Si_zes",
                        GA_Selected,      (BOOL) files.checksize,
                        GA_Disabled,      outerghost,
                    TAG_END),
                    CHILD_WeightedWidth,  0,
                    LAYOUT_AddChild,      files_gadgets[GID_10_CB6] =
                    NewObject
                    (   CHECKBOX_GetClass(), NULL,
                        GA_ID,            GID_10_CB6,
                        GA_RelVerify,     TRUE,
                        GA_Text,          "_Dates/times",
                        GA_Selected,      (BOOL) files.checkdatetime,
                        GA_Disabled,      outerghost,
                    TAG_END),
                    CHILD_WeightedWidth,  0,
                    LAYOUT_AddChild,      files_gadgets[GID_10_CB5] =
                    NewObject
                    (   CHECKBOX_GetClass(), NULL,
                        GA_ID,            GID_10_CB5,
                        GA_RelVerify,     TRUE,
                        GA_Text,          "_Versions",
                        GA_Selected,      (BOOL) files.checkversion,
                        GA_Disabled,      outerghost,
                    TAG_END),
                    CHILD_WeightedWidth,  0,
                TAG_END),
                CHILD_WeightedHeight,         0,
            TAG_END),
            CHILD_WeightedWidth,      0,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),    NULL,
                LAYOUT_Orientation,   LAYOUT_ORIENT_VERT,
                LAYOUT_SpaceOuter,    TRUE,
                LAYOUT_VertAlignment, LALIGN_TOP,
                LAYOUT_HorizAlignment,LALIGN_CENTER,
                LAYOUT_BevelStyle,    BVS_NONE,
                LAYOUT_ShrinkWrap,    TRUE,
                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_VERT,
                        LAYOUT_SpaceOuter,    TRUE,
                        LAYOUT_VertAlignment, LALIGN_CENTER,
                        LAYOUT_HorizAlignment,LALIGN_CENTER,
                        LAYOUT_BevelStyle,    BVS_FIELD,
                        LAYOUT_ShrinkWrap,    TRUE,
                        LAYOUT_AddChild,
                        NewObject
                        (   LAYOUT_GetClass(),    NULL,
                            LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                            LAYOUT_SpaceOuter,    TRUE,
                            LAYOUT_VertAlignment, LALIGN_TOP,
                            LAYOUT_HorizAlignment,LALIGN_LEFT,
                            LAYOUT_BevelStyle,    BVS_NONE,
                            LAYOUT_ShrinkWrap,    TRUE,
                            LAYOUT_AddImage,
                            NewObject
                            (   LABEL_GetClass(), NULL,
                                LABEL_Text,       "Mod_e:",
                            TAG_END),
                            CHILD_WeightedWidth,  0,
                        TAG_END),
                        CHILD_WeightedHeight,     0,
                        LAYOUT_AddChild,          files_gadgets[GID_10_RA1] =
                        NewObject
                        (   RADIOBUTTON_GetClass(),NULL,
                            GA_ID,                GID_10_RA1,
                            GA_RelVerify,         TRUE,
                            GA_Text,              ModeOptions,
                            RADIOBUTTON_Selected, (WORD) files.mode,
                        TAG_END),
                    TAG_END),
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),    NULL,
                        LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                        LAYOUT_SpaceOuter,    TRUE,
                        LAYOUT_VertAlignment, LALIGN_CENTER,
                        LAYOUT_HorizAlignment,LALIGN_CENTER,
                        LAYOUT_BevelStyle,    BVS_NONE,
                        LAYOUT_ShrinkWrap,    TRUE,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(), NULL,
                            LABEL_Text,       "_Snapshot pathname:",
                        TAG_END),
                        CHILD_WeightedWidth,  0,
                        LAYOUT_AddChild,      files_gadgets[GID_10_ST2] =
                        NewObject
                        (   STRING_GetClass(),NULL,
                            GA_ID,            GID_10_ST2,
                            GA_TabCycle,      TRUE,
                            STRINGA_TextVal,  files.snapshot,
                            STRINGA_MaxChars, PATHNAMEFIELD,
                            STRINGA_MinVisible,10,
                        TAG_END),
                        CHILD_WeightedWidth,  100,
                        LAYOUT_AddChild,      files_gadgets[GID_10_BU4] =
                        NewObject
                        (   NULL,             "button.gadget",
                            GA_ID,            GID_10_BU4,
                            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_SpaceOuter,    TRUE,
                        LAYOUT_VertAlignment, LALIGN_CENTER,
                        LAYOUT_HorizAlignment,LALIGN_CENTER,
                        LAYOUT_BevelStyle,    BVS_NONE,
                        LAYOUT_ShrinkWrap,    TRUE,
                        LAYOUT_AddImage,
                        NewObject
                        (   LABEL_GetClass(), NULL,
                            LABEL_Text,       "_Base path:",
                        TAG_END),
                        CHILD_WeightedWidth,  0,
                        LAYOUT_AddChild,      files_gadgets[GID_10_ST3] =
                        NewObject
                        (   STRING_GetClass(),NULL,
                            GA_ID,            GID_10_ST3,
                            GA_TabCycle,      TRUE,
                            STRINGA_TextVal,  files.basepath,
                            STRINGA_MinVisible,10,
                            STRINGA_MaxChars, PATHNAMEFIELD,
                        TAG_END),
                        CHILD_WeightedWidth,  100,
                        LAYOUT_AddChild,      files_gadgets[GID_10_BU4] =
                        NewObject
                        (   NULL,             "button.gadget",
                            GA_ID,            GID_10_BU5,
                            GA_RelVerify,     TRUE,
                            BUTTON_AutoButton,BAG_POPFILE,
                        TAG_END),
                        CHILD_WeightedWidth,  0,
                    TAG_END),
                    CHILD_WeightedHeight,     0,
                    LAYOUT_AddChild,          files_gadgets[GID_10_FG1] =
                    NewObject
                    (   FUELGAUGE_GetClass(), NULL,
                        GA_ID,                GID_10_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_SpaceOuter,    TRUE,
                        LAYOUT_VertAlignment, LALIGN_CENTER,
                        LAYOUT_HorizAlignment,LALIGN_CENTER,
                        LAYOUT_BevelStyle,    BVS_NONE,
                        LAYOUT_AddChild,      files_gadgets[GID_10_BU2] =
                        NewObject
                        (   NULL,             "button.gadget",
                            GA_ID,            GID_10_BU2,
                            GA_RelVerify,     TRUE,
                            GA_Text,          "_Update",
                        TAG_END),
                        CHILD_WeightedWidth,  50,
                        LAYOUT_AddChild,      files_gadgets[GID_10_BU3] =
                        NewObject
                        (   NULL,             "button.gadget",
                            GA_ID,            GID_10_BU3,
                            GA_RelVerify,     TRUE,
                            GA_Text,          "Stop",
                            GA_Disabled,      TRUE,
                        TAG_END),
                        CHILD_WeightedWidth,  50,
                    TAG_END),
                    CHILD_WeightedHeight,     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_ShrinkWrap,    TRUE,
                    LAYOUT_AddChild,
                    NewObject
                    (   LAYOUT_GetClass(),    NULL,
                        LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                        LAYOUT_SpaceOuter,    TRUE,
                        LAYOUT_VertAlignment, LALIGN_CENTER,
                        LAYOUT_HorizAlignment,LALIGN_CENTER,
                        LAYOUT_BevelStyle,    BVS_NONE,
                        LAYOUT_ShrinkWrap,    TRUE,
                        LAYOUT_AddChild,      files_gadgets[GID_10_CB7] =
                        NewObject
                        (   CHECKBOX_GetClass(), NULL,
                            GA_ID,               GID_10_CB7,
                            GA_RelVerify,        TRUE,
                            GA_Text,             "_Log to:",
                            GA_Selected,         (BOOL) files.log,
                            GA_Disabled,         outerghost,
                        TAG_END),
                        CHILD_WeightedWidth,  0,
                        LAYOUT_AddChild,      files_gadgets[GID_10_ST1] =
                        NewObject
                        (   STRING_GetClass(), NULL,
                            GA_ID,            GID_10_ST1,
                            GA_TabCycle,      TRUE,
                            STRINGA_TextVal,  files.logfile,
                            STRINGA_MinVisible,10,
                            STRINGA_MaxChars, PATHNAMEFIELD,
                            GA_Disabled,      innerghost,
                        TAG_END),
                        LAYOUT_AddChild,      files_gadgets[GID_10_BU1] =
                        NewObject
                        (   NULL,             "button.gadget",
                            GA_ID,            GID_10_BU1,
                            GA_RelVerify,     TRUE,
                            BUTTON_AutoButton,BAG_POPFILE,
                            GA_Disabled,      innerghost,
                        TAG_END),
                        CHILD_WeightedWidth,  0,
                    TAG_END),
                    CHILD_WeightedHeight, 0,
                TAG_END),
                CHILD_WeightedHeight,     0,
            TAG_END),
            CHILD_WeightedHeight,         0,
        TAG_END),
        CHILD_WeightedHeight,         0,
        LAYOUT_AddChild,              files_gadgets[GID_10_LB1] =
        NewObject
        (   LISTBROWSER_GetClass(),   NULL,
            GA_ID,                    GID_10_LB1,
            GA_ReadOnly,              TRUE,
            GA_TextAttr,              Gadget.ng_TextAttr,
            LISTBROWSER_Labels,       (ULONG) &EmptyResultList,
            LISTBROWSER_ColumnInfo,   (ULONG) &ResultColumnInfo,
            LISTBROWSER_ColumnTitles, TRUE,
            LISTBROWSER_HorizontalProp,TRUE,
            LISTBROWSER_VirtualWidth, 620 + 620 + 310,
            LISTBROWSER_MinVisible,   6,
        TAG_END),
        CHILD_WeightedHeight,         100,
        CHILD_MinHeight,              128,
    TAG_END),
    TAG_END
    )))
    {   rq("Can't create ReAction object(s)!");
    }
    unlockscreen();
    openwindow();
    ActivateLayoutGadget(files_gadgets[GID_10_LY1], MainWindowPtr, NULL, (Object) files_gadgets[GID_10_ST2]);

    loop();

    closewindow();
    files_exit();
}

MODULE void updatefiles(void)
{   STRPTR stringptr;

    /* Most gadgets are ghosted during the operation. Then their ghosting
    status returns to normal (not necessarily unghosted).

        0: Gadget handling.
        1: Set up lists, do the directory examination.
           (For each `source' directory in the `queue', before doing it
           we check for a break. `Break opportunity 1'.)
    At this point we have an empty DirList, and a full FoundList, and an
    empty ResultList.
        2: Create the ResultList.
           (For each `source' file in the `queue', before doing it we
           check for a break. `Break opportunity 2'.)
        3: Show results.

       DirList: an Exec list (NameNodes ) of directories found, awaiting examination.
     FoundList: an Exec list (FilesNodes) of files found, awaiting processing.
  SnapshotList: an Exec list (FilesNodes) of files in the snapshot file.
    ResultList: a listbrowser list of files found, for display.

    Apparently the way strings work is that you get told an address where
    the string is stored. This is apparently a pointer into system memory;
    it doesn't need explicit allocation/deallocation by our appliprog. */

    if (!(GetAttr
    (   STRINGA_TextVal, files_gadgets[GID_10_ST1], (ULONG *) &stringptr
    )))
    {   rq("Unsupported inquiry!"); // should never happen
    }
    strcpy(files.logfile, stringptr);

    if (!(GetAttr
    (   STRINGA_TextVal, files_gadgets[GID_10_ST2], (ULONG *) &stringptr
    )))
    {   rq("Unsupported inquiry!"); // should never happen
    }
    strcpy(files.snapshot, stringptr);

    if (!(GetAttr
    (   STRINGA_TextVal, files_gadgets[GID_10_ST3], (ULONG *) &stringptr
    )))
    {   rq("Unsupported inquiry!"); // should never happen
    }
    strcpy(files.basepath, stringptr);

    gadgetstate(TRUE);

    SetGadgetAttrs
    (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 8,
        GA_Text,         "Reading from volume...",
        TAG_END
    ); // we don't know how many files in advance...

    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, NULL,
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, (ULONG) &EmptyResultList,
        TAG_END
    );
    if (ResultNodes)
    {   clearreactionlist(&ResultList);
        ResultNodes = FALSE;
    }
    filesfound = 0;

    status = STATUS_BUSY;
    stage1();
    if (status == STATUS_BUSY)
    {   stage2();
    }
    FreeFilesNodes(&FoundList);
    FreeFilesNodes(&SnapshotList);

    if (status == STATUS_BUSY)
    {   stage3();
    }
    status = STATUS_READY;

    gadgetstate(FALSE);

    SetGadgetAttrs
    (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 0,
        GA_Text,         "Ready.",
        TAG_END
    );
}

MODULE void stage1(void)
{   struct NameNode* NameNodePtr;

    NewList(&FoundList);
    NewList(&DirList);
    NewList(&ResultList);
    NewList(&SnapshotList);
    AddNameToTail(&DirList, "");
    // pop all the directories from the work stack, and send them one
    // at a time to files_work() for processing.
    while ((DirList.lh_Head)->ln_Succ) // while the list is non-empty
    {   if (ra_checkbreak() == 1) // we don't yet support completely quitting
        {   status = STATUS_STOPPING;
            stopping("Stopping...");
            break;
        }

        /* remove the dir-node from the list, copy its path to
           stringholder1, call files_work, then free the dir-node. */

        if (!(NameNodePtr = (struct NameNode *) RemTail(&DirList)))
        {   rq("RemTail() failed (list is empty!)"); // this should never happen
        }
        strcpy(stringholder1, NameNodePtr->nn_Data);
        files_work();
        FreeMem(NameNodePtr, sizeof(struct NameNode));
    }
    FreeNameNodes(&DirList); // Not required unless stopping early (although harmless).
}

MODULE void stage2(void)
{   ABOOL                 matched;
    ULONG                 filesdone = 0,
                          i, j, sizeval,
                          length, snapshots;
    struct FilesNode     *FoundNodePtr,
                         *SnapshotNodePtr;
    TEXT                  filename[PATHNAMEFIELD + 1],
                          pathname[PATHNAMEFIELD + 1],
                          datetime[18 + 1],
                          version[VLONGFIELD + 1],
                          size[13 + 1];
    struct FileInfoBlock* FIBPtr;
    STRPTR                MemoryPtr;

    // At this point, we have the list of files found.

/* 1: Check versions of found files.
   2: Read the snapshot file into memory.
   3: Parse the snapshot file into a list of SnapshotNodes.
   4: Open logfile and write logfile header.
   5: Check each found node against each snapshot node.
   6: Decide missing files.

1 --------------------------------------------------------------------- */

if (files.mode == FILESMODE_BUILD || files.checkversion)
{   SetGadgetAttrs
    (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 16,
        GA_Text,         "Checking versions...",
        TAG_END
    );

    if ((FoundList.lh_Head)->ln_Succ) // if the list is non-empty
    {   for
        (   FoundNodePtr = FoundList.lh_Head;
            FoundNodePtr->Node.ln_Succ;
            FoundNodePtr = FoundNodePtr->Node.ln_Succ
        )
        {   if (ra_checkbreak() == 1)
            {   status = STATUS_STOPPING;
                stopping("Stopping...");
                return;
            }
            strcpy(pathname, files.basepath);
            if (!(AddPart(pathname, FoundNodePtr->pathname, PATHNAMEFIELD)))
            {   rq("AddPart() failed!");
            }
            getversion(pathname, FoundNodePtr->version);
            strcpy(FoundNodePtr->version, "?");
}   }   }

// 2 ---------------------------------------------------------------------

    if (files.mode == FILESMODE_BUILD)
    {   if (!(TheHandle = (BPTR) Open(files.snapshot, MODE_NEWFILE)))
        {   rq("Can't open snapshot file for writing!");
    }   }
    else
    {   assert(files.mode == FILESMODE_COMPARE);

        SetGadgetAttrs
        (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Min,   0,
            FUELGAUGE_Max,   100,
            FUELGAUGE_Level, 24,
            GA_Text,         "Reading snapshot file...",
            TAG_END
        );


        if (!(TheHandle = (BPTR) Lock(files.snapshot, ACCESS_READ)))
        {   rq("Can't lock snapshot file!");
        }
        if (!(FIBPtr    = AllocDosObject(DOS_FIB, NULL)))
        {   UnLock(TheHandle);
            TheHandle = NULL;
            rq("Can't allocate DOS object!");
        }
        if (!(Examine(TheHandle, FIBPtr)))
        {   FreeDosObject(DOS_FIB, FIBPtr);
            FIBPtr = NULL;
            UnLock(TheHandle);
            TheHandle = NULL;
            rq("Can't examine snapshot file!");
        }
        sizeval = FIBPtr->fib_Size;
        comma(sizeval, size);
        stcl_d(size, sizeval);
        FreeDosObject(DOS_FIB, FIBPtr);
        FIBPtr = NULL;
        UnLock(TheHandle);
        TheHandle = NULL;

        // Open the snapshot file, read it into memory, then close it.
        if (!(TheHandle = (BPTR) Open(files.snapshot, MODE_OLDFILE)))
        {   rq("Can't open snapshot file for reading!");
        }
        if (!(MemoryPtr = AllocVec(sizeval, NULL)))
        {   rq("Out of memory!");
        }

        if (Read(TheHandle, MemoryPtr, sizeval) == -1)
        {   rq("Can't read from snapshot file!");
        }
        Close(TheHandle);
        TheHandle = NULL;

// 3 ---------------------------------------------------------------------

        i         =
        snapshots = 0;

        do
        {   j = 0;
            while (*(MemoryPtr + i) != LF)
            {   pathname[j] = *(MemoryPtr + i);
                i++;
                j++;
            }
            pathname[j] = 0;
            i++;

            j = 0;
            while (*(MemoryPtr + i) != LF)
            {   datetime[j] = *(MemoryPtr + i);
                i++;
                j++;
            }
            datetime[j] = 0;
            i++;

            j = 0;
            while (*(MemoryPtr + i) != LF)
            {   version[j] = *(MemoryPtr + i);
                i++;
                j++;
            }
            version[j] = 0;
            i++;

            j = 0;
            while (*(MemoryPtr + i) != LF)
            {   size[j] = *(MemoryPtr + i);
                i++;
                j++;
            }
            size[j] = 0;
            i++;
            AddFilesNode(&SnapshotList, pathname, datetime, version, size);

            snapshots++;
        } while (i < sizeval);
        FreeVec(MemoryPtr);

// 4 ---------------------------------------------------------------------

        if (files.log)
        {   if (!(TheHandle = (BPTR) Open(files.logfile, MODE_READWRITE)))
            {   rq("Can't open logfile for appending!");
            }
            Seek(TheHandle, 0, OFFSET_END);

            strcpy(IOBuffer, files.basepath);
            strcat(IOBuffer, " files as at ");
            getdate();
            strcat(IOBuffer, timestring);
            strcat(IOBuffer, " on ");
            strcat(IOBuffer, weekdaystring);
            strcat(IOBuffer, " ");
            strcat(IOBuffer, datestring);
            strcat(IOBuffer, ":\n\n");
            if (Write(TheHandle, IOBuffer, strlen(IOBuffer)) == -1)
            {   rq("Can't append to logfile!");
    }   }   }

/* 5 ---------------------------------------------------------------------

We go through FoundList destructively, ie. freeing each node after we
have used it.

For each file found, we check it against each snapshot node. If it doesn't
match any, it's missing. */

    if (files.mode == FILESMODE_COMPARE)
    {   SetGadgetAttrs
        (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Min,   0,
            FUELGAUGE_Max,   filesfound * 3,
            FUELGAUGE_Level, filesfound,
            GA_Text,         "Comparing...",
            TAG_END
        );
    } else
    {   assert(files.mode == FILESMODE_BUILD);
        SetGadgetAttrs
        (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
            FUELGAUGE_Min,   0,
            FUELGAUGE_Max,   filesfound * 3,
            FUELGAUGE_Level, filesfound,
            GA_Text,         "Writing snapshot file...",
            TAG_END
        );
    }

    while ((FoundList.lh_Head)->ln_Succ) // while the list is non-empty
    {   if (ra_checkbreak() == 1)
        {   status = STATUS_STOPPING;
            stopping("Stopping...");
            if (files.mode == FILESMODE_BUILD || files.log)
            {   strcpy(IOBuffer, "Aborted by user!");
                if (Write(TheHandle, IOBuffer, strlen(IOBuffer)) == -1)
                {   if (files.mode == FILESMODE_COMPARE)
                    {   rq("Can't append to logfile!");
                    } else
                    {   assert(files.mode == FILESMODE_BUILD);
                        rq("Can't write to snapshot file!");
                }   }
                Close(TheHandle); // Close() doesn't return an error code
                TheHandle = NULL;
            }
            return;
        }

        assert(status != STATUS_STOPPING);

        if (!(filesdone % 20))
        {   SetGadgetAttrs
            (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
                FUELGAUGE_Level, filesfound + filesdone,
                TAG_END
            );
        }
        filesdone++;

        if (!(FoundNodePtr = (struct FilesNode *) RemTail(&FoundList)))
        {   rq("RemTail() failed (list is empty!)"); // should never happen
        }
        strcpy(stringholder1, FoundNodePtr->pathname);
        length = strlen(stringholder1);
        strcpy(datetime, FoundNodePtr->datetime);
        strcpy(version,  FoundNodePtr->version );
        strcpy(size,     FoundNodePtr->size    );
        FreeMem(FoundNodePtr, sizeof(struct FilesNode));
        // FilesNodePtr = NULL;

        strcpy(filename, files.basepath);
        strcat(filename, stringholder1);

        if (files.mode == FILESMODE_BUILD)
        {   strcpy(liststring[COLUMN_PATHNAME], " ");
            strcat(liststring[COLUMN_PATHNAME], stringholder1);
            strcpy(liststring[COLUMN_DATETIME], datetime);
            strcpy(liststring[COLUMN_SIZE    ], size);
            strcpy(liststring[COLUMN_VERSION ], version);
            strcpy(liststring[COLUMN_SSDATETIME], "n/a");
            strcpy(liststring[COLUMN_SSSIZE   ],  "n/a");
            strcpy(liststring[COLUMN_SSVERSION],  "n/a");
            dobuffer(4);
        } else
        {   // now we have removed the file from the list, and copied its
            // name into stringholder1. Now we check it against each system
            // file.
            matched = FALSE;

            if ((SnapshotList.lh_Head)->ln_Succ) // if the list is non-empty
            {   for
                (   SnapshotNodePtr = SnapshotList.lh_Head;
                    SnapshotNodePtr->Node.ln_Succ;
                    SnapshotNodePtr = SnapshotNodePtr->Node.ln_Succ
                )
                {   if (!matched && !(stricmp(stringholder1, SnapshotNodePtr->pathname)))
                    {   // we have a match
                        matched = TRUE;
                        SnapshotNodePtr->matched = (ABOOL) TRUE;
        
                        if (files.checkdatetime)
                        {   strcpy(liststring[COLUMN_DATETIME],   datetime);
                            strcpy(liststring[COLUMN_SSDATETIME], SnapshotNodePtr->datetime);
                        } else
                        {   strcpy(liststring[COLUMN_DATETIME],   "n/a");
                            strcpy(liststring[COLUMN_SSDATETIME], "n/a");
                        }                                                                
        
                        if (files.checksize)
                        {   strcpy(liststring[COLUMN_SIZE  ], size);
                            strcpy(liststring[COLUMN_SSSIZE], SnapshotNodePtr->size);
                        } else
                        {   strcpy(liststring[COLUMN_SIZE  ], "n/a");
                            strcpy(liststring[COLUMN_SSSIZE], "n/a");
                        }

                        if (files.checkversion)
                        {   strcpy(liststring[COLUMN_VERSION],   version);
                            strcpy(liststring[COLUMN_SSVERSION], SnapshotNodePtr->version);
                        } else
                        {   strcpy(liststring[COLUMN_VERSION  ], "n/a");
                            strcpy(liststring[COLUMN_SSVERSION], "n/a");
                        }
                        if
                        (   (files.checkdatetime && strcmp(SnapshotNodePtr->datetime, liststring[COLUMN_DATETIME]))
                         || (files.checkversion  && strcmp(SnapshotNodePtr->version,  liststring[COLUMN_VERSION ]))
                         || (files.checksize     && strcmp(SnapshotNodePtr->size,     liststring[COLUMN_SIZE    ]))
                        )
                        {   strcpy(liststring[COLUMN_PATHNAME], "!");
                            strcat(liststring[COLUMN_PATHNAME], stringholder1);
                            if (files.show[1])
                            {   dobuffer(1);
                        }   }
                        else
                        {   strcpy(liststring[COLUMN_PATHNAME], ".");
                            strcat(liststring[COLUMN_PATHNAME], stringholder1);
                            if (files.show[0])
                            {   dobuffer(0);
                        }   }
                        break;
            }   }   }

            if (files.show[2] && !matched) // third-party
            {   strcpy(liststring[COLUMN_PATHNAME], "+");
                strcat(liststring[COLUMN_PATHNAME], stringholder1);

                if (files.checkdatetime)
                {   strcpy(liststring[COLUMN_DATETIME], datetime);
                } else
                {   strcpy(liststring[COLUMN_DATETIME], "?");
                }
                if (files.checkversion)
                {   strcpy(liststring[COLUMN_VERSION], version);
                } else
                {   strcpy(liststring[COLUMN_VERSION], "?");
                }
                if (files.checksize)
                {   strcpy(liststring[COLUMN_SIZE], size);
                } else
                {   strcpy(liststring[COLUMN_SIZE], "?");
                }

                strcpy(liststring[COLUMN_SSDATETIME], "n/a");
                strcpy(liststring[COLUMN_SSVERSION ], "n/a");
                strcpy(liststring[COLUMN_SSSIZE    ], "n/a");
                dobuffer(2);
    }   }   }

/* 6 ---------------------------------------------------------------------

Missing files can't be determined until we have looked through all the
found files. We go through the snapshot list looking at the `matched'
member variable. */

    if (files.mode == FILESMODE_COMPARE)
    {   // missing files

        if (status == STATUS_BUSY && files.show[3])
        {   SetGadgetAttrs
            (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
                FUELGAUGE_Min,   0,
                FUELGAUGE_Max,   100,
                FUELGAUGE_Level, 83,
                GA_Text,         "Deciding missing...",
                TAG_END
            );

            if ((SnapshotList.lh_Head)->ln_Succ) // if the list is non-empty
            {   for
                (   SnapshotNodePtr = SnapshotList.lh_Head;
                    SnapshotNodePtr->Node.ln_Succ;
                    SnapshotNodePtr = SnapshotNodePtr->Node.ln_Succ
                )
                {   if (!(SnapshotNodePtr->matched))
                    {   strcpy(liststring[COLUMN_PATHNAME], "-");
                        strcat(liststring[COLUMN_PATHNAME], SnapshotNodePtr->pathname);
                        strcpy(liststring[COLUMN_VERSION ], "n/a");
                        strcpy(liststring[COLUMN_DATETIME], "n/a");
                        strcpy(liststring[COLUMN_SIZE    ], "n/a");

                        if (files.checkdatetime)
                        {    strcpy(liststring[COLUMN_SSDATETIME], SnapshotNodePtr->datetime);
                        } else
                        {    strcpy(liststring[COLUMN_SSDATETIME], "n/a");
                        }
                        if (files.checkversion)
                        {    strcpy(liststring[COLUMN_SSVERSION], SnapshotNodePtr->version);
                        } else
                        {    strcpy(liststring[COLUMN_SSVERSION], "n/a");
                        }
                        if (files.checksize)
                        {    strcpy(liststring[COLUMN_SSSIZE], SnapshotNodePtr->size);
                        } else
                        {    strcpy(liststring[COLUMN_SSSIZE], "n/a");
                        }
                        dobuffer(3);
    }   }   }   }   }
    if (TheHandle)
    {   Close(TheHandle); // Close() doesn't return an error code
        TheHandle = NULL;
}   }

MODULE void stage3(void)
{   SetGadgetAttrs
    (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
        FUELGAUGE_Min,   0,
        FUELGAUGE_Max,   100,
        FUELGAUGE_Level, 0,
        GA_Text,         "Ready.",
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, NULL,
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, (ULONG) &ResultList,
        TAG_END
    );
}

/* FileNamePtr[]: each of these is a pointer to the allocated memory
area holding the pathname of that file found (excepting the base path
component).

FileNamesAllocated = number of elements of FileNamePtr[]. Or, another way
to think of it, the number of files under the base path. Element 0 of
the array is not used. */

MODULE void files_work(void)
{   BOOL                 more;      // BOOL, not ABOOL
    BPTR                 DirHandle; // = NULL;
    LONG                 ed;
    struct ExAllControl* eac;       // = NULL;
    struct ExAllData*    ead;
    struct DateTime      DateTime;
    ULONG                baselength    = strlen(files.basepath);
    TEXT                 datetime[18 + 1],
                         size[14 + 1];

    DateTime.dat_Format  = FORMAT_DOS;
    DateTime.dat_Flags   = NULL;
    DateTime.dat_StrDay  = NULL;
    DateTime.dat_StrDate = datestring;
    DateTime.dat_StrTime = timestring;

    /* Service routine for stage1(). Each call of this routine
    handles one directory from the work stack. This routine is the one
    that actually makes the DOS calls. It pushes any subdirectories
    found onto the stack.

    stringholder1 contains the pathname of the directory to examine
        (without base path).
    lockstring contains the pathname of the directory to examine
        (with base path).
    stringholder2 will contain the pathname of each file/dir found
        (with base path). */

    strcpy(lockstring, files.basepath);
    strcat(lockstring, stringholder1);

    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 (files.mode == FILESMODE_BUILD || files.checkdatetime)
    {   ed = ED_DATE; // we want name, type, size and date
    } elif (files.checksize)
    {   ed = ED_SIZE; // we want name, type and size
    } else
    {   ed = ED_TYPE; // we want name and type
    }
    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(stringholder2, lockstring);
            if (!AddPart(stringholder2, ead->ed_Name, PATHNAMEFIELD))
            {   FreeDosObject(DOS_EXALLCONTROL, eac);
                eac = NULL;
                UnLock(DirHandle);
                DirHandle = NULL;
                rq("Can't add filename/dirname to pathname!");
            }
            if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
            {   AddNameToTail(&DirList,  &(stringholder2[baselength]));
            } elif (ead->ed_Type == -3) // if it's a file
            {   if (files.mode == FILESMODE_BUILD || files.checkdatetime)
                {   DateTime.dat_Stamp.ds_Days   = ead->ed_Days;
                    DateTime.dat_Stamp.ds_Minute = ead->ed_Mins;
                    DateTime.dat_Stamp.ds_Tick   = ead->ed_Ticks;
                    DateToStr(&DateTime);
                    strcpy(datetime, timestring);
                    strcat(datetime, " ");
                    strcat(datetime, datestring);
                }
                if (files.mode == FILESMODE_BUILD || files.checksize)
                {   comma(ead->ed_Size, size);
                }
                AddFilesNode
                (   &FoundList,
                    &(stringholder2[baselength]),
                    datetime,
                    NULL,
                    size
                );

                filesfound++;
            }

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

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

MODULE void dobuffer(ULONG whichpen)
{   struct Node* ListBrowserNodePtr;
    TEXT         codestring[512 + 1], // codestrings are sometimes longer than 256 chacters
                 spaces[LONGFIELD + 1];
    SLONG        gap;
    ULONG        i;

    /* Service routine for stage2(). */

    if (!(ListBrowserNodePtr = AllocListBrowserNode
    (   7,              // columns,
        LBNA_Column,    0,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     &liststring[0][1],
        LBNA_Column,    1,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[1],
        LBNA_Column,    2,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[2],
        LBNA_Column,    3,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[3],
        LBNA_Column,    4,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[4],
        LBNA_Column,    5,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[5],
        LBNA_Column,    6,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     liststring[6],
    TAG_END)))
    {   rq("Can't create ReAction listbrowser.gadget node(s)!");
    }
    AddTail(&ResultList, ListBrowserNodePtr); // AddTail() has no return code
    ResultNodes = TRUE;

    if (files.mode == FILESMODE_BUILD)
    {   strcpy(codestring, &liststring[COLUMN_PATHNAME][1]);
        strcat(codestring, "\n");
        strcat(codestring, liststring[COLUMN_DATETIME]);
        strcat(codestring, "\n");
        strcat(codestring, liststring[COLUMN_VERSION]);
        strcat(codestring, "\n");
        strcat(codestring, liststring[COLUMN_SIZE   ]);
        strcat(codestring, "\n");

        if (Write(TheHandle, codestring, strlen(codestring)) == -1)
        {   rq("Can't write to snapshot file!");
    }   }
    else
    {   assert(files.mode == FILESMODE_COMPARE);
        if (files.log)
        {   strcpy(codestring, liststring[COLUMN_PATHNAME]);
            if (strlen(liststring[COLUMN_PATHNAME]) >= 35)
            {   strcat(codestring, "\n"
                "                                    "); // 36 spaces
            } else
            {   gap = 36 - strlen(liststring[COLUMN_PATHNAME]);
                for (i = 0; i < gap; i++)
                {   spaces[i] = ' ';
                }
                spaces[gap] = 0;
                strcat(codestring, spaces);
            }
            strcat(codestring, liststring[COLUMN_SIZE    ]);
            gap = 14 - strlen(liststring[COLUMN_SIZE     ]);
            if (gap >= 1)
            {   for (i = 0; i < gap; i++)
                {   spaces[i] = ' ';
                }
                spaces[gap] = 0;
                strcat(codestring, spaces);
            }
            strcat(codestring, liststring[COLUMN_DATETIME]);
            gap = 20 - strlen(liststring[COLUMN_DATETIME]);
            if (gap >= 1)
            {   for (i = 0; i < gap; i++)
                {   spaces[i] = ' ';
                }
                spaces[gap] = 0;
                strcat(codestring, spaces);
            }                                    
            strcat(codestring, liststring[COLUMN_VERSION]);
            strcat(codestring, "\n"
            "                                    "); // 36 spaces
            strcat(codestring, liststring[COLUMN_SSSIZE    ]);
            gap = 14 - strlen(liststring[COLUMN_SSSIZE    ]);
            if (gap >= 1)
            {   for (i = 0; i < gap; i++)
                {   spaces[i] = ' ';
                }
                spaces[gap] = 0;
                strcat(codestring, spaces);
            }
            strcat(codestring, liststring[COLUMN_SSDATETIME]);
            gap = 20 - strlen(liststring[COLUMN_SSDATETIME]);
            if (gap >= 1)
            {   for (i = 0; i < gap; i++)
                {   spaces[i] = ' ';
                }
                spaces[gap] = 0;
                strcat(codestring, spaces);
            }
            strcat(codestring, liststring[COLUMN_SSVERSION]);
            strcat(codestring, "\n");

            if (Write(TheHandle, codestring, strlen(codestring)) == -1)
            {   rq("Can't append to logfile!");
}   }   }   }

AGLOBAL void files_init(void)
{   ULONG        i;
    struct Node* ListBrowserNodePtr;

    lockscreen();
    for (i = 0; i <= 4; i++)
    {   penn[i].pennumber = FindColor
        (   ScreenPtr->ViewPort.ColorMap,
            penn[i].red,
            penn[i].green,
            penn[i].blue,
            -1
        );
    }
    unlockscreen();

    NewList(&ResultList);
    NewList(&EmptyResultList);
    if (!(ListBrowserNodePtr = AllocListBrowserNode
    (   7,           // columns
        /* LBNCA_ tags are those that apply to the specific column. */
        LBNA_Column, 0,
        LBNA_Column, 1,
        LBNA_Column, 2,
        LBNA_Column, 3,
        LBNA_Column, 4,
        LBNA_Column, 5,
        LBNA_Column, 6,
        TAG_END
    )))
    {   rq("Can't create ReAction listbrowser.gadget node(s)!");
    }
    AddTail(&EmptyResultList, ListBrowserNodePtr); // AddTail() has no return code
    EmptyResultNodes = TRUE;
}

AGLOBAL void files_exit(void)
{   // The window should be closed before calling this.

    if (ResultNodes)
    {   clearreactionlist(&ResultList);
        ResultNodes = FALSE;
    }
    if (TheHandle)
    {   Close(TheHandle); // Close() doesn't return an error code
        TheHandle = NULL;
}   }

AGLOBAL void files_die(void)
{   ULONG i;

    IOBuffer[23] = (UBYTE) files.log;
    for (i = 0; i <= 3; i++)
    {   IOBuffer[i + 8] = (UBYTE) files.show[i];
    }
    IOBuffer[7]  = (UBYTE) files.checkversion;
    IOBuffer[12] = (UBYTE) files.checkdatetime;
    IOBuffer[13] = (UBYTE) files.mode;
    IOBuffer[14] = (UBYTE) files.checksize;
}

AGLOBAL void files_config(void)
{   UBYTE i;

    files.log = (ULONG) IOBuffer[23];
    for (i = 0; i <= 3; i++)
    {   files.show[i] = (ULONG) IOBuffer[i + 8];
    }
    files.checkversion  = (ULONG) IOBuffer[7];
    files.checkdatetime = (ULONG) IOBuffer[12];
    files.mode          = (ULONG) IOBuffer[13];
    files.checksize     = (ULONG) IOBuffer[14];
}

AGLOBAL void files_loop(ULONG gid)
{   STRPTR stringptr;

    switch (gid)
    {
    case GID_10_RA1:
        if (!(GetAttr
        (   RADIOBUTTON_Selected, files_gadgets[GID_10_RA1], (ULONG *) &files.mode
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        files_updatelog();
    break;
    case GID_10_CB1:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB1], (ULONG *) &files.show[0]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB2:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB2], (ULONG *) &(files.show[1])
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB3:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB3], (ULONG *) &files.show[2]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB4:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB4], (ULONG *) &files.show[3]
        )))                                                                    
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB5:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB5], (ULONG *) &files.checkversion
        )))                                                                    
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB6:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB6], (ULONG *) &files.checkdatetime
        )))                                                                    
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB7:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB7], &files.log
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        files_updatelog();
        if (files.log)
        {   ActivateLayoutGadget(files_gadgets[GID_10_LY1], MainWindowPtr, NULL, (Object) files_gadgets[GID_10_ST1]);
        }
    break;
    case GID_10_CB8:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB8], (ULONG *) &files.checksize
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_ST1:
        if (!(GetAttr
        (   STRINGA_TextVal, files_gadgets[GID_10_ST1], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(files.logfile, stringptr);
    break;
    case GID_10_ST2:
        if (!(GetAttr
        (   STRINGA_TextVal, files_gadgets[GID_10_ST2], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(files.snapshot, stringptr);
    break;
    case GID_10_ST3:
        if (!(GetAttr
        (   STRINGA_TextVal, files_gadgets[GID_10_ST3], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(files.basepath, stringptr);
    break;
    case GID_10_BU1:
        if (asl("#?.log"))
        {   strcpy(files.logfile, aslresult);
            SetGadgetAttrs
            (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
                STRINGA_TextVal, files.logfile,
                TAG_END
           );
        }
    break;
    case GID_10_BU2:
        updatefiles();
    break;
    case GID_10_BU4:
        if (asl("#?.snapshot"))
        {   strcpy(files.snapshot, aslresult);
            SetGadgetAttrs
            (   files_gadgets[GID_10_ST2], MainWindowPtr, NULL,
                STRINGA_TextVal, files.snapshot,
                TAG_END
            );
        }
    break;
    case GID_10_BU5:
        if (dirasl())
        {   strcpy(files.basepath, aslresult);
            SetGadgetAttrs
            (   files_gadgets[GID_10_ST3], MainWindowPtr, NULL,
                STRINGA_TextVal, files.basepath,
                TAG_END
            );
        }
    break;
    default:
    break;
}   }

AGLOBAL ULONG Hook10Func(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;

    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_E:
        if (files.mode == 0)
        {   files.mode = 1;
        } else
        {   files.mode = 0;
        }
        SetGadgetAttrs
        (   files_gadgets[GID_10_RA1], MainWindowPtr, NULL,
            RADIOBUTTON_Selected, files.mode,
        TAG_END);
        // we must explicitly refresh
        RefreshGadgets((struct Gadget *) files_gadgets[GID_10_RA1], MainWindowPtr, NULL);
        files_updatelog();
    break;
    case SCAN_S:
        ActivateLayoutGadget(files_gadgets[GID_10_LY1], MainWindowPtr, NULL, (Object) files_gadgets[GID_10_ST2]);
    break;
    case SCAN_B:
        ActivateLayoutGadget(files_gadgets[GID_10_LY1], MainWindowPtr, NULL, (Object) files_gadgets[GID_10_ST3]);
    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
        (   files_gadgets[GID_10_LB1],    // 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 stopping(STRPTR status)
{   SetGadgetAttrs
    (   files_gadgets[GID_10_FG1], MainWindowPtr, NULL,
        GA_Text, status,
        TAG_END
    );
    SetGadgetAttrs // `Stop'
    (   files_gadgets[GID_10_BU3], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
}

MODULE void files_updatelog(void)
{   ULONG i;

    updateghosting();

    SetGadgetAttrs // `Log to file:' (string)
    (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
        GA_Disabled, innerghost,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_ST1], MainWindowPtr, NULL);

    SetGadgetAttrs // `Log to file:' (ASL button)
    (   files_gadgets[GID_10_BU1], MainWindowPtr, NULL,
        GA_Disabled, innerghost,
        TAG_END
    );
    // buttons can autorefresh

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB5], MainWindowPtr, NULL,
        GA_Disabled, outerghost,
    TAG_END);
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB5], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB6], MainWindowPtr, NULL,
        GA_Disabled, outerghost,
    TAG_END);
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB6], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB7], MainWindowPtr, NULL,
        GA_Disabled, outerghost,
    TAG_END);
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB7], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB8], MainWindowPtr, NULL,
        GA_Disabled, outerghost,
    TAG_END);
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB8], MainWindowPtr, NULL);

    for (i = 0; i <= 3; i++)
    {   SetGadgetAttrs
        (   files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL,
            GA_Disabled, outerghost,
        TAG_END);
        RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL);
}   }

MODULE void updateghosting(void)
{   /* Decides whether certain gadgets should be ghosted or not, and
    sets variables accordingly. */

    if (files.mode == FILESMODE_COMPARE)
    {   outerghost = FALSE;
        if (files.log)
        {   innerghost = FALSE;
        } else
        {   innerghost = TRUE;
    }   }
    else
    {   assert(files.mode == FILESMODE_BUILD);
        outerghost =
        innerghost = TRUE;
}   }

MODULE void gadgetstate(ABOOL starting)
{   /* If starting is TRUE,  we are starting the operation.
       If starting is FALSE, we are   ending the operation. */

    BOOL  innerstate, outerstate, inverse;
    ULONG i;

    if (starting)
    {   innerstate =
        outerstate = starting;
        inverse    = FALSE;
    } else
    {   innerstate = innerghost;
        outerstate = outerghost;
        inverse    = TRUE;
    }

    // Disabled during operation, enabled otherwise ----------------------

    SetGadgetAttrs // `Update'
    (   files_gadgets[GID_10_BU2], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    // buttons can autorefresh

    SetGadgetAttrs
    (   files_gadgets[GID_10_ST2], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_ST2], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_ST3], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_ST3], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_BU4], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    // buttons can autorefresh

    SetGadgetAttrs
    (   files_gadgets[GID_10_BU5], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    // buttons can autorefresh

    /* This should work but seems not to :-(
    SetGadgetAttrs
    (   files_gadgets[GID_10_RA1], MainWindowPtr, NULL,
        GA_Disabled, starting,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_RA1], MainWindowPtr, NULL); */

    // Enabled during operation, disabled otherwise ----------------------

    SetGadgetAttrs // `Stop'
    (   files_gadgets[GID_10_BU3], MainWindowPtr, NULL,
        GA_Disabled, inverse,
        TAG_END
    );
    // buttons can autorefresh

    // Disabled during operation and/or in build-mode --------------------

    for (i = 0; i <= 3; i++)
    {   SetGadgetAttrs
        (   files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL,
            GA_Disabled, outerstate,
            TAG_END
        );
        RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL);
    }

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB5], MainWindowPtr, NULL,
        GA_Disabled, outerstate,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB5], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB6], MainWindowPtr, NULL,
        GA_Disabled, outerstate,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB6], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB7], MainWindowPtr, NULL,
        GA_Disabled, outerstate,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB7], MainWindowPtr, NULL);

    SetGadgetAttrs
    (   files_gadgets[GID_10_CB8], MainWindowPtr, NULL,
        GA_Disabled, outerstate,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB8], MainWindowPtr, NULL);

    // Disabled during operation and/or in build-mode and/or without
    // logging -----------------------------------------------------------

    SetGadgetAttrs // `Log to file:' (string)
    (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
        GA_Disabled, innerstate,
        TAG_END
    );
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_ST1], MainWindowPtr, NULL);

    SetGadgetAttrs // `Log to file:' (ASL button)
    (   files_gadgets[GID_10_BU1], MainWindowPtr, NULL,
        GA_Disabled, innerstate,
        TAG_END
    );
    // buttons can autorefresh
}

MODULE void AddFilesNode(struct List* ListPtr, STRPTR pathname, STRPTR datetime, STRPTR version, STRPTR size)
{   struct FilesNode* FilesNodePtr;

    if (!(FilesNodePtr = AllocMem(sizeof(struct FilesNode), MEMF_CLEAR)))
        rq("Out of memory!");
    strcpy(FilesNodePtr->pathname, pathname);
    strcpy(FilesNodePtr->datetime, datetime);
    strcpy(FilesNodePtr->version,  version );
    strcpy(FilesNodePtr->size,     size    );
    FilesNodePtr->matched = FALSE;
    FilesNodePtr->Node.ln_Name = NULL;
    FilesNodePtr->Node.ln_Type = NT_USER;
    FilesNodePtr->Node.ln_Pri  = 0;
    AddTail((struct List *) ListPtr, (struct Node *) FilesNodePtr);
}

MODULE void FreeFilesNodes(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 FilesNode *WorkNodePtr, *NextNodePtr;
	
    WorkNodePtr = (struct FilesNode *) (ListPtr->lh_Head); /* first node */
    while (NextNodePtr = (struct FilesNode *) (WorkNodePtr->Node.ln_Succ))
    {   FreeMem(WorkNodePtr, sizeof(struct FilesNode));
        WorkNodePtr = NextNodePtr;
}   }

