#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 LIMIT_DIRS        8196
#define SIZELISTVIEWCHARS 37
#define TRUNCATE          40
#define LVTRUNCATE        25

#define GID_8_LB1 0
#define GIDS_8    GID_8_LB1

/* Limitations:
   LIMIT_DIRS (top-level list entries).
   MAX_FILES (total files/dirs in path). */

MODULE void pop(void);
MODULE void push(STRPTR name, ULONG theindex);
MODULE void subdir(ULONG theindex);
MODULE void parent(void);
MODULE void root(void);
MODULE void pathasl(void);
MODULE void pathstring(void);
MODULE void comma(ULONG value);
MODULE ABOOL dirasl(void);
MODULE void showduplicates(void);
MODULE void addduplicate(STRPTR path, STRPTR filename);
MODULE void flipfind(ABOOL keyboard);
MODULE void writeline(void);

MODULE struct ColumnInfo TheColumnInfo[] =
{ { 50,            // WORD   ci_Width
    "Pathname",    // STRPTR ci_Title
    0,             // ULONG  ci_Flags
  },
  { 10,
    "Size",
    0,
  },
  { 10,
    "Date",
    0,
  },
  { 30,
    "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
} };

IMPORT struct Library*     ListBrowserBase;
IMPORT ABOOL               fillwindows;
IMPORT TEXT                asldir[VLONGFIELD + 1],
                           aslresult[MEDFIELD + 1];
MODULE TEXT                logstring[256 + 1],
                           liststring[4][256 + 1];
MODULE UBYTE               valuestring[11],
                           commastring[14];
MODULE struct Gadget*      gadgets[GIDS_8 + 1];

IMPORT  SBYTE              page;
MODULE  ULONG              DirNamesAllocated  = 0,
                           FileNamesAllocated = 0;
IMPORT  TEXT               globalname[VLONGFIELD + 1];
IMPORT  TEXT               weekdaystring[10],
                           datestring[10],
                           timestring[9];
IMPORT struct SharedStruct shared;
IMPORT struct NewGadget    Gadget;
IMPORT struct Screen*      ScreenPtr;
IMPORT struct Window*      MainWindowPtr;
IMPORT struct Gadget      *BU99_Left,
                          *BU99_Right,
                          *ST99_Output,
                          *BU99_OutputASL,
                          *CB99_Log,
                          *BU99_Update,
                          *BU99_Stop,
                          *GListPtr,
                          *PrevGadPtr;
IMPORT struct ExAllData*   EADataPtr;
IMPORT TEXT                IOBuffer[LONGESTFIELD + 1];

#define MAX_FILES (128 * 1024)

MODULE ABOOL               DupNodes           = FALSE,
                           EmptyColumnarNodes = FALSE,
                           SizeNodes          = FALSE;
MODULE struct List         SizeList,
                           DupList,
                           EmptyColumnarList;
MODULE struct Gadget      *LV81_Listview = NULL,
                          *CB81_Find     = NULL,
                          *ST81_Path     = NULL,
                          *BU81_PathASL  = NULL,
                          *BU81_Root     = NULL,
                          *BU81_Parent   = NULL,
                          *TE81_Total    = NULL,
                          *TE81_Free     = NULL,
                          *TE81_Status   = NULL;
MODULE ULONG              files          = 0;
// how many files have been found. Counting from 1.
MODULE STRPTR*            PathnamePtr;
MODULE STRPTR*            FilenamePtr;
MODULE STRPTR*            StackPtr;
MODULE STRPTR*            DirNamePtr;
MODULE ABOOL*             IsDup;
MODULE UBYTE              pathlength;
MODULE BPTR               DirHandle      = NULL,
                          LogFileHandle  = NULL;

MODULE struct
{   ULONG entries, totalbytesval, freeval, stackentries;
    TEXT  totalbytesstring[11], freestring[11],
          oldpath[VLONGFIELD + 1], path[VLONGFIELD + 1];
    ULONG bytes[LIMIT_DIRS], topdir[LIMIT_DIRS];
    LONG  type[LIMIT_DIRS];
    ABOOL finddup;
} size;
MODULE ABOOL stop;

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

    strcpy(size.path, "RAM:");
    size.finddup = FALSE;

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

AGLOBAL void size1(void)
{   verynewwindow
    (   SIZE1WIDTH, SIZE1HEIGHT,
        "Report+: Path Size Report",
        BUTTONIDCMP | STRINGIDCMP | LISTVIEWIDCMP
    );
    if (fillwindows)
    {   SetAPen(MainWindowPtr->RPort, 0);
        RectFill(MainWindowPtr->RPort,  12,  44,  12 + 348,  44 + 126 - 3);     // LV81_Listview
        RectFill(MainWindowPtr->RPort, 228, 168, 228 + 116, 168 + 12);          // TE81_Total
        RectFill(MainWindowPtr->RPort, 228, 180, 228 + 116, 180 + 12);          // TE81_Free
        RectFill(MainWindowPtr->RPort, 562,  20, 562 +  68,  20 + 12);          // TE81_Status
    }

    /* We can't just use EmptyList, we actually need the LBNA_Column
    tags such as in EmptyColumnarList.

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

    if (!(gadgets[GID_8_LB1] = (struct Gadget *) NewObject
    (   LISTBROWSER_GetClass(),     NULL,
        GA_ID,                      GID_8_LB1,
        GA_Left,                    10,
        GA_Top,                     226,
        GA_Width,                   620,
        GA_Height,                  262,
        GA_ReadOnly,                TRUE,
        GA_TextAttr,                Gadget.ng_TextAttr,
        GA_Disabled,                !size.finddup,
        LISTBROWSER_Labels,         (ULONG) &EmptyColumnarList,
        LISTBROWSER_ColumnInfo,     (ULONG) &TheColumnInfo,
        LISTBROWSER_ColumnTitles,   TRUE,
        LISTBROWSER_HorizontalProp, TRUE,
        LISTBROWSER_VirtualWidth,   620 + 155,
        TAG_END
    )))
    {   rq("Can't create ReAction gadgets!");
    }
    AddGList(MainWindowPtr, gadgets[GID_8_LB1], -1, -1, NULL);
    RefreshGList(gadgets[GID_8_LB1], MainWindowPtr, NULL, -1);

    /* path */
    setgadget(60, 20, 268, 12, "_Path:", NULL);
    ST81_Path = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(size.path),
        GTST_MaxChars, VLONGFIELD,
        GA_TabCycle, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* path... */
    setgadget(330, 20, 28, 12, "_...", NULL);
    BU81_PathASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    // root
    setgadget(10, 32, 174, 12, "_Root", NULL);
    BU81_Root = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    // parent
    setgadget(184, 32, 174, 12, "P_arent", NULL);
    BU81_Parent = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    setgadget(10, 44, 348, 126, NULL, NULL);
    LV81_Listview = PrevGadPtr = (struct Gadget *) CreateGadget
    (   LISTVIEW_KIND,
        PrevGadPtr,
        &Gadget,
        GTLV_Labels, NULL,
        TAG_DONE
    );

    /* total bytes */
    setgadget(226, 168, 116, 12, "Path bytes:", NULL);
    TE81_Total = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, size.totalbytesstring,
        GTTX_Border, TRUE,
        TAG_DONE
    );
    /* free bytes */
    setgadget(226, 180, 116, 12, "Free bytes:", NULL);
    TE81_Free = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, size.freestring,
        GTTX_Border, TRUE,
        TAG_DONE
    );

    // log to file?
    setgadget(368, 45, 0, 0, "_Log to file?", PLACETEXT_RIGHT);
    CB99_Log = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GTCB_Checked, shared.log,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* output */
    if (!shared.output[0])
        strcpy(shared.output, "RAM:Report.txt");
    setgadget(368, 72, 234, 12, "_Output pathname:", PLACETEXT_ABOVE);
    ST99_Output = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(shared.output),
        GTST_MaxChars, LONGFIELD,
        GA_TabCycle, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* output... */
    setgadget(602, 72, 28, 12, "...", NULL);
    BU99_OutputASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        TAG_DONE
    );

    // find duplicates?
    setgadget( 10, 212, 0, 0, "Find _duplicates?", PLACETEXT_RIGHT);
    CB81_Find = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GTCB_Checked, size.finddup,
        GT_Underscore, '_',
        TAG_DONE
    );

    /* update */
    setgadget(368, 20, 60, 12, "_Update", NULL);
    BU99_Update = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* stop */
    setgadget(428, 20, 60, 12, "Stop", NULL);
    BU99_Stop = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        TAG_DONE
    );

    // status
    setgadget(558, 20, 72, 12, "Status:", NULL);

    TE81_Status = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "Ready",
        GTTX_Border, TRUE,
        TAG_DONE
    );

    drawgadgets((UWORD) ~0);

    if (!shared.log)
    {   GT_SetGadgetAttrs
        (   ST99_Output,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_OutputASL,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }
    updatesize();
    ActivateGadget(ST81_Path, MainWindowPtr, NULL);
    loop();
    strcpy
    (   shared.output,
        ((struct StringInfo *) ST99_Output->SpecialInfo)->Buffer
    );
    closewindow();
    size_exit();
}

AGLOBAL void updatesize(void)
{   BOOL                  more; // note that it is BOOL, not ABOOL
    TEXT                  tempstring1[16], tempstring2[SIZELISTVIEWCHARS + 1],
                          tempstring3[VLONGFIELD + 1], tempname[LONGFIELD + 1];
    ULONG                 i, j, tempbytes;
    LONG                  templength, temptype; // these both MUST be signed
    ABOOL                 failed      = FALSE;
    struct FileInfoBlock* FIBPtr      = NULL;
    struct InfoData*      InfoDataPtr = NULL;
    struct ExAllControl*  eac         = NULL;
    struct ExAllData*     ead;

    if (SizeNodes)
    {   clearlist(&SizeList);
        SizeNodes = FALSE;
    }
    if (!DirNamePtr)
    {   if (!(DirNamePtr = AllocVec(LIMIT_DIRS * sizeof(STRPTR), NULL)))
        {   rq("Out of memory!");
    }   }

    // 1: (Un)ghost relevant gadgets, for duration of the operation.

    stop = FALSE;
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   TE81_Status,
        MainWindowPtr, NULL,
        GTTX_Text, "Reading",
        TAG_DONE
    );

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

    size.stackentries = size.entries = 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);
        GT_SetGadgetAttrs
        (   BU99_Right,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_Update,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_Stop,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        strcpy(size.path, size.oldpath);
        GT_SetGadgetAttrs
        (   ST81_Path,
            MainWindowPtr, NULL,
            GTST_String, size.path,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   TE81_Status,
            MainWindowPtr, NULL,
            GTTX_Text, "Failed",
            TAG_DONE
        );
        return;
    }

    GT_SetGadgetAttrs
    (   ST81_Path,
        MainWindowPtr, NULL,
        GTST_String, size.path,
        TAG_DONE
    );

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

    if (!(InfoDataPtr = AllocVec(sizeof(struct InfoData), MEMF_CLEAR)))
    {   rq("Out of memory!");
    }
    if (Info(DirHandle, InfoDataPtr))
    {   size.freeval =
        (InfoDataPtr->id_NumBlocks - InfoDataPtr->id_NumBlocksUsed) *
         InfoDataPtr->id_BytesPerBlock;
        stcl_d(tempstring1, size.freeval);
        templength = 10 - strlen(tempstring1);
        strcpy(size.freestring, "");
        for (i = 1; i <= templength; i++)
            strcat(size.freestring, " ");
        strcat(size.freestring, tempstring1);
    } else strcpy(size.freestring, "         ?");
    FreeVec(InfoDataPtr);
    InfoDataPtr = NULL;

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

    size.topdir[]     = the 'owning' top-level directory for this subdir.
                        Top-level dirs are owned by themselves.
    size.bytes[]      = the size of this top-level directory/file.
    size.entries      = how many top-level dirs, ie. how many names in the
                        listview gadget.
    size.path         = the path (drive, partition, etc.) the user is doing
                        the report on.
    size.stackentries = the current size of the stack.
    DirNamesAllocated = the number of allocated chunks for the
                        topdir names. */

    if (!(        StackPtr = AllocVec(LIMIT_DIRS * sizeof(STRPTR), NULL)))
    {   rq("Out of memory!");
    }

    files = 0;
    if (size.finddup)
    {   if (!( PathnamePtr = AllocVec(MAX_FILES * sizeof(STRPTR), NULL)))
        {   rq("Out of memory!");
        }
        if (!( FilenamePtr = AllocVec(MAX_FILES * sizeof(STRPTR), NULL)))
        {   rq("Out of memory!");
        }
        if (!(       IsDup = AllocVec(MAX_FILES * sizeof(ABOOL) , NULL)))
        {   rq("Out of memory!");
    }   }

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

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

        // OK, ExAll() has generated up to 2K of data.
        ead = (struct ExAllData *) EADataPtr;
        do
        {   /* use ead here */

            if (ead->ed_Type == -3 || ead->ed_Type == 2) // if a normal file or normal directory (ie. not a link)
            {   size.entries++;
                if (size.entries == LIMIT_DIRS)
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    rq("Overflow!");
                }
                size.type[size.entries] = ead->ed_Type;
                if (size.entries > DirNamesAllocated)
                {   if (!(DirNamePtr[DirNamesAllocated + 1] = AllocMem(VLONGFIELD, MEMF_PUBLIC)))
                    {   FreeDosObject(DOS_EXALLCONTROL, eac);
                        eac = NULL;
                        rq("Out of memory!");
                    }
                    DirNamesAllocated++;
                }
                strcpy(DirNamePtr[size.entries], ead->ed_Name);
                if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
                {   size.bytes[size.entries] = 0;
                    strcpy(tempstring3, size.path);
                    if (!AddPart(tempstring3, ead->ed_Name, VLONGFIELD))
                    {   FreeDosObject(DOS_EXALLCONTROL, eac);
                        eac = NULL;
                        rq("Can't add filename to pathname!");
                    }
                    push(tempstring3, size.entries);
                } else
                {   addduplicate("", ead->ed_Name);
                    size.bytes[size.entries] = ead->ed_Size;
            }   }

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

    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
    pop();

    // Now handle the list.

    GT_SetGadgetAttrs
    (   LV81_Listview,
        MainWindowPtr,
        NULL,
        GTLV_Labels, (~0),
        TAG_DONE
    );

    if (!stop)
    {   /* size.entries is the actual number of entries. The array elements are
        from 1 to size.entries.

        bubble sort routine */
        GT_SetGadgetAttrs
        (   TE81_Status,
            MainWindowPtr, NULL,
            GTTX_Text, "Sorting",
            TAG_DONE
        );
        for (i = size.entries; i >= 2; i--)
        {   for (j = 2; j <= i; j++) 
            {   if (size.bytes[j - 1] < size.bytes[j]) 
                {   // swap them
                    tempbytes = size.bytes[j - 1];
                    size.bytes[j - 1] = size.bytes[j]; 
                    size.bytes[j] = tempbytes;
                    temptype = size.type[j - 1];
                    size.type[j - 1] = size.type[j];
                    size.type[j] = temptype;
                    strcpy(tempname, DirNamePtr[j - 1]);
                    strcpy(DirNamePtr[j - 1], DirNamePtr[j]);
                    strcpy(DirNamePtr[j], tempname);
        }   }   }

        getdate();
        if (shared.log)
        {   tempstring1[0] = QUOTE;
            tempstring1[1] = 0;
    
            strcpy(logstring, "Path: ");
            strcat(logstring, tempstring1);
            strcat(logstring, size.path);
            strcat(logstring, tempstring1);
            strcat(logstring, " at ");
            strcat(logstring, timestring);
            strcat(logstring, " on ");
            strcat(logstring, weekdaystring);
            strcat(logstring, " ");
            strcat(logstring, datestring);
            strcat(logstring, ":\n");
            if (!(LogFileHandle = (BPTR) Open(shared.output, MODE_READWRITE)))
                rq("Can't open file for appending!");
            Seek(LogFileHandle, 0, OFFSET_END);
            writeline();
        }

        // very long dirnames (truncated ones) are not selectable, unfortunately.
        size.totalbytesval = 0;
        for (i = 1; i <= size.entries; i++)
        {   comma(size.bytes[i]);
    
            if (size.type[i] == 2)
            {   strcpy(tempstring2, "*");
                strcpy(tempstring3, "*");
            } else
            {   strcpy(tempstring2, " ");
                strcpy(tempstring3, " ");
            }

            if (strlen(DirNamePtr[i]) > TRUNCATE)
                *(DirNamePtr[i] + TRUNCATE) = 0; // truncate
            strcat(tempstring3, DirNamePtr[i]);
            templength = TRUNCATE - strlen(DirNamePtr[i]);
            if (templength >= 1)
                for (j = 1; j <= templength; j++)
                    strcat(tempstring3, " ");

            if (strlen(DirNamePtr[i]) > LVTRUNCATE)
                *(DirNamePtr[i] + LVTRUNCATE) = 0; // truncate
            strcat(tempstring2, DirNamePtr[i]); /* tempstring2 = "*<name>" */
            templength = LVTRUNCATE + 1 - strlen(DirNamePtr[i]);
            if (templength >= 1)
                for (j = 1; j <= templength; j++)
                    strcat(tempstring2, " "); /* tempstring2 = "*<name>   " */

            strcat(tempstring2, commastring);
            strcat(tempstring3, commastring);

            AddNameToTail(&SizeList, tempstring2);
            SizeNodes = TRUE;
            if (shared.log)
            {   strcpy(logstring, tempstring3);
                strcat(logstring, "\n");
                writeline();
            }
            size.totalbytesval += size.bytes[i];
        }

        comma(size.totalbytesval);
        GT_SetGadgetAttrs
        (   TE81_Total,
            MainWindowPtr, NULL,
            GTTX_Text, commastring,
            TAG_DONE
        );
        comma(size.freeval);
        GT_SetGadgetAttrs
        (   TE81_Free,
            MainWindowPtr,
            NULL,
            GTTX_Text, commastring,
            TAG_DONE
        );
        if (shared.log)
        {   strcpy(logstring, "\nPath bytes:                              ");
            comma(size.totalbytesval);
            strcat(logstring, commastring);
            strcat(logstring, "\nFree bytes:                              "); // these must be adjusted according to TRUNCATE
            comma(size.freeval);
            strcat(logstring, commastring);
            strcat(logstring, "\n\n");
            writeline();
        }
        GT_SetGadgetAttrs
        (   LV81_Listview,
            MainWindowPtr,
            NULL,
            GTLV_Labels, &SizeList,
            TAG_DONE
        );
        showduplicates();
    } else
    {   GT_SetGadgetAttrs
        (   TE81_Total,
            MainWindowPtr, NULL,
            GTTX_Text, "",
            TAG_DONE
        );
        comma(size.freeval);
        GT_SetGadgetAttrs
        (   TE81_Free,
            MainWindowPtr,
            NULL,
            GTTX_Text, "",
            TAG_DONE
        );
        SetGadgetAttrs
        (   gadgets[GID_8_LB1], MainWindowPtr, NULL,
            LISTBROWSER_Labels, NULL,
            TAG_END
        );
        if (DupNodes)
        {   clearreactionlist(&DupList);
            DupNodes = FALSE;
        }
        GT_SetGadgetAttrs
        (   LV81_Listview,
            MainWindowPtr,
            NULL,
            GTLV_Labels, &SizeList,
            TAG_DONE
        );
    }

    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   TE81_Status,
        MainWindowPtr, NULL,
        GTTX_Text, "Ready",
        TAG_DONE
    );

    if (LogFileHandle)
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
    }
    while (FileNamesAllocated)
    {   FreeMem(StackPtr[FileNamesAllocated], VLONGFIELD);
        StackPtr[FileNamesAllocated--] = NULL;
    }
    FreeVec(StackPtr);
    StackPtr = NULL;

    // We need the DirNamePtr[] array to remain valid, because
    // the user can click on the listview gadget.
}

MODULE void subdir(ULONG topdir)
{   BOOL                 more;
    TEXT                 tempstring[VLONGFIELD + 1];
    struct ExAllControl* eac       = NULL;
    struct ExAllData*    ead;

    /* Instead of passing a pointer to the path, we use a global string.
    There are memory corruption problems if you pass pointers to variables
    which are really local to other functions.

    This is dynamically allocated as it must be at least word-aligned;
    also remember not to allocate large arrays on the stack. */

    if (!(DirHandle = (BPTR) Lock(globalname, ACCESS_READ)))
    {   DisplayBeep(ScreenPtr);
        return;
    }
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Can't allocate DOS object!");
    }

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

        do
        {   /* use ead here */

            if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
            {   strcpy(tempstring, globalname);
                if (!AddPart(tempstring, ead->ed_Name, VLONGFIELD))
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Can't add filename to pathname!");
                }
                push(tempstring, topdir);
            } elif (ead->ed_Type == -3) // if it's a file
            {   addduplicate(&globalname[pathlength], ead->ed_Name);
                size.bytes[topdir] += ead->ed_Size;
            }

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

    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
    // don't call pop() yourself!
}

MODULE void pop(void)
{   ULONG breakval;

    pathlength = strlen(size.path);
    while (size.stackentries && !stop)
    {   size.stackentries--;
        strcpy(globalname, StackPtr[size.stackentries + 1]);
        subdir(size.topdir[size.stackentries + 1]);
        breakval = checkbreak();
        if (breakval == 2)
            cleanexit(EXIT_SUCCESS);
        elif (breakval == 1)
            stop = TRUE;
}   }
MODULE void push(STRPTR name, ULONG theindex)
{   size.stackentries++;
    if (size.stackentries == LIMIT_DIRS)
        rq("Overflow!");
    if (size.stackentries > FileNamesAllocated)
    {   if (!(StackPtr[FileNamesAllocated + 1] = AllocMem(VLONGFIELD, MEMF_PUBLIC)))
            rq("Out of memory!");
        FileNamesAllocated++;
    }
    strcpy(StackPtr[size.stackentries], name);
    size.topdir[size.stackentries] = theindex;
}

AGLOBAL void size_loop(ULONG class, struct Gadget* addr, UWORD code, UWORD qual)
{   UWORD moveto;

    if (class == IDCMP_RAWKEY)
    {   if (qual & IEQUALIFIER_CONTROL)
        {   if (code == SCAN_UP)
            {   GT_SetGadgetAttrs
                (   LV81_Listview,
                    MainWindowPtr,
                    NULL,
                    GTLV_Top, 0,
                    TAG_DONE
                );
            } elif (code == SCAN_DOWN)
            {   if (size.entries > 15)          // this number must change
                {   moveto = size.entries - 15; // if the height of the
                } else moveto = 0;              // listview changes
                GT_SetGadgetAttrs
                (   LV81_Listview,
                    MainWindowPtr,
                    NULL,
                    GTLV_Top, moveto,
                    TAG_DONE
                );
    }   }   }
    elif (class == IDCMP_VANILLAKEY)
    {   code = toupper(code);
        if (code == ESCAPE)
        {   page = 0;
        } elif (code == 'A')
            parent();
        elif (code == 'R')
            root();
        elif (code == '.')
            pathasl();
        elif (code == 'P')
        {   ActivateGadget(ST81_Path, MainWindowPtr, NULL);
        } elif (code == 'L')
            fliplog(TRUE);
        elif (code == 'O')
        {   ActivateGadget(ST99_Output, MainWindowPtr, NULL);
        } elif (code == 'U')
            updatesize();
        elif (code == 'D')
        {   flipfind(TRUE);
    }   }
    elif (class == IDCMP_GADGETUP)
    {   /* IDCMP_GADGETUP is sent by the string gadget
        when the user presses RETURN, ENTER, Help, Tab
        or Shift-Tab inside the string gadget. */

        if (addr == BU99_Right)
        {   page = 0;
        } elif (addr == BU99_Update)
        {   updatesize();
        } elif (addr == BU81_PathASL)
        {   pathasl();
        } elif (addr == ST81_Path)
        {   pathstring();
        } elif (addr == CB99_Log)
        {   fliplog(FALSE);
        } elif (addr == ST99_Output)
        {   outputstring();
        } elif (addr == BU99_OutputASL)
        {   outputasl();
        } elif (addr == BU81_Root)
        {   root();
        } elif (addr == BU81_Parent)
        {   parent();
        } elif (addr == CB81_Find)
        {   flipfind(FALSE);
        } elif (addr == LV81_Listview)
        {   /* code is the position within the list, starting from 0. */

            if (code + 1 <= size.entries && size.type[code + 1] == 2)
            {   if (!(AddPart(size.path, DirNamePtr[code + 1], VLONGFIELD + 1)))
                    rq("Can't add filename to pathname!");
                else updatesize();
}   }   }   }

AGLOBAL void size_exit(void)
{   if (DirHandle)
    {   UnLock(DirHandle);
        DirHandle = NULL;
    }
    if (LogFileHandle)
    {   Close(LogFileHandle);
        LogFileHandle = NULL;
    }

    // Only call this if the list gadget is detached or not present.
    // (ie. after closewindow()).
    if (SizeNodes)
    {   clearlist(&SizeList);
        SizeNodes = FALSE;
    }
    if (DupNodes)
    {   clearreactionlist(&DupList);
        DupNodes = FALSE;
    }
    while (FileNamesAllocated)
    {   FreeMem(StackPtr[FileNamesAllocated], VLONGFIELD);
        StackPtr[FileNamesAllocated--] = NULL;
    }
    if (StackPtr)
    {   FreeVec(StackPtr);
        StackPtr = NULL;
    }
    while ( DirNamesAllocated)
    {   FreeMem(DirNamePtr[DirNamesAllocated], VLONGFIELD);
        DirNamePtr[DirNamesAllocated--] = NULL;
    }
    if (DirNamePtr)
    {   FreeVec(DirNamePtr);
        DirNamePtr = NULL;
}   }

AGLOBAL void size_die(void)
{   if (EmptyColumnarNodes)
    {   clearreactionlist(&EmptyColumnarList);
        EmptyColumnarNodes = FALSE;
    }
    IOBuffer[6] = size.finddup;
}

AGLOBAL void size_config(void)
{   size.finddup = IOBuffer[6];
}

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

    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, VLONGFIELD))
        rq("Can't get name from lock!");
    UnLock(DirHandle);
    DirHandle = NULL;

    updatesize();
}

MODULE void root(void)
{   BPTR  DirHandle    = NULL,
          ParentHandle = NULL;
    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, VLONGFIELD))
        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);
        GT_SetGadgetAttrs
        (   ST81_Path,
            MainWindowPtr, NULL,
            GTST_String, size.path,
            TAG_DONE
        );
        updatesize();
}   }

MODULE void pathstring(void)
{   strcpy
    (   size.path,
        ((struct StringInfo *) ST81_Path->SpecialInfo)->Buffer
    );
    updatesize();
}

MODULE void comma(ULONG value)
{   ABOOL yeah = FALSE;

    strcpy(commastring, " ,   ,   ,   ");
    //                    1   5   9

    valuestring[ 0] = '0' + (value / 1000000000);
    value %= 1000000000;
    if (valuestring[ 0] != '0')
    {   commastring[0] = valuestring[0];
        yeah = TRUE;
    } else
    {   commastring[ 0] = ' ';
        commastring[ 1] = ' ';
    }

    valuestring[ 1] = '0' + (value /  100000000);
    value %=  100000000;
    if (yeah || valuestring[1] != '0')
    {   commastring[2] = valuestring[1];
        yeah = TRUE;
    } else commastring[2] = ' ';

    valuestring[ 2] = '0' + (value /   10000000);
    value %=   10000000;
    if (yeah || valuestring[2] != '0')
    {   commastring[3] = valuestring[2];
        yeah = TRUE;
    } else commastring[3] = ' ';

    valuestring[ 3] = '0' + (value /    1000000);
    value %=    1000000;
    if (yeah || valuestring[3] != '0')
    {   commastring[4] = valuestring[3];
        yeah = TRUE;
    } else
    {   commastring[4] = ' ';
        commastring[5] = ' ';
    }

    valuestring[ 4] = '0' + (value /     100000);
    value %=     100000;
    if (yeah || valuestring[4] != '0')
    {   commastring[6] = valuestring[4];
        yeah = TRUE;
    } else commastring[6] = ' ';

    valuestring[ 5] = '0' + (value /      10000);
    value %=      10000;
    if (yeah || valuestring[5] != '0')
    {   commastring[7] = valuestring[5];
        yeah = TRUE;
    } else commastring[7] = ' ';

    valuestring[ 6] = '0' + (value /       1000);
    value %=       1000;
    if (yeah || valuestring[6] != '0')
    {   commastring[8] = valuestring[6];
        yeah = TRUE;
    } else
    {   commastring[8] = ' ';
        commastring[9] = ' ';
    }

    valuestring[ 7] = '0' + (value /        100);
    value %=        100;
    if (yeah || valuestring[7] != '0')
    {   commastring[10] = valuestring[7];
        yeah = TRUE;
    } else commastring[10] = ' ';

    valuestring[ 8] = '0' + (value /         10);
    value %=         10;
    if (yeah || valuestring[8] != '0')
    {   commastring[11] = valuestring[8];
        yeah = TRUE;
    } else commastring[11] = ' ';

    valuestring[ 9] = '0' +  value              ;
    commastring[12] = valuestring[9];
}

MODULE ABOOL dirasl(void)
{   struct FileRequester* ASLRqPtr;
    ABOOL                 success;

    if (!(ASLRqPtr = AllocAslRequestTags(ASL_FileRequest, ASL_Pattern, "~(#?.info)", ASL_Window, MainWindowPtr, ASL_ExtFlags1, FIL1F_NOFILES, TAG_DONE)))
    {   rq("Can't create ASL request!");
    }
    if (AslRequestTags(ASLRqPtr, ASL_Dir, asldir, ASL_Hail, "Report+ path selector", ASL_FuncFlags, FILF_PATGAD, TAG_DONE))
    {   strcpy(aslresult, ASLRqPtr->rf_Dir);
        success = TRUE;
    } else success = FALSE;
    FreeAslRequest(ASLRqPtr);
    return(success);
}

MODULE void showduplicates(void)
{   ULONG                 breakval, i, j, k, where;
    ABOOL                 found = FALSE,
                          started = FALSE,
                          success;
    struct DateTime       DateTime;
    LONG                  templength;
    struct FileInfoBlock* FIBPtr = NULL;
    TEXT                  pathnamestring[VLONGFIELD + 1],
                          commandstring[VLONGFIELD + 1],
                          lastfilename[VLONGFIELD + 1];
    struct Node*          ListBrowserNodePtr = NULL;
    STRPTR                tempPathnamePtr, tempFilenamePtr;
    ULONG                 firstpen, secondpen, currentpen, dups;

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

    if (size.finddup)
    {   SetGadgetAttrs
        (   gadgets[GID_8_LB1], MainWindowPtr, NULL,
            LISTBROWSER_Labels, NULL,
            TAG_END
        );
        if (DupNodes)
        {   clearreactionlist(&DupList);
            DupNodes = FALSE;
        }
        lockscreen();
        firstpen = FindColor
        (   ScreenPtr->ViewPort.ColorMap,
            0xDDDDDDDD,
            0xDDDDDDDD,
            0xDDDDDDDD,
            -1
        );
        secondpen = FindColor
        (   ScreenPtr->ViewPort.ColorMap,
            0xAAAAAAAA,
            0xAAAAAAAA,
            0xAAAAAAAA,
            -1
        );
        unlockscreen();
        currentpen = firstpen;

        if (shared.log)
        {   strcpy(logstring, "Duplicates found:\n");
            writeline();
        }

        /* Now we iterate through the complete file list (last entry index
        of files). For each file (i), we look at every other file (j). As
        soon as we find a matching file, we add the file (i) to the
        duplicate file list. */

        GT_SetGadgetAttrs
        (   TE81_Status,
            MainWindowPtr, NULL,
            GTTX_Text, "Matching",
            TAG_DONE
        );
        for (i = 1; i <= files; i++)
        {   breakval = checkbreak();
            if (breakval == 2)
            {   cleanexit(EXIT_SUCCESS);
            } elif (breakval == 1)
            {   stop = TRUE;
                GT_SetGadgetAttrs
                (   TE81_Status,
                    MainWindowPtr, NULL,
                    GTTX_Text, "Stopping",
                    TAG_DONE
                );
                break;
            }
            // if the user doesn't want us to stop...
            for (j = 1; j <= files; j++)
            {   if (i != j && !stricmp(FilenamePtr[i], FilenamePtr[j]))
                {   found = TRUE; // we found (at least one) match
                    IsDup[i] = TRUE;
                    break;
        }   }   }
        if (!stop)
        {   GT_SetGadgetAttrs
            (   TE81_Status,
                MainWindowPtr, NULL,
                GTTX_Text, "Condense",
                TAG_DONE
            );

            // condense list
            dups = 0;
            for (i = 1; i <= files; i++)
            {   if (IsDup[i])
                {   dups++;
                    FilenamePtr[dups] = FilenamePtr[i];
                    PathnamePtr[dups] = PathnamePtr[i];
                    IsDup[dups]       = IsDup[i];
                } else
                {   FreeVec(PathnamePtr[i]);
                    FreeVec(FilenamePtr[i]);
            }   }
            GT_SetGadgetAttrs
            (   TE81_Status,
                MainWindowPtr, NULL,
                GTTX_Text, "Sorting",
                TAG_DONE
            );
            // bubble sort routine
            for (i = dups; i >= 2; i--)
            {   for (j = 2; j <= i; j++) 
                {   if (stricmp(FilenamePtr[j - 1], FilenamePtr[j]) > 0)
                    {   // swap them
                        tempPathnamePtr     = PathnamePtr[j - 1];
                        PathnamePtr[j - 1]  = PathnamePtr[j];
                        PathnamePtr[j]      = tempPathnamePtr;
                        tempFilenamePtr     = FilenamePtr[j - 1];
                        FilenamePtr[j - 1]  = FilenamePtr[j];
                        FilenamePtr[j]      = tempFilenamePtr;
            }   }   }

            GT_SetGadgetAttrs
            (   TE81_Status,
                MainWindowPtr, NULL,
                GTTX_Text, "Examine",
                TAG_DONE
            );
            for (i = 1; i <= dups; i++)
            {   breakval = checkbreak();
                if (breakval == 2)
                {   cleanexit(EXIT_SUCCESS);
                } elif (breakval == 1)
                {   stop = TRUE;
                    GT_SetGadgetAttrs
                    (   TE81_Status,
                        MainWindowPtr, NULL,
                        GTTX_Text, "Stopping",
                        TAG_DONE
                    );
                    break;
                }

                // begin creating the line of text
                if (PathnamePtr[i][0] == '/')
                {   strcpy(liststring[0], &(PathnamePtr[i][1]));
                } else
                {   strcpy(liststring[0], PathnamePtr[i]);
                }
                strcpy(logstring, " ");
                strcat(logstring, liststring[0]);
                if (strlen(logstring) > TRUNCATE)
                {   *(logstring + TRUNCATE) = 0; // truncate
                }
                templength = TRUNCATE + 1 - strlen(logstring);
                if (templength >= 1)
                {   for (k = 1; k <= templength; k++)
                    {   strcat(logstring, " ");
                }   }

                strcpy(pathnamestring, size.path);
                if (PathnamePtr[i][0] != '/')
                {   if (!AddPart(pathnamestring, PathnamePtr[i], 256))
                    {   rq("AddPart() failed!");
                }   }
                else
                {   if (!AddPart(pathnamestring, &PathnamePtr[i][1], 256))
                    {   rq("AddPart() failed!");
                }   }
                success = FALSE;

                // now get size and date of file
                if (DirHandle  = (BPTR) Lock(pathnamestring, ACCESS_READ))
                {   if (FIBPtr =        AllocDosObject(DOS_FIB, NULL))
                    {   success = TRUE;

                        if (Examine(DirHandle, FIBPtr))
                        {   comma(FIBPtr->fib_Size);
                            strcat(logstring, commastring);
                            for (j = 0; j <= 13; j++)
                            {   if (commastring[j] != ' ')
                                {   where = j;
                                    break;
                            }   }
                            strcpy(liststring[1], &commastring[where]);
                        } else
                        {   strcpy(liststring[1], "?");
                            strcat(logstring, "            ?");
                        }
                        strcat(logstring, " ");

                        DateTime.dat_Stamp   = FIBPtr->fib_Date;
                        if (DateToStr(&DateTime))
                        {   strcpy(liststring[2], datestring);
                        } else
                        {   strcpy(liststring[2], "        ?");
                        }
                        strcat(logstring, liststring[2]);
                        strcat(logstring, " ");

                        FreeDosObject(DOS_FIB, FIBPtr);
                        FIBPtr = NULL;
                        UnLock(DirHandle);
                        DirHandle = NULL;
                }   }
                if (!success)
                {   strcpy(liststring[1], "            ?");
                    strcpy(liststring[2], "        ?");
                    strcat(logstring, liststring[1]);
                    strcat(logstring, " ");
                    strcat(logstring, liststring[2]);
                    strcat(logstring, " ");
                }

                // now get the file version
                strcpy(commandstring, "Version ");
                strcat(commandstring, pathnamestring);
                strcat(commandstring, " >T:ReportPlus.temp");
                if (!SystemTags(commandstring, SYS_Output, Open("NIL:", MODE_NEWFILE), TAG_DONE))
                {   readordie("T:ReportPlus.temp");
                    for (k = 0; k <= VLONGFIELD; k++)
                    {   if (IOBuffer[k] == LF)
                        {   IOBuffer[k] = 0;
                            break;
                    }   }
                    strcpy(liststring[3], IOBuffer);
                } else
                {   strcpy(liststring[3], "?");
                }
                strcat(logstring, liststring[3]);

                if (started)
                {   if (stricmp(FilenamePtr[i], lastfilename))
                    {   if (currentpen == firstpen)
                        {   currentpen = secondpen;
                        } else currentpen = firstpen;
                }   }
                else started = TRUE;
                if (!(ListBrowserNodePtr = AllocListBrowserNode
                (   4,                       // columns,
                    LBNA_Flags,              LBFLG_CUSTOMPENS,
/* LBNCA_ tags are those that apply to the specific column. */
                    LBNA_Column,             0,
                        LBNCA_CopyText,      TRUE,
                        LBNCA_Text,          liststring[0],
                        LBNCA_FGPen,         BLACK,
                        LBNCA_BGPen,         currentpen,
                    LBNA_Column,             1,
                        LBNCA_CopyText,      TRUE,
                        LBNCA_Text,          liststring[1],
                        LBNCA_Justification, LCJ_RIGHT,
                        LBNCA_FGPen,         BLACK,
                        LBNCA_BGPen,         currentpen,
                    LBNA_Column,             2,
                        LBNCA_CopyText,      TRUE,
                        LBNCA_Text,          liststring[2],
                        LBNCA_FGPen,         BLACK,
                        LBNCA_BGPen,         currentpen,
                    LBNA_Column,             3,
                        LBNCA_CopyText,      TRUE,
                        LBNCA_Text,          liststring[3], 
                        LBNCA_FGPen,         BLACK,
                        LBNCA_BGPen,         currentpen,
                    TAG_END
                )))
                {   rq("Can't create ReAction listbrowser.gadget node(s)!");
                }
                AddTail(&DupList, ListBrowserNodePtr); // AddTail() has no return code
                DupNodes = TRUE;
                strcpy(lastfilename, FilenamePtr[i]);
                
                if (shared.log) // write to logfile
                {   strcat(logstring, "\n");
                    writeline();
            }   }
            for (i = 1; i <= dups; i++)
            {   FreeVec(PathnamePtr[i]);
                FreeVec(FilenamePtr[i]);
        }   }
        else
        {   for (i = 1; i <= files; i++)
            {   FreeVec(PathnamePtr[i]);
                FreeVec(FilenamePtr[i]);
        }   }
        files = 0;
        FreeVec(IsDup);
        IsDup = NULL;
        FreeVec(PathnamePtr);
        PathnamePtr = NULL;
        FreeVec(FilenamePtr);
        FilenamePtr = NULL;
        DeleteFile("T:ReportPlus.temp"); /* returns FALSE for failure */

        if (shared.log)
        {   if (!found)
            {   strcpy(logstring, " None\n");
            }
            strcat(logstring, "\n");
            writeline();
        }

        SetGadgetAttrs
        (   gadgets[GID_8_LB1], MainWindowPtr, NULL,
            LISTBROWSER_Labels, &DupList,
            TAG_END
        );
}   }

MODULE void addduplicate(STRPTR path, STRPTR filename)
{   TEXT tablestring[257];

    if (size.finddup)
    {   files++;
        if (files >= MAX_FILES)
        {   rq("Too many files!");
        }
        IsDup[files] = FALSE;

        if (!(FilenamePtr[files] = AllocVec(strlen(filename) + 1, NULL)))
        {   rq("Out of memory!");
        }
        strcpy(FilenamePtr[files], filename);

        strcpy(tablestring, path);
        if (!AddPart(tablestring, filename, 256))
        {   rq("AddPart() failed!");
        }
        if (!(PathnamePtr[files] = AllocVec(strlen(tablestring) + 1, NULL)))
        {   rq("Out of memory!");
        }
        strcpy(PathnamePtr[files], tablestring);

    /* size.path   path           filename    PathnamePtr[]           FilenamePtr[]
       "SYS:Tools" "Commodities/" "AutoPoint" "Commodities/AutoPoint" "AutoPoint" */
}   }

MODULE void flipfind(ABOOL keyboard)
{   if (keyboard)
    {   if (CB81_Find->Flags & GFLG_SELECTED)
        {   size.finddup = FALSE;
        } else
        {   size.finddup = TRUE;
    }   }
    else
    {   if (CB81_Find->Flags & GFLG_SELECTED)
        {   size.finddup = TRUE;
        } else
        {   size.finddup = FALSE;
    }   }

    GT_SetGadgetAttrs
    (   CB81_Find,
        MainWindowPtr, NULL,
        GTCB_Checked, size.finddup,
        TAG_DONE
    );
    SetGadgetAttrs
    (   gadgets[GID_8_LB1], MainWindowPtr, NULL,
        GA_Disabled, !size.finddup,
        TAG_END
    );
}

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 we are also doing duplicates, we now do our thinking,
    write the results a line at a time, then close the file. */

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

